コンパイラの解析 (1)

47
1 Suguru ARAKAWA Faculty of Computer and Information Sciences, Hosei University ココココココココ (1) ココココココココココココ

Upload: mari

Post on 12-Jan-2016

39 views

Category:

Documents


4 download

DESCRIPTION

コンパイラの解析 (1). プログラムのリンクと実行. Table of Contents. プログラムはどうやって動くか リンカのコマンド gdb libgcj. プログラムはどうやって動くか. メモリ上にプログラムを展開して、プログラムカウンタをプログラム開始位置に指定する その他、レジスタの初期化、ワークの確保など だれが、どうやって、どんなプログラムをメモリ上に配置する?. プログラムをメモリ上に配置. Hello.exe や a.out などは実行バイナリと呼ばれる 実行ファイルをプログラムローダに渡して、メモリ上に展開してもらう - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: コンパイラの解析 (1)

1

Suguru ARAKAWA

Faculty of Computer and Information Sciences,

Hosei University

コンパイラの解析 (1)

プログラムのリンクと実行

Page 2: コンパイラの解析 (1)

2

Table of Contents

プログラムはどうやって動くかリンカのコマンドgdblibgcj

Page 3: コンパイラの解析 (1)

3

プログラムはどうやって動くか

メモリ上にプログラムを展開して、プログラムカウンタをプログラム開始位置に指定する その他、レジスタの初期化、ワークの確保

など

だれが、どうやって、どんなプログラムをメモリ上に配置する?

Page 4: コンパイラの解析 (1)

4

プログラムをメモリ上に配置

Hello.exe や a.out などは実行バイナリと呼ばれる

実行ファイルをプログラムローダに渡して、メモリ上に展開してもらう

ローダにあった実行バイナリを作れば、プログラムは実行できる

Page 5: コンパイラの解析 (1)

5

コンパイラ・ドライバ

gcc や cl は「コンパイラ ドライバ」・ 実行バイナリを生成するところまで一気に

行う

コンパイラ ドライバは次の作業を行う・ コンパイル-> コンパイラの役目 アセンブル-> アセンブラの役目 リンク -> リンカの役目

Page 6: コンパイラの解析 (1)

6

コンパイラ・ドライバ (2)

コンパイラの役割 ソースプログラムを、アセンブルファイル

に変換アセンブラの役割

アセンブルファイルをオブジェクトファイルに変換

リンカの役割 複数のオブジェクトファイルをかき集めて

実行バイナリに変換

Page 7: コンパイラの解析 (1)

7

コンパイラの役割

C 言語などのプログラムを、ターゲットマシンのアセンブルプログラムに変換 関数名などは解決しない 高級言語 -> アセンブル言語へのトランス

レータ

gcc –S hello.c -> hello.s が作成される

Page 8: コンパイラの解析 (1)

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

一部省略

Page 9: コンパイラの解析 (1)

9

アセンブラの役割

アセンブルプログラムをオブジェクトコードに変換 同一ファイル内のシンボルはここで解決できる ファイルをまたぐシンボルはここでは解決しな

gcc –c hello.s as –o hello.o hello.s

どちらも hello.o を作成

Page 10: コンパイラの解析 (1)

10

オブジェクトファイルの解析

objdump コマンドが便利 objdump –t hello.o : シンボルを表示 objdump –d hello.o : プログラムを逆アセ

ンブル

例 objdump –d hello.o

Page 11: コンパイラの解析 (1)

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

未解決なのでシンボルテーブルを参照している

Page 12: コンパイラの解析 (1)

12

リンカの役割

オブジェクトコードをまとめて実行バイナリにする シンボルはこの時点で全て解決する

gcc hello.old hello.o

Page 13: コンパイラの解析 (1)

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'

リンクにはシンボルの全ての情報が必要

Page 14: コンパイラの解析 (1)

14

リンクに必要なもの

_start シンボル プログラムエントリ 後述の crt1.o に含まれる

puts シンボル C 言語標準関数 後述の libc.so.6 に含まれる

Page 15: コンパイラの解析 (1)

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 をリンクして実行可能にするだけで、これだけのものが必要

Page 16: コンパイラの解析 (1)

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

Page 17: コンパイラの解析 (1)

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

Page 18: コンパイラの解析 (1)

18

main が無いときのエラー

$ gcc nomain.c/usr/lib/crt1.o(.text+0x18): In function

`_start':

: undefined reference to `main‘

crt1.o をリンクする際のエラーなので、初心者には不親切?

Page 19: コンパイラの解析 (1)

19

crt1.o (2)

次の 2 つも呼び出す ( どちらも libc が持つ ) __libc_csu_init: 実行前に呼び出す __libc_csu_fini: 実行後に呼び出す

プログラムの初期化、終了処理に使える これらもリンクしないと実行できない

Page 20: コンパイラの解析 (1)

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

Page 21: コンパイラの解析 (1)

21

ld-linux.so.2

共有ライブラリを実行時にロードする ELF 形式のバイナリ

ld -dynamic-linker /lib/ld-linux.so.2 Linux 版のダイナミックローダ 共有ライブラリを一つでも使用してたら必

須今回は puts を使ったので必須

Page 22: コンパイラの解析 (1)

22

-lc

libc.so という C 言語の標準ライブラリをリンク puts を使うだけでもリンクが必要 ただし、実体は libc.so にない

実際に使われる際に動的にリンクされる 前掲の ld-linux.so.2 の仕事

Page 23: コンパイラの解析 (1)

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 )

Page 24: コンパイラの解析 (1)

24

/ lib/libc.so.6

標準関数の実体を持つライブラリ

$ objdump -T /lib/tls/libc.so.6 | grep puts…00508980 w DF .text 000001be GLIBC_2.0

puts

Page 25: コンパイラの解析 (1)

25

動的シンボル解決

Linux/i386 では、シンボルを動的に解決するためのコードが自動で挿入される

call puts@plt…puts@plt:jmp

*(_GLOBAL_OFFSET_TABLE_+12)

最初は動的リンクを行うリンカを呼び出すプログラ

ム2 回目以降は puts の実体を

呼び出す

Page 26: コンパイラの解析 (1)

26

/usr/lib/libc_nonshared.a

C 言語のプログラムを起動するために必要な処理を静的にプログラムへリンク

含まれる関数 __libc_csu_init

_init を呼び出す __libc_csu_fini

_fini を呼び出す そのほかにも色々と

Page 27: コンパイラの解析 (1)

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

Page 28: コンパイラの解析 (1)

28

crti.o, crtn.o

_init, _fini を解決する __libc_csu_(init|fini) から呼び出される

Page 29: コンパイラの解析 (1)

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 に続きが無い これだとハングアップする?

Page 30: コンパイラの解析 (1)

30

crtn.o の意味

crtn.o の .init セクションをダンプしてみる

Disassembly of section .init:00000000 <.init>: 0: c9 leave 1: c3 ret

_init() の続き crti.o と組み合わさって一つの関数 init()

Page 31: コンパイラの解析 (1)

31

セクションのマジック

セクションの結合 複数のオブジェクトにまたがる同一セクショ

ンは、リンカによって 1 箇所にまとめられる コマンドラインに指定した順序を保持する

ld … crti.o crtn.o の順に並べると _init()は一つの関数として完成する crti.o と crtn.o の間に .init セクションを持つ

オブジェクトをはさめば、 _init() に任意のコードを追加できる

Page 32: コンパイラの解析 (1)

32

セクション

オブジェクトコードはセクションごとにプログラムやデータを配置する .text

Read only, Executable, Initialized プログラムを配置する

.data Read/Write, Initialized 初期化するデータ ( グローバル変数など )

.bss Read/Write 実行時に割り当てられるデータ ( スタック )

セクションごとにまとめてメモリ上に配置される

Page 33: コンパイラの解析 (1)

33

gdb (1)

実行バイナリの解析は GNU Debuggerが便利

プログラムの挙動を 1 命令ずつ追える ソースコードが手元になくても気合でト

レースできる

gdb a.out

Page 34: コンパイラの解析 (1)

34

gdb (2) – start

$ gdb a.outGNU gdb Red Hat Linux (6.3.0.0-

1.132.EL4rh)…(gdb)

起動するとプロンプトが表示されて停止 (gdb) 以降に gdb のコマンドを書く

Page 35: コンパイラの解析 (1)

35

gdb (3) – break _start

_start でプログラムが停止するようにブレークポイントを設定

(gdb) break _startBreakpoint 1 at 0x804828c

Page 36: コンパイラの解析 (1)

36

gdb (4) – run

プログラムを開始する(gdb) runStarting program:

/home/arakawa/tmp/a.outBreakpoint 1, 0x0804828c in _start ()

先ほど設定したブレークポイントにヒット

Page 37: コンパイラの解析 (1)

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 はプログラムカウンタの位置を保持して

いる

Page 38: コンパイラの解析 (1)

38

gdb (6) – si

一命令だけ進める(gdb) si0x0804828e in _start ()

Step Instruction

Page 39: コンパイラの解析 (1)

39

gdb (7) – display/i $pc

常に現在の命令を表示(gdb) display/i $pc1: x/i $pc 0x804828e <_start+2>: pop %esi

Display Instruction

Page 40: コンパイラの解析 (1)

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

Page 41: コンパイラの解析 (1)

41

Gdb (9) – q

プログラムを終了させる(gdb) qThe program is running. Exit anyway? (y or

n) y

Quit

Page 42: コンパイラの解析 (1)

42

シンボル解決

シンボルはリンカが解決する リンカが動くまでにシンボルが揃っていれ

ばよい

下記のようなプログラムでも“コンパイル”は可能

int main(int argc, char** argv) {

puts("Hello, world!");

}

Page 43: コンパイラの解析 (1)

43

libgcj

GNU Java Compiler (gcj) が使用するJava の実行時ライブラリ Java VM + Java API をコンパイルしたもの

これを外側から使用すれば、 Java コンパイラの作成が可能

Page 44: コンパイラの解析 (1)

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));

}

Page 45: コンパイラの解析 (1)

45

java.lang.Math.sin の外部利用 (2)

実行例

で、ちょっとしたルールって?

$ gcc sin.c -lgcj$ ./a.outsin(3.14) = 0.001593

Page 46: コンパイラの解析 (1)

46

libgcj の利用にあたって

Java の機能を全て実現するには、下記のことも考慮しなければならない Java の名前空間とオブジェクトファイルの名前空間 クラスの登録 クラスの初期化 インスタンスの生成 ポリモーフィズムの実現 配列の扱い インスタンスの破棄 ガーベジコレクタとの調和 例外の処理 synchronize の処理

Page 47: コンパイラの解析 (1)

47

続く

ちょっとしたルールの解析方法 libgcj を外部から完全に利用するまでの作

おそらく全 3~5回くらい