Javaでハローワールドをコンパイルしてclassファイルを解析してみました。
※ 仕様書を参照したため、リバースエンジニアリングではありません。
Hello.java
class Hello { public static void main(String[] args) { System.out.println("hello"); } }
実行結果
$ javac Hello.java $ java Hello hello
Hello.classが生成されます。
javapで解析
JDKのツールで解析してみます。
$ javap -v Hello.class Classfile Hello.class Last modified 2012/06/18; size 409 bytes MD5 checksum c28fbdaf7f0c86009c9101a9852a7646 Compiled from "Hello.java" class Hello SourceFile: "Hello.java" minor version: 0 major version: 51 flags: ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // hello #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Hello #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Hello.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 hello #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Hello #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { Hello(); flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 }
ダンプ
000000 ca fe ba be 00 00 00 33 00 1d 0a 00 06 00 0f 09 000010 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07 000020 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 000040 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69 000050 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 000060 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 000070 72 63 65 46 69 6c 65 01 00 0a 48 65 6c 6c 6f 2e 000080 6a 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00 000090 19 01 00 05 68 65 6c 6c 6f 07 00 1a 0c 00 1b 00 0000a0 1c 01 00 05 48 65 6c 6c 6f 01 00 10 6a 61 76 61 0000b0 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 0000c0 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 0000d0 00 03 6f 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 0000e0 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 0000f0 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 000100 65 61 6d 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 000110 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 000120 6e 67 3b 29 56 00 20 00 05 00 06 00 00 00 00 00 000130 02 00 00 00 07 00 08 00 01 00 09 00 00 00 1d 00 000140 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 01 000150 00 0a 00 00 00 06 00 01 00 00 00 01 00 09 00 0b 000160 00 0c 00 01 00 09 00 00 00 25 00 02 00 01 00 00 000170 00 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 01 00 000180 0a 00 00 00 0a 00 02 00 00 00 03 00 08 00 04 00 000190 01 00 0d 00 00 00 02 00 0e
【追記】注釈つきのバイナリダンプを掲載しました。
http://7shi.hateblo.jp/entry/2012/06/19/203632
ClassFile
仕様書を見ながらバイナリを読み取ります。
- Java仮想マシン仕様書 (The Java Virtual Machine Specification)
http://docs.oracle.com/javase/specs/
P.70 より
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
配列を読み飛ばして解析します。数値はビッグエンディアンです。
※ 配列部分のデータサイズは中身を見ないと分かりません。個数がゼロであればデータもありません。
000000 ca fe ba be u4 magic 000004 00 00 u2 minor_version 000006 00 33 u2 major_version 000008 00 1d u2 constant_pool_count 00000a ... cp_info constant_pool[] 000125 00 20 u2 access_flags 000127 00 05 u2 this_class 000129 00 06 u2 super_class 00012b 00 00 u2 interfaces_count u2 interfaces[] 00012d 00 00 u2 fields_count field_info fields[] 00012f 00 02 u2 methods_count 000131 ... method_info methods[] 00018f 00 01 u2 attributes_count 000191 ... attribute_info attributes[]
次に配列の中身を見ていきます。
constant_pool
000008 00 1d u2 constant_pool_count
配列の個数は 0x1d = 29 個です。
ただし実際に入っているデータは1個少ないです。これは仕様書に明記されています。
cp_info constant_pool[constant_pool_count-1];
なぜ1個少ないかですが、通常なら#0~#28で29個となりますが、#0は無効なデータを指すため欠番になっていて、データとして含まれないためのようです。(P.71)
※ javapの解析で出てきた#1~#28に該当します。
個々のデータは以下の構造を持っています。
P.84
cp_info { u1 tag; u1 info[]; }
infoの内容はtagの種類によって決まるため、まずtagを見ます。種類はP.85に列挙されています。
データを1つずつ見ていきます。
00000a 0a CONSTANT_Methodref
javapの結果と種類が同じであることが確認できます。
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
Methodrefの具体的な構造は以下の通りです。これとcp_infoの関係は、C言語で言う共用体に相当します。
P.87
CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
このようにtagを見ながら構造を調べると以下のようになります。
#1 00000a 0a tag (CONSTANT_Methodref) 00000b 00 06 class_index 00000d 00 0f name_and_type_index #2 00000f 09 tag (CONSTANT_Fieldref) 000010 00 10 class_index 000012 00 11 name_and_type_index #3 000014 08 tag (CONSTANT_String) 000015 00 12 string_index #4 000017 0a tag (CONSTANT_Methodref) 000018 00 13 class_index 00001a 00 14 name_and_type_index #5 00001c 07 tag (CONSTANT_Class) 00001d 00 15 name_index #6 00001f 07 tag (CONSTANT_Class) 000020 00 16 name_index #7 000022 01 tag (CONSTANT_Utf8) 000023 00 06 length 000025 ... bytes[] "<init>" #8 00002b 01 tag (CONSTANT_Utf8) 00002c 00 03 length 00002e ... bytes[] "()V" #9 000031 01 tag (CONSTANT_Utf8) 000032 00 04 length 000034 ... bytes[] "Code" #10 000038 01 tag (CONSTANT_Utf8) 000039 00 0f length 00003b ... bytes[] "LineNumberTable" #11 00004a 01 tag (CONSTANT_Utf8) 00004b 00 04 length 00004d ... bytes[] "main" #12 000051 01 tag (CONSTANT_Utf8) 000052 00 16 length 000054 ... bytes[] "([Ljava/lang/String;)V" #13 00006a 01 tag (CONSTANT_Utf8) 00006b 00 0a length 00006d ... bytes[] "SourceFile" #14 000077 01 tag (CONSTANT_Utf8) 000078 00 0a length 00007a ... bytes[] "Hello.java" #15 000084 0c tag (CONSTANT_NameAndType) 000085 00 07 name_index 000087 00 08 descriptor_index #16 000089 07 tag (CONSTANT_Class) 00008a 00 17 name_index #17 00008c 0c tag (CONSTANT_NameAndType) 00008d 00 18 name_index 00008f 00 19 descriptor_index #18 000091 01 tag (CONSTANT_Utf8) 000092 00 05 length 000094 ... bytes[] "hello" #19 000099 07 tag (CONSTANT_Class) 00009a 00 1a name_index #20 00009c 0c tag (CONSTANT_NameAndType) 00009d 00 1b name_index 00009f 00 1c descriptor_index #21 0000a1 01 tag (CONSTANT_Utf8) 0000a2 00 05 length 0000a4 ... bytes[] "Hello" #22 0000a9 01 tag (CONSTANT_Utf8) 0000aa 00 10 length 0000ac ... bytes[] "java/lang/Object" #23 0000bc 01 tag (CONSTANT_Utf8) 0000bd 00 10 length 0000bf ... bytes[] "java/lang/System" #24 0000cf 01 tag (CONSTANT_Utf8) 0000d0 00 03 length 0000d2 ... bytes[] "out" #25 0000d5 01 tag (CONSTANT_Utf8) 0000d6 00 15 length 0000d8 ... bytes[] "Ljava/io/PrintStream;" #26 0000ed 01 tag (CONSTANT_Utf8) 0000ee 00 13 length 0000f0 ... bytes[] "java/io/PrintStream" #27 000103 01 tag (CONSTANT_Utf8) 000104 00 07 length 000106 ... bytes[] "println" #28 00010d 01 tag (CONSTANT_Utf8) 00010e 00 15 length 000110 ... bytes[] "(Ljava/lang/String;)V"
定数構造
定数を具体的に追っていきます。
#1
00000a 0a tag (CONSTANT_Methodref) 00000b 00 06 class_index 00000d 00 0f name_and_type_index
class_indexとname_and_type_indexは定数プールを指しています。
class_index = 0x0006 → #6 name_and_type_index = 0x000f → #15
該当する項目を見ていきます。
#6
00001f 07 tag (CONSTANT_Class) 000020 00 16 name_index
name_indexの指す先は文字列です。
#22
name_index = 0x0016 → #22 = "java/lang/Object"
つまり、#6はクラスを表し、#22は名前を表しています。このように要素は小分けに格納されています。
次にname_and_type_indexが指す#15を見てみます。
#15
000084 0c tag (CONSTANT_NameAndType) 000085 00 07 name_index 000087 00 08 descriptor_index
name_indexとdescriptor_indexは文字列を指しています。
name_index = 0x0007 → #7 = "<init>" descriptor_index = 0x0008 → #8 = "()V"
これはNameAndTypeという型の通り、名前が"
これらを踏まえて#1を見ると、クラスが"java/lang/Object"、名前が"
この関係を図示します。
Methodref: class_index → Class: name_index → "java/lang/Object" name_and_type_index → NameAndType: name_index → "<init>" descriptor_index → "()V"
今はこれ以上は追わないで、このような関係になっているということだけを把握するのに留めておくことにします。具体的にどう処理するのかは必要に応じて考えようと思います。
methods
P.99
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
P.102
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
attribute_infoのinfoは後回しにしてダンプを読み取っていきます。
methods[0]
000131 00 00 u2 access_flags 000133 00 07 u2 name_index 000135 00 08 u2 descriptor_index 000137 00 01 u2 attributes_count 000139 00 09 u2 attribute_name_index 00013b 00 00 00 1d u4 attribute_length 00013f ... u1 info[]
methods[1]
00015c 00 09 u2 access_flags 00015e 00 0b u2 name_index 000160 00 0c u2 descriptor_index 000162 00 01 u2 attributes_count 000164 00 09 u2 attribute_name_index 000166 00 00 00 25 u4 attribute_length 00016a ... u1 info[]
メソッド属性
先ほど後回しにしたメソッド属性を調べてみます。
methods[0]
000137 00 01 u2 attributes_count 000139 00 09 u2 attribute_name_index 00013b 00 00 00 1d u4 attribute_length 00013f ... u1 info[attribute_length]
attribute_name_indexは属性名を示しています。
attribute_name_index → #9 → "Code"
Code属性のデータが含まれている、ということが分かります。Code属性の構造を示します。
P.107
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
これはattribute_infoと共用体の関係にあるため、attribute_name_indexとattribute_lengthは共通です。そのためinfoに該当するのはmax_stack以下となります。
00013f 00 01 u2 max_stack 000141 00 01 u2 max_locals 000143 00 00 00 05 u4 code_length 000145 2a b7 00 01 b1 u1 code[code_length] 00014a 00 00 u2 exception_table_length 00014c 00 01 u2 attributes_count 00014e 00 0a u2 attribute_name_index 000150 00 00 00 06 u4 attribute_length
code[]はバイトコードです。解析は後回しにします。
属性を見ていきます。
attribute_name_index → #10 → "LineNumberTable"
P.125
LineNumberTable_attribute { u2 attribute_name_index; u4 attribute_length; u2 line_number_table_length; { u2 start_pc; u2 line_number; } line_number_table[line_number_table_length]; }
デバッグ情報としてコード位置とソース行との対応関係を表しています。
000154 00 01 u2 line_number_table_length 000156 00 00 u2 start_pc 000158 00 01 u2 line_number
methods[1]
000162 00 01 u2 attributes_count 000164 00 09 u2 attribute_name_index 000166 00 00 00 25 u4 attribute_length
Code属性です。
00016a 00 02 u2 max_stack 00016c 00 01 u2 max_locals 00016e 00 00 00 09 u4 code_length 000172 ... u1 code[code_length] 00017b 00 00 u2 exception_table_length 00017d 00 01 u2 attributes_count 00017f 00 0a u2 attribute_name_index 000181 00 00 00 0a u4 attribute_length
LineNumberTable属性です。
000185 00 02 u2 line_number_table_length 000187 00 00 u2 start_pc 000189 00 03 u2 line_number 00018b 00 08 u2 start_pc 00018d 00 04 u2 line_number
バイトコード
先ほど後回しにしたバイトコードを見ていきます。
Java仮想マシンの命令コードです。P.566~568のオペコード表を参考に逆アセンブルします。
methods[0].attributes[0].code
000145 2a aload_0 000146 b7 00 01 invokespecial #1 000149 b1 return
methods[1].attributes[0].code
000172 b2 00 02 getstatic #2 000175 12 03 ldc #3 000177 b6 00 04 invokevirtual #4 00017a b1 return
バイトコードのインタプリタを作るには、ここを中心に見ていくと良さそうです。
attributes
クラスの属性です。メソッドに使われているのと同じ構造が使用されています。
00018f 00 01 u2 attributes_count 000191 00 0d u2 attribute_name_index 000193 00 00 00 02 u4 attribute_length 000197 00 0e u1 info[attribute_length]
以上でファイル全体の構造解析は終了です。