x86_64 Linux函数和系统调用之间的ABI差异

时间:2016-07-25 21:19:39

标签: assembly linux-kernel x86-64 system-calls abi

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 个答案:

答案 0 :(得分:6)

syscall instruction旨在提供一种更快速的方法来输入Ring-0以执行系统调用。这是对旧方法的改进,即在Linux上引发软件中断(int 0x80)。

指令更快的部分原因是因为它不会改变内存,甚至不会将rsp更改为指向内核堆栈。与软件中断不同,CPU强制允许操作系统恢复运行而不会破坏任何内容,对于此命令,CPU可以假设软件知道此处发生了某些事情。

特别是,syscall将两个用户空间状态存储在寄存器中。呼叫后返回的RIP存储在rcx中,并且标志存储在R11because 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的函数调用约定,这对任何其他寄存器都是不可能的。

BTW,32位系统调用ABI也可以通过mov %rcx, %r10访问,这需要用户空间和内核空间之间的协作,以允许在sysenter之后返回用户空间。 (即在运行sysenter之前将一些状态存储在用户空间中)。这比sysenter的性能更高,但很尴尬。不过,glibc使用它(通过跳转到vdso页面中的用户空间代码,内核映射到每个进程的地址空间)。

AMD的int 0x80是另一种与英特尔syscall相同的想法:通过不保留绝对所有东西来降低内核的进入/退出成本。

答案 1 :(得分:4)

AMD的syscall clo rcx注册,因此使用了r10