読者です 読者をやめる 読者になる 読者になる

【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

classファイルの解析

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

仕様書を見ながらバイナリを読み取ります。

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という型の通り、名前が""、型が"()V"ということを表しています。

これらを踏まえて#1を見ると、クラスが"java/lang/Object"、名前が""、型が"()V"というメソッドを表していることが分かります。

この関係を図示します。

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]

以上でファイル全体の構造解析は終了です。