x86_64 SysV ABI函数调用约定定义要在rcx
寄存器中传递的整数参数#4。另一方面,Linux内核系统调用ABI使用r10
来实现同样的目的。所有其他参数都在函数和系统调用的相同寄存器中传递。
这会导致一些奇怪的事情。例如,查看x32平台的glibc中mmap
的实现(存在相同的差异):
00432ce0 <__mmap>:
432ce0: 49 89 ca mov %rcx,%r10
432ce3: b8 09 00 00 40 mov $0x40000009,%eax
432ce8: 0f 05 syscall
所以除了我们将rcx
移到r10
之外,所有注册都已到位。
我想知道为什么不将syscall ABI定义为与函数调用ABI相同,考虑到它们已经非常相似。
答案 0 :(得分:6)
syscall
instruction旨在提供一种更快速的方法来输入Ring-0以执行系统调用。这是对旧方法的改进,即在Linux上引发软件中断(int 0x80
)。
指令更快的部分原因是因为它不会改变内存,甚至不会将rsp
更改为指向内核堆栈。与软件中断不同,CPU强制允许操作系统恢复运行而不会破坏任何内容,对于此命令,CPU可以假设软件知道此处发生了某些事情。
特别是,syscall
将两个用户空间状态存储在寄存器中。呼叫后返回的RIP
存储在rcx
中,并且标志存储在R11
(because RFLAGS is masked with a kernel-supplied value before entry to the kernel)中。这意味着这些寄存器都被指令破坏了。
由于它们被破坏,系统调用ABI使用另一个寄存器而不是rcx
,因此使用r10
作为第四个参数。
r10
是一种自然的选择,因为in the x86-64 SystemV ABI它不用于传递函数args,函数不需要保留其调用者的{{ {1}}。因此,系统调用包装函数可以r10
而无需任何保存/恢复。对于6-arg系统调用和SysV ABI的函数调用约定,这对任何其他寄存器都是不可能的。
mov %rcx, %r10
访问,这需要用户空间和内核空间之间的协作,以允许在sysenter
之后返回用户空间。 (即在运行sysenter
之前将一些状态存储在用户空间中)。这比sysenter
的性能更高,但很尴尬。不过,glibc使用它(通过跳转到vdso页面中的用户空间代码,内核映射到每个进程的地址空间)。
AMD的int 0x80
是另一种与英特尔syscall
相同的想法:通过不保留绝对所有东西来降低内核的进入/退出成本。
答案 1 :(得分:4)
AMD的syscall
clo rcx
注册,因此使用了r10
。