在 x86_64汇编程序中使用 RBP 寄存器的目的是什么?

所以我想学一点汇编,因为我需要它来上计算机体系结构课。我写了一些程序,比如打印斐波那契数列。

我意识到,每当我编写一个函数时,我都会使用这3行代码(正如我通过比较从 gcc生成的汇编代码与它的 C等价物所学到的那样) :

pushq   %rbp
movq    %rsp, %rbp
subq    $16, %rsp

我有两个问题:

  1. 首先,为什么我需要使用 %rbp?使用 %rsp不是更简单吗,因为它的内容在第2行被移动到 %rbp
  2. 为什么我必须从 %rsp中减去任何东西?我的意思是它不总是 16,当我像 printf7或8变量,然后我会减去 2428

我使用 Manjaro 64位的虚拟机(4GB 内存) ,英特尔64位处理器

128792 次浏览

rbp是 x86 _ 64上的帧指针。在生成的代码中,它获得栈指针(rsp)的快照,这样当对 rsp进行调整时(即在栈上为局部变量或 pushing 值保留空间) ,局部变量和函数参数仍然可以通过与 rbp的恒定偏移量访问。

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

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

Linux 使用 SystemVABI 实现 x86-64(AMD64)体系结构; 有关详细信息,请参阅 OSDev Wiki 上的 System V ABI

这意味着堆栈 grows down; 较小的地址在堆栈中位于“较高”的位置

        pushq   %rbp        ; Save address of previous stack frame
movq    %rsp, %rbp  ; Address of current stack frame
subq    $16, %rsp   ; Reserve 16 bytes for local variables


; ... function ...


movq    %rbp, %rsp  ; \ equivalent to the
popq    %rbp        ; / 'leave' instruction
ret

为本地变量保留的内存总是16字节的倍数,以便将堆栈对齐到16字节。如果局部变量不需要堆栈空间,则不存在 subq $16, %rsp或类似的指令。

(Note that the return address and the previous %rbp pushed to the stack are both 8 bytes in size, 16 bytes in total.)

While %rbp points to the current stack frame, %rsp points to the top of the stack. Because the compiler knows the difference between %rbp and %rsp at any point within the function, it is free to use either one as the base for the local variables.

堆栈帧只是本地函数的操场: 当前函数使用的堆栈区域。

当前版本的 GCC 在使用优化时禁用堆栈帧。这是有意义的,因为对于用 C 编写的程序来说,堆栈帧对于调试是最有用的,但除此之外就没什么用了。(但是,您可以使用例如 -O2 -fno-omit-frame-pointer来保留堆栈帧,同时启用其他优化。)

尽管同样的 ABI 适用于所有的二进制文件,但是不管它们是用什么语言写的,某些其他语言确实需要堆栈帧来“解除”(例如,“抛出异常”给当前函数的祖先调用者) ; 也就是说,“解除”堆栈帧,一个或多个函数可以被中止,控制传递给某个祖先函数,而不会在堆栈上留下不需要的东西。

当忽略堆栈帧(GCC 的 -fomit-frame-pointer)时,函数实现实质上更改为

        subq    $8, %rsp    ; Re-align stack frame, and
; reserve memory for local variables


; ... function ...


addq    $8, %rsp
ret

因为没有堆栈帧(%rbp用于其他目的,它的值从不被推到堆栈中) ,每个函数调用只将返回地址推到堆栈中,这是一个8字节的数量,所以我们需要从 %rsp中减去8,使其保持为16的倍数。(一般来说,从 %rsp减去并加上的值是8的奇数倍。)

函数参数通常在寄存器中传递。有关详细信息,请参阅本答案开头的 ABI 链接,但是简而言之,整型类型和指针在寄存器 %rdi%rsi%rdx%rcx%r8%r9中传递,在 %xmm0%xmm7寄存器中使用浮点参数。

在某些情况下,您将看到 rep ret而不是 rep。不要混淆: rep ret的意思与 ret完全相同; rep前缀,虽然通常与字符串指令(重复指令)一起使用,但是在应用到 ret指令时没有任何作用。只是某些 AMD 处理器的分支预测器不喜欢跳转到 ret指令,建议的解决方案是使用 rep ret

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