x86_64汇编器中RBP寄存器的用途是什么?


74

所以我尝试学习一些汇编,因为计算机架构课程需要它。我写了一些程序,例如打印斐波那契数列。

我认识到,每当我编写一个函数时,我都会使用这三行代码(正如我从将生成的汇编代码gcc与其C等效代码进行比较中学到的那样):

我对此有2个问题:

  1. 首先,为什么我需要使用%rbp%rsp它的内容移至%rbp第二行,使用起来更简单吗?
  2. 为什么我必须从中减去任何东西%rsp?我的意思是,并非总是如此16,当我要printf输入7或8个变量时,我会减去2428

我在虚拟机(4 GB RAM)和Intel 64位处理器上使用Manjaro 64位


1
您忘记启用优化。至于要减去的数量取决于对齐要求以及是否可以使用红色区域。
小丑

@Jester启用优化并不一定意味着也将启用帧指针省略
Govind Parmar

7
可能重复的内容什么是基本指针和堆栈指针?他们指出了什么?。IOW与x86_32代码中的相同。
Jongware '17

1
@GovindParmar取决于编译器,但是您自己猜到了gcc,它在哪里。同样,无缘无故从rsp中减去(OP暗示)也表示没有优化。
小丑

Answers:


75

rbp是x86_64上的帧指针。在生成的代码中,它获取堆栈指针(rsp)的快照,以便在进行调整时rsp(即,为局部变量保留空间或将push值存入堆栈),仍然可以从常量偏移量访问局部变量和函数参数。来自rbp

许多编译器都提供省略帧指针作为优化选项。这将使生成的汇编代码访问变量相对于rsp相反,并释放rbp为功能中使用的另一个通用寄存器。

对于GCC,我想您是从AT&T汇编器语法中使用的,则该开关为-fomit-frame-pointer。尝试使用该开关编译代码,然后查看获得的汇编代码。您可能会注意到,当访问相对于rsp而不是的值时rbp,指针的偏移量在整个函数中会有所不同。


1
确实,它使用%rsp!。但是,当我movl第一次常数时,movl $10它将移到4(%rsp)。为什么不-4呢?顺便说一句,为什么它仍然从中减去一些值%rsp?我无法通过评论了解它

2
@FrynioS编译器会在函数enter上为本地值分配一些空间。这就是为什么它在输入时从%rsp减去值的原因。这与%rbp是否用作帧指针无关。之后,该位置将以%rsp的正偏移量使用。同样,如果此函数调用另一个函数,则每次调用时%rsp应在16字节边界上对齐,因此,在这种情况下,编译器应在每次输入时从%rsp中减去8。
Netch

@FrynioS注意,在%rsp之前还有一个128字节的空间(“红色区域”),用于在函数调用之间保留其内容,但在中断期间由OS保留。因此,非常临时的值(在函数调用之间)可以用于%rsp的负偏移量。并非所有编译器都利用此功能。
Netch

55

Linux将System V ABI用于x86-64(AMD64)架构;有关详细信息,请参见OSDev Wiki上的System V ABI

这意味着堆栈长大了; 较小的地址在堆栈中“较高”。典型的C函数被编译为

为局部变量保留的内存量始终是16字节的倍数,以使堆栈对齐到16字节。如果局部变量不需要堆栈空间,则没有subq $16, %rsp或类似的指令。

(请注意,返回地址和前一个%rbp被压入堆栈的大小均为8个字节,总计为16个字节。)

%rbp指向当前堆栈框架时,%rsp指向堆栈顶部。由于编译器知道函数之间%rbp%rsp函数之间任何地方的区别,因此可以随意使用其中之一作为局部变量的基础。

堆栈框架只是本地函数的游乐场:当前函数使用的堆栈区域。

每当使用优化时,当前版本的GCC都会禁用堆栈框架。这是有道理的,因为对于用C编写的程序,堆栈帧对于调试最有用,而其他方面则没有什么用。(但是,您可以使用例如-O2 -fno-omit-frame-pointer保留堆栈帧,同时启用其他优化。)

尽管相同的ABI应用于所有二进制文件,但是无论它们以哪种语言编写,某些其他语言的确需要使用堆栈框架来“展开”(例如,向当前函数的祖先调用者“抛出异常”);即“展开”堆栈帧,可以中止一个或多个功能并将控制权传递给某些祖先功能,而无需在堆栈上保留不必要的内容。

如果省略了堆栈框架(-fomit-frame-pointer对于GCC),则函数实现本质上将变为

因为没有堆栈帧(%rbp用于其他目的,并且它的值永远不会被压入堆栈),所以每个函数调用仅将返回地址压入堆栈,这是一个8字节的数量,因此我们需要从中减去8%rsp使其保持16的倍数。(通常,从中减去并加到的值%rsp是8的奇数倍。)

函数参数通常在寄存器中传递。看到这个答案细节开始的ABI链接,但在短,整型和指针寄存器传递%rdi%rsi%rdx%rcx%r8,和%r9在与浮点参数%xmm0%xmm7寄存器。

在某些情况下,您会看到rep ret而不是rep。不要混淆:rep ret含义与ret;完全相同;rep尽管通常与字符串指令(重复指令)一起使用,但该前缀在应用于该ret指令时没有任何作用。只是某些AMD处理器的分支预测变量不喜欢跳转至ret指令,建议的解决方法是改用rep ret那儿。

最后,我忽略了堆栈顶部上方的红色区域(地址小于的128个字节%rsp)。这是因为它对于典型功能并不是真正有用:在正常的had-stack-frame情况下,您希望将本地内容放在堆栈框架中,以使调试成为可能。在省略堆栈框架的情况下,堆栈对齐要求已经意味着我们需要从中减去8 %rsp,因此在该减法中包括局部变量所需的内存不会花费任何成本。


哇,伙计,这对我很有帮助!感谢您的回答(可惜我将另一个答案标记为已接受:D)

@FrynioS:不用担心,这一切都很好!我认为这更像是“某些人可能会觉得有用的背景信息”,或者如果您想知道这些东西可能想知道的事情,而Govind Parmar则问了您提出的问题。
名义动物

@NominalAnimal请修正错误:正确的选项是-fomit-frame-pointer
Netch

@Netch:哦!接得好; 感谢您的注意。立即修复。
名义动物

1
虽然这是事实,eh_frame品牌帧指针不必要的这是不正确的说,eh_frame不使用帧指针:如果一个函数编译器使用帧指针都eh_framedebug_frame一定会使用它们。实际上,如果要减少二进制大小,通常的建议是-fno-omit-frame-pointer:增加代码大小/性能的代价可能会导致eh_frame段显着减小,因为帧指针是找到堆栈帧的最简单方法,因此DWARF的表示非常简洁。
BeeOnRope
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.