top / index / prev / next / target / source

2006-01-06 diary: Apache Jakarta BCELを用いたクラスファイル(バイトコード)解析サンプル

いがぴょんの日記 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

Apache Jakarta BCELを用いたクラスファイル(バイトコード)解析サンプル

blancoDb Enterprise Edition 1.4.0, blancoResourceBundle 0.4.5, BlancoSqlEditorPlugin 0.3.4について、出荷候補版をリリースします。

Apache Jakarta BCELを用いたクラスファイル(バイトコード)解析サンプル

不意に30分ほど時間が自由になったので、Apache Jakarta BCELを用いたクラスファイル解析(バイトコード解析)を試してみました。とても簡単に動作させることが出来ました。

import org.apache.bcel.classfile.ClassFormatException; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.classfile.Utility;

/** * Apache Jakarta BCELを用いたクラスファイル(バイトコード)解析サンプル * * @author IGA Tosiki / public class BcelReadClassSample { /* * 解析を行いたいクラスファイル名を指定します。 */ private static final String CLASS_MODULE = "./bin/BcelReadClassSample.class";

public static final void main(final String[] args) {
    new BcelReadClassSample().process();
}

private final void process() {
    try {
        final JavaClass javaClass = new ClassParser(CLASS_MODULE).parse();
        System.out.println("クラス名:" + javaClass.getClassName());
        System.out.println("親クラス:" + javaClass.getSuperclassName());

        final org.apache.bcel.classfile.Method[] methods = javaClass
                .getMethods();
        for (int indexMethod = 0; indexMethod < methods.length; indexMethod++) {
            final Method method = methods[indexMethod];
            System.out.println("メソッド:" + method.getName());
            final Code code = method.getCode();
            if (code == null) {
                continue;
            }

            /**
             * メソッドのなかのバイトコードを解析
             */
            final String result = Utility.codeToString(code.getCode(),
                    javaClass.getConstantPool(), 0, -1, false);
            System.out.println(result);
        }
    } catch (ClassFormatException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

} ```

実行結果は下記のようになります。主立った処理は Utility.codeToString のなかで行われています。 実行結果

クラス名:BcelReadClassSample親クラス:java.lang.Objectメソッド:<init>
0:    aload_0
1:    invokespecial     java.lang.Object.<init> ()V
4:    returnメソッド:main
0:    new               <BcelReadClassSample>
3:    dup
4:    invokespecial     BcelReadClassSample.<init> ()V
7:    invokespecial     BcelReadClassSample.process ()V
10:   returnメソッド:process
0:    new               <org.apache.bcel.classfile.ClassParser>
3:    dup
4:    ldc               "./bin/BcelReadClassSample.class"
6:    invokespecial     org.apache.bcel.classfile.ClassParser.<init> (Ljava/lang/String;)V
9:    invokevirtual     org.apache.bcel.classfile.ClassParser.parse ()Lorg/apache/bcel/classfile/JavaClass;
12:   astore_1
13:   getstatic         java.lang.System.out Ljava/io/PrintStream;
16:   new               <java.lang.StringBuffer>
19:   dup
20:   ldc               "クラス名:"
22:   invokespecial     java.lang.StringBuffer.<init> (Ljava/lang/String;)V
25:   aload_1
26:   invokevirtual     org.apache.bcel.classfile.JavaClass.getClassName ()Ljava/lang/String;
29:   invokevirtual     java.lang.StringBuffer.append (Ljava/lang/String;)Ljava/lang/StringBuffer;
32:   invokevirtual     java.lang.StringBuffer.toString ()Ljava/lang/String;
35:   invokevirtual     java.io.PrintStream.println (Ljava/lang/String;)V
38:   getstatic         java.lang.System.out Ljava/io/PrintStream;
41:   new               <java.lang.StringBuffer>
44:   dup
45:   ldc               "親クラス:"
47:   invokespecial     java.lang.StringBuffer.<init> (Ljava/lang/String;)V
50:   aload_1
51:   invokevirtual     org.apache.bcel.classfile.JavaClass.getSuperclassName ()Ljava/lang/String;
54:   invokevirtual     java.lang.StringBuffer.append (Ljava/lang/String;)Ljava/lang/StringBuffer;
57:   invokevirtual     java.lang.StringBuffer.toString ()Ljava/lang/String;
60:   invokevirtual     java.io.PrintStream.println (Ljava/lang/String;)V
63:   aload_1
64:   invokevirtual     org.apache.bcel.classfile.JavaClass.getMethods ()[Lorg/apache/bcel/classfile/Method;
67:   astore_2
68:   iconst_0
69:   istore_3
70:   goto              #147
73:   aload_2
74:   iload_3
75:   aaload
76:   astore            %4
78:   getstatic         java.lang.System.out Ljava/io/PrintStream;
81:   new               <java.lang.StringBuffer>
84:   dup
85:   ldc               "メソッド:"
87:   invokespecial     java.lang.StringBuffer.<init> (Ljava/lang/String;)V
90:   aload             %4
92:   invokevirtual     org.apache.bcel.classfile.Method.getName ()Ljava/lang/String;
95:   invokevirtual     java.lang.StringBuffer.append (Ljava/lang/String;)Ljava/lang/StringBuffer;
98:   invokevirtual     java.lang.StringBuffer.toString ()Ljava/lang/String;
101:  invokevirtual     java.io.PrintStream.println (Ljava/lang/String;)V
104:  aload             %4
106:  invokevirtual     org.apache.bcel.classfile.Method.getCode ()Lorg/apache/bcel/classfile/Code;
109:  astore            %5
111:  aload             %5
113:  ifnonnull         #119
116:  goto              #144
119:  aload             %5
121:  invokevirtual     org.apache.bcel.classfile.Code.getCode ()[B
124:  aload_1
125:  invokevirtual     org.apache.bcel.classfile.JavaClass.getConstantPool ()Lorg/apache/bcel/classfile/ConstantPool;
128:  iconst_0
129:  iconst_m1
130:  iconst_0
131:  invokestatic      org.apache.bcel.classfile.Utility.codeToString ([BLorg/apache/bcel/classfile/ConstantPool;IIZ)Ljava/lang/String;
134:  astore            %6
136:  getstatic         java.lang.System.out Ljava/io/PrintStream;
139:  aload             %6
141:  invokevirtual     java.io.PrintStream.println (Ljava/lang/String;)V
144:  iinc              %3      1
147:  iload_3
148:  aload_2
149:  arraylength
150:  if_icmplt         #73
153:  goto              #169
156:  astore_1
157:  aload_1
158:  invokevirtual     org.apache.bcel.classfile.ClassFormatException.printStackTrace ()V
161:  goto              #169
164:  astore_1
165:  aload_1
166:  invokevirtual     java.io.IOException.printStackTrace ()V
169:  return

この例だと あまりにもBCELのUtility.codeToStringに依存しすぎているので、すこしマジメにプログラムとして解析する例を示します。 BcelReadClassSample.java

/**
 * Apache Jakarta BCELを用いたクラスファイル(バイトコード)解析サンプル
 */
import java.io.IOException;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

/**
 * Apache Jakarta BCELを用いたクラスファイル(バイトコード)解析サンプル。
 * 
 * 少しマジメに解析する例
 * 
 * @author IGA Tosiki
 */
public class BcelReadClassSample {
    /**
     * 解析を行いたいクラスファイル名を指定します。
     */
    private static final String CLASS_MODULE = "./bin/BcelReadClassSample.class";

    public static final void main(final String[] args) {
        new BcelReadClassSample().process();
    }

    private final void process() {
        try {
            final JavaClass javaClass = new ClassParser(CLASS_MODULE).parse();
            System.out.println("クラス名:" + javaClass.getClassName());
            System.out.println("親クラス:" + javaClass.getSuperclassName());

            final org.apache.bcel.classfile.Method[] methods = javaClass
                    .getMethods();
            for (int indexMethod = 0; indexMethod < methods.length; indexMethod++) {
                final Method method = methods[indexMethod];
                System.out.println("メソッド:" + method.getName());
                final Code code = method.getCode();
                if (code == null) {
                    continue;
                }

                /**
                 * メソッドのなかのバイトコードを解析
                 */
                final byte[] codes = code.getCode();
                for (int indexCode = 0; indexCode < codes.length; indexCode++) {
                    final short opcode = (short) (codes[indexCode] < 0 ? ((short) codes[indexCode]) + 0x100
                            : (short) codes[indexCode]);
                    int oplen = Constants.NO_OF_OPERANDS[opcode];
                    System.out.println(Constants.OPCODE_NAMES[opcode]);

                    // オペレーション分進めます。
                    indexCode += oplen;
                }
            }
        } catch (ClassFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

命令部の表示しかしていないので、実行後の結果は少なくなってしまいます。 実行結果

クラス名:BcelReadClassSample親クラス:java.lang.Objectメソッド:<init>
aload_0
invokespecial
returnメソッド:main
new
dup
invokespecial
invokespecial
returnメソッド:process
new
dup
ldc
invokespecial
invokevirtual
astore_1
getstatic
new
dup
ldc
invokespecial
aload_1
invokevirtual
invokevirtual
invokevirtual
invokevirtual
getstatic
new
dup
ldc
invokespecial
aload_1
invokevirtual
invokevirtual
invokevirtual
invokevirtual
aload_1
invokevirtual
astore_2
iconst_0
istore_3
goto
aload_2
iload_3
aaload
astore
getstatic
new
dup
ldc
invokespecial
aload
invokevirtual
invokevirtual
invokevirtual
invokevirtual
aload
invokevirtual
astore
aload
ifnonnull
goto
aload
invokevirtual
astore
iconst_0
istore
goto
aload
iload
baload
ifge
aload
iload
baload
i2s
sipush
iadd
goto
aload
iload
baload
i2s
i2s
istore
getstatic
iload
saload
istore
getstatic
getstatic
iload
aaload
invokevirtual
iload
iload
iadd
istore
iinc
iload
aload
arraylength
if_icmplt
iinc
iload_3
aload_2
arraylength
if_icmplt
goto
astore_1
aload_1
invokevirtual
goto
astore_1
aload_1
invokevirtual
return

結構 BCELの敷居が低いことがわかりました。なんだか低水準に触れることが出来て 嬉しかったです。

そういえば、私にとって BCELの最も身近な利用例は FindBugsです。FindBugsはバイトコード解析に BCELを利用していることが知られています。

注意: このプログラムを実行すると バイトコードが可視化されます。何かしらの事情によりリバースエンジニアリングが禁止されているクラスファイルは解析すべきでないと考えます。

2006.01.10追記 FindBugsのソースコードは BCELの利用例としてとても参考になりました。BCELを詳しく知りたい方には FindBugsのソースコードを読まれることをお勧めしたいと思いました。

関連するソース

関連する日記

blancoDb EE 1.4.0, blancoResourceBundle 0.4.5, BlancoSqlEditorPlugin 0.3.4出荷候補版リリース

blanco Framework の構成要素のうち、blancoDb Enterprise Edition 1.4.0, blancoResourceBundle 0.4.5, BlancoSqlEditorPlugin 0.3.4について、出荷候補版をリリースします。この一週間の間に出荷候補版において特に問題 (特にデグレードなど) が見つからなければ、これを最新安定版と位置づける予定です。特に関係者の方はチェックをお願いいたします。なお、blancoDbは 仕様上の変更点が重要であると考え、特に下記にリストアップします。

blancoDb Enterprise Edition の 1.2.0から1.4.0への主な変更点は下記のようになります。


この日記について