Javaのクラスファイルをバイナリで読んでみたい
やりたいこと
Javaのクラスファイル読解入門として、バイナリから出力内容の場所を特定する。
環境
端末: MacBook Pro (Retina, 13-inch, Mid 2014) OS: macOS 10.14 Mojave
本題
基本的なHello Worldのコードは次の通りです。
public class HelloWorld { public static void main(String[] args){ System.out.println("Hello World!"); } }
実際にコンパイルして実行すると、言うまでもないですが次のとおりになります。
kmori@edelweiss ~/Desktop/javahex $ javac HelloWorld.java kmori@edelweiss ~/Desktop/javahex $ java HelloWorld Hello World!
さてここからが本題。Macでバイナリを読むには「hexdump」が便利です。
早速clasファイルを読んでみましょう。
kmori@edelweiss ~/Desktop/javahex $ hexdump -C HelloWorld.class
出力結果(見やすくするためにわざとスペースを追加しております)
00000000 ca fe ba be 00 00 00 34 00 1d 0a 00 06 00 0f 09 |…….4……..| 00000010 00 10 00 11 08 00 12 0a 00 13 00 14 07 00 15 07 |…………….| 00000020 00 16 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |…..<init>…()| 00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V…Code…LineN| 00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69 |umberTable…mai| 00000050 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 |n…([Ljava/lang| 00000060 2f 53 74 72 69 6e 67 3b 29 56 01 00 0a 53 6f 75 |/String;)V…Sou| 00000070 72 63 65 46 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 |rceFile…HelloW| 00000080 6f 72 6c 64 2e 6a 61 76 61 0c 00 07 00 08 07 00 |orld.java…….| 00000090 17 0c 00 18 00 19 01 00 0c 48 65 6c 6c 6f 20 57 |………Hello W| 000000a0 6f 72 6c 64 21 07 00 1a 0c 00 1b 00 1c 01 00 0a |orld!………..| 000000b0 48 65 6c 6c 6f 57 6f 72 6c 64 01 00 10 6a 61 76 |HelloWorld…jav| 000000c0 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 01 00 10 |a/lang/Object…| 000000d0 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d |java/lang/System| 000000e0 01 00 03 6f 75 74 01 00 15 4c 6a 61 76 61 2f 69 |…out…Ljava/i| 000000f0 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 01 00 |o/PrintStream;..| 00000100 13 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 |.java/io/PrintSt| 00000110 72 65 61 6d 01 00 07 70 72 69 6e 74 6c 6e 01 00 |ream…println..| 00000120 15 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 |.(Ljava/lang/Str| 00000130 69 6e 67 3b 29 56 00 21 00 05 00 06 00 00 00 00 |ing;)V.!……..| 00000140 00 02 00 01 00 07 00 08 00 01 00 09 00 00 00 1d |…………….| 00000150 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 |……..*…….| 00000160 01 00 0a 00 00 00 06 00 01 00 00 00 01 00 09 00 |…………….| 00000170 0b 00 0c 00 01 00 09 00 00 00 25 00 02 00 01 00 |……….%…..| 00000180 00 00 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 01 |…………….| 00000190 00 0a 00 00 00 0a 00 02 00 00 00 03 00 08 00 04 |…………….| 000001a0 00 01 00 0d 00 00 00 02 00 0e |……….| 000001aa
目的の文字列は00000090のところにありそうですね。
JavaのClassFileは以下の構造になっており、上記のバイナリに1対1で対応する模様です。
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]; }
uxのxはバイト数。つまり、 u4 magic;
は4バイトのマジックナンバーであり、上記のバイナリならばca fe ba be
がそれを意味します。
出力内容だと、可変長であるためそこからあたりをつけます。
今回出力されている文字列はcp_info付近にありそう。ってことで更に見てみます。
cp_info { u1 tag; u1 info[]; }
tagがなんの変数なのかを示すものであり、その後のinfoにて情報が記述されています。cp_infoには00 1d
より29-1=28個ある模様。
Constant Type | Value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
つまり、Stringの部分は、valueが8のところを探して、下記に照らし合わせたindexを探せばよさそう。
CONSTANT_String_info { u1 tag; u2 string_index; }
そんな具合でたどっていけば見つかる・・・はずだと思います。
次回はもっと細かく見ていきたいと思います。