gpt4 book ai didi

c - Linux 系统调用、libc、VDSO 和实现剖析

转载 作者:IT王子 更新时间:2023-10-29 00:34:29 26 4
gpt4 key购买 nike

我剖析了最后一个 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 的代码在哪里?是上面这段代码吗?我不明白实现。以下代码将第 6 个参数加载到 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(1, "A", 1)  ----->   LIBC   ----->   VDSO   -----> KERNEL
load reg ?
jump to vdso
|---------------------------------------------------|--------------|
user land kernel land

我不明白 VDSO 实用程序! vdso 选择系统调用方法(sysenter 或 int 0x80)。

预先感谢您的帮助。抱歉,我的英语很烂。

最佳答案

以 exit 系统调用为例,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 的已保存值,它在第一次解析 ELF header 时存储在那里VDSO 所在的位置。

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

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

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

这是调用 VDSO 的一个更简单的代码示例,专门用于单参数系统调用,来 self 维护的库 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,将其他寄存器恢复到它们之前的状态,并返回系统调用的结果。

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

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com