Linux x86-64 系统调用何时破坏 %r8、%r9 和 %r10?

When does Linux x86-64 syscall clobber %r8, %r9 and %r10?

我刚刚浏览了 Linux 内核源代码树并阅读了文件 tools/include/nolibc/nolibc.h

我看到此文件中的 syscall 在 clobber 列表中使用了 %r8%r9%r10
还有一条评论说:

rcx and r8..r11 may be clobbered, others are preserved.

据我所知,syscall 只会破坏 %rax%rcx%r11(以及记忆)。

有没有 syscall 破坏 %r8%r9%r10 的真实例子?

只有 64 位模式下的 32 位系统调用(例如,通过 int 0x80)才会对这些寄存器以及 R11 进行操作。 ().

syscall 正确地 saves/restores 所有 regs 包括 R8、R9 和 R10,因此使用它的用户 space 可以假设它们保持它们的值,除了 RAX return 价值。 (内核的系统调用入口点甚至保存了 RCX 和 R11,但此时它们已经被 syscall 指令本身用原始 RIP 和屏蔽前 RFLAGS 值覆盖。)


那些与 R11 一起,是在 函数 调用约定中被调用破坏的非遗留寄存器,因此编译器自然地为内核中的 C 函数生成代码保留 R12-R15,即使 asm 入口点没有保存它们。

目前,64 位 int 0x80 入口点只是将 0 推送到进程状态结构中的调用破坏 R8-R11 寄存器,它将从 return 之前恢复发送给用户 space,而不是原始寄存器值。

从历史上看,来自 32 位用户的 int 0x80 入口点 - space 根本没有 save/restore 这些寄存器。所以它们的值是编译器生成的内核代码。这被认为是无辜的,因为 32 位模式无法读取这些寄存器,直到意识到 user-space 可以远跳到 64 位模式,使用内核使用的相同 CS 值正常的 64 位用户 space 进程,选择系统范围的 GDT 条目。 因此存在内核数据的实际信息泄漏,通过将这些寄存器置零来修复。

IDK 64 位用户 space 与 32 位用户是否曾经或仍然存在单独的入口点,或者它们在 struct pt_regs 布局上有何不同。 int 0x80 泄露 r8..r11 的历史情况对 64 位用户来说没有意义-space;泄漏本来是显而易见的。所以如果他们现在统一了,他们一定不是过去。

根据 x86-64 ABI 关于系统调用部分 A.2 AMD64 Linux 内核 约定,A.2.1 调用约定 [1]:

  1. User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9.

  2. A system-call is done via the syscall instruction. The kernel destroys registers %rcx and %r11.

  3. The number of the syscall has to be passed in register %rax.

  4. System-calls are limited to six arguments, no argument is passed directly on the stack.

  5. Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is -errno.

  6. Only values of class INTEGER or class MEMORY are passed to the kernel.

从 (2)、(5) 和 (6),我们可以得出结论,Linux x86-64 系统调用破坏了 %rax%rcx%r11 (和 "memory").

Link: https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/x86-64-psABI [1]