コンパイラの解析 (3) クラスとインスタンスの初期化 Suguru ARAKAWA Faculty of Computer and Information Sciences, Hosei University 1 Table of Contents シンボル名 クラスの初期化 インスタンスの生成 2 シンボル名 JavaやC++のシンボルは名前マングルがかかった 状態でオブジェクトファイルに格納される C言語のシンボルよりも多くの情報を含む 名前空間 引数情報 3 extern “C” extern “C” の記述は、マングルの抑制 extern “C” をしないとマングルされる extern "C" void sample_c(int a) {} void sample_cxx(int a) {} $ objdump -t mangle.o | grep sample 00000000 g F .text 00000005 sample_c 00000006 g F .text 00000005 _Z10sample_cxxi 4 名前デマングルツール binutilsのc++filtコマンドでデマングルできる 慣れれば脳内にフィルタを作れる? $ objdump -t mangle.o | grep sample | c++filt 00000000 g F .text 00000005 sample_c 00000006 g F .text 00000005 sample_cxx(int) 5 名前マングルの略式法則 フィールド _Z<フィールド名> メソッド _Z<メソッド名><引数1><引数2>… <>一つ分で一つのシンボル/基本型を表す 6 名前マングル – シンボル 通常のシンボルは次の形式でマングル <名前文字数><名前> 例 a -> 1a sample -> 6sample 7 名前マングル – 名前空間つきシンボル 名前空間つきのシンボル N<シンボル1><シンボル2>…E 例 a::b -> N1a1bE java::lang::Math -> N4java4lang4MathE 8 名前マングル – 基本型 基本型は1文字にマングルされる int -> i double -> d void -> v … メソッドのマングル時、引数に利用 foo() は foo(void) と常に解釈 9 名前マングル – ポインタ ポインタは次の形式でマングル P<シンボル> 例 int * -> Pi int ** -> Pii java::lang::Object* -> PN4java4lang6ObjectE 10 名前マングル – テンプレート テンプレートは次の形式 I<シンボル1><シンボル2>…E 例 JArray<int> -> 6JArrayIiE JArray<Hoge> -> 6JArrayIN4HogeEE 11 名前マングル – ここまでの例 フィールド java::lang::Object::class$ -> _ZN4java4lang6Object6class$E java::lang::Math::PI -> _ZN4java4lang4Math2PIE メソッド hoge::foo(int, java::lang::Object*) -> _ZN4hoge3fooEiPN4java4lang6ObjectE Sample::main(JArray<java::lang::String *>*) -> _ZN6Sample4mainEP6JArrayIPN4java4lang6StringEE 12 名前マングル – ここまでの例 (分解) Sample::main(JArray<java::lang::String *>*) _Z N 6Sample 4main E P 6JArray I P N 4java 4lang 6String E E 13 名前マングル – 省略形 同じ名前にでてくるシンボルは再利用できる S<数字>_ なし, 0, 1, … の順番で先頭から数字がつき、再利用可能 hoge::Foo::b(int, hoge::Bar) -> _ZN4hoge3Foo1bEiNS_3BarE hoge::Foo::a(int, hoge::Foo) -> _ZN4hoge3Foo1aEiS0_ foo::f(java::lang::Object*,java::lang::Object*) _ZN3foo1fEPN4java4lang6ObjectES3_ 14 クラスの初期化 (1) ここの部分はどうコンパイルされる? 変数の初期化用コード public class StaticInit { public static double SQRT2 = Math.sqrt(2); } 15 クラスの初期化 (2) 逆アセンブルしてみる javap –c <class> $ javap -c StaticInit Compiled from "StaticInit.java“ .. static {}; Code: 0: ldc2_w #2; //double 2.0d 3: invokestatic #4; //Method java/lang/Math.sqrt:(D)D 6: putstatic #5; //Field SQRT2:D 9: return 16 クラス初期化子 (1) Static initializer (クラス初期化子) public class ClassInit { static { System.out.println("clinit"); } public static void main(String[] args) { System.out.println("main"); } } 17 クラス初期化子 (2) 実行すると、mainメソッドの前に呼び出される $ java ClassInit clinit main 18 クラス初期化子 (3) 誰がクラスの初期化を行うのか? mainが行っている形跡はない $ javap -c ClassInit Compiled from "ClassInit.java“ .. public static void main(java.lang.String[]); Code: 0: getstatic #2; // System.out 3: ldc #3; // "main“ 5: invokevirtual #4; // PrintStream.println(String) 8: return 19 クラス初期化のルール Java Virtual Machine Specification 2nd 次の場合、クラスTはVMによって初期化される 1. 2. 3. 4. Tの子クラスを初期化する直前 Tのインスタンスを生成する直前 Tのクラスメソッドを起動する直前 Tの定数でないクラス変数を参照する直前 同じクラスは1度しか初期化されない →Mainメソッドを呼び出す直前に初期化 20 一息 (1) Subを実行した結果は? class Circu { static final String S1 = Sub.S2; } class Sub extends Circu { static final String S2 = new String("Sub"); public static void main(String[] args) { System.out.println(Circu.S1); } } 21 一息 (2) トレースしてみる class Circu { static final String S1 = Sub.S2; } class Sub extends Circu { static final String S2 = new String("Sub"); public static void main(String[] args) { System.out.println(Circu.S1); } } クラスメソッドの起動 →Subの初期化 子クラスの初期化 →Circuの初期化 S2は非定数 →初期化までnull S2は未初期化 →S1 == null 22 一息 (3) “null”と表示される Circu.S1初期化の時点でSub.S2が未初期化 $ java Sub null 23 閑話休題 gcjでは明示的にクラスの初期化をする gcjではコンパイル済みコードを実行 クラスの初期化方法を解析 24 GCJにおけるクラスの初期化 (1) 他のクラスを初期化するコードをコンパイル クラスメソッドの呼び出しはクラス初期化の原因 java.lang.Mathを初期化する public class GcjClinit { public static double sqrt(double d) { return Math.sqrt(d); } } 25 GCJにおけるクラスの初期化 (2) コンパイルしたものを分析 $ gcj -S GcjClinit.java _ZN9GcjClinit4sqrtEd: .. (prologue) pushl $_ZN9GcjClinit6class$E call _Jv_InitClass .. call _ZN4java4lang4Math4sqrtEd .. (epilogue) 26 GCJにおけるクラスの初期化 (3) _Jv_InitClassとその引数について分析 $ echo '_ZN9GcjClinit6class$E' | c++filt GcjClinit::class$ $ objdump -T /usr/lib/libgcj.so.5 | grep _Jv_InitClass 06cc8632 w DF .text 0000002b Base _Jv_InitClass 27 GCJにおけるクラスの初期化 (4) CNIからクラスの初期化を行う実験 下記のクラスをCNIから初期化する public class ClassInit { static { System.out.println("clinit"); } public static void main(String[] args) { System.out.println("main"); } } 28 GCJにおけるクラスの初期化 (5) _Jv_InitClass(Class)を直接呼び出す Initializer::mainをCNIで作成 #include <stdio.h> #include "Initializer.h" extern "C" void *_ZN9ClassInit6class$E; extern "C" void _Jv_InitClass(void *); void Initializer::main(JArray<java::lang::String *> *) { puts("init >>"); _Jv_InitClass(&_ZN9ClassInit6class$E); puts("<< init"); } 29 GCJにおけるクラスの初期化 (6) コンパイルして実行 予想通りの場所に“clinit”の表示 $ gcj --main=Initializer ClassInit.java Initializer.java cni.cc $ ./a.out init >> clinit << init 30 GCJにおけるクラスの初期化 (7) _Jv_InitClassを使うとクラスを初期化できる コンパイラのソースlibjava/prims.ccで定義 次のことが可能になった インスタンスの生成 クラスメソッドの呼び出し (定数でない) クラスフィールドの参照 31 インスタンスの生成 (1) 簡単なソースコードを書いて検証 コンパイルしてアセンブルファイルを読む public class New { int a, b; public New(int a, int b) { this.a = a; this.b = b; } public static void main(String[] args) { new New(123, 456); } } 32 インスタンスの生成 (2) _ZN3New4mainEP6JArrayIPN4java4lang6StringEE: .. pushl $_ZN3New6class$E call _Jv_InitClass .. pushl $4 pushl $_ZN3New6class$E call _Jv_AllocObjectNoFinalizer .. pushl $456 pushl $123 pushl %eax call _ZN3NewC1Eii … 33 インスタンスの生成 (3) インスタンスの生成は次の3ステップ 1. クラスの初期化 2. インスタンス領域の割り当て 3. _Jv_InitClass 引数にclass _Jv_AllocObjectNoFinalizer 引数にclass(, インスタンスのサイズ) コンストラクタの呼び出し _ZN3NewC1Eii -> New::New(int, int) 第一引数に割り当てたインスタンス 34 インスタンスの生成 (4) 3つのステップを忠実に再現 クラス初期化、メモリ割り当て、コンストラクタ呼出し #include "Initializer.h" ... void Initializer::main(JArray<java::lang::String *> *) { /* 1 */ _Jv_InitClass(&_ZN3New6class$E); /* 2 */ void *obj = _Jv_AllocObjectNoFinalizer( (java::lang::Class*)&_ZN3New6class$E, 4); /* 3 */ _ZN3NewC1Eii(obj, 123, 456); } 35 インスタンスの生成 (5) 成功したかどうか分からないので、フィールドの値 を表示するようにしてみる 36 インスタンスの生成 (6) 構造を無理矢理たどって再現 #include <stdio.h> #include "Initializer.h“ ... void Initializer::main(JArray<java::lang::String *> *) { /* 1 */ _Jv_InitClass(&_ZN3New6class$E); /* 2 */ void *obj = _Jv_AllocObjectNoFinalizer( (java::lang::Class*)&_ZN3New6class$E, 4); /* 3 */ _ZN3NewC1Eii(obj, 123, 456); printf("a=%d, b=%d\n", ((int *)obj)[1], ((int *)obj)[2]); } 37 インスタンスの生成 (7) 実行してみる $ gcj --main=Initializer New.java Initializer.java cni.cc $ ./a.out a=123, b=456 38 次回 興味のあるところから ポリモーフィズムの実現 Javaの名前空間とオブジェクトファイルの名前空間 クラスの初期化 インスタンスの生成 クラスの登録 配列の扱い 例外の処理 synchronizeの処理 インスタンスの破棄 ガーベジコレクタとの調和 39
© Copyright 2024 ExpyDoc