スライド 1

Javaへの変換による
安全なC言語の実装
上嶋 祐紀 住井 英二郎
(東北大学 大学院 情報科学研究科)
背景
 C言語ではメモリ安全性が保証されていない

予期せぬ動作やセキュリティーホールの原因
 Java言語はメモリ安全とされている
2
目的
C言語のプログラムをJava言語のプログラムに変換
(そのためのトランスレータを実装)
Cプログラムのメモリ安全性を保証
(Java処理系のメモリ安全性を仮定すれば)
+ CプログラムをJavaバイトコードで配布したり、
ブラウザ上でアプレットとして動かすことも可能に
メモリ安全な言語への変換

仮にトランスレータにバグがあっても、
実行系が保証する安全性を確保できる
3
方針
 C言語の仕様で定義されている動作:
Javaプログラムにおいて安全に模倣
 C言語の仕様で「未定義」とされている動作:
実行時検査により確実にエラーとする
あるいは
安全な(未定義でない)動作で置き換える
4
実現方法
Cのメモリモデルとポインタ演算をJavaで模倣


メモリブロック(連続したメモリ領域)
 Javaの配列により模倣
 「どのように読み書きするべきか」を表す
アクセスメソッド[大岩 04] を持たせる
Blockクラスとして実装
ポインタ
 「ベース」と「オフセット」の2ワードで表現
(Fat Pointer [Austin 他 94] [大岩 他 01] )
FatPtrクラスとして実装
型の異なるポインタ間のキャストもサポート
5
Outline
 変換の例




配列の宣言
アドレス演算
ポインタと整数の加算
キャスト
 変換に利用するJavaクラスの詳細
 最適化
 実験結果と考察
 関連研究
 結論と今後の課題
6
変換に利用するJavaのクラス (詳細は後述)
 1ワードメモリブロック


readFat
フィールド : contents
…
メソッド : アクセスメソッド
 readFat : 1ワード読み出し
 writeFat : 1ワード書き込み
 readByte : 1バイト読み出し
…
 ポインタ

: FatBlockクラス
: FatPtrクラス
contents
base offset
フィールド
 base : FatBlockオブジェクトへの参照
 offset : 現在指しているメモリが
baseから何バイト離れているかを表す整数
7
変換の例 : 配列の宣言
int i; int *p;
int a[3];
配列以外の変数は、
1要素の配列とみなす
FatBlock i = new FatBlock(1); FatBlock p = new FatBlock(1);
FatBlock a = new FatBlock(3);
p
writeFat
…
offset 0
readFat
i
…
offset 0
a
readFat
…
0
4
8
8
変換の例 : アドレス演算
p = &a[1];
p.writeFat(0 * 4, new FatPtr(a, 1 * 4));
writeFat
p
…
offset 0
base
offset
4
a
readFat
…
0
4
8
9
変換の例 : ポインタと整数の加算
p+1
FatPtr temp = p.readFat(0 * 4);
new FatPtr(temp.base, temp.offset + 1 * 4)
temp
p
readFat
…
offset 0
4
4
a
readFat
…
0
4
8
10
変換の例 : ポインタと整数の加算
p+1
FatPtr temp = p.readFat(0 * 4);
new FatPtr(temp.base, temp.offset + 1 * 4)
temp
p
readFat
…
offset 0
4
8
a
readFat
…
0
4
8
11
変換の例 : キャスト
*(char *)p // ただし、a[1]にはintが格納されているものとする
FatPtr temp = p.readFat(0 * 4);
temp.base.readByte(temp.offset)
intを模倣するJavaオブジェクト(後述)
temp
p
readFat
…
offset 0
4
4
a
readByte
…
0x12345678
0
4
8
12
変換の例 : キャスト
実行時に何もしない
(ポインタの中身は変わらない)
*(char *)p
FatPtr temp = p.readFat(0 * 4);
temp.base.readByte(temp.offset)
1バイトの整数を模倣する
Javaオブジェクト(Byteオブジェクト)
temp
p
readFat
…
offset 0
4
4
a
readByte
…
0x12
offset 4
0x12345678
0
4
8
13
Outline
 変換の例
 変換に利用するJavaクラスの詳細

Block抽象クラス
 FatBlockクラス
 FatPtrクラス
 FatIntクラス
 Fat抽象クラス
 最適化
 実験結果と考察
 関連研究
 結論と今後の課題
14
Block抽象クラス
 メモリブロックをJavaで実装
 格納される要素の型に応じた子クラスを持つ

FatBlock, ByteBlock, …
Block抽象クラス
FatBlockクラス
ByteBlockクラス
・・・
15
Block抽象クラス
abstract class Block {
int objsize; // 格納されている要素の(Cにおける)サイズ
int size; // メモリブロックのサイズ
int addr; // メモリブロックの先頭アドレス
Object[] contents;// 連続したメモリ領域を表現
abstract Fat readFat(int vo);
// 1ワード読み出し
abstract void writeFat(int vo, Fat f); // 1ワード書き込み
… // 各型の値を読み書きするためのアクセスメソッド
}
16
FatBlockクラス
class FatBlock extends Block {
Fat[] contents; // Fatは後述
FatBlock(int n) {
objsize = 4; size = 4 * n; contents = new Fat[n];
addr = AddrCounter.getAddr();
AddrCounter.incrAddr(size);
} // AddrCounterは、「現在のヒープポインタ」を模倣
Fat readFat(int vo) {//1ワード読み出し用アクセスメソッド
if(vo % 4 == 0) return this.contents[vo / 4];
else // 例外を発生
}
… // 各型の値を読み書きするためのアクセスメソッド
}
17
Fat関連のクラス
Fat抽象クラス
FatPtrクラス
FatIntクラス
 FatPtrクラス:
Fat Pointer [Austin 他 94] [大岩 他 01] をJavaで実装
 FatIntクラス:
Fat Integer [大岩 他 01] をJavaで実装
 Fat抽象クラス:
FatPtrとFatIntに共通する親クラス
18
FatPtrクラス
class FatPtr extends Fat {
/* Block base; int offset; */
FatPtr(Block b, int n) { base = b; offset = n; }
int asInt() { // ポインタを整数として表示したときの値を返す
if(this.base == null) return this.offset;
else return this.base.addr + this.offset;
}
}
19
FatIntクラス
Cでは整数とポインタを相互にキャストすることが可能:
整数もポインタと同様に表現しなければならない
→ Fat



Integer [大岩 他 01] をJavaで実装
C言語の整数も2ワードで表現する
FatPtrと同じフィールド、メソッドを持つ
baseは常にnull
20
Fat抽象クラス
FatPtrとFatIntに共通する親クラス

整数とポインタ間のキャストは何もしなくてよい
abstract class Fat {
Block base;
int offset;
abstract int asInt();
}
21
Outline
 変換の例
 変換に利用するJavaクラスの詳細
 最適化


最適化以前
データフロー解析による最適化
 実験結果と考察
 関連研究
 結論と今後の課題
22
最適化以前

→

→
Cの基本型の変数
全てJavaのBlockクラスの(サブクラスの)オブジェクトに変換、
読み書きのたびにアクセスメソッドを使用
Cの基本型の値
全てJavaのオブジェクトに変換
(Cの通常の整数は、ポインタとまったく関係のないものも
全てFatIntオブジェクトに変換)
オブジェクト生成やメソッド呼び出しが大量に起こる

通常のCプログラムの実行と比べ、大きなオーバーヘッド
23
データフロー解析による最適化
以下の条件を全て満たすCの変数を、
(BlockやFatではなく)Javaの通常の基本型変数に変換

int, long, doubleといった、Cの基本型を持つ
 ポインタを代入されることがない
 アドレス演算子(&)によりアドレスを取られることがない
 関数の仮引数ではない
24
更なる最適化
Cの基本型の値はJavaの(オブジェクトではなく)
基本型の値に変換、
「必要になる」までオブジェクトを生成しない

「必要になる」とは
 配列、構造体、ないしBlockオブジェクトに変換された
変数に代入される
 関数の実引数または返り値になる
これらの最適化により、プログラム実行時間の
ある程度の削減に成功
25
Outline
 変換の例
 変換に利用するJavaクラスの詳細
 最適化
 実験結果と考察



トランスレータ
実験結果
考察
 関連研究
 結論と今後の課題
26
トランスレータ
 Cプログラムを入力として、Javaプログラムを出力
 Objective
Camlで実装
 Cプログラムの字句・構文解析には
CILライブラリ [Necula 他 02] を利用
 Javaプログラムのpretty
printerには
Joustライブラリ [Cooper] を利用
27
実験
 The
Computer Language Shootout Benchmarksの
9個のCプログラムを用いた
 実験環境

CPU : Pentium 4 2.80 GHz
 メインメモリ : 2 GB
 OS : Linux 2.6
 GCC : 4.0.0 –O3 オプション
 JavaコンパイラおよびJava仮想マシン : JDK 1.5.0
28
実験結果
nsieve
ループ内で配列を用いて整数演算を行う
spectral-norm 浮動小数の配列で表された行列・ベクトルの演算を行う
mandelbrot
recursive
partial-sums
再帰関数内で整数演算あるいは浮動小数演算を行う
ループ内で浮動小数演算を行う
FSC2J (unopt) / C
FSC2J (opt) / C
65.85
1.65
1.63
recursive
5.43
1.87
mandelbrot
Java / C
23.29
3.55
3.90
spectral-norm
44.84
1.27
nsieve
26.81
57.22
3.06
27.52
49.91
70
60
50
40
30
20
10
0
ループ内で浮動小数演算を行う
partial-sums
29
実験結果
nsieve-bits
ループ内で配列を用いてビット演算を行う
fannkuch
ループ内で配列を用いて整数演算を行う
binary-trees
ループ内で構造体の確保、読み書き、解放を行う
ループ内で構造体の配列を用いて浮動小数演算を行う
n-body
90
80
1.95
binary-trees
38.68
69.67
1.35
38.99
fannkuch
42.62
2.34
10.72
nsieve-bits
50.87
17.84
0
Java / C
37.94
40
30
20
10
FSC2J (opt) / C
87.31
70
60
50
FSC2J (unopt) / C
n-body
30
考察
 mandelbrot,

partial-sums
最適化により、手書きの場合とほぼ同じ
Javaプログラムをトランスレータが生成
31
考察
 binary-trees,
n-body

全プログラムにわたって構造体を用いている
 構造体のメンバが全てBlockクラスのオブジェクトと
なることによるオーバーヘッドが大きい
→構造体のメンバをBlockとしない実装に変更

ループや再帰関数の中でポインタを用いている
 FatPtrクラスのオーバーヘッドがある
→更なる解析・最適化が必要
オフセットが常に0であるようなポインタを検出し、
通常のJavaの参照に変換?
32
考察
 nsieve,
spectral-norm, recursive, nsieve-bits,
fannkuch, binary-trees, n-body

関数の引数がBlockクラスのオブジェクトとなる
 ループや再帰関数の中でアクセスされることによる
オーバーヘッドが大きい
→関数定義 f(t x) {…} を f(t x’) { t x = x’; …} と書き換える
x’は(決してアドレスを取られないので)
Blockとする必要がない
xを最適化することも可能(最適化の条件を満たせば)
33
考察
 nsieve,
spectral-norm, nsieve-bits,
fannkuch, n-body
配列がBlockオブジェクトとなることによる
オーバーヘッドがある
→ 更なる解析・最適化が必要
アクセスメソッドを必要としない配列を検出し、
通常のJavaの配列に変換?

34
Outline
 変換の例
 変換に利用するJavaクラスの詳細
 最適化
 実験結果と考察
 関連研究


CからJavaへのトランスレータ
安全なCの処理系に関する研究
 結論と今後の課題
35
関連研究 : CからJavaへのトランスレータ
 Jazillian
[Jazillian, Inc.]
 Ephedra [Martin 他 01]


共に、CプログラムをJava言語に移植するための
支援ツール
ポインタのキャストなど、Java言語に存在しない機能は
あまりサポートしていない
我々のトランスレータ
CプログラムをJava環境で安全に実行することが目的
アクセスメソッドを使用することにより、
キャストされたポインタによるメモリアクセスにも対応
36
関連研究 : 安全なCの処理系に関する研究

CCured [Necula 他 02]




静的解析と実行時検査により、Cプログラムを安全かつ
高速に実行するための、CからCへのトランスレータ
出力されるコードの安全性は、
トランスレータの正当性によってのみ保証
ポインタやキャストの実現が十分ではなく、
ポインタと整数のキャストに一部対応していない
Fail-Safe C [Oiwa 他 01]

Cプログラムを安全に実行するための、CからCへのトランスレータ
我々のトランスレータ
出力を高水準で安全なJavaプログラムとした
→ Javaの安全性やクラス機構を利用して、
Fail-Safe Cの概念をより明確かつ単純に実現した
37
Outline
 変換の例
 変換に利用するJavaクラスの詳細
 最適化
 実験結果と考察
 関連研究
 結論と今後の課題
38
結論
メモリ安全な言語(Java)への変換により、
Cプログラムのメモリ安全性を確保する手法を提案
39
今後の課題


ANSI C フルセットのサポート
 符号なし整数、共用体、関数ポインタ、多次元配列、goto文
標準ないし既存のCライブラリへの対応
 Cプログラム(を変換したJavaプログラム)から
Javaのライブラリを利用する方法についても検討
当座の目標 : SPEC CPU ベンチマーク(の大半)の動作


(考察で述べた)最適化
 JITコンパイラがどのような最適化をどこまで自動的に行うか
(あるいは行わないか)できるだけ具体的に調べる
他の安全な言語(C#やMLなど)への変換
 変換されたプログラムの性能
40
 C言語の仕様をどこまでサポートできるか