简体   繁体   English

Linux系统调用,libc,VDSO和实现解析

[英]Linux syscall, libc, VDSO and implementation dissection

I dissects the syscall call in the last libc: 我在最后一个libc中剖析了syscall调用:

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

And I have this code in sysdeps/unix/sysv/linux/i386/sysdep.h: 我在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")

If I understand well this code, the LOADREGS_##nr(args) macro loads the argument in the registers ebx, ecx, edx, esi, edx and ebp. 如果我理解这段代码,那么LOADREGS _ ## nr(args)宏会将参数加载到寄存器ebx,ecx,edx,esi,edx和ebp中。

sysdeps/unix/sysv/linux/i386/sysdep.h 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

Where is the code which load the argument in the registers ebx, ecx, edx, esi, edx and ebp? 在ebx,ecx,edx,esi,edx和ebp寄存器中加载参数的代码在哪里? it's this code above? 这是上面的代码吗? I don't understand the implementation. 我不明白实施。 the following code load the 6th argument in the ebx register? 以下代码加载ebx寄存器中的第6个参数?

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

What does this code: 这段代码是什么:

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

It loads the first argument in the ebx register? 它加载ebx寄存器中的第一个参数?

Then the "call *%%gs:%P2" jump to the VDSO code ? 然后“call * %% gs:%P2”跳转到VDSO代码? this code correspond to "call *gs:0x10"? 这段代码对应“call * gs:0x10”?

so, this following diagram for the write syscall, it's good?: 那么,这个写系统调用的下图,它很好吗?:

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

I doesn't understand the VDSO utility! 我不懂VDSO实用程序! the vdso choose the syscall method (sysenter or int 0x80). vdso选择syscall方法(sysenter或int 0x80)。

Thank's you in advance for your help. 提前谢谢你的帮助。 And sorry my inglish is very bad. 抱歉,我的英语非常糟糕。

The macros involved in glibc's syscalls will expand to something like the following, for the example of the exit syscall. 对于退出系统调用的例子,glibc的系统调用中涉及的宏将扩展为类似下面的内容。

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) will expand to LOADREGS_0() , which will expand to nothing - LOADREGS_*(...) only need to adjust registers when more parameters are provided. LOADREGS_1(args)将扩展为LOADREGS_0() ,它将扩展为LOADREGS_*(...) - LOADREGS_*(...)只需在提供更多参数时调整寄存器。

ASMARGS_1(args) will expand to ASMARGS_0 (), "b" ((unsigned int) (arg1)) , which will expand to , "b" ((unsigned int) (arg1) . ASMARGS_1(args)将扩展为ASMARGS_0 (), "b" ((unsigned int) (arg1)) ,它将扩展为, "b" ((unsigned int) (arg1)

__NR_exit is 1 on x86. x86上__NR_exit为1。

As such, the code will expand to something like: 因此,代码将扩展为:

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

ASMARGS_* don't actually execute code per se - they're instructions to gcc to make sure that certain values (such as (unsigned int) (arg1) ) are in certain registers (such as b , aka ebx ). ASMARGS_*实际上并不执行代码本身 - 它们是gcc指令,以确保某些值(例如(unsigned int) (arg1) )在某些寄存器中(例如b ,aka ebx )。 As such, the combination of parameters to asm volatile (which isn't a function, of course, but just a gcc builtin) simply specify how gcc should prepare for the syscall and how it should continue after the syscall completes. 因此, asm volatile的参数组合(当然,这不是一个函数,只是一个gcc内置函数)只是简单地指定gcc应该如何为系统调用做准备以及在系统调用完成后它应该如何继续。

Now, the generated assembly will look something like this: 现在,生成的程序集将如下所示:

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

%gs is a segment register that references thread-local storage - specifically, glibc is referencing a saved value that points to the VDSO, which it stored there when it first parsed the ELF headers that told it where the VDSO was at. %gs是一个引用线程本地存储的段寄存器 - 具体来说,glibc引用一个指向VDSO的保存值,当它首次解析说明VDSO所在位置的ELF头时,它存储在那里。

Once the code enters the VDSO, we don't know exactly what happens - it varies depending on the kernel version - but we do know that it uses the most efficient available mechanism to run a syscall, such as the sysenter instruction or the int 0x80 instruction. 一旦代码进入VDSO,我们不确切知道发生了什么 - 它根据内核版本而变化 - 但我们知道它使用最有效的可用机制来运行系统调用,例如sysenter指令或int 0x80指令。

So, yes, your diagram is accurate: 所以,是的,你的图表是准确的:

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

Here's a simpler example of code to call into the VDSO, specifically for one-parameter syscalls, from a library that I maintain called libsyscall : 这是一个更简单的代码调用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

This simply moves parameters from the stack into registers, calls into the VDSO via a pointer loaded from memory, restores the other registers to their previous state, and returns the result of the syscall. 这只是将参数从堆栈移到寄存器中,通过从内存加载的指针调用VDSO,将其他寄存器恢复到先前的状态,并返回系统调用的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM