コンパイラの解析 (1)
プログラムのリンクと実行
Suguru ARAKAWA
Faculty of Computer and Information Sciences,
Hosei University
1
Table of Contents
プログラムはどうやって動くか
リンカのコマンド
gdb
libgcj
2
プログラムはどうやって動くか
メモリ上にプログラムを展開して、プログラムカウ
ンタをプログラム開始位置に指定する
 その他、レジスタの初期化、ワークの確保など
だれが、どうやって、どんなプログラムをメモリ上に
配置する?
3
プログラムをメモリ上に配置
Hello.exeやa.outなどは実行バイナリと呼ばれ
る
実行ファイルをプログラムローダに渡して、メモリ
上に展開してもらう
ローダにあった実行バイナリを作れば、プログラム
は実行できる
4
コンパイラ・ドライバ
gccやclは「コンパイラ・ドライバ」
 実行バイナリを生成するところまで一気に行う
コンパイラ・ドライバは次の作業を行う
 コンパイル
 アセンブル
 リンク
-> コンパイラの役目
-> アセンブラの役目
-> リンカの役目
5
コンパイラ・ドライバ (2)
コンパイラの役割
 ソースプログラムを、アセンブルファイルに変換
アセンブラの役割
 アセンブルファイルをオブジェクトファイルに変換
リンカの役割
 複数のオブジェクトファイルをかき集めて実行バイナリ
に変換
6
コンパイラの役割
C言語などのプログラムを、ターゲットマシンのア
センブルプログラムに変換
 関数名などは解決しない
 高級言語->アセンブル言語へのトランスレータ
gcc –S hello.c
 ->
hello.s が作成される
7
コンパイラの作成するコード
int main(int argc, char** argv) {
puts("Hello, world!");
}
.LC0:
.string "Hello, world!"
main:
pushl %ebp
movl
%esp, %ebp
subl $8, %esp
andl
$-16, %esp
subl $28, %esp
一部省略
pushl $.LC0
call puts
leave
ret
8
アセンブラの役割
アセンブルプログラムをオブジェクトコードに変換
 同一ファイル内のシンボルはここで解決できる
 ファイルをまたぐシンボルはここでは解決しない
 gcc –c hello.s
 as –o hello.o hello.s
 どちらもhello.oを作成
9
オブジェクトファイルの解析
objdumpコマンドが便利
 objdump
–t hello.o : シンボルを表示
 objdump –d hello.o : プログラムを逆アセンブル
例
 objdump
–d hello.o
10
objdump –d hello.o
$ objdump -d hello.o
hello.o:
file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55
push %ebp
1: 89 e5
mov %esp,%ebp
3: 83 ec 08
sub $0x8,%esp
6: 83 e4 f0
and $0xfffffff0,%esp
9: 83 ec 1c
sub $0x1c,%esp
c: 68 00 00 00 00
push $0x0
11: e8 fc ff ff ff
call 12 <main+0x12>
16: c9
leave
17: c3
ret
未解決なので
シンボルテーブルを
参照している
11
リンカの役割
オブジェクトコードをまとめて実行バイナリにする
 シンボルはこの時点で全て解決する
gcc hello.o
ld hello.o
12
リンクエラー
ld hello.o だとリンクできない!
$ ld hello.o
ld: warning: cannot find entry symbol _start;
defaulting to 08048094
hello.o(.text+0x12): In function `main':
: undefined reference to `puts'
リンクにはシンボルの全ての情報が必要
13
リンクに必要なもの
_startシンボル
 プログラムエントリ
 後述のcrt1.oに含まれる
putsシンボル
 C言語標準関数
 後述のlibc.so.6に含まれる
14
リンカのコマンド
ld /usr/lib/crt1.o \
hello.o \
-dynamic-linker /lib/ld-linux.so.2 \
-lc \
/usr/lib/crti.o /usr/lib/crtn.o
hello.oをリンクして実行可能にするだけで、こ
れだけのものが必要
15
リンカのコマンド (1)
ld /usr/lib/crt1.o \
hello.o \
-dynamic-linker /lib/ld-linux.so.2 \
-lc \
/usr/lib/crti.o /usr/lib/crtn.o
16
crt1.o (1)
プログラムエントリのための_startを含む
 mainではなく_startからプログラムは開始
 ここからmainが呼び出される
 ただし、__libc_start_mainを経由
$ objdump -t /usr/lib/crt1.o | grep main
00000000
*UND* 00000000 main
00000000
*UND* 00000000 __libc_start_main
17
mainが無いときのエラー
$ gcc nomain.c
/usr/lib/crt1.o(.text+0x18): In function `_start':
: undefined reference to `main‘
crt1.oをリンクする際のエラーなので、初心者に
は不親切?
18
crt1.o (2)
次の2つも呼び出す (どちらもlibcが持つ)
 __libc_csu_init:
実行前に呼び出す
 __libc_csu_fini: 実行後に呼び出す
プログラムの初期化、終了処理に使える
 これらもリンクしないと実行できない
19
リンカのコマンド (2)
ld /usr/lib/crt1.o \
hello.o \
-dynamic-linker /lib/ld-linux.so.2 \
-lc \
/usr/lib/crti.o /usr/lib/crtn.o
20
ld-linux.so.2
共有ライブラリを実行時にロードする
 ELF形式のバイナリ
ld -dynamic-linker /lib/ld-linux.so.2
 Linux版のダイナミックローダ
 共有ライブラリを一つでも使用してたら必須
 今回はputsを使ったので必須
21
-lc
libc.soというC言語の標準ライブラリをリンク
 putsを使うだけでもリンクが必要
 ただし、実体はlibc.soにない
実際に使われる際に動的にリンクされる
 前掲のld-linux.so.2の仕事
22
libc.soの実体
実はただのリンカスクリプト
 /lib/libc.so.6
の動的リンク
 /usr/libc_nonshared.aの静的リンク
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )
23
/lib/libc.so.6
標準関数の実体を持つライブラリ
$ objdump -T /lib/tls/libc.so.6 | grep puts
…
00508980 w DF .text 000001be GLIBC_2.0 puts
…
24
動的シンボル解決
Linux/i386では、シンボルを動的に解決するた
めのコードが自動で挿入される
最初は動的リンクを行う
リンカを呼び出すプログラム
2回目以降はputsの実体を
呼び出す
call [email protected][email protected]:
jmp *(_GLOBAL_OFFSET_TABLE_+12)
25
/usr/lib/libc_nonshared.a
C言語のプログラムを起動するために必要な処
理を静的にプログラムへリンク
含まれる関数
 __libc_csu_init
 _initを呼び出す
 __libc_csu_fini
 _finiを呼び出す
 そのほかにも色々と
26
リンカのコマンド (3)
ld /usr/lib/crt1.o \
hello.o \
-dynamic-linker /lib/ld-linux.so.2 \
-lc \
/usr/lib/crti.o /usr/lib/crtn.o
27
crti.o, crtn.o
_init, _finiを解決する
 __libc_csu_(init|fini)から呼び出される
28
_init()@crti.o
Disassembly of section .init:
00000000 <_init>:
0: 55
push %ebp
1: 89 e5
mov %esp,%ebp
3: 83 ec 08
sub $0x8,%esp
6: e8 fc ff ff ff
call 7 <_init+0x7>
callに続きが無い
 これだとハングアップする?
29
crtn.oの意味
crtn.oの.initセクションをダンプしてみる
Disassembly of section .init:
00000000 <.init>:
0: c9
leave
1: c3
ret
_init()の続き
 crti.oと組み合わさって一つの関数init()
30
セクションのマジック
セクションの結合
 複数のオブジェクトにまたがる同一セクションは、リン
カによって1箇所にまとめられる
 コマンドラインに指定した順序を保持する
ld … crti.o crtn.o の順に並べると_init()は
一つの関数として完成する
 crti.oとcrtn.oの間に.initセクションを持つオブジェ
クトをはさめば、_init()に任意のコードを追加できる
31
セクション
 オブジェクトコードはセクションごとにプログラムやデータを
配置する

.text
 Read
only, Executable, Initialized
 プログラムを配置する

.data
 Read/Write,
Initialized
 初期化するデータ(グローバル変数など)

.bss
 Read/Write
 実行時に割り当てられるデータ(スタック)
 セクションごとにまとめてメモリ上に配置される
32
gdb (1)
実行バイナリの解析はGNU Debuggerが便利
プログラムの挙動を1命令ずつ追える
 ソースコードが手元になくても気合でトレースできる
gdb a.out
33
gdb (2) – start
$ gdb a.out
GNU gdb Red Hat Linux (6.3.0.0-1.132.EL4rh)
…
(gdb)
起動するとプロンプトが表示されて停止
 (gdb)
以降にgdbのコマンドを書く
34
gdb (3) – break _start
_startでプログラムが停止するようにブレークポイ
ントを設定
(gdb) break _start
Breakpoint 1 at 0x804828c
35
gdb (4) – run
プログラムを開始する
(gdb) run
Starting program:
/home/arakawa/tmp/a.out
Breakpoint 1, 0x0804828c in _start ()
先ほど設定したブレークポイントにヒット
36
gdb (5) – x/i $pc
プログラムカウンタ以降の命令を表示
(gdb) x/4i $pc
0x804828c <_start>:
0x804828e <_start+2>:
0x804828f <_start+3>:
0x8048291 <_start+5>:
xor %ebp,%ebp
pop %esi
mov %esp,%ecx
and $0xfffffff0,%esp
Examine memory/4 Instructions
 $pc
はプログラムカウンタの位置を保持している
37
gdb (6) – si
一命令だけ進める
(gdb) si
0x0804828e in _start ()
Step Instruction
38
gdb (7) – display/i $pc
常に現在の命令を表示
(gdb) display/i $pc
1: x/i $pc 0x804828e <_start+2>:
pop
%esi
Display Instruction
39
gdb (8) – example
 こんな感じで次々と追える
0x080482a8 in _start ()
1: x/i $pc 0x80482a8 <_start+28>:
call 0x804827c
(gdb) x/i 0x804827c
0x804827c:
jmp *0x8049490
(gdb) x/2i *0x8049490
0x8048282:
push $0x8
0x8048287:
jmp 0x804825c
(gdb) x/2i 0x804825c
0x804825c:
pushl 0x8049484
0x8048262:
jmp *0x8049488
(gdb) x/i *0x8049488
0x4a6b90 <_dl_runtime_resolve>: push %eax
40
Gdb (9) – q
プログラムを終了させる
(gdb) q
The program is running. Exit anyway? (y or n) y
Quit
41
シンボル解決
シンボルはリンカが解決する
 リンカが動くまでにシンボルが揃っていればよい
下記のようなプログラムでも“コンパイル”は可能
int main(int argc, char** argv) {
puts("Hello, world!");
}
42
libgcj
GNU Java Compiler (gcj)が使用するJavaの
実行時ライブラリ
 Java
VM + Java APIをコンパイルしたもの
これを外側から使用すれば、Javaコンパイラの
作成が可能
43
java.lang.Math.sinの外部利用 (1)
ちょっとしたルールさえ知っていれば、JavaのAPI
をC言語からも使える
例:sin.c
double _ZN4java4lang4Math3sinEd(double);
int main() {
printf("sin(3.14) = %lf\n", _ZN4java4lang4Math3sinEd(3.14));
}
44
java.lang.Math.sinの外部利用 (2)
実行例
$ gcc sin.c -lgcj
$ ./a.out
sin(3.14) = 0.001593
で、ちょっとしたルールって?
45
libgcjの利用にあたって
 Javaの機能を全て実現するには、下記のことも考慮し
なければならない










Javaの名前空間とオブジェクトファイルの名前空間
クラスの登録
クラスの初期化
インスタンスの生成
ポリモーフィズムの実現
配列の扱い
インスタンスの破棄
ガーベジコレクタとの調和
例外の処理
synchronizeの処理
46
続く
ちょっとしたルールの解析方法
 libgcjを外部から完全に利用するまでの作業
おそらく全3~5回くらい
47
ダウンロード

libgcj1