我在最后一个libc中剖析了syscall调用:

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)宏会将参数加载到寄存器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的代码在哪里?这是上面的代码吗?我不了解执行情况。
以下代码将第6个参数加载到ebx寄存器中?
register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6);

此代码是什么:
ASMARGS_0 (), "b" ((unsigned int) (arg1))

它将第一个参数加载到ebx寄存器中吗?

然后,“call * %% gs:%P2”跳转到VDSO代码?此代码对应于“调用* gs:0x10”?

所以,下面的这个写syscall的图表,好吗?:
write(1, "A", 1)  ----->   LIBC   ----->   VDSO   -----> KERNEL
                          load reg           ?
                        jump to vdso
|---------------------------------------------------|--------------|
       user land                                       kernel land

我不了解VDSO实用程序! vdso选择syscall方法(sysenter或int 0x80)。

预先感谢您的帮助。抱歉,我的英语非常不好。

最佳答案

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)将扩展为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,即ebx)。这样,asm volatile的参数组合(当然这不是函数,而只是内置的gcc)仅指定gcc应该如何为系统调用做准备,以及如何在系统调用完成后继续。

现在,生成的程序集将如下所示:
; set up other registers...
movl $1, %eax
call *%gs:0x10
; tear down
%gs是一个段寄存器,它引用线程本地存储-特别地,glibc引用了一个指向VDSO的保存值,该值是在它第一次解析告诉它VDSO所在位置的ELF header 时存储在该值中的。

一旦代码进入VDSO,我们将无法确切知道会发生什么-它取决于内核版本-但我们确实知道它使用最有效的可用机制来运行syscall,例如sysenter指令或int 0x80指令。

因此,是的,您的图表是准确的:
write(1, "A", 1)  ----->   LIBC   ----->   VDSO   -----> KERNEL
                          load reg           ?
                        jump to vdso
|---------------------------------------------------|--------------|
       user land                                       kernel land

这是一个更简单的代码示例,可以从我维护的一个名为libsyscall的库中调用VDSO,专门用于单参数syscall:
_lsc_syscall1:
    xchgl 8(%esp), %ebx
    movl 4(%esp), %eax
    call *_lsc_vdso_ptr(,1)
    movl 8(%esp), %ebx
    # pass %eax out
    ret

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

关于c - Linux syscall,libc,VDSO和实现剖析,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35115470/

10-09 13:08