Coins Java - Experiment16

gcjを使用しないでJavaを動かす

[Coins Java - Experiment]

目的

以下のプログラムと等価なプログラムをC言語で記述する。

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello, world!");
  }
}

作業

データ構造の用意

まずは、gcjで使用されるデータ型と同じ型を用意する。

(今回の実験では、フィールドを作成しないためフィールドのデータ型は割愛する)

1. primitive

type/primitive.h に以下のデータ型を用意する。

名前 対応する型 サイズ gccでの型
cjboolean boolean 8bit int(QI)
cjbyte byte 8bit int(QI)
cjshort short 16bit int(HI)
cjchar char 16bit unsigned int(HI)
cjint int 32bit int(SI)
cjlong long 64bit int(DI)
cjfloat float 32bit float
cjdouble double 64bit double
cjushort (nothing) 16bit unsigned int(HI)

ちなみに、QI,HI,SI,DIはgccで整数のサイズを指定するオプションである。

名前 意味 サイズ
QI Quarter Integer 8bit
HI Half Integer 16bit
SI Single Integer 32bit
DI Double Integer 64bit

2. リテラル

データ型ではないが、type/primitive.h にリテラルを用意する。

名前
null ((void *)0)

3. UTF-8定数

Javaでは文字列の表現としてjava.lang.Stringクラスのインスタンスを利用することが多いが、これは実行時までアドレスの計算が行えないため、gcjでは文字列定数の表現にjava.lang.Stringを比較的容易に生成できる Utf8Const というデータ型を利用する。

今回の実験では、Utf8Constと同じ形式の構造体を type/utf8const.h に cj_utf8 という名前で作成した。

メンバ名 備考
hash cjushort この文字列のハッシュ値を格納する
length cjushort この文字列の バイト数 を格納する
data cjbyte[] 末尾にNULLバイトを持つUTF-8形式の文字列表現

4. Method Table

java.lang.Classで使用されるMethod Tableは、gcj上では_Jv_Methodという名前の構造体の配列で表現される。今回の実験では、これと同じ形式の構造体を type/method.h に cj_method という名前で作成した。

メンバ名 備考
name const cj_utf8 * メソッド名を表す
signature const cj_utf8 * メソッドディスクリプタを表す
accflags cjushort アクセスフラグ
index cjushort VTable上でのインデックス
ncode void (*)() メソッドの実体
throws void ** スローする例外

5. Class

java.lang.Classと同じ形式の構造体を type/class.h に cj_class という名前で作成した。

メンバ名 備考
my_vtable void * java.lang.ClassVTable
next cj_class * 初期値 null
name const cj_utf8 * クラスの名前 (通常の完全参照名)
accflags cjushort アクセスフラグ
superclass cj_class * 親クラス
constants_size cjint コンスタントプールのサイズ
constants_tags cjbyte * コンスタントプールのタグ(0番は0)
constants_data void ** コンスタントプールのデータ(0番は無効)
methods void * このクラスで宣言したメソッドのテーブル
method_count cjshort このクラスで宣言したメソッドの個数
vtable_method_count cjshort VTableに格納されているメソッドの個数
fieldlist void * このクラスで宣言したフィールドのリスト
size_in_bytes cjint インスタンスの大きさ(フィールドサイズ+padding+4)
field_count cjshort このクラスで宣言したフィールドの個数
static_field_count cjshort このクラスで宣言したクラスフィールドの個数
vtable void * このクラスのVTable
otable void * 初期値 null
otable_syms void * 初期値 null
interfaces cj_class ** インタフェース
loader void * 初期値 null
interface_count cjshort インターフェースの個数
state cjbyte 初期値 0 (NOTHING)
thread void * 初期値 null
depth cjshort 初期値 0
ancestors cj_class ** 初期値 null
idt void * 初期値 null
arrayclass cj_class * 初期値 null
protectionDomain void * 初期値 null
chain cj_class * 初期値 null

5. VTable

VTableと同じ形式の構造体を、types/vtable.hにcj_vtableという名前で作成した。

メンバ名 備考
clas cj_class * クラス
gc_descr cjint (解析中)
methods void *[] vtableに含まれるメソッド

gcjで宣言されるVTableは、なぜか最初の8バイトが空になっていて

_ZTVN4HogeE + 8

というように、その8バイトをスキップしてクラスに登録していた。もしかすると管理領域か何かに使用されている可能性があるため、ここも真似る。(MEMO mallocしたときの管理情報と同じ?)

これを真似た型を types/vtable.h に cj_wrappedvtable という名前で作成する。

メンバ名 備考
padding0 void * 4byte
padding1 void * 4byte
vtable cj_vtable vtableに含まれるメソッド

この型が他のアーキテクチャでもこのサイズか、後ほど調査することにする。

6. インスタンス

インスタンスと同じ形式の構造体を、types/object.hにcj_objectという名前で作成する。

メンバ名 備考
vtable cj_vtable * このインスタンスのVTable
fields cjbyte[] インスタンスフィールド

Java -> Bytecode

今回は、以下のようなクラスをC言語で表現する。

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello, world!");
  }
}

このうち、mainメソッドを手動でコンパイルすると、以下のようなバイトコードを生成するはずである。

getstatic <java.io.PrintStream: java.lang.System.out>
ldc <String: "Hello, world!">
invokevirtual <java.io.PrintStream: println(java.lang.String)>
return

用意すべきデータを洗い出す

データ 目的
Hello.class Helloクラス
Hello Helloクラス.name
Constant Pool Helloクラス.constant*
Method Table Helloクラス.methods
vtable for Hello Helloクラス.vtable
main Method Table
([Ljava.lang.String;)V Method Table
Hello, world! Constant Pool
Hello#main(String[]) メソッド本体

必要な外部参照シンボルを洗い出す

名前 目的
vtable for java.lang.Class Helloのクラスインスタンスを作成する
java.lang.Object.class Helloのクラスインスタンスを作成する
java.lang.System.class Systemクラスの初期化
java.lang.System.out System.outの取得

外部シンボルのmangle

libgcj内で使用されているシンボルのほとんどは、mangleされて判別しにくくなっている。

これらのmangle後の名前を推測する。

名前 mangle後の名前
vtable for java.lang.Class cj_wappedvtable _ZTVN4java4lang5ClassE
java.lang.Object.class cj_class _ZN4java4lang6Object6class$E
java.lang.System.class cj_class _ZN4java4lang6System6class$E
java.lang.System.out cj_object * _ZN4java4lang6System3out$E

文字列定数の用意

用意すべきデータをもとに、文字列定数を用意する。

名前 hash length 文字列 備考
class_name_Hello 10418 5 Hello Helloクラスの名前
method_name_main 1465 4 main mainメソッドの名前
method_desc_main 59434 22 ([Ljava.lang.String;)V mainメソッドのディスクリプタ
str_helloworld 52213 13 Hello, world! 表示したい文字列

結局、以下のようなデータを用意することになる。

static const cj_utf8 class_name_Hello = {
	10418,
	5,
	"Hello"
};
static const cj_utf8 method_name_main = {
	1465,
	4,
	"main"
};
static const cj_utf8 method_desc_main = {
	59434,
	22,
	"([Ljava.lang.String;)V"
};
static const cj_utf8 str_helloworld = {
	52213,
	13,
	"Hello, world!"
};

コンスタントプールの用意

今回、使用される文字列は "Hello, world!" のみなので、長さ2(0番は未使用)のコンスタントプールを用意する。

index type データ 備考
0 0 null
1 CONSTANT_String(8) &str_helloworld Hello, world!

結局、以下のようなデータを用意することになる。

static cjbyte _CT_Hello[] = {
	0,  // EMPTY
	8   // String
};
static void *_CD_Hello[] = {
	null,                    // EMPTY
	(void *)&str_helloworld  // "Hello, world!"
};

Method Tableの用意

本来は暗黙のコンストラクタが自動的に追加されるが、今回は面倒なので割愛する。

以下のようなデータを用意した。

static cj_method _MT_Hello[] = {
	// Hello.main(String[])
	{
		&method_name_main, // name
		&method_desc_main, // ([Ljava.lang.String;)V
		0x4009,            // COMPILED PUBLIC STATIC
		-1,                // index (not a vtable method)
		Hello_main,        // ncode
		0                  // throws
	}
};

Hello.class の用意

ここまでの情報を元に、Hello.classを作成する。

cj_class Hello_class = {
 	&(_ZTVN4java4lang5ClassE.vtable),   // (vtable)
	null,                               // next
	&class_name_Hello,                  // name
	1,                                  // acc flags
	&_ZN4java4lang6Object6class$E,      // superclass
	2,                                  // constants.count
	&_CT_Hello[0],                      // constants.tags
	&_CD_Hello[0],                      // constants.data
	&_MT_Hello[0],                      // methodtable
	1,                                  // method_count
	5,                                  // vtable_method_count (from object)
	null,                               // fields
	4,                                  // size_in_bytes(contains implicit vtable)
	0,                                  // field_count
	0,                                  // static_field_count
	null,                               // vtable
	null,                               // otable
	null,                               // otable_syms
	null,                               // interfaces
	null,                               // loader
	0,                                  // interface_count
	0,                                  // state (= nothing)
	null,                               // thread
	0,                                  // depth
	null,                               // ancestors
	null,                               // idt
	null,                               // arrayclass
	null,                               // protectionDomain
	null                                // chain
};

Hello#main(String[]) の用意

バイトコードマッピングを元に、メソッドをCの関数で表現する。例外処理を行わないため、invokevirtualでnullチェックを行っていない。

$ emacs hello.c
--- hello.c
#include "hello.h"
#include "types/class.h"

void _Jv_InitClass(cj_class *);

void Hello_main(cj_object *args) {
	
	cj_object *atmp0, *atmp1;
	
	// enter
	_Jv_InitClass(&Hello_class);
	
	// getstatic java.lang.System#out
	_Jv_InitClass(&_ZN4java4lang6System6class$E);
	atmp0 = _ZN4java4lang6System3outE;
	
	// ldc "Hello, world!"
	atmp1 = (cj_object *)(Hello_class.constants_data[1]);
	
	// java.io.PrintStream#println(String);
	((void (*)(cj_object *, cj_object *))atmp0->vtable->methods[27])(atmp0, atmp1);
	
	// return
	return;
}
---

main関数の用意

前回を参考に、main関数を用意する。

$ emacs main.c
--- main.c
#include "types/class.h"

void JvRunMain(cj_class *, int, const char **);

extern const char **_Jv_Compiler_Properties;
extern cj_class Hello_class;

static const char *empty[] = { 0 };

int main (int argc, const char **argv) {
  _Jv_Compiler_Properties = empty;
  JvRunMain (&Hello_class, argc, argv);
  return 0;
}
---

コンパイル

前回同様に、gcjを用いないでコンパイルを行う。

$ gcc -lgcj -Wall main.c hello.c

結果

以下のようになった。

$ ./a.out
Hello, world!

mainメソッドが呼び出せていることが確認できる。

考察

今回の実験では、作成したHelloクラスをプールに登録して、ReflectionAPIから呼び出すことができない。次回の実験でそれを試してみることにする。

Copyright (C) 2002-2006 s.arakawa