0

在linux kernel 2.6.11中,使用sysenter进行系统调用时,几乎和init 0x80一样,使用save_all将所有寄存器压入内核栈,但是调用完成后,如果没有设置相关标志,我们使用 sysexit 返回,但不恢复所有已保存在堆栈上的寄存器。

某些系统调用可能会更改寄存器值,为什么我们不需要重新设置所有寄存器

我已经阅读了相应的 i386 文档,它说

“Intel386 上的所有寄存器都是全局的,因此对调用函数和被调用函数都是可见的。寄存器 %ebp、%ebx、%edi、%esi 和 %esp“属于”调用函数。换句话说,被调用函数“

所以保存工作是 glibc 包装函数的责任,我已经阅读了一些 glibc 代码来确保它。所以在使用sysenter/sysexit做系统调用的时候,我们先将%ebp,%edx,%ecx压入用户栈是有道理的,因为%edx和%ecx不在保存寄存器中,需要等完成后再恢复系统调用,我们在调用系统服务例程之前也使用 %ebp 来保存用户堆栈指针,所以我们需要恢复它来传递参数

4

2 回答 2

2

原因与为什么不使用 RCX 将参数传递给系统调用,在 64 位模式下被 R10 替换sysenter的原因相同:因为andsysexit指令的工作方式。即,来自英特尔文档的sysexit指令:

在执行 SYSEXIT 之前,软件必须通过将值写入以下 MSR 和通用寄存器来指定特权级 3 代码段和代码入口点,以及特权级 3 堆栈段和堆栈指针:

• IA32_SYSENTER_CS(MSR 地址 174H)— 包含一个 32 位值,用于确定特权级 3 代码和堆栈段的段选择器(请参阅操作部分)

RDX — 该寄存器中的规范地址被加载到 RIP 中(因此,该值引用用户代码中要执行的第一条指令)。如果返回不是 64 位模式,则仅加载位 31:0。

ECX — 该寄存器中的规范地址被加载到 RSP 中(因此,该值包含特权级 3 堆栈的堆栈指针)。如果返回不是 64 位模式,则仅加载位 31:0。

因此rdx( edx) 和rcx( ecx) 被指令保留。现在呢ebp?好吧,从说明文档中sysenter

SYSENTER 和 SYSEXIT 指令是伴随指令,但它们不构成调用/返回对。在执行 SYSENTER 指令时,处理器不保存用户代码的状态信息(例如,指令指针),并且 SYSENTER 和 SYSEXIT 指令都不支持在堆栈上传递参数。

RSP这在替换为IA32_SYSENTER_ESPon的事实中很明显sysenter,因此操作系统甚至不知道用户空间堆栈应该在哪里,至少这不是易学的。所以 Linuxebp正是为了这个目的而保留的:为操作系统提供用户堆栈。现在调用者必须保存ebp,因为它必须esp在做之前用sysenter.

为什么 Linux 不专门edxecx出于传递堆栈指针的目的——这两个寄存器没有被覆盖sysenter?我认为这是为了速度:ebp当用于在通常的int 0x80调用中传递参数时,是最后一个可能的(第六个)参数。系统调用很少需要超过 5 个参数,因此 Linux 只需为具有 6 个参数的系统调用执行此操作,而不是读取几乎所有系统调用的用户空间堆栈(如果edxecx用于堆栈指针)。(注意在做之前你必须ebp最后推送sysenter——这正是因为内核必须知道在哪里可以找到第六个参数)。

这一切都在 Linux 源代码中进行了总结arch/x86/entry/vdso/vdso32/sysenter.S

/*
 * The caller puts arg2 in %ecx, which gets pushed. The kernel will use
 * %ecx itself for arg2. The pushing is because the sysexit instruction
 * (found in entry.S) requires that we clobber %ecx with the desired %esp.
 * User code might expect that %ecx is unclobbered though, as it would be
 * for returning via the iret instruction, so we must push and pop.
 *
 * The caller puts arg3 in %edx, which the sysexit instruction requires
 * for %eip. Thus, exactly as for arg2, we must push and pop.
 *
 * Arg6 is different. The caller puts arg6 in %ebp. Since the sysenter
 * instruction clobbers %esp, the user's %esp won't even survive entry
 * into the kernel. We store %esp in %ebp. Code in entry.S must fetch
 * arg6 from the stack.
 *
 * You can not use this vsyscall for the clone() syscall because the
 * three words on the parent stack do not get copied to the child.
 */
于 2016-05-06T15:22:55.947 回答
0

这应该由使用的 ABI(调用约定)定义。一些寄存器在函数调用中保留,而有些则没有。您可以查看平台上使用的 ABI。

至于 X64, http: //x86-64.org/documentation/abi.pdf记录了它。见图 3.4

跨调用保留意味着寄存器是被调用者保存的,因此函数应该在返回之前恢复它;

不保留意味着调用者保存,因此函数可以直接使用它但不能恢复它。

于 2015-04-21T02:17:34.647 回答