コンパイラの解析 (2) GCJのデータ構造 - 1
Table of Contents おさらい Cygnus Native Interface インスタンス構造の推測 Virtual Method Table Instance Fields
おさらい (1) GNU Java Compiler (gcj)が使用するJavaの実行時ライブラリ Java VM + Java APIをコンパイルしたもの これを外側から使用すれば、Javaコンパイラの作成が可能
おさらい (2) ちょっとしたルールさえ知っていれば、JavaのAPIをC言語からも使える 例:sin.java.lang.Math.sinの外部利用 (2) 実行例 double _ZN4java4lang4Math3sinEd(double); int main() { printf("sin(3.14) = %lf\n", _ZN4java4lang4Math3sinEd(3.14)); }
おさらい (3) Javaの機能を全て実現するには、下記のことも考慮しなければならない Javaの名前空間とオブジェクトファイルの名前空間 クラスの登録 クラスの初期化 インスタンスの生成 ポリモーフィズムの実現 配列の扱い インスタンスの破棄 ガーベジコレクタとの調和 例外の処理 synchronizeの処理
Cygnus Native Interface (CNI) GCJでC++を使ってネィティブメソッドを書く方法 JNIより効率よくメソッド呼び出し 移植性は低い (GCJでしか動かない)
CNIの利用 (1) まずは単純なクラスを作成 nativeメソッドとして宣言 public class Cni { public static native void main(String[] args); }
CNIの利用 (2) 次のコマンドを入力 javac Cni.java gcjh Cni クラスファイルを生成 (gcj –C Cni.java と同様) gcjh Cni CniクラスのCNIヘッダを生成 javac Cni.java gcjh Cni
CNIの利用 (3) 以下のような宣言を含むヘッダが生成される class Cni : public ::java::lang::Object { public: Cni (); static void main (JArray< ::java::lang::String *> *); static ::java::lang::Class class$; };
CNIの利用 (4) 次のように書ける #include "Cni.h" #include <stdio.h> #include <gcj/array.h> #include <java/lang/String.h> void Cni::main(JArray<java::lang::String *> *args) { puts("Hello, CNI!"); }
CNIの利用 (5) コンパイルして実行 $ gcj --main=Cni Cni.class natCni.cc $ ./a.out Hello, CNI!
仮説 同じようなオブジェクトコードを生成すればJavaから呼び出せる C言語からJavaの関数を呼び出せるのは前述の通り
問題点 GCJはJavaをC言語に変換できない アセンブラを読む/オブジェクトコードを読む 大規模なので相当がんばる必要あり -S でアセンブルコードには変換できる アセンブラを読む/オブジェクトコードを読む デバッガも併用 大規模なので相当がんばる必要あり
インスタンス構造の推測 インスタンスは2つの情報を持つ インスタンスごとに固有の情報 同じクラスのインスタンスで共通の情報 インスタンスフィールド 同じクラスのインスタンスで共通の情報 クラスの情報 (instance of java.lang.Class) Virtual Method Table (vtable)
For your information… 古いOfficial Java VMでのインスタンス構造 Instance Fields + Instance Methods
Virtual Method Table (VTable) 仮想メソッド(Javaの普通のメソッド)を呼び出すためのテーブル ポリモーフィズムの実現に Interface Method Tableもある
VTableの構造 (1) 基底クラスでは通常のメソッドテーブル call obj->vtable[5] : Object::toString()
VTableの構造 (2) オーバーライドしたメソッドはテーブルを上書き call obj->vtable[6] : Hoge::toString()
VTableの構造 (3) 同じシーケンスで子クラスのメソッドを呼べる インスタンスごとに何を呼ぶのか変動する
Virtual Method呼び出しのトレース Hoge.javaをコンパイルして追ってみる Hoge::foo _ZN4Hoge3fooEv: ; Hoge::foo() ... (prologue) ; eax = this movl (%eax), %edx ; edx = this->vtable addl $20, %edx ; edx = &(this->vtable[5]) pushl %eax ; args[0] = this movl (%edx), %eax ; eax = this->vtable[5] call *%eax ; call this->vtable[5]
解析1 – VTableを探す (1) どうやって解析しよう?
VTableを探す (2) 下記(Hoge.s:34)にブレークポイントを貼る _ZN4Hoge3fooEv: ; Hoge::foo() ... (prologue) ; eax = this movl (%eax), %edx ; edx = this->vtable addl $20, %edx ; edx = &(this->vtable[5]) pushl %eax ; args[0] = this movl (%edx), %eax ; eax = this->vtable[5] call *%eax ; call this->vtable[5]
VTableを探す (3) Break Point + Examine Memory $ gdb a.out (gdb) break Hoge.s:34 (gdb) run Breakpoint 1, Hoge::foo () at Hoge.s:34 34 movl (%eax), %edx (gdb) x *$eax 0x80492c8 <_ZTVN4HogeE+8>: ...
VTableを探す (4) Hoge.s:163 – 明らかにVTable _ZTVN4HogeE: .long 0 .long 0 .long _ZN4Hoge6class$E .long 4 .long _ZN4java4lang6Object8finalizeEv .long _ZN4java4lang6Object8hashCodeEv .long _ZN4java4lang6Object6equalsEPS1_ .long _ZN4Hoge8toStringEv .long _ZN4java4lang6Object5cloneEv .long _ZN4Hoge3fooEv
インスタンスの構造 (1) ここまでは判明
解析2 – Instance Fieldを探す (1) 今度はどうする?
Instance Fieldを探す (2) 方針 インスタンスフィールドを使うプログラムを用意 アセンブルファイルへとコンパイル ハンドトレース デバッガで確認
Instance Fieldを探す (3) 下記のようなクラスを作ってコンパイル gcj –S –O0 Foo.java # -> Foo.s public class Foo { int field0; int field1; void m0() { this.field0 = 123; } void m1() { this.field1 = 456; } }
Instance Fieldを探す (4) ハンドトレースすると自明なものが登場 デバッガでトレースするまでも無い _ZN3Foo2m0Ev: ; this.field0 = 123 .. (prologue) ; eax = this movl $123, 4(%eax) ; *(int *)(this+4) = 123 _ZN3Foo2m1Ev: ; this.field1 = 456 .. (prologue) ; eax = this movl $456, 8(%eax) ; *(int *)(this+8) = 456
インスタンスの構造 (2) 最低限は判明
次回 興味のあるところから Javaの名前空間とオブジェクトファイルの名前空間 クラスの登録 クラスの初期化 インスタンスの生成 ポリモーフィズムの実現 : 今回分 配列の扱い インスタンスの破棄 ガーベジコレクタとの調和 例外の処理 synchronizeの処理