コンパイラの解析 (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 puts@plt … puts@plt: 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
© Copyright 2024 ExpyDoc