プログラムがmain()にたどりつくまで
この話は、最近読んでいる
Binary Hacks ―ハッカー秘伝のテクニック100選
- 作者: 高林哲,鵜飼文敏,佐藤祐介,浜地慎一郎,首藤一幸
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2006/11/14
- メディア: 単行本(ソフトカバー)
- 購入: 23人 クリック: 383回
- この商品を含むブログ (223件) を見る
環境について
Fedora 17
gcc バージョン 4.7.2 20120921 (Red Hat 4.7.2-2) (GCC)
GNU bash, バージョン 4.2.39(1)-release (i686-redhat-linux-gnu)
です。
対象ソース
これが今回の対象です。いわゆるhello World!です。
[foo@localhost tmp]$ cat hello.c #include <stdio.h> int main(void) { printf("Hello World!"); return 0; }
最初にしたこと
straceコマンドにより、システムコールを調べた。
[foo@localhost tmp]$ strace ./hello execve("./hello", ["./hello"], [/* 45 vars */]) = 0 brk(0) = 0x94ec000 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7783000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=95319, ...}) = 0 mmap2(NULL, 95319, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb776b000 close(3) = 0 open("/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\2207\355F4\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=2011688, ...}) = 0 mmap2(0x46eba000, 1776316, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x46eba000 mprotect(0x47065000, 4096, PROT_NONE) = 0 mmap2(0x47066000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ab) = 0x47066000 mmap2(0x47069000, 10940, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x47069000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb776a000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb776a6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0x47066000, 8192, PROT_READ) = 0 mprotect(0x46eb6000, 4096, PROT_READ) = 0 munmap(0xb776b000, 95319) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7782000 write(1, "Hello World!", 12Hello World!) = 12 exit_group(0) = ? +++ exited with 0 +++
本を読むとbashの場合は、bash_4.2/execute.cmdのshell_execveからexecve(command, args, env);呼ばれている。ここのパラメータは、実行ファイル名, mainのargs, mainの環境変数がそのまま渡ってくるはずです。
execve関数は、glibc/sysdeps/unix/sysv/linux/execv.cで定義されているとあるが、実際にどうか調べてみる。
ファイルの最後にweak_alias(__execve, execve)とあり、
__execve(file, argv, envp)の定義があるのでおそらく、__execveに読み替えられると推測した。
__execveの最後でINLINE_SYSCALL (execve, 3, CHECK_STRING (file), ubp_argv, ubp_envp);としている。
glibc/sysdeps/unix/sysv/linux/i386/sysdep.hの中に定義がある。 >|| #undef INLINE_SYSCALL #define INLINE_SYSCALL(name, nr, args...) \ ({ \ unsigned int resultvar = INTERNAL_SYSCALL (name, , nr, args); \ if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (resultvar, ), 0)) \ { \ __set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, )); \ resultvar = 0xffffffff; \ } \ (int) resultvar; })
INTERNAL_SYSCALLの定義も同じファイルにあり、下記のようになっている。いくつも定義があったが、
おそらくソフトウェア割り込み(int $80)をしているものだと思い下記定義を参照した。
# define INTERNAL_SYSCALL(name, err, nr, args...) \ ({ \ register unsigned int resultvar; \ EXTRAVAR_##nr \ asm volatile ( \ LOADARGS_##nr \ "movl %1, %%eax\n\t" \ "int $0x80\n\t" \ RESTOREARGS_##nr \ : "=a" (resultvar) \ : "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \ (int) resultvar; })
nameは、execveだったので、__NR_##nameは、__NR_execveに展開されます。
__NR_execveの値は、/usr/include/asm/unistd.hより11なので、eaxレジスタに11をセットして
ソフトウェア割り込みを発生しています。ここまでユーザ空間での動作です。
カーネル空間へ
SYSCALL_VECTOR(0x80)には、カーネル初期化時にlinux-3.6.9-2.fc17.i686/arch/x86/kernel/traps.cにある
__init trap_init()の中からset_system_trap_gate(SYSCALL_VECTOR, &system_call);を読んで初期化されています。
int $0x80で発生した割り込みで、system_callが呼ばれます。
実装は、linux-3.6.9-2.fc17.i686/arch/x86/kernel/entry_32.Sの中にある。
ENTRY(system_call) RING0_INT_FRAME # can't unwind into user space anyway pushl_cfi %eax # save orig_eax (省略) syscall_call: call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF
grepしてそれらしいところも見つけました。こんな書き方あるのかい??
このファイルは,linux-3.6.9-2.fc17.i686/arch/x86/kernel/syscall_32.cです。
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { /* * Smells like a compiler bug -- it doesn't work * when the & below is removed. */ [0 ... __NR_syscall_max] = &sys_ni_syscall, #include <asm/syscalls_32.h> };
カーネルをビルドすると
linux-3.6.9-2.fc17.i686/arch/x86/include/generated/asm/syscalls_32.hができてその中は、
システムコール一覧がかかれている。(※ビルドしないとできない。)
syscalls_32.hがどうやって生成されるか調べる
linux-3.6.9-2.fc17.i686/arch/x86/syscalls/Makefileを見ると、
先頭にgenerated/asmと記載がある。
out := $(obj)/../include/generated/asm
このMakefileの中に規則がありそうなのでもう少し詳しくみていくと。
syscall32 := $(srctree)/$(src)/syscall_32.tbl syscall64 := $(srctree)/$(src)/syscall_64.tbl syshdr := $(srctree)/$(src)/syscallhdr.sh systbl := $(srctree)/$(src)/syscalltbl.sh
とあり、syscall_32.tblというファイルが怪しそう。
さらに、$(out)/syscalls_32.hのtargetがあるので、ここですね。
$(out)/syscalls_32.h: $(syscall32) $(systbl) $(call if_changed,systbl)
syscalltbl.shはwhile read nr abi name entry compactとしていて、ここの引数の数がsyscall_32.tblのフォーマットと同じであり、さらに結合すると__SYSCALL_I386となるため、ここでできる。
以上でシステムコールまわりの生成処理は、終わり。11は、__SYSCALL_I386(11, ptregs_execve, stub32_execve)なっている。
あとは、本のとおりソースを眺めた。ほとんど分からなかった。