首页 文章

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

提问于
浏览
10

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 回答

  • 4

    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它需要保留其调用者的 r10 值 . 因此,系统调用包装函数可以 mov %rcx, %r10 而无需任何保存/恢复 . 这不会是函数调用约定 .


    顺便说一句,32位系统调用ABI也可以通过 sysenter 访问,这需要用户空间和内核空间之间的协作,以允许在 sysenter 之后返回用户空间 . (即在运行 sysenter 之前将一些状态存储在用户空间中) . 这比 int 0x80 更高,但很尴尬 . 仍然,glibc使用它(跳转到vdso页面中的用户空间代码,内核映射到每个进程的地址空间) .

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

  • 5

    AMD的 syscall clobbers是 rcx 寄存器,因此使用了 r10 .

相关问题