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