Linux x64 调用约定 - 为什么前 6 个参数与 RBP 的负偏移量相同?

Linux x64 Calling Convention - Why are the first 6 args at negative offsets from RBP?

提问人:Xiaoyong Guo 提问时间:7/13/2023 最后编辑:Sep RolandXiaoyong Guo 更新时间:7/15/2023 访问量:114

问:

我正在阅读此页面 Linux x64 调用约定,但对通过寄存器和堆栈传递函数参数感到困惑。它说:

参数 1-6 在修改之前通过寄存器 RDI、RSI、RDX、RCX、R8、R9 访问,或者通过 RBP 寄存器的偏移量访问,如下所示:rbp - $offset。

如果前 6 个参数是由寄存器传递的,为什么它们也可以从堆栈中读取和写入?前 6 个参数是否同时存储在寄存器和堆栈中?

Linux 程序集 x86-64 调用约定

评论

0赞 Shawn 7/13/2023
它假设所有参数也存在于调用函数的堆栈中,在传递给被调用方时以相反的顺序存在。似乎是一个不安全的假设......
1赞 Nate Eldredge 7/13/2023
有关更好的解释,请参阅 stackoverflow.com/questions/2535989/...
0赞 Shawn 7/13/2023
@NateEldredge 这是关于Linux系统调用abi的,它与普通的用户空间不同(不同的寄存器;特别是r10而不是rcx)
1赞 Nate Eldredge 7/13/2023
哦,我想我明白了。他们正在查看 gcc 的未优化输出,这会将所有寄存器传递的参数溢出到堆栈上(例如,它们有一个定义明确的地址供调试器查看)。但这绝不是调用约定的一部分,如果启用优化,则不会再发生溢出。
0赞 Nate Eldredge 7/13/2023
@Shawn:如果你继续阅读,用户空间 ABI 也会在那里描述。那篇帖子的标题很烦人,但内容很好。

答:

3赞 Nate Eldredge 7/14/2023 #1

本文将实际的调用约定(仅要求在寄存器中传递前 6 个参数)与特定编译器为被调用函数发出代码的方式混淆。

具体来说,当 gcc 在关闭优化的情况下使用时,它将通过将所有寄存器参数“溢出”到堆栈内存中来启动函数。没有必要这样做 - 只需使用传递它们的寄存器中的参数,就可以正常工作,并且效率更高。但是,当优化关闭时,gcc 的首要任务是快速编译和轻松调试,代价是发出通常效率低得可笑的代码。

关于调试,其目标之一是程序中的每个变量(包括函数参数)在内存中都有一个定义明确的地址。(声明的变量可能是一个例外。此外,该值在使用它的每行代码之前从该地址加载(即使该值恰好已经在寄存器中),然后存储回去。这样一来,当您在调试器中单步执行程序时,可以轻松检查和修改变量的值,而不会遇到可能已优化的变量问题。register

我不认为编译器在决定哪个参数在哪个堆栈偏移时遵循任何特定的模式。当参数从左到右时,它可能只是沿着堆栈向下移动,但你不会想依赖它。这当然不是记录在案的行为。

如果你打开优化(、等),你会看到溢出消失。编译器将仅使用传递参数值的寄存器中的参数值。根据需要,它可能会将它们移动到其他寄存器中,或者在不再需要它们时覆盖它们。-O-O2

因此,简而言之,在寄存器中传递参数是一种定义明确且可预测的行为,由 ABI 标准指定。将它们溢出到堆栈是仅在使用一组特定选项使用特定编译器进行构建时才会发生的行为,并且无论如何都不是标准的或可预测的。