JVM仕様にあるクラスファイルのフォーマットについて
これはJVMアドベントカレンダー1日目の記事です。
JVM Advent Calendar 2014 - Qiita
JVM仕様にあるクラスファイルのフォーマットについて書きます。
JavaのクラスファイルのフォーマットはJVM仕様の4章で定義されています。
Chapter 4. The class File Format
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]; }
全く分からない状態からこれらのフォーマットについてひとつづつ読んでいくのは辛いと思うのでざっくり説明します。
クラスファイルが必要なので適当なJavaのソースコードを作ってコンパイルします。
class AdventCalendar2014 { public static void main(String[] args) { System.out.println("Hello JVM Advent Calendar 2014!"); } }
% javac AdventCalendar2014.java % ls AdventCalendar2014.class AdventCalendar2014.java
クラスファイルが出来たので今度はバイナリを覗いてみましょう。
以前そういうやつ書いたことがあるのでこれを使うことにします。
これをそのままコピってきてコンパイルしてさっきのクラスファイルを引数に指定すると、
2進数と16進数でクラスファイルの中身を出力します。
% java ClassReader /path/to/AdventCalendar2014.class Binary |Hex ------------ 11001010 CA 11111110 FE 10111010 BA 10111110 BE 00000000 0 00000000 0 00000000 0 00110100 34 00000000 0 00011101 1D 00001010 A 00000000 0 00000110 6 00000000 0 00001111 F 00001001 9 00000000 0 00010000 10 00000000 0 00010001 11 00001000 8 00000000 0 00010010 12 00001010 A 00000000 0 00010011 13 00000000 0 00010100 14 00000111 7 00000000 0 00010101 15 00000111 7 00000000 0 00010110 16 00000001 1 00000000 0 00000110 6 00111100 3C 01101001 69 01101110 6E 01101001 69 01110100 74 00111110 3E ・・・
2進数は読みにくいので16進数だけにしたら以下のような感じになりました。
CAFEBABE00000034001D0A0006000F09001000110800120A001300140700150700160100063C696E 69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100046D6169 6E010016285B4C6A6176612F6C616E672F537472696E673B295601000A536F7572636546696C6501 0017416476656E7443616C656E646172323031342E6A6176610C000700080700170C001800190100 1F48656C6C6F204A564D20416476656E742043616C656E64617220323031342107001A0C001B001C 010012416476656E7443616C656E646172323031340100106A6176612F6C616E672F4F626A656374 0100106A6176612F6C616E672F53797374656D0100036F75740100154C6A6176612F696F2F507269 6E7453747265616D3B0100136A6176612F696F2F5072696E7453747265616D0100077072696E746C 6E010015284C6A6176612F6C616E672F537472696E673B2956002000050006000000000002000000 070008000100090000001D00010001000000052AB70001B100000001000A00000006000100000002 0009000B000C00010009000000250002000100000009B200021203B60004B100000001000A000000 0A000200000005000800060001000D00000002000E
これを最初のJVM仕様のクラスフォーマットと照らし合わせていきましょう。
magic っていうのはマジックナンバーでJavaクラスの先頭はこの値が入ります。
これは Cafe(C++, A Front End)というC++コンパイラのプロダクトマネージャの Kim Polese さんの愛称(babe)からきているというのがまことしやかに噂として流れています。
CA FE BA BE // u4 magic; 00 00 // u2 minor_version; 00 34 // u2 major_version; 00 1D // u2 constant_pool_count;
ってみていくと cp_info っていうよく分からないのが登場しました。
これもJVM仕様読んでいくと 4.4 で以下のように定義されています。
cp_info { u1 tag; u1 info[]; }
一番最初の tag でデータの種類を定義していて、その値によって次の info[] の読み方が変わります。
例えば tag = 7 の場合は CONSTANT_Class なので、その場合は CONSTANT_Class_info を読みます。
というような感じで進めていくと以下のようになります。
CA FE BA BE // u4 magic 00 00 // u2 minor_version = 0 00 34 // u2 major_version = 57 00 1D // u2 constant_pool_count = 29 // cp_info #1 0A // tag = 10 (CONSTANT_Methodref) 00 06 // u2 class_index = 6 00 0F // u2 name_and_type_index = 15 // cp_info #2 09 // tag = 9 (CONSTANT_Fieldref) 00 10 // u2 class_index = #16 00 11 // u2 name_and_type_index = #17 // cp_info #3 08 // tag = 8 (CONSTANT_String) 00 12 // u2 string_index = #18 // cp_info #4 0A // tag = 10 (CONSTANT_Methodref) 00 13 // u2 class_index = #19 00 14 // u2 name_and_type_index = #20 // cp_info #5 07 // tag = 7 (CONSTANT_Class) 00 15 // u2 name_index = #21 // cp_info #6 07 // tag = 7 (CONSTANT_Class) 00 16 // u2 name_index = #18 // cp_info #7 01 // u1 tag = 1 (CONSTANT_Utf8) 00 06 // u2 length = 6 3C 69 6E 69 74 // u1 bytes[length] = <init> 3E // cp_info #8 01 // u1 tag = 1 (CONSTANT_Utf8) 00 03 // u2 length = 3 28 29 56 // u1 bytes[length] = ()V // cp_info #9 01 // u1 tag = 1 (CONSTANT_Utf8) 00 04 // u2 length = 4 43 6F 64 65 // u1 bytes[length] = Code // cp_info #10 01 // u1 tag = 1 (CONSTANT_Utf8) 00 0F // u2 length = 15 4C 69 6E 65 4E // u1 bytes[length] = LineNumberTable 75 6D 62 65 72 54 61 62 6C 65 // cp_info #11 01 // u1 tag = 1 (CONSTANT_Utf8) 00 04 // u2 length = 4 6D 61 69 6E // u1 bytes[length] = main // cp_info #12 01 // u1 tag = 1 (CONSTANT_Utf8) 00 16 // u2 length = 22 28 5B 4C 6A 61 // u1 bytes[length] = ([Ljava/lang/String;)V 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 // cp_info #13 01 // u1 tag = 1 (CONSTANT_Utf8) 00 0A // u2 length = 10 53 6F 75 72 63 // u1 bytes[length] = SourceFile 65 46 69 6C 65 // cp_info #14 01 // u1 tag = 1 (CONSTANT_Utf8) 00 17 // u2 length = 23 41 64 76 65 6E // u1 bytes[length] = AdventCalendar2014.java 74 43 61 6C 65 6E 64 61 72 32 30 31 34 2E 6A 61 76 61 // cp_info #15 0C // u1 tag = 12 (CONSTANT_NameAndType) 00 07 // u2 name_index = #7 00 08 // u2 descriptor_index = #8 // cp_info #16 07 // tag = 7 (CONSTANT_Class) 00 17 // u2 name_index = #23 0C // u1 tag = 12 (CONSTANT_NameAndType) 00 18 // u2 name_index = #24 00 19 // u2 descriptor_index = #25 ・・・
まぁあとは同じです。。
なんと驚くべきことに、ここまですべて手作業で解析してきましたが、さすがにもうしんどいです。
Javaチョットデキル人はご存知の人も多いですが、この解析は javap すれば簡単に出力してくれます。
% javap -c -verbose AdventCalendar2014 Classfile /path/to/AdventCalendar2014.class Last modified 2014/11/30; size 461 bytes MD5 checksum 94ccbcbc2ad1fdedf9c05cf088ca89f5 Compiled from "AdventCalendar2014.java" class AdventCalendar2014 minor version: 0 major version: 52 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 JVM Advent Calendar 2014! #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // AdventCalendar2014 #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 AdventCalendar2014.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello JVM Advent Calendar 2014! #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 AdventCalendar2014 #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 { AdventCalendar2014(); descriptor: ()V 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 2: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V 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 JVM Advent Calendar 2014! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 } SourceFile: "AdventCalendar2014.java"
こんな感じです。
明日は @sugarlife さんの「OpenJDKのソースコード配置(1-2サブディレクトリ程度まで)」です!
よろしくお願いします!