Download presentation
Presentation is loading. Please wait.
1
コンパイラの解析 (1) プログラムのリンクと実行
2
Table of Contents プログラムはどうやって動くか リンカのコマンド gdb libgcj
3
プログラムはどうやって動くか メモリ上にプログラムを展開して、プログラムカウンタをプログラム開始位置に指定する
その他、レジスタの初期化、ワークの確保など だれが、どうやって、どんなプログラムをメモリ上に配置する?
4
プログラムをメモリ上に配置 Hello.exeやa.outなどは実行バイナリと呼ばれる
実行ファイルをプログラムローダに渡して、メモリ上に展開してもらう ローダにあった実行バイナリを作れば、プログラムは実行できる
5
コンパイラ・ドライバ gccやclは「コンパイラ・ドライバ」 コンパイラ・ドライバは次の作業を行う
実行バイナリを生成するところまで一気に行う コンパイラ・ドライバは次の作業を行う コンパイル -> コンパイラの役目 アセンブル -> アセンブラの役目 リンク -> リンカの役目
6
コンパイラ・ドライバ (2) コンパイラの役割 アセンブラの役割 リンカの役割 ソースプログラムを、アセンブルファイルに変換
アセンブルファイルをオブジェクトファイルに変換 リンカの役割 複数のオブジェクトファイルをかき集めて実行バイナリに変換
7
コンパイラの役割 C言語などのプログラムを、ターゲットマシンのアセンブルプログラムに変換 gcc –S hello.c
関数名などは解決しない 高級言語->アセンブル言語へのトランスレータ gcc –S hello.c -> hello.s が作成される
8
コンパイラの作成するコード 一部省略 int main(int argc, char** argv) {
.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 int main(int argc, char** argv) { puts("Hello, world!"); } 一部省略
9
アセンブラの役割 アセンブルプログラムをオブジェクトコードに変換 gcc –c hello.s as –o hello.o hello.s
同一ファイル内のシンボルはここで解決できる ファイルをまたぐシンボルはここでは解決しない 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.o
hello.o: file format elf32-i386 Disassembly of section .text: <main>: 0: push %ebp 1: 89 e mov %esp,%ebp 3: 83 ec sub $0x8,%esp 6: 83 e4 f and $0xfffffff0,%esp 9: 83 ec 1c sub $0x1c,%esp c: push $0x0 11: e8 fc ff ff ff call 12 <main+0x12> 16: c9 leave 17: c3 ret 未解決なので シンボルテーブルを参照している
12
リンカの役割 オブジェクトコードをまとめて実行バイナリにする シンボルはこの時点で全て解決する gcc hello.o ld hello.o
13
リンクエラー ld hello.o だとリンクできない! リンクにはシンボルの全ての情報が必要 $ ld hello.o
ld: warning: cannot find entry symbol _start; defaulting to hello.o(.text+0x12): In function `main': : undefined reference to `puts' リンクにはシンボルの全ての情報が必要
14
リンクに必要なもの _startシンボル putsシンボル プログラムエントリ 後述のcrt1.oに含まれる 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からプログラムは開始
ただし、__libc_start_mainを経由 $ objdump -t /usr/lib/crt1.o | grep main *UND* main *UND* __libc_start_main
18
mainが無いときのエラー $ gcc nomain.c : undefined reference to `main‘
/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 共有ライブラリを実行時にロードする ld -dynamic-linker /lib/ld-linux.so.2
ELF形式のバイナリ ld -dynamic-linker /lib/ld-linux.so.2 Linux版のダイナミックローダ 共有ライブラリを一つでも使用してたら必須 今回はputsを使ったので必須
22
-lc libc.soというC言語の標準ライブラリをリンク 実際に使われる際に動的にリンクされる putsを使うだけでもリンクが必要
前掲の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 … w DF .text be GLIBC_2.0 puts
25
動的シンボル解決 Linux/i386では、シンボルを動的に解決するためのコードが自動で挿入される call puts@plt …
jmp *(_GLOBAL_OFFSET_TABLE_+12) 最初は動的リンクを行う リンカを呼び出すプログラム 2回目以降はputsの実体を 呼び出す
26
/usr/lib/libc_nonshared.a
含まれる関数 __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 callに続きが無い Disassembly of section .init:
0: push %ebp 1: 89 e mov %esp,%ebp 3: 83 ec sub $0x8,%esp 6: e8 fc ff ff ff call 7 <_init+0x7> callに続きが無い これだとハングアップする?
30
crtn.oの意味 crtn.oの.initセクションをダンプしてみる Disassembly of section .init:
0: c leave 1: c ret _init()の続き crti.oと組み合わさって一つの関数init()
31
セクションのマジック セクションの結合 ld … crti.o crtn.o の順に並べると_init()は一つの関数として完成する
複数のオブジェクトにまたがる同一セクションは、リンカによって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
ソースコードが手元になくても気合でトレースできる gdb a.out
34
gdb (2) – start 起動するとプロンプトが表示されて停止 $ gdb a.out
GNU gdb Red Hat Linux ( EL4rh) … (gdb) 起動するとプロンプトが表示されて停止 (gdb) 以降にgdbのコマンドを書く
35
gdb (3) – break _start _startでプログラムが停止するようにブレークポイントを設定
Breakpoint 1 at 0x804828c
36
gdb (4) – run プログラムを開始する (gdb) run
Starting program: /home/arakawa/tmp/a.out Breakpoint 1, 0x c in _start () 先ほど設定したブレークポイントにヒット
37
gdb (5) – x/i $pc プログラムカウンタ以降の命令を表示 Examine memory/4 Instructions
0x804828c <_start>: xor %ebp,%ebp 0x804828e <_start+2>: pop %esi 0x804828f <_start+3>: mov %esp,%ecx 0x <_start+5>: and $0xfffffff0,%esp Examine memory/4 Instructions $pc はプログラムカウンタの位置を保持している
38
gdb (6) – si 一命令だけ進める (gdb) si 0x0804828e in _start ()
Step Instruction
39
gdb (7) – display/i $pc 常に現在の命令を表示 Display Instruction
1: 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 0x804827c 0x804827c: jmp *0x (gdb) x/2i *0x 0x : push $0x8 0x : jmp 0x804825c (gdb) x/2i 0x804825c 0x804825c: pushl 0x 0x : jmp *0x (gdb) x/i *0x 0x4a6b90 <_dl_runtime_resolve>: push %eax
41
Gdb (9) – q プログラムを終了させる Quit (gdb) q
The 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.out sin(3.14) =
46
libgcjの利用にあたって Javaの機能を全て実現するには、下記のことも考慮しなければならない
クラスの登録 クラスの初期化 インスタンスの生成 ポリモーフィズムの実現 配列の扱い インスタンスの破棄 ガーベジコレクタとの調和 例外の処理 synchronizeの処理
47
続く ちょっとしたルールの解析方法 libgcjを外部から完全に利用するまでの作業 おそらく全3~5回くらい
Similar presentations
© 2024 slidesplayer.net Inc.
All rights reserved.