前書き

お馴染み、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のクラスファイルを、人力で読むことに挑戦してみたいと思います。

真面目に読むのは初挑戦なので、誤りがあれば指摘お願いします。

@CretedDate 2011/08/14
@Versions Java6

クラスファイルの仕様

クラスファイルの仕様はこちら。

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

バージョン情報の次に記述されるのは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_Utf8
7だったらClass、9だったらField、10だったらMethod、というような取決めのようです。

constant_pool[1]

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]の内容は後々出てきます。

constant_pool[2]

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]で指定されているようです。

constant_pool[3]

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]を参照すれば良いそうです。

constant_pool[4]

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を参照します。

constant_pool[5]

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にクラス名が指定してあるようです。

constant_pool[6]

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にいます。

constant_pool[7]

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>は確かコンストラクタのことだったはず。

constant_pool[8]

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の意味。

constant_pool[9]

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という文字列が格納されています。

constant_pool[10]

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という文字列が格納されています。

constant_pool[11]

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メソッドの名称として使われるのでしょう。

constant_pool[12]

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のメソッドという意味のようです。こちらはコンストラクタっぽいですね。

constant_pool[13]

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という文字列が格納されています。

constant_pool[14]

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という文字列です。これは今回のソースのファイル名ですね。

constant_pool[15]

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のようですね。

constant_pool[16]

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]にいる、という記述のようです。

constant_pool[17]

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)になっています。

constant_pool[18]

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という文字列を格納しています。これは今回出力している文字列ですね。

constant_pool[19]

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に入っているそうです。

constant_pool[20]

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。

constant_pool[21]

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として指定していましたね。

constant_pool[22]

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という文字列。

constant_pool[23]

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という文字列。

constant_pool[24]

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だと思われます。

constant_pool[25]

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型なので、その情報だと思われます。

constant_pool[26]

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。

constant_pool[27]

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。

constant_pool[28]

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に関する記述は終了です。まとめると、こういうことになります。

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割、いってみましょう。

access_flags

次の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)は昔のコンパイラとの互換性のための機能で古いコンパイラにはないとか説明が載っていましたが、詳細は未調査です。

this_class

次の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クラスという意味ですね。

super_class

次の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だという意味ですね。

interfaces

次の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の情報もなしです。

fields

次の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の記述もなし。

methods

メソッドの数と詳細を記述する箇所です。ここはけっこう手強い記述になっています。

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の構造は以下です。

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_info

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のmethod_info

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バイト続くことになります。

mainメソッドのCode Attribute

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

ようやく最後の項にたどり着きました。最後はクラス自体の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の仕様とか今まであまり触れてこなかったものに接するきっかけになったので、勉強という意味ではけっこう良い試みだったと思います。

この経験を活かして、いつかバイナリエディタを片手にデバッグできるようになりたいものです。