2012-05-21 18 views
11

展開されたJavaプログラムの文字列定数、つまりコンパイルされた.classファイル内の値を変更する必要があります。再起動することはできますが、簡単に再コンパイルすることはできません(ただし、この質問で回答が得られない場合は不便です)。これは可能ですか?コンパイルされたクラスの文字列定数を変更する

更新:私はちょうど16進エディタでファイルを見て、簡単にその文字列を変更できるように見えます。それはうまくいく、つまりファイルの何らかのシグネチャを無効にすることはないでしょうか?古い文字列と新しい文字列は両方とも英数字で、必要に応じて同じ長さにすることができます。

更新2:修正しました。変更する必要のある特定のクラスは非常に小さく、新しいバージョンのプロジェクトでは変更されなかったので、それをコンパイルして新しいクラスをそこから取得できます。まだ教育目的のために、コンパイルを伴わない答えに興味があります。あなたはこのクラスのソースを持っている場合は

+0

ASMとBCELのようなバイトコード操作ライブラリがあり、好みのクラスファイルを調整することができます。より良い解決法、IMOは、プロパティとして定数を抽出し、抽出に必要な一度だけ再コンパイルの不便さを経験することです。 –

+0

@ NathanD.Ryan私は確かにそれを抽出して将来の設定ファイルに入れますが、この特定のケースではむしろ古いバージョンである展開されたバージョンを再コンパイルするのは非常に不便です。 –

+0

問題の文字列はコンパイル時定数ですか? –

答えて

7

、その後、私のアプローチは次のとおりです。

  • は、単一のクラス
  • のソースを入手JARファイル
  • を取得し、クラスパス上のJARにソースをコンパイルします(そうすれば、他のものをコンパイルする必要はなく、JARにはすでにバイナリが含まれていることに害はありません)。これには最新のJavaバージョンを使用できます。 -source-targetを使用してコンパイラをダウングレードするだけです。
  • jar uを使用して、新しいものやAntタスクのためのAntタスク

例のJAR内のクラスファイルを置き換えます。私もマニフェストを更新

ここ
 <jar destfile="${jar}" 
      compress="true" update="true" duplicate="preserve" index="true" 
      manifest="tmp/META-INF/MANIFEST.MF" 
     > 
      <fileset dir="build/classes"> 
       <filter /> 
      </fileset> 
      <zipfileset src="${origJar}"> 
       <exclude name="META-INF/*"/> 
      </zipfileset> 
     </jar> 

を。最初に新しいクラスを配置し、元のJARからすべてのファイルを追加します。 duplicate="preserve"は、新しいコードが上書きされないようにします。

コードが署名されていない場合、新しい文字列が古いものと完全に同じ長さであれば、バイトを置き換えることもできます。 Javaはコードをチェックしますが、no checksum in the .class filesがあります。

長さを保存する必要があります。そうしないと、クラスローダーが混乱します。

+0

私はソース(質問参照)でそれを修正しましたが、ソースなしの答えを見つけることにはまだ興味があります。 16進エディタでクラスを調べると、文字列の長さが1バイト以上前に格納されていることがわかりました。私もそれを更新する場合、私は文字列の長さを変更することができますか、または編集する必要があるさらに長さのフィールドがありますか? –

+0

文字列の長さを変更する場合は、文字列の後にバイト数を挿入/削除する必要があります。これは、クラスリーダーが文字列を読み取って次の有効なアイテム - >クラッシュ –

+1

@Aaronを期待するためです。文字列の先頭にある長さフィールドを更新する限り、すべて正常に動作します。クラスローダーがハードコードされたマジックオフセットを持っているのと同じではありません。 – Antimony

1

多くのバイトコードエンジニアリングライブラリを使用して.classを変更できます。たとえば、javaassistを使用してください。

しかし、静的なfinalメンバーを置き換えようとすると、コンパイラはどこでもこの定数をインライン展開するので、望ましい結果は得られません。この場合

//ConstantHolder.java

public class ConstantHolder { 

public static final String HELLO="hello"; 

public static void main(String[] args) { 
    System.out.println("Value:" + ConstantHolder.HELLO); 
} 
} 

//ModifyConstant.java

import java.io.IOException; 

import javassist.CannotCompileException; 
import javassist.ClassPool; 
import javassist.CtClass; 
import javassist.CtField; 
import javassist.NotFoundException; 

//ModifyConstant.java 
public class ModifyConstant { 
public static void main(String[] args) { 
    modifyConstant(); 
} 

private static void modifyConstant() { 
    ClassPool pool = ClassPool.getDefault(); 
    try { 
    CtClass pt = pool.get("ConstantHolder"); 
    CtField field = pt.getField("HELLO"); 
    pt.removeField(field); 
    CtField newField = CtField.make("public static final String HELLO=\"hell\";", pt); 
    pt.addField(newField); 
    pt.writeFile(); 
    } catch (NotFoundException e) { 
    e.printStackTrace();System.exit(-1); 
    } catch (CannotCompileException e) { 
    e.printStackTrace();System.exit(-1); 
    } catch (IOException e) { 
    e.printStackTrace();System.exit(-1); 
    } 
} 
} 

をjavaassist.jar使用

サンプルコード、プログラムが正常にハローの値を変更「こんにちは」から「地獄」まで。ただし、ConstantHolderクラスを実行すると、コンパイラによるインライン展開のために、 "Value:Hello"が出力されます。

希望します。

+0

署名の意味は何ですか? –

+0

をご説明ください。わたしはあなたの質問がわからなかった。 – krishnakumarp

4

定数プール内の文字列(技術的にUtf8アイテム)を変更する場合に必要な追加データは、長さフィールド(データの前に2バイトのビッグエンディアン)です。変更を必要とするチェックサムやオフセットはありません。

2つの注意点があります。

  • 文字列は、他の場所で使用することができるが。たとえば、メソッドコード属性に「コード」が使用されているため、コードを変更するとファイルが破損します。
  • 文字列はModified Utf8形式で格納されます。そのため、基本平面外のヌルバイトとユニコード文字は、別々にエンコードされます。長さフィールドは文字ではなくバイト数で、65535に制限されています。

これをたくさん行う予定がある場合は、クラスファイルエディタツールを使用する方がよいでしょうが、16進エディタは役に立ちます迅速な変更。

+0

コンパイルされたクラスを編集するためのサンプルツールは、http://sourceforge.net/projects/classeditor/です(.classファイルを選択する必要があります.jarsを開くことはできません)。 – Marcin

2

ASMとJarJarは、以下の問題があったので、私は最近、自分のConstantPoolマッパーを書きました:

  • は、すべてのクラスの依存関係
  • ずに書き換えをサポートしていませんでした
  • のストリーミングをサポートしていませんでした遅くする

    • Tree APIモードでRemapperをサポートしていません
    • StackMapsを展開して折りたたまなければならない

    私は次のようになってしまった:

    public void process(DataInputStream in, DataOutputStream out, Function mapper) throws IOException { 
        int magic = in.readInt(); 
        if (magic != 0xcafebabe) throw new ClassFormatError("wrong magic: " + magic); 
        out.writeInt(magic); 
    
        copy(in, out, 4); // minor and major 
    
        int size = in.readUnsignedShort(); 
        out.writeShort(size); 
    
        for (int i = 1; i < size; i++) { 
         int tag = in.readUnsignedByte(); 
         out.writeByte(tag); 
    
         Constant constant = Constant.constant(tag); 
         switch (constant) { 
          case Utf8: 
           out.writeUTF(mapper.apply(in.readUTF())); 
           break; 
          case Double: 
          case Long: 
           i++; // "In retrospect, making 8-byte constants take two constant pool entries was a poor choice." 
           // See http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5 
          default: 
           copy(in, out, constant.size); 
           break; 
         } 
        } 
        Streams.copyAndClose(in, out); 
    } 
    
    private final byte[] buffer = new byte[8]; 
    
    private void copy(DataInputStream in, DataOutputStream out, int amount) throws IOException { 
        in.readFully(buffer, 0, amount); 
        out.write(buffer, 0, amount); 
    } 
    

    そして

    public enum Constant { 
        Utf8(1, -1), 
        Integer(3, 4), 
        Float(4, 4), 
        Long(5, 8), 
        Double(6,8), 
        Class(7, 2), 
        String(8, 2), 
        Field(9, 4), 
        Method(10, 4), 
        InterfaceMethod(11, 4), 
        NameAndType(12, 4), 
        MethodHandle(15, 3), 
        MethodType(16, 2), 
        InvokeDynamic(18, 4); 
    
    public final int tag, size; 
    
    Constant(int tag, int size) { this.tag = tag; this.size = size; } 
    
    private static final Constant[] constants; 
    static{ 
        constants = new Constant[19]; 
        for (Constant c : Constant.values()) constants[c.tag] = c; 
    } 
    
    public static Constant constant(int tag) { 
        try { 
         Constant constant = constants[tag]; 
         if(constant != null) return constant; 
        } catch (IndexOutOfBoundsException ignored) { } 
        throw new ClassFormatError("Unknown tag: " + tag); 
    } 
    

    はちょうどそれからハッキングを開始するのは非常にいい場所だと私はライブラリのない代替案を示したいと思いました。私のコードはjavapのソースコードからインスピレーションを受けました

    関連する問題