システムコールはどのように呼び出されるか

システムコールはどのように呼び出されるか?gettimeofday(2)を例に調べてみた。環境はx86, CentOS 5.4。

以下のような簡単なプログラム書いて、gettimeofdayのところをデバッガで追ってみることにする。


#include
#include
int main(void){
struct timeval tv;
while(1) gettimeofday(&tv, NULL);
}

gdbからプログラム起動。


$ gdb ./a.out
(gdb) b main
(gdb) run

si(stepi)で一命令ごとに実行。gettimeofdayの中に入るまで進める。


(gdb) si
0x0804839d 5 while(1) gettimeofday(&tv, NULL);
(gdb)
0x080483a0 5 while(1) gettimeofday(&tv, NULL);
:
(gdb)
0x00afd4b5 in _dl_runtime_resolve () from /lib/ld-linux.so.2
(gdb)
0x00b8ffc0 in gettimeofday () from /lib/libc.so.6

gettimeofday () from /lib/libc.so.6 、ということはgettimeofdayはlibcにある。これは目的のライブラリ関数がどのライブラリに入っているか調べる方法 - nash’s blog でも述べたとおり。
ちなみにgettimeofday以外でも、システムコール関数はlibcのラッパー関数らしい。

ここでdisas(disassemble) で逆アセンブルしてみると、


(gdb) disas
Dump of assembler code for function gettimeofday:
0x00b8ffc0 : mov %ebx,%edx
0x00b8ffc2 : mov 0x8(%esp),%ecx
0x00b8ffc6 : mov 0x4(%esp),%ebx
0x00b8ffca : mov $0x4e,%eax
0x00b8ffcf : call *%gs:0x10
0x00b8ffd6 : mov %edx,%ebx
0x00b8ffd8 : cmp $0xfffff001,%eax
0x00b8ffdd : jae 0xb8ffe0
0x00b8ffdf : ret
0x00b8ffe0 : call 0xc182e8 <__i686.get_pc_thunk.cx>
:
0x00b8ffc2-0x00b8ffc6はシステムコールの引数の準備。BINARY HACKS #59 によると、システムコールの引数はebx, ecx, edx, eci, edi, ebp の順にレジスタに格納されるらしいが、確かにその通りの処理をしている。
0x00b8ffca の0x4e という値は/usr/include/asm/unistd.hで定義されているgettimeofdayのシステムコール番号(78=0x4e)。
0x00b8ffcf のcall 文でどこかに飛んでいるがこの先でシステムコールの実体を呼んでいるのかな?

さらに一命令ずつ進めてみる。


(gdb)
0x00b8ffc0 in gettimeofday () from /lib/libc.so.6
:
(gdb)
0x00b8ffcf in gettimeofday () from /lib/libc.so.6
(gdb)
0x00529400 in __kernel_vsyscall ()
__kernel_vsyscall () という関数に飛んだ。

ここでまた逆アセする。


(gdb) disas
Dump of assembler code for function __kernel_vsyscall:
0x00529400 <__kernel_vsyscall+0>: int $0x80
0x00529402 <__kernel_vsyscall+2>: ret
int $0x80 ということで、ここでシステムコールの正体を呼んでいる。

ここまでで、(この環境では)システムコール関数が呼ばれるとlibcから int 0x80 命令でシステムコールが発行されるという流れが分かった。

しかし、libcで


0x00b8ffcf : call *%gs:0x10
から

0x00529400 <__kernel_vsyscall+0>: int $0x80

に飛ぶ理屈が分からなかった。call *%gs:0x10 というのは、「gsセグメント+0x10のアドレスをcall する」ということみたいだが、このアドレスが実際に0x00529400 になっていることまでは確かめられなかった。