【问题标题】:Linux syscall, libc, VDSO and implementation dissectionLinux 系统调用、libc、VDSO 和实现剖析
【发布时间】:2016-05-09 00:21:45
【问题描述】:

我剖析了最后一个 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 寄存器中的第 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 实用程序! vdso 选择系统调用方法(sysenter 或 int 0x80)。

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

【问题讨论】:

  • glibc 由于其复杂的抽象层而极其复杂。我建议你先看一个更简单的 libc。
  • 一个简单的libc很容易理解,syscall参数存储在寄存器中,执行int 0x80或sysenter指令进入内核模式。
  • @tutuen 你在找人来解释 VDSO 或 glibc 与之交互的方法吗?如果您只是想要 VDSO 的解释,那会更容易提供。

标签: c linux system-calls libc vdso


【解决方案1】:

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)

__NR_exit 在 x86 上为 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,特别是用于单参数系统调用:

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

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

【讨论】:

    猜你喜欢
    • 2023-03-11
    • 2016-05-25
    • 1970-01-01
    • 1970-01-01
    • 2012-09-19
    • 2015-05-24
    • 2011-04-28
    • 2022-07-19
    • 2012-08-27
    相关资源
    最近更新 更多