バイナリエディタなどの説明
そもそもクラスファイルはバイナリの集合
16進数
- 0-9とA-Fで16を数える
- バイナリは16進数で数える
- 10は16進数だと16ですし、11は16進数だと17
- 暗算で10進数と16進数を変換できる必要はないけど、数の大小は判別できるようにするといいし、9の次はAで、Fの次は10といった感じで次の数が何かは意識する
- よく使う数はそのうち覚える(100→256)
2進数
- 1と0で数字を数える
- およそ進数というのは数字に-1した数までしか使わない(10進数なら0-9 2進数なら0-1)
- 1を足すと繰り上がる 0 10 11 100 101 110 111
計算
- 2進数については、1が立っている桁の部分だけで足し算をしてみる。
- 2進数から16進数の変換は4桁ずつで区切ってから計算することが可能だが、10進数と2進数の変換は面倒(逆に2進数と16進数は楽)
Pythonでの計算
>>> hex(9) '0x9' >>> bin(15) '0b1111' >>> hex(0b01101111111000001101111101011000) '0x6fe0df58' >>> bin(0xcafebabe) '0b11001010111111101011101010111110'
なお、cafebabeはクラスファイルのシグネチャー
ファイルとバイナリの関係
- コンピュータのデータはすべて16進数で表現できるけど、それを見るためのエディタがバイナリエディタ
class Hello { public static void main(String[] args) { System.out.println("hello"); } }
$ javac hello.java $ hexedit Hello.class
1バイト=8ビット(00=0000 0000)
(viやEmacsだと最後に勝手に改行してくるので、0aが入って6バイトになっている)
6865 6c6c 6f0a
バイナリエディタの右側にアスキーコードが表示されていて、1文字が1バイトで表示されている
アドレス
- ファイル先頭からのバイト数のこと。
- バイナリエディタの左側に16進数で表示されている
- 区切りがいいので、16バイトごとに区切られている
クラスファイルのヘッダの説明
概要
- JVM仕様とはなにか
- javapを使う
- classファイルフォーマットの読み方概要
Javaのコンパイルと実行
.java→コンパイル→中間コード(.class)
- JVM プラットフォーム依存部分を吸収して、どのプラットフォームでも動かすようにするやつ
ハードウェア→OS→JVM
JVM仕様
クラスファイルのデータ構造と出力である振る舞いのルールを定義するもので、このルールを満たす実装をJVMと呼ぶ(特定のJVMの仕様は定めない)
クラスファイルにさえなれば、元のコードの言語がGroovyだろうがScalaだろうがどっちでもいい
JVMの実装の詳細には踏み込まない
classファイルフォーマットとは
バイナリの並びを定義したもの
ツール
- javap バイナリデータをJavaのクラスファイルとして解釈し、JVM仕様で定義されたデータ構造とマッピングを表示する
- バイナリエディタ バイナリデータを見る。どんなバイナリでもいい
javap
- Javaの標準ツール
- OpenJDKベースのJDKに入っている
- Javaクラスファイルを逆アセンブルするのに使う
- コマンドライン上で実行
どんなときにjavapを使うか
- 基本は使わない
- 複数のコンパイラを使う状況で、コンパイラごとに出力される中間コードの差分を見たい
- 処理系を作るときに期待する入力と実際の中間コードの差分を見たい
javapを使う前提条件
- Javaがインストール
- OpenJDKがインストールされている
- JREだけではなくJDKが必要
javapを試す
Hello.javaを書いてコンパイル
class Hello { public static void main(String[] args) { System.out.println("hello"); } }
$ javac hello.java $ javap Hello Compiled from "hello.java" class Hello { Hello(); public static void main(java.lang.String[]); }
vオプションをつけてjavapを試す
javap -v hello
Classfile /home/shigemk2/junk/2014/12/Hello.class Last modified 2014/12/20; size 409 bytes MD5 checksum ddcaca2219a4a4a964e368d745b4fa33 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 }
参考
JVM仕様の目次をニーモニックでページ検索してみる(アセンブラのニーモニックとは肌色が少し違う)
javapの便利オプション
- -private アクセス制御子のコードを表示
- -c 逆アセンブルされたコードを表示
- -l 行とローカル変数テーブルを表示する
- -classpath カレントディレクトリからクラスパスへの相対 絶対パスを指定
classファイルフォーマットの読み方
P63
classファイルとはコンパイルによって生成される中間ファイル(8ビットのバイトストリームから成り立っている)
JVM仕様書のChapter4を見よ
全体は擬似構造体で、u4 u2は符号なしバイト、magicは項目 constant_poolは可変長table interfaces_countは配列の要素数 interfacesは固定長配列。クラスやインターフェースと1体1で存在する
型(u1 u2 u4)
1バイト=8ビット
magic(P64)
Java classファイルを識別するための定数値
minor_version major_version(P64)
classファイルフォーマットのバージョン
constant_pool_count(P64)
constant_poolの数(1以上必須)
constant_pool(P65)
- 可変長のtable
- 中身を読まなければ全体が何バイトあるか分からない
- 個々の要素はjavap
cp_info(P70)
コンスタントプールの構造をあらわしている
access_flags
- アクセス許可やクラスの属性をあらわす
- javapにも出力されていた項目
this_class super_class
constant_poolテーブルへのインデックス インデックス先はconstant_class_info構造体
interfaces_count interfaces
- interface_count クラス、インターフェースの直接のsuper interfaceの数
fileds_count fields
あとは対称性のある情報になっている なお、methods_countは自動生成されたメソッドも含まれる
attributes_count attributes
構造体と、その数をあらわす。新たなattributeを定義できる。
まとめ
- JVM仕様とは、入力のクラスファイルのデータ構造と出力の振る舞いのルールを定義する
- javapはclassファイルのバイナリをJVM仕様とマッピング
- classファイルフォーマットの表記ルールと読み進め方
クラスファイルを読む練習
constant_poolは配列でconstant_pool_countは配列の要素数を表すけども、実際の要素数は1少ない。countが00 1Dとすると、実際の数は28個。
構造体は擬似言語。
cp_info { u1 tag; u1 info[]; }
tagは種類を、infoは具体的な中身を指す
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
このあたりがこすい
#18 = Utf8 hello #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
定数構造
- indexとつくやつは定数プール
- classを呼ぶと自動的にObjectが生成されるんだねっていう推測
- name_indexが指す先は文字列で実体は別に存在する
- name_and_type_index 名前がinit型がV
Methodref: class_index Class
メソッド
- code属性 P85
- line_number_table P88
以上、ここまでが前置き 全体
バイトコード
本丸。このあと話す
補足
そういえばクラスファイルはビッグエンディアンでした
Javaバイトコードの説明
簡単なJavaバイトコードの読み方
内容
Javaバイトコードとは
Java仮想マシンが実際に実行する命令形式で、Java仮想マシン用アセンブリ言語のようなもの
バイトコードとコンパイラを学ぶことはJavaプログラマの助けになる
読みかた
$ javap Hello.class
$ javap -v Hello.class
実装していないけどコンストラクタ部分のクラスファイルもある Codeの下の部分がバイトコード
{ 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 }
main部分のバイトコード
chapter6に各命令の仕様がのっているから、それみるといいよ
getstatic #2 // フィールド取得 ldc #3 // 引数を渡す invokevirtual #4 // println実行 return // void返却
getstaticはb2で、なんか引数を与える
実行持データ領域とフレーム
- 引数を渡すときの挙動
- くわしいところは2.5 2.6をみてくれ
フレーム
メソッドごとに作成されて、そのメソッドのローカル変数やオペランドスタックをもつ
オペランドスタック
(機械語でいうところのパラメータ)
- メソッドを呼び出すときに渡すパラメータの準備
- 呼び出したメソッドから結果の受け取り
など
オペランドスタックに引数を渡しておいて、必要なときにニーモニックに引数を渡す
ローカル変数配列
メソッド起動時のパラメータ引き渡しに使われる配列。
読み方 2
Succ(); 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=3, locals=3, args_size=1 0: iconst_1 1: istore_1 2: iconst_3 3: istore_2 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: iload_1 8: iload_2 9: iadd 10: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 16: iload_1 17: invokestatic #4 // Method succ:(I)I 20: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 23: return LineNumberTable: line 3: 0 line 4: 2 line 5: 4 line 6: 13 line 7: 23 private static int succ(int); flags: ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iload_0 1: iconst_1 2: iadd 3: ireturn LineNumberTable: line 10: 0 }
実際に眺めてみると
ローカル変数配列でいろいろごにょごにょして、オペランドスタックに積み込んで、オペランドスタックの中身を呼び出す仕組み。でもローカル変数配列の値を参照するメソッドもあったりする。
main関数
iconst_1 istore_1 iconst_3 istore_2 getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; iload_1 iload_2 iadd invokevirtual #3 // Method java/io/PrintStream.println:(I)V getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; iload_1 invokestatic #4 // Method succ:(I)I invokevirtual #3 // Method java/io/PrintStream.println:(I)V return
succ関数
iload_0 iconst_1 iadd ireturn
iconstは-1から使える
ローカル変数に入れるのがistore オペランドスタックにぶち込むのがiconst
バイトコードを読む練習
バイトコードの確認と逆コンパイル
バイトコードを真面目に読めば、Javaを復元できる
Classfile /home/shigemk2/junk/2014/12/jclass_abc/Test.class Last modified 2014/12/20; size 724 bytes MD5 checksum 8c8353723075b5cf6cd10b75545fd551 Compiled from "Test.java" class Test SourceFile: "Test.java" minor version: 0 major version: 51 flags: ACC_SUPER Constant pool: #1 = Methodref #12.#24 // java/lang/Object."<init>":()V #2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #27 // java/lang/StringBuilder #4 = Methodref #3.#24 // java/lang/StringBuilder."<init>":()V #5 = String #28 // #6 = Methodref #3.#29 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #7 = Methodref #11.#30 // Test.fib:(I)I #8 = Methodref #3.#31 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; #9 = Methodref #3.#32 // java/lang/StringBuilder.toString:()Ljava/lang/String; #10 = Methodref #33.#34 // java/io/PrintStream.println:(Ljava/lang/String;)V #11 = Class #35 // Test #12 = Class #36 // java/lang/Object #13 = Utf8 <init> #14 = Utf8 ()V #15 = Utf8 Code #16 = Utf8 LineNumberTable #17 = Utf8 main #18 = Utf8 ([Ljava/lang/String;)V #19 = Utf8 fib #20 = Utf8 (I)I #21 = Utf8 StackMapTable #22 = Utf8 SourceFile #23 = Utf8 Test.java #24 = NameAndType #13:#14 // "<init>":()V #25 = Class #37 // java/lang/System #26 = NameAndType #38:#39 // out:Ljava/io/PrintStream; #27 = Utf8 java/lang/StringBuilder #28 = Utf8 #29 = NameAndType #40:#41 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #30 = NameAndType #19:#20 // fib:(I)I #31 = NameAndType #40:#42 // append:(I)Ljava/lang/StringBuilder; #32 = NameAndType #43:#44 // toString:()Ljava/lang/String; #33 = Class #45 // java/io/PrintStream #34 = NameAndType #46:#47 // println:(Ljava/lang/String;)V #35 = Utf8 Test #36 = Utf8 java/lang/Object #37 = Utf8 java/lang/System #38 = Utf8 out #39 = Utf8 Ljava/io/PrintStream; #40 = Utf8 append #41 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #42 = Utf8 (I)Ljava/lang/StringBuilder; #43 = Utf8 toString #44 = Utf8 ()Ljava/lang/String; #45 = Utf8 java/io/PrintStream #46 = Utf8 println #47 = Utf8 (Ljava/lang/String;)V { Test(); 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=3, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: ldc #5 // String 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: bipush 10 17: invokestatic #7 // Method fib:(I)I 20: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 23: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 26: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 29: return LineNumberTable: line 3: 0 line 4: 29 private static int fib(int); flags: ACC_PRIVATE, ACC_STATIC Code: stack=3, locals=1, args_size=1 0: iload_0 1: iconst_1 2: if_icmpgt 7 5: iload_0 6: ireturn 7: iload_0 8: iconst_2 9: isub 10: invokestatic #7 // Method fib:(I)I 13: iload_0 14: iconst_1 15: isub 16: invokestatic #7 // Method fib:(I)I 19: iadd 20: ireturn LineNumberTable: line 7: 0 line 8: 5 line 10: 7 StackMapTable: number_of_entries = 1 frame_type = 7 /* same */ }