お馴染み、hello worldのソース。
class Hello {
public static void main(String[] args) {
System.out.println("hello world");
}
}
このコードはコンパイルされると、こんなバイトコードになります。
ca fe ba be 00 00 00 32 00 22 07 00 02 01 00 05 48 65 6c 6c 6f 07 00 04 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 56 01 00 04 43 6f 64 65 0a 00 03 00 09 0c 00 05 00 06 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 00 04 74 68 69 73 01 00 07 4c 48 65 6c 6c 6f 3b 01 00 04 6d 61 69 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 09 00 11 00 13 07 00 12 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 0c 00 14 00 15 01 00 03 6f 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 08 00 17 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 0a 00 19 00 1b 07 00 1a 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 0c 00 1c 00 1d 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 00 21 00 01 00 03 00 00 00 00 00 02 00 01 00 05 00 06 00 01 00 07 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 08 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00 01 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00 0f 00 01 00 07 00 00 00 37 00 02 00 01 00 00 00 09 b2 00 10 12 16 b6 00 18 b1 00 00 00 02 00 0a 00 00 00 0a 00 02 00 00 00 03 00 08 00 04 00 0b 00 00 00 0c 00 01 00 00 00 09 00 1e 00 1f 00 00 00 01 00 20 00 00 00 02 00 21
ファイルのサイズはたったの415バイトです。
$ ls -l Hello.class -rw-rw-r--. 1 user group 415 2011-08-12 21:40 Hello.class
なんだかこれくらいなら読めそうな気がします。
というわけで、今日はJavaのhello worldのクラスファイルを、人力で読むことに挑戦してみたいと思います。
真面目に読むのは初挑戦なので、誤りがあれば指摘お願いします。
クラスファイルの仕様はこちら。
The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
この資料によると、クラスファイルの構造は以下のようになっているそうです。
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];
}
これを見ただけだとよく分からないけど、とりあえずこの情報を頼りに1バイトずつ読み進めてみます。
まず最初に、u4 magic(マジックナンバー、4byte)が記述してあります。ここの値は固定で、0xCAFEBABEになるそうです。
実際にHello.classの内容を出力してみましょう。
$ od -tx1 Hello.class 0000000 ca fe ba be 00 00 00 32 00 1d 0a 00 06 00 0f 09 ...
確かにそうなっています。
試しにバイナリエディタでマジックナンバーを他の値に変えてみたところ、実行時にこんなエラーが出ました。
$ java Hello Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 3405757118 in class file Hello
次の4バイトはマイナーバージョンとメジャーバージョンが書かれています。
u2 minor_version; u2 major_version;
0000000 ca fe ba be 00 00 00 32 00 1d 0a 00 06 00 0f 09 ...
00 00 00 32と記載されています。マイナーバージョンが0、メジャーバージョンが16進数で32、つまり10進数で50になるようです。
バージョンの対応表は下記。
java1.4 : 0 48 java1.5 : 0 49 java1.6 : 0 50 java1.7 : 0 51
今回はjava6でコンパイルしているので、0 50で合ってますね。これはjavapで確認しても同じように出ます。
$ javap -verbose Hello Compiled from "Hello.java" public class Hello extends java.lang.Object SourceFile: "Hello.java" minor version: 0 major version: 50
試しにjava1.5でコンパイルすると、出来上がったバイトコードはちゃんと31に変化していました。
$ javac -target 1.5 Hello.java $ od -tx1 Hello.class 0000000 ca fe ba be 00 00 00 31 00 1d 0a 00 06 00 0f 09 ...
また、バージョンを36(そんなバージョンはない)に編集したら、java.lang.UnsupportedClassVersionError」というエラーが起きました。
バージョン情報の次に記述されるのはconstant_poolです。
u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1];
まずconstant_pool_countで個数を指定して、その数分だけconstant_poolの情報を記述しているようです。
0000000 ca fe ba be 00 00 00 32 00 1d 0a 00 06 00 0f 09
上記のように、本クラスのconstant_pool_countは、00 1d(29)になっています。つまり、1〜29-1まで、計28個のconstant_poolの情報がこの後指定されることになります。1オリジンなところが注意です。
constant_poolの情報は、cp_infoという構造体で表現されます。
cp_info {
u1 tag;
u1 info[];
}
まず、1バイトのtagがいます。tagは、以下のような対応表になります。
7 CONSTANT_Class 9 CONSTANT_Fieldref 10 CONSTANT_Methodref 11 CONSTANT_InterfaceMethodref 8 CONSTANT_String 3 CONSTANT_Integer 4 CONSTANT_Float 5 CONSTANT_Long 6 CONSTANT_Double 12 CONSTANT_NameAndType 1 CONSTANT_Utf87だったらClass、9だったらField、10だったらMethod、というような取決めのようです。
1つ目のconstant_poolを見てみましょう。
0000000 ca fe ba be 00 00 00 32 00 1d 0a 00 06 00 0f 09
1バイト目が0aになっているので、Methodrefになります。Methodrefは以下のような構造を持ちます。
CONSTANT_Methodref_info {
u1 tag; //<= 0a(10)
u2 class_index; //<= 今回は00 06(6)が入っている
u2 name_and_type_index; //<= 今回は00 0f(15)が入ってる
}
class_indexは、このメソッドが所属するクラスのconstant_poolのindexを指定します。
name_and_type_indexは、このメソッドの名前と型がペアで入っているNameAndTypeのindexを指定します。
要約すると、constant_pool[1]のメソッドは、constant_pool[6]のクラスに所属し、名称や型はconstant_pool[15]に指定してある、という意味になるようです。
constant_pool[6]や[15]の内容は後々出てきます。
0000000 ca fe ba be 00 00 00 32 00 1d 0a 00 06 00 0f 09 0000020 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07
2つ目のconstant_poolは09で始まるので、Fieldrefです。Fieldrefの構造は以下です。
CONSTANT_Fieldref_info {
u1 tag; //<= 09(9)
u2 class_index; //<= 00 10(16)
u2 name_and_type_index; //<= 00 11(17)
}
見方は先程のMethodrefと同じですね。
constant_pool[2]のフィールドは、constant_pool[16]のクラスに所属し、名称と型はconstant_pool[17]で指定されているようです。
0000020 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07
08なので、String。Stringの構造は以下。
CONSTANT_String_info {
u1 tag; //<= 08(8)
u2 string_index; //<= 00 12(18)
}
constant_pool[3]の文字列の情報は、constant_pool[18]を参照すれば良いそうです。
0000020 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07
0aはMethodref。
CONSTANT_Methodref_info {
u1 tag; //<= 0a(10)
u2 class_index; //<= 00 13(19)
u2 name_and_type_index; //<= 00 14(20)
}
constant_pool[4]のメソッドは、19のClassと20のNameAndTypeを参照します。
0000020 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07
07はClassです。Classの構造体は下記。
CONSTANT_Class_info {
u1 tag; //<= 07(7)
u2 name_index; //<= 00 15(21)
}
constant_pool[5]のクラスは、21にクラス名が指定してあるようです。
0000020 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07 0000040 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
07、Class。
CONSTANT_Class_info {
u1 tag; //<= 07(7)
u2 name_index; //<= 00 16(22)
}
constant_pool[6]のクラス名は、22にいます。
0000040 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
01はUtf8。文字列情報が格納されています。
CONSTANT_Utf8_info {
u1 tag; //<= 01
u2 length; //<= 00 06
u1 bytes[length]; //<= 文字列
}
lengthが00 06なので、6バイトの文字列が格納されているようです。文字列の部分のバイト列をod -cで表示するとこうなる。
$ od -tx1c Hello.class 0000040 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 \0 026 001 \0 006 < i n i t > 001 \0 003 ( )
constant_pool[7]には、<init>という文字列が格納されているようです。<init>は確かコンストラクタのことだったはず。
000040 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 \0 026 001 \0 006 < i n i t > 001 \0 003 ( ) 0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e V 001 \0 004 C o d e 001 \0 017 L i n e N
01、Utf8。00 03文字の文字列です。
constant_pool[8]には、()Vという文字列が格納されているようです。Vは確かvoidの意味。
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e V 001 \0 004 C o d e 001 \0 017 L i n e N
01、Utf8。00 04文字。
constant_pool[9]には、Codeという文字列が格納されています。
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e V 001 \0 004 C o d e 001 \0 017 L i n e N 0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69 u m b e r T a b l e 001 \0 004 m a i
01、Utf8。00 0f文字。
constant_pool[10]には、LineNumberTableという文字列が格納されています。
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69 u m b e r T a b l e 001 \0 004 m a i 0000120 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 n 001 \0 026 ( [ L j a v a / l a n g
01、Utf8。00 04文字。
constant_pool[11]には、mainという文字列が格納されている。おそらくmainメソッドの名称として使われるのでしょう。
0000120 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 n 001 \0 026 ( [ L j a v a / l a n g 0000140 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 / S t r i n g ; ) V 001 \0 \n S o u
01、Utf8。00 16(22)文字。
constant_pool[12]には、([Ljava/lang/String;)Vという文字列が格納されているようです。
[Lは配列のインスタンス、Vはvoidのことなので、文字列配列が引数でvoidが戻り値という意味を持っていそうです。mainメソッドと関連していそうですね。
constant_pool[8]ででてきた()Vという記述は、引数なしのvoidのメソッドという意味のようです。こちらはコンストラクタっぽいですね。
0000140 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 / S t r i n g ; ) V 001 \0 \n S o u 0000160 72 63 65 46 69 6c 65 01 00 0a 48 65 6c 6c 6f 2e r c e F i l e 001 \0 \n H e l l o .
01、Utf8。00 0a(10)文字。
constant_pool[13]には、SourceFileという文字列が格納されています。
0000160 72 63 65 46 69 6c 65 01 00 0a 48 65 6c 6c 6f 2e r c e F i l e 001 \0 \n H e l l o . 0000200 6a 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00 j a v a \f \0 \a \0 \b \a \0 027 \f \0 030 \0
01、Utf8。00 0a(10)文字。
constant_pool[14]は、Hello.javaという文字列です。これは今回のソースのファイル名ですね。
0000200 6a 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00
0c(12)はNameAndType。NameAndTypeの構造は下記。
CONSTANT_NameAndType_info {
u1 tag; //<= 0c
u2 name_index; //<= 00 07
u2 descriptor_index; //<= 00 08
}
consntant_pool[15]は、7に名前が、8にdescriptorが入っているようです。
ちなみに7は<init>、8は()Vが入っていたので、コンストラクタのNameAndTypeのようですね。
0000200 6a 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00
07、Class。name_indexは00 07(23)。
constant_pool[16]の名前はconstant_pool[23]にいる、という記述のようです。
0000200 6a 61 76 61 0c 00 07 00 08 07 00 17 0c 00 18 00 0000220 19 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 07
0c、NameAndType。nameの参照先は00 18(24)、descriptorは00 19(25)になっています。
0000220 19 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 07 031 001 \0 \v h e l l o w o r l d \a
01、Utf8。
constant_pool[18]は、hello worldという文字列を格納しています。これは今回出力している文字列ですね。
0000220 19 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 07 0000240 00 1a 0c 00 1b 00 1c 01 00 05 48 65 6c 6c 6f 01
07、Class。nameは1a(26)。
constant_pool[19]の名前は、26に入っているそうです。
0000240 00 1a 0c 00 1b 00 1c 01 00 05 48 65 6c 6c 6f 01
0c、NameAndType。
constant_pool[20]のnameは27、descriptorは28。
0000240 00 1a 0c 00 1b 00 1c 01 00 05 48 65 6c 6c 6f 01 \0 032 \f \0 033 \0 034 001 \0 005 H e l l o 001
01、Utf8。
constant_pool[21]は、Helloという文字列。
これは今回コンパイルしたクラスの名前です。constant_pool[5]のclassがname_indexとして指定していましたね。
0000240 00 1a 0c 00 1b 00 1c 01 00 05 48 65 6c 6c 6f 01 \0 032 \f \0 033 \0 034 001 \0 005 H e l l o 001 0000260 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 \0 020 j a v a / l a n g / O b j e 0000300 63 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 c t 001 \0 020 j a v a / l a n g / S
01、Utf8。
constant_pool[22]は、java/lang/Objectという文字列。
0000300 63 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 c t 001 \0 020 j a v a / l a n g / S 0000320 79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a y s t e m 001 \0 003 o u t 001 \0 025 L j
01、Utf8。
constant_pool[23]は、java.lang.Systemという文字列。
0000320 79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a y s t e m 001 \0 003 o u t 001 \0 025 L j
01、Utf8。
constant_pool[24]は、out。System.out.printlnのoutだと思われます。
0000320 79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a y s t e m 001 \0 003 o u t 001 \0 025 L j 0000340 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 a v a / i o / P r i n t S t r e 0000360 61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 a m ; 001 \0 023 j a v a / i o / P r
01、Utf8。
constant_pool[25]は、Ljava/io/PrintStream;。
System.outはjava.io.PrintStream型なので、その情報だと思われます。
0000360 61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 a m ; 001 \0 023 j a v a / i o / P r 0000400 69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e i n t S t r e a m 001 \0 \a p r i n
01、Utf8。
constant_pool[26]は、java.io.PrintStream。
0000400 69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e i n t S t r e a m 001 \0 \a p r i n 0000420 74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e t l n 001 \0 025 ( L j a v a / l a n
01、Utf8。
constant_pool[27]は、println。
0000420 74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e t l n 001 \0 025 ( L j a v a / l a n 0000440 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 g / S t r i n g ; ) V \0 ! \0 005 \0
01、Utf8。
constant_pool[28]は、(Ljava/lang/String;)V。
Stringを引数に取って戻り値はvoid。printlnメソッドの型を示しているようです。
これでconstant_poolに関する記述は終了です。まとめると、こういうことになります。
constant_pool[1] : Method, class_index -> 6, name_and_type_index -> 15 constant_pool[2] : Field, class_index -> 16, name_and_type_index -> 17 constant_pool[3] : String, string_index -> 18 constant_pool[4] : Method, class_index -> 19, name_and_type_index -> 20 constant_pool[5] : Class, name_index -> 21 constant_pool[6] : Class, name_index -> 22 constant_pool[7] : Utf8, <init> constant_pool[8] : Utf8, ()V constant_pool[9] : Utf8, Code constant_pool[10] : Utf8, LineNumberTable constant_pool[11] : Utf8, main constant_pool[12] : Utf8, ([Ljava/lang/String;)V constant_pool[13] : Utf8, SourceFile constant_pool[14] : Utf8, Hello.java constant_pool[15] : NameAndType, name_index -> 7, descriptor_index -> 8 constant_pool[16] : Class, name_index -> 23 constant_pool[17] : NameAndType, name_index -> 24, descriptor_index -> 25 constant_pool[18] : Utf8, hello world constant_pool[19] : Class, name_index -> 26 constant_pool[20] : NameAndType, name_index -> 27, descriptor_index -> 28 constant_pool[21] : Utf8, Hello constant_pool[22] : Utf8, java/lang/Object constant_pool[23] : Utf8, java.lang.System constant_pool[24] : Utf8, out constant_pool[25] : Utf8, Ljava/io/PrintStream; constant_pool[26] : Utf8, java.io.PrintStream constant_pool[27] : Utf8, println constant_pool[28] : Utf8, (Ljava/lang/String;)V
さらに参照先などをまとめると、こういうことになります。
constant_pool[1] : Method -> java.lang.Object, <init>, ()V constant_pool[2] : Field -> java.lang.System, out, java.io.PrintStream constant_pool[3] : String -> hello world constant_pool[4] : Method -> java.io.PrintStream, println, (Ljava/lang/String;)V constant_pool[5] : Class -> Hello constant_pool[6] : Class -> java.lang.Object constant_pool[16] : Class -> java.lang.System constant_pool[19] : Class -> java.io.PrintStream
1はObjectクラスのコンストラクタ、2はSystem.outフィールド、3はhello worldという文字列、4はprintlnメソッド、5はHelloクラス、6はObjectクラス、16はSystemクラス、19はPrintStreamクラスということのようです。
mainメソッドに関する記述が見当たりませんが、その辺は後に出てくるmethod_info methods[methods_count]あたりで定義されることになります。
ここまででバイトコードの7割は読み終わりました。残り3割、いってみましょう。
次の2バイトはアクセスフラグです。
u2 access_flags;
0000440 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00
00 21が指定されています。対応表は以下。
0x0001 public 0x0010 final 0x0020 super 0x0200 interface 0x0400 abstract
publicでsuperなクラスということになります。
super(ACC_SUPER)は昔のコンパイラとの互換性のための機能で古いコンパイラにはないとか説明が載っていましたが、詳細は未調査です。
次の2バイトはthis_class。
u2 this_class;
0000440 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00
00 05。constant_pool[5]の中身は、Hello。this_classはHelloクラスという意味ですね。
次の2バイトはsuper_class。
u2 super_class;
0000440 67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00 0000460 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00
00 06。constant_pool[6]の中身は、java.lang.Object。HelloクラスのsuperクラスはObjectだという意味ですね。
次の2バイトはinterfaces_count。その次はinterfaces_countで指定された数だけ、interfacesの詳細を記述します。
u2 interfaces_count; u2 interfaces[interfaces_count];
0000460 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00
今回は何もimplementsしていないので、interface_countは0個。interfacesの情報もなしです。
次の2バイトはfields_count。その次はfields_countで指定された数だけ、fieldsの記述が続きます。
u2 fields_count; field_info fields[fields_count];
0000460 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00
今回はフィールドが0個なので、00 00。field_infoの記述もなし。
メソッドの数と詳細を記述する箇所です。ここはけっこう手強い記述になっています。
u2 methods_count; method_info methods[methods_count];
0000460 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00
まず、methods_count。本例では2個になっています。コンストラクタとmainの2つだと思われます。
この後に2つのメソッドの詳細が記述されます。
method_infoの構造は以下です。
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
0000460 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 0000500 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00
まず、最初の2バイトがアクセスフラグ。00 01。アクセスフラグの詳細は以下。
0x0001 public 0x0002 private 0x0004 protected 0x0008 static 0x0010 final 0x0040 volatile 0x0080 transient
ということで、publicのメソッドということが分かります。
次にname_indexが00 07。constant_pool[7]の値は<init>。ということで、この子はコンストラクタの記述のようです。
descriptor_indexが00 08。constant_pool[8]の値は、()V。引数なしの戻り値なし。
attributes_countは、00 01。この後、attributeが1つ記述されるようです。
attributeの情報は以下。
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
0000460 06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00 0000500 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00
attribute_name_indexは、00 09。constant_pool[9]は、Codeという文字列が入っていました。
次にattribute_lengthが00 00 00 1d(29)。この後、29バイト分だけCode Attributeの記述が続くという意味になります。
Code Attributeについてはmainの方が分かりやすいので、ここでは説明を飛ばします。
mainメソッドの情報はこんな感じです。
0000540 00 01 00 09 00 0b 00 0c 00 01 00 09 00 00 00 25
access_flagsは00 09(public static)。
name_indexは00 0b(11)。constant_pool[11]の値はmainです。
descriptor_indexは、00 0c(12)。constant_pool[12]の値は、([Ljava/lang/String;)Vです。引数はString[]で戻り値がvoidという意味ですね。
attributes_countは、00 01。1個。
次にattributeの詳細。
0000540 00 01 00 09 00 0b 00 0c 00 01 00 09 00 00 00 25
attribute_name_indexは09なので、Code。attribute_lengthは、00 00 00 25(37)。ということで、この後、Code Attributeの記述が37バイト続くことになります。
0000560 00 02 00 01 00 00 00 09 b2 00 02 12 03 b6 00 04 0000600 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 0000620 03 00 08 00 04 00 01 00 0d 00 00 00 02 00 0e
上記の37バイトがコンストラクタのCode Attributeの記述。Code_attributeの構造は以下。
Code_attribute {
u2 attribute_name_index; //<= 前述の00 09(Code)
u4 attribute_length; //<= 前述の00 00 00 25(37)
u2 max_stack; //<= 00 02(2)
u2 max_locals; //<= 00 01(1)
u4 code_length; //<= 00 00 00 09(9)
u1 code[code_length]; //<= b2 00 02 12 03 b6 00 04 b1
u2 exception_table_length; //<= 00 00
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count; //<= 00 01
attribute_info attributes[attributes_count];
}
max_stackは最大のstackの数、max_localsは最大のローカル変数の数のようです。
code_lengthはcodeの長さ。code[code_length]には実際のコードが記述されています。
今回のcodeは、b2 00 02 12 03 b6 00 04 b1。
これの意味を知るには、Wikipediaのニーモニック表が見やすいです。
http://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3
b2はgetstatic。クラス変数の値を取得します。b2 00 02で、constant_pool[2](System.out)を取得することになります。
l2はldc。スタックに積みます。l2 03は、constant_pool[3](hello world)をスタックに積むという意味になります。
b6はinvokevirtual。メソッドを呼び出します。b6 00 04で、constant_pool[4](println)を呼び出します。
b1はreturn。
というわけで、static変数のSystem.outを呼び出し、hello worldという文字列をスタックに積んで、printlnメソッドを呼び出して、returnするという、mainメソッドに記述した処理がそのまま書いてあることが分かります。
codeの記述が終わると、次はexception_tableの記述を行うことになっていますが、今回は特に例外を扱っていないため、00 00が指定されています。
その次に、またAttributeの指定が待っています。
0000600 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 0000620 03 00 08 00 04 00 01 00 0d 00 00 00 02 00 0e
u2 attributes_count; //<= 00 01
attribute_info attributes[attributes_count];
attributeが1個います。次の2バイトが00 0a(10)なので、constant_pool[10]のLineNumberTableのAttributeだということが分かります。デバッグ時にソースコードの行数と対応付けてくれたり、逆コンパイル時に行番号を教えてくれたりするありがたい情報ですね。
LineNumberTable Attributeの構造は以下です。
LineNumberTable_attribute {
u2 attribute_name_index; //<= 00 0a
u4 attribute_length; //<= 00 00 00 0a
u2 line_number_table_length; //<= 00 02
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
今回は2個のline_number_tableの情報がいるようです。要約すると、下記のような情報になります。
start_pc = 0, line_number = 3 start_pc = 8, line_number = 4
これはcodeのバイトの場所と関連付けられるようです。0はcodeの中のgetstaticがある場所、8はreturnがある場所です。getstaticしている場所はコードの3行目、returnは4行目にある、という意味になるようです。
メソッドに関する記述はこれで終了。
ようやく最後の項にたどり着きました。最後はクラス自体のattributesです。
u2 attributes_count; attribute_info attributes[attributes_count];
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
0000620 03 00 08 00 04 00 01 00 0d 00 00 00 02 00 0e
attributes_countが00 01。1個。
attribute_name_indexが、00 0d。SourceFile。SourceFile Attributeは下記のような構造。
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_lengthが、00 00 00 02。2バイト。
最後に、00 0e(14)。constant_pool[14]は、Hello.java。つまり、SourceFileはHello.javaですという意味ですね。
これでhell worldのclassファイルは全て読み終わりました。いかがでしたでしょうか。個人的には読んでいるうちに、数独やピクロスをやっているようなちょっと楽しい気分になりました。
ちなみに人間が頑張らなくてもjavapを使えばこんな情報を見ることができます。
$ javap -v Hello Compiled from "Hello.java" public class Hello extends java.lang.Object SourceFile: "Hello.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #6.#15; // java/lang/Object."<init>":()V const #2 = Field #16.#17; // java/lang/System.out:Ljava/io/PrintStream; const #3 = String #18; // hello world const #4 = Method #19.#20; // java/io/PrintStream.println:(Ljava/lang/String;)V const #5 = class #21; // Hello const #6 = class #22; // java/lang/Object const #7 = Asciz <init>; const #8 = Asciz ()V; const #9 = Asciz Code; const #10 = Asciz LineNumberTable; const #11 = Asciz main; const #12 = Asciz ([Ljava/lang/String;)V; const #13 = Asciz SourceFile; const #14 = Asciz Hello.java; const #15 = NameAndType #7:#8;// "<init>":()V const #16 = class #23; // java/lang/System const #17 = NameAndType #24:#25;// out:Ljava/io/PrintStream; const #18 = Asciz hello world; const #19 = class #26; // java/io/PrintStream const #20 = NameAndType #27:#28;// println:(Ljava/lang/String;)V const #21 = Asciz Hello; const #22 = Asciz java/lang/Object; const #23 = Asciz java/lang/System; const #24 = Asciz out; const #25 = Asciz Ljava/io/PrintStream;; const #26 = Asciz java/io/PrintStream; const #27 = Asciz println; const #28 = Asciz (Ljava/lang/String;)V; { public Hello(); 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[]); Code: Stack=2, Locals=1, Args_size=1 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String hello world 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 }
自分が一晩かけて仕様書見ながら読んだ内容が、綺麗に出力されていますね。なんだか少しだけ負けた気がします。やはりclassファイルは人間が読むものではないのかもしれません。
とはいえ、実際にバイトコードを読んでみて初めて分かったこともありますし、ニーモニック表とかJVMの仕様とか今まであまり触れてこなかったものに接するきっかけになったので、勉強という意味ではけっこう良い試みだったと思います。
この経験を活かして、いつかバイナリエディタを片手にデバッグできるようになりたいものです。