詳解Linuxカーネルの10章に関連するシステムコールを呼び出す部分のコードを読んだ。
カーネルバージョン
v4.1.13
システムコールの入り口
次の呼び出し方がある。
- int 0x80:割り込みベクタにエントリポイントを入れておく
- SYSENTER:専用レジスタにエントリポイントを入れておく
- SYSCALL:本にはのっていなかったが、ある?MSR_LSTARを参照するらしい。
どうやって呼び分けるのか
必ずリンクされているlinux-vdsoなるものに含まれる?
__kernel_vsyscallはもう実体が存在しないみたい。違うようだ。
% ldd /bin/ls
linux-vdso.so.1 => (0x00007ffec3d5a000)
参考になりそうなページを見つけたので、後で読む→System calls in the Linux kernel. Part 3.
システムコールの実行
どの呼び出し方でも処理は同じで、raxにシステムコール番号を入れて、system_call関数を呼び出す。
entry_64.S
をみた。システムコールの呼び出しはこの部分。番号がNR_syscall_max
よりも大きい場合は実行せずに-ENOSYSで返る。
system_call_fastpath:
#if __SYSCALL_MASK == ~0
cmpq $__NR_syscall_max,%rax
#else
andl $__SYSCALL_MASK,%eax
cmpl $__NR_syscall_max,%eax
#endif
ja 1f /* return -ENOSYS (already in pt_regs->ax) */
movq %r10,%rcx
call *sys_call_table(,%rax,8)
movq %rax,RAX(%rsp)
1:
sys_call_table
のリストは./arch/x86/include/generated/asm/syscalls_64.h
にある。
これはコンパイル中に生成されているらしい。
cmd_arch/x86/syscalls/../include/generated/asm/syscalls_64.h := /bin/bash './arch/x86/syscalls/syscalltbl.sh' arch/x86/syscalls/syscall_64.tbl arch/x86/syscalls/../include/generated/asm/syscalls_64.h
それを読み込むのがここ。sys_ni_syscall
は初期値(コンパイラのバグ対策とコメントにはある)で、実際はincludeしたファイルの中身が入っている。
asmlinkage 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_64.h>
};
syscalls_64.hの抜粋
__SYSCALL_COMMON(0, sys_read, sys_read)
__SYSCALL_COMMON(1, sys_write, sys_write)
__SYSCALL_COMMON(2, sys_open, sys_open)
__SYSCALL_COMMON(3, sys_close, sys_close)
__SYSCALL_COMMON(4, sys_newstat, sys_newstat)
__SYSCALL_COMMON(5, sys_newfstat, sys_newfstat)
__SYSCALL_COMMON(6, sys_newlstat, sys_newlstat)
__SYSCALL_COMMON(7, sys_poll, sys_poll)
__SYSCALL_COMMON(8, sys_lseek, sys_lseek)
__SYSCALL_COMMON(9, sys_mmap, sys_mmap)
__SYSCALL_COMMON(10, sys_mprotect, sys_mprotect)
__SYSCALL_COMMON(11, sys_munmap, sys_munmap)
__SYSCALL_COMMON(12, sys_brk, sys_brk)
__SYSCALL_64(13, sys_rt_sigaction, sys_rt_sigaction)
__SYSCALL_COMMON(14, sys_rt_sigprocmask, sys_rt_sigprocmask)
__SYSCALL_64(15, stub_rt_sigreturn, stub_rt_sigreturn)
__SYSCALL_64(16, sys_ioctl, sys_ioctl)
__SYSCALL_COMMON(17, sys_pread64, sys_pread64)
マクロはこんな感じ、なのでシステムコール番号に関数ポインタが入った配列になっている。
#define __SYSCALL_64(nr, sym, compat) [nr] = sym,
なので、このcallではその関数ポインタをよぶ。
補足:アセンブリの括弧のルールをいつも忘れる・・→X86アセンブラ/GASでの文法
sys_call_table
のアドレスにシステムコール番号(rax)*ポインタのサイズ(8)を足したアドレスが関数ポインタになる。
call *sys_call_table(,%rax,8)