vsyscall
Rev.1を表示中。最新版はこちら。
86ではシステムコールとしてint80とペンティアムで実装されているsysenterとがあるのですが、glibcでは直接int80ないしsysenterとしてコード展開していません。glibcは__kernel_vsyscallとしてリンクしています。カーネルは立ち上がり時、ラベルを__kernel_vsyscallとして、int80ないしsysenterで、システムコールするモジュールをカーネルのページに読み込み、ユーザプロセスを起動する時に、このページのモジュールをプロセスにリンクすることで、int80ないしsysenterに透過的な動作を実現しています。
start_kernel()関数からcheck_bugs()関数が呼ばれ、そこからidentify_boot_cpu()そして、sysenter_setup()とコールされます。ここでシステムコールのモジュールがページに読み込まれます。
get_zeroed_page関数で取得したページのリニアアドレスを、vdso32_pagesにセットします。syscallのCPUなら、vsyscallにsyscallにそのモジュールのアドレスを、vsyscall_lenにそのサイズを設定します。同様にsysenterならsysenterのモジュールを、どちらでもないなら、int80のモジュールを設定しています。
そして、memcpy関数でvdso32_pagesに複写し、relocate_vdso関数でモジュールをリロケーションしています。
int __init sysenter_setup(void) { void *syscall_page = (void *)get_zeroed_page(GFP_ATOMIC); const void *vsyscall; size_t vsyscall_len; vdso32_pages[0] = virt_to_page(syscall_page); #ifdef CONFIG_X86_32 gate_vma_init(); #endif if (vdso32_syscall()) { vsyscall = &vdso32_syscall_start; vsyscall_len = &vdso32_syscall_end - &vdso32_syscall_start; } else if (vdso32_sysenter()){ vsyscall = &vdso32_sysenter_start; vsyscall_len = &vdso32_sysenter_end - &vdso32_sysenter_start; } else { vsyscall = &vdso32_int80_start; vsyscall_len = &vdso32_int80_end - &vdso32_int80_start; } memcpy(syscall_page, vsyscall, vsyscall_len); relocate_vdso(syscall_page); return 0; }root/arch/x86/vdso/vdso32.S
__INITDATA .globl vdso32_int80_start, vdso32_int80_end vdso32_int80_start: .incbin "arch/x86/vdso/vdso32-int80.so" vdso32_int80_end: .globl vdso32_syscall_start, vdso32_syscall_end vdso32_syscall_start: #ifdef CONFIG_COMPAT .incbin "arch/x86/vdso/vdso32-syscall.so" #endif vdso32_syscall_end: .globl vdso32_sysenter_start, vdso32_sysenter_end vdso32_sysenter_start: .incbin "arch/x86/vdso/vdso32-sysenter.so" vdso32_sysenter_end: __FINITsyscallはAMDプロセッサーで実装されているsysenter相当のコマンドだそうです。
root/arch/x86/vdso/vdso32/syscall.S
.globl __kernel_vsyscall .type __kernel_vsyscall,@function ALIGN __kernel_vsyscall: .LSTART_vsyscall: push %ebp .Lpush_ebp: movl %ecx, %ebp syscall movl $__USER32_DS, %ecx movl %ecx, %ss movl %ebp, %ecx popl %ebp .Lpop_ebp: retroot/arch/x86/vdso/vdso32/sysenter.S
.text .globl __kernel_vsyscall .type __kernel_vsyscall,@function ALIGN __kernel_vsyscall: .LSTART_vsyscall: push %ecx .Lpush_ecx: push %edx .Lpush_edx: push %ebp .Lenter_kernel: movl %esp,%ebp sysenter /* 7: align return point with nop's to make disassembly easier */ .space 7,0x90 /* 14: System call restart point is here! (SYSENTER_RETURN-2) */ jmp .Lenter_kernel /* 16: System call normal return point is here! */ VDSO32_SYSENTER_RETURN: /* Symbol used by sysenter.c via vdso32-syms.h */ pop %ebp .Lpop_ebp: pop %edx .Lpop_edx: pop %ecx .Lpop_ecx: retroot/arch/x86/vdso/vdso32/int80.S
.text .globl __kernel_vsyscall .type __kernel_vsyscall,@function ALIGN __kernel_vsyscall: .LSTART_vsyscall: int $0x80 retプロセスがelfフォーマットで起動される時、load_elf_binary関数で、そのプロセスの動作環境が整えられます。そして、そこからarch_setup_additional_pages関数がコールされ、VDSO(vertual dinamic share object)としての、vsyscallの処理がなされます。
vdso_enabledの値によって、処理が異なるようです。vdso_enabledはsysctrlのvdso値で設定できるようです。とりあえず、vdso_enabledがVDSO_COMPATなら、vsyscallのアドレスはVDSO_HIGH_BASEとなり、そうでないならプロセスの空いているエリアから取得するようです。
そしてそのアドレスでもって、vdso32_pagesをプロセス内にマッピングしているようですが・・・
enum { VDSO_DISABLED = 0, VDSO_ENABLED = 1, VDSO_COMPAT = 2, }; int arch_setup_additional_pages(struct linux_binprm *bprm, int exstack) { struct mm_struct *mm = current->mm; unsigned long addr; int ret = 0; bool compat; if (vdso_enabled == VDSO_DISABLED) return 0; down_write(&mm->mmap_sem); compat = (vdso_enabled == VDSO_COMPAT); map_compat_vdso(compat); if (compat) addr = VDSO_HIGH_BASE; else { addr = get_unmapped_area_prot(NULL, 0, PAGE_SIZE, 0, 0, 1); if (IS_ERR_VALUE(addr)) { ret = addr; goto up_fail; } } if (compat_uses_vma || !compat) { ret = install_special_mapping(mm, addr, PAGE_SIZE, VM_READ|VM_EXEC| VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC| VM_ALWAYSDUMP, vdso32_pages); if (ret) goto up_fail; } current->mm->context.vdso = (void *)addr; current_thread_info()->sysenter_return = VDSO32_SYMBOL(addr, SYSENTER_RETURN); up_fail: up_write(&mm->mmap_sem); return ret; }