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

7

我解析了最后一个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) 宏将参数加载到 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 寄存器中吗?
register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6);

这段代码的含义是什么:
ASMARGS_0 (), "b" ((unsigned int) (arg1))

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

然后,“call *%%gs:%P2”跳转到VDSO代码?这段代码对应于“call *gs:0x10”吗?

所以,下面这个关于write系统调用的图示,正确吗?

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

我不理解VDSO实用程序!vdso选择syscall方法(sysenter或int 0x80)。
非常感谢您的帮助。对不起,我的英语很糟糕。

glibc由于其复杂的抽象层而变得非常复杂。我建议您先看一下更简单的libc。 - fuz
理解一个简单的libc非常简单,系统调用参数存储在寄存器中,并执行int 0x80或sysenter指令以进入内核模式。 - tutuen
@tutuen 你是想找人解释VDSO还是glibc与之交互的方法?如果你只想要关于VDSO的解释,那会更容易提供。 - Cel Skeggs
也许可以看一下Musl的实现?https://github.com/esmil/musl/blob/master/src/internal/vdso.c - o11c
1个回答

3

对于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,又称为ebx)中。因此,asm volatile 的参数组合(当然不是函数,只是一个内置的 gcc 工具)仅仅指定了 gcc 在系统调用之前如何准备和在系统调用完成后如何继续。
现在,生成的汇编代码看起来像这样:
; set up other registers...
movl $1, %eax
call *%gs:0x10
; tear down

%gs 是一个段寄存器,用于引用线程本地存储 - 具体而言,glibc 引用了一个保存的值,该值指向 VDSO,在首次解析告诉它 VDSO 位置的 ELF 标头时将其存储在那里。

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

因此,是的,您的图表是准确的:

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

以下是一段更简单的代码示例,用于从我维护的名为libsyscall的库中调用VDSO中的单参数系统调用。请注意保留HTML标记。

这里有一个更简单的代码示例,用于从我维护的名为libsyscall的库中调用VDSO,特别是针对单参数系统调用:

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接