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相同,考虑到它们已经非常相似 .
2 回答
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它需要保留其调用者的r10
值 . 因此,系统调用包装函数可以mov %rcx, %r10
而无需任何保存/恢复 . 这不会是函数调用约定 .顺便说一句,32位系统调用ABI也可以通过
sysenter
访问,这需要用户空间和内核空间之间的协作,以允许在sysenter
之后返回用户空间 . (即在运行sysenter
之前将一些状态存储在用户空间中) . 这比int 0x80
更高,但很尴尬 . 仍然,glibc使用它(跳转到vdso页面中的用户空间代码,内核映射到每个进程的地址空间) .AMD的
syscall
是与英特尔的sysenter
相同的另一种方法:通过不保留绝对所有东西来降低内核的进入/退出成本 .AMD的
syscall
clobbers是rcx
寄存器,因此使用了r10
.