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

7
我刚浏览了Linux内核源代码树,并阅读了文件tools/include/nolibc/nolibc.h。在这个文件中,我看到syscall使用了%r8、%r9%r10。此外,有一个注释说:

rcx和r8..r11可能被破坏,其他都保留。

据我所知,syscall只会破坏%rax%rcx%r11(以及内存)。
是否有真正的syscall示例会破坏%r8%r9%r10

现在,我有点担心我没有将%r8%r9%r10放入我的系统调用破坏列表(内联汇编),而它目前正在生产应用程序上运行。但是到目前为止,我还没有看到任何问题。 - Ammar Faizi
我仍在思考syscall入口代码,希望%r8...%r10没有被破坏。https://elixir.bootlin.com/linux/latest/source/arch/x86/entry/entry_64.S#L50 - Ammar Faizi
3
请注意第107行的PUSH_AND_CLEAR_REGS和第193行的POP_REGS。它们保存和恢复了r8-r10,因此除非系统调用显式地干扰了栈中的副本,否则它们将被保留。当然,其中一个例子是exec,但它不返回,所以没有什么好担心的 :) - Jester
2
是的,这似乎是nolibc.h中一个微小无害的错误。 - Nate Eldredge
1
感谢您的回答和评论,我刚刚发送了一个补丁来修复它,让我们看看它是否被接受。https://lore.kernel.org/lkml/20211011040344.437264-1-ammar.faizi@students.amikom.ac.id/T/ - Ammar Faizi
2个回答

6
仅有32位的系统调用(例如通过int 0x80进行)在64位模式下影响那些寄存器以及R11。(参见使用32位int 0x80 Linux ABI在64位代码中会发生什么?syscall正确保存/恢复所有的寄存器,包括R8、R9和R10,因此使用它的用户空间可以假定它们保持原值,除了RAX返回值。(内核的syscall入口甚至保存RCX和R11,但此时它们已经被syscall指令本身覆盖为原始RIP和未屏蔽的RFLAGS值。)
这些寄存器连同R11是在函数调用约定中被污染的非传统寄存器,所以在内核中编译器生成的C函数代码自然会保留R12-R15,即使一个asm入口点没有保存它们。
当前的64位int 0x80入口点只是在进程状态结构中为被调用污染的寄存器R8-R11推送0,而不是原始的寄存器值。
历史上,从32位用户空间的int 0x80入口点根本不保存/恢复这些寄存器。因此它们的值是由编译器生成的内核代码留下的。这被认为是无害的,因为32位模式无法读取这些寄存器,直到意识到用户空间可以远跳转到64位模式,使用与内核用于正常64位用户空间进程相同的CS值,选择系统范围的GDT条目。所以存在着实际的内核数据信息泄漏,这已通过将这些寄存器清零来修复。 我不知道是否过去还是现在有一个不同的64位用户空间与32位用户空间之间的单独入口点,或者它们在struct pt_regs布局上有何区别。历史上,int 0x80泄漏r8..r11对于64位用户空间没有意义;那个泄漏是显而易见的。因此,如果它们现在已经统一,那么过去它们肯定是不同的。

3
@SepRoland说:“是的,我的意思是实际回答非常直接,但让它有趣且值得回答的部分是从64位模式下int 0x80总结的调用约定观察结果。但基本上这只是一个括号说明,关于为什么有人可能会这样想的猜测。也许使用---水平线更好一些。是的,我将这样做,因为我想编辑其他东西。” - Peter Cordes

3
根据 x86-64 ABI 中的关于 AMD64 Linux 内核规约的系统调用部分 A.2,A.2.1 调用规约[1]:
1. 用户级应用程序使用整数寄存器传递顺序为 %rdi%rsi%rdx%rcx%r8%r9 的参数。内核接口使用 %rdi%rsi%rdx%r10%r8%r9
2. 通过 syscall 指令实现系统调用。内核会销毁寄存器 %rcx%r11
3. 系统调用号必须通过寄存器 %rax 传递。
4. 系统调用的参数限制为六个,没有参数直接在堆栈上传递。
5. 从系统调用中返回时,寄存器 %rax 包含系统调用的结果。范围在 -4095 到 -1 之间的值表示错误,它是 -errno。
6. 只有属于整型或内存类的值才能传递到内核。
从 (2)、(5) 和 (6) 中可以得出结论,Linux x86-64 系统调用破坏了寄存器 %rax%rcx%r11(以及 "memory")。
链接:https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/x86-64-psABI [1]

2
请注意,A.2被标记为“仅供参考”。这不是Linux必须遵循的规则。相反,内核开发人员定义了系统调用的调用约定,并且为读者方便起见,将这些约定简单地包含在ABI附录中。不幸的是,就我所知,内核开发人员从未觉得有必要在内核源码之外正式记录这些约定,因此ABI作者可能只是描述了内核实际执行的操作。 - Nate Eldredge
1
幸运的是,ABI文档以及其中的摘要或引用对于Linux来说是准确的。(但对于其他使用x86-64 SysV ABI作为用户空间调用约定的操作系统,如Darwin / MacOS,则不适用,例如MacOS通过CF返回错误状态而不是通过-errno在带内返回。) 这个答案所说的一切已经在SO的其他地方(例如this Q&A)中了,但把它们都放在一个地方也不是坏事。 - Peter Cordes
1
虽然说内核摧毁了RCX和R11不太准确。正如我在我的回答中指出的那样,syscall指令本身会在内核获得控制权之前摧毁它们。调用内核才会摧毁它们。为什么x86-64 Linux系统调用会修改RCX,并且这个值的含义是什么? - Peter Cordes
@NateEldredge 是的,看起来是这样。现在他们将澄清ABI文档,以使该合同更加明确,请在此处查看完整故事:https://lore.kernel.org/lkml/alpine.LSU.2.20.2110131601000.26294@wotan.suse.de/ - Ammar Faizi
@AmmarFaizi:感谢您提供有关您补丁的LKML链接。正如在syscall(7)手册页中记录的那样,内核通常不会破坏任何寄存器。在sysret返回路径中清零一些寄存器是可能的,因为我们知道它们没有被ptrace更改,但此时对于任何内联syscall的内容(而不是像nolibc的宏那样做出非标准假设),这将破坏ABI。 - Peter Cordes
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接