Linux系统调用,libc,VDSO和实施解剖

我parsing了最后一个libc中的系统调用

git clone git://sourceware.org/git/glibc.git 

我在sysdeps / unix / sysv / linux / i386 / sysdep.h中有这样的代码:

 # define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \ LOADREGS_##nr(args) \ asm volatile ( \ "call *%%gs:%P2" \ : "=a" (resultvar) \ : "a" (__NR_##name), "i" (offsetof (tcbhead_t, sysinfo)) \ ASMARGS_##nr(args) : "memory", "cc") 

如果我理解了这段代码,LOADREGS _ ## nr(args)macros会将参数加载到寄存器ebx,ecx,edx,esi,edx和ebp中。

sysdeps / UNIX / SYSV / LINUX / I386 / sysdep.h中

 # define LOADREGS_0() # define ASMARGS_0() # define LOADREGS_1(arg1) \ LOADREGS_0 () # define ASMARGS_1(arg1) \ ASMARGS_0 (), "b" ((unsigned int) (arg1)) # define LOADREGS_2(arg1, arg2) \ LOADREGS_1 (arg1) # define ASMARGS_2(arg1, arg2) \ ASMARGS_1 (arg1), "c" ((unsigned int) (arg2)) # define LOADREGS_3(arg1, arg2, arg3) \ LOADREGS_2 (arg1, arg2) # define ASMARGS_3(arg1, arg2, arg3) \ ASMARGS_2 (arg1, arg2), "d" ((unsigned int) (arg3)) # define LOADREGS_4(arg1, arg2, arg3, arg4) \ LOADREGS_3 (arg1, arg2, arg3) # define ASMARGS_4(arg1, arg2, arg3, arg4) \ ASMARGS_3 (arg1, arg2, arg3), "S" ((unsigned int) (arg4)) # define LOADREGS_5(arg1, arg2, arg3, arg4, arg5) \ LOADREGS_4 (arg1, arg2, arg3, arg4) # define ASMARGS_5(arg1, arg2, arg3, arg4, arg5) \ ASMARGS_4 (arg1, arg2, arg3, arg4), "D" ((unsigned int) (arg5)) # define LOADREGS_6(arg1, arg2, arg3, arg4, arg5, arg6) \ register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6); \ LOADREGS_5 (arg1, arg2, arg3, arg4, arg5) # define ASMARGS_6(arg1, arg2, arg3, arg4, arg5, arg6) \ ASMARGS_5 (arg1, arg2, arg3, arg4, arg5), "r" (_a6) #endif /* GCC 5 */ enter code here 

在寄存器ebx,ecx,edx,esi,edx和ebp中加载参数的代码在哪里? 这是上面的代码? 我不明白的实施。 下面的代码加载ebx寄存器中的第6个参数?

 register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6); 

这个代码是什么:

 ASMARGS_0 (), "b" ((unsigned int) (arg1)) 

它加载ebx寄存器中的第一个参数?

那么“call * %% gs:%P2”跳转到VDSO代码? 这个代码对应于“call * gs:0x10”?

所以,下面这个写系统调用的图,很好吗?

 write(1, "A", 1) -----> LIBC -----> VDSO -----> KERNEL load reg ? jump to vdso |---------------------------------------------------|--------------| user land kernel land 

我不明白VDSO实用程序! vdsoselectsyscall方法(sysenter或int 0x80)。

提前感谢您的帮助。 对不起,我的英文很糟糕。

涉及到glibc的系统调用的宏将扩展到如下所示,对于exit系统调用的例子。

 LOADREGS_1(args) asm volatile ( "call *%%gs:%P2" : "=a" (resultvar) : "a" (__NR_exit), "i" (offsetof (tcbhead_t, sysinfo)) ASMARGS_1(args) : "memory", "cc") 

LOADREGS_1(args)将扩展为LOADREGS_0() ,这将扩展为无 – LOADREGS_*(...)只需要在提供更多参数时调整寄存器。

ASMARGS_1(args)将扩展为ASMARGS_0 (), "b" ((unsigned int) (arg1)) ,它将扩展为, "b" ((unsigned int) (arg1)

在x86上__NR_exit是1。

因此,代码将扩展到如下所示:

 asm volatile ( "call *%%gs:%P2" : "=a" (resultvar) : "a" (1), "i" (offsetof (tcbhead_t, sysinfo)) , "b" ((unsigned int) (arg1) : "memory", "cc") 

ASMARGS_*实际上并不执行代码 – 它们是gcc指令,以确保某些值(如(unsigned int) (arg1) )在某些寄存器(如b ,aka ebx )中。 因此,参数组合asm volatile (当然不是函数,而只是一个gcc内置函数)只是指定了gcc应该如何为系统调用做准备,以及在系统调用完成后应该如何继续。

现在,生成的程序集将如下所示:

 ; set up other registers... movl $1, %eax call *%gs:0x10 ; tear down 

%gs是引用线程本地存储的段寄存器 – 具体来说,glibc引用了一个保存的值,指向VDSO,当VDSO第一次解析ELF头文件时,该头文件告诉VDSO所处的位置。

一旦代码进入VDSO,我们不知道究竟发生了什么 – 它取决于内核版本 – 但我们知道它使用最有效的可用机制来运行系统调用,例如sysenter指令或int 0x80指令。

所以,是的,你的图是准确的:

 write(1, "A", 1) -----> LIBC -----> VDSO -----> KERNEL load reg ? jump to vdso |---------------------------------------------------|--------------| user land kernel land 

下面是一个简单的代码调用VDSO的例子,特别是对于一个参数系统调用,我维护一个名为libsyscall的库:

 _lsc_syscall1: xchgl 8(%esp), %ebx movl 4(%esp), %eax call *_lsc_vdso_ptr(,1) movl 8(%esp), %ebx # pass %eax out ret 

这只是将参数从堆栈移入寄存器,通过从内存加载的指针调用VDSO,将其他寄存器恢复到之前的状态,并返回系统调用的结果。