在linux内核2.6.11中, 当使用sysenter进行系统调用时, 它与init 0x80几乎相同,使用save_all来推送内核堆栈上的所有寄存器,但是在调用完成后, 如果未设置相关标志,我们使用sysexit返回,但不恢复已保存在堆栈上的所有寄存器。
我已经阅读了相应的i386 doc,它说
“Intel386上的所有寄存器都是全局的,因此对于调用和被调用函数都是可见的。寄存器%ebp,%ebx,%edi,%esi和%esp”属于“调用函数。换句话说,被调用的函数必须为其调用者保留这些寄存器的值。剩余的寄存器“属于”被调用的函数。如果调用函数想要在函数调用中保留这样的寄存器值,它必须将值保存在其本地堆栈帧中。 “
因此glibc包装函数负责执行保存工作,并且我已经阅读了一些glibc代码来确保它。 因此,使用sysenter / sysexit进行系统调用时有意义, 我们首先在用户堆栈上推送%ebp,%edx,%ecx 因为%edx和%ecx不在保存寄存器中,所以我们需要在完成系统调用后再恢复它们 我们还使用%ebp在调用系统服务例程之前保存用户堆栈指针,因此我们需要将其恢复为传递参数
答案 0 :(得分:0)
这应该由使用的ABI(调用约定)定义。有些寄存器在函数调用中保留,而有些则不是。您可以查看平台上使用的ABI。
至于X64,http://x86-64.org/documentation/abi.pdf记录了它。见图3.4
跨越调用保留意味着寄存器被callee保存,因此函数应该在返回之前恢复它;
未保留表示调用者已保存,因此函数可以直接使用它,但不能将其恢复。
答案 1 :(得分:0)
原因与64位模式下的why RCX is not used for passing parameters to system calls, being replaced with R10相同:因为sysenter
和sysexit
指令的工作原理。即,来自英特尔关于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_ESP
上被sysenter
取代,这显然很明显,因此操作系统甚至不知道用户空间堆栈的位置,至少这不是一件容易学的事。因此Linux完全为此目的保留ebp
:为操作系统提供用户堆栈。现在,来电者必须保存ebp
,因为在执行esp
之前,我必须使用sysenter
覆盖它。
为什么Linux没有将edx
或ecx
专用于传递堆栈指针 - 这两个寄存器是否在sysenter
上被覆盖?我认为它的速度:ebp
,当用于通常的int 0x80
调用中的参数传递时,是最后一个可能的(第六个)参数。系统调用很少需要超过5个参数,因此几乎所有系统调用(如果edx
或ecx
用于堆栈指针)都不是读取用户空间堆栈,而是只需要Linux为具有6个参数的系统调用执行此操作。 (注意在执行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.
*/