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

What is the purpose of the RBP register in x86_64 assembler?

所以我正在尝试学习一点汇编,因为计算机体系结构需要它 class。我写了几个程序,比如打印斐波那契数列。

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

pushq   %rbp
movq    %rsp, %rbp
subq    , %rsp

我有两个问题:

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

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

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

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

对于 GCC,我猜您使用的是 AT&T 汇编程序语法,该开关是 -fomit-frame-pointer。尝试用那个开关编译你的代码,看看你得到了什么汇编代码。您可能会注意到,当访问相对于 rsp 而不是 rbp 的值时,指针的偏移量在整个函数中会发生变化。

Linux 使用适用于 x86-64 (AMD64) 架构的 System V ABI;有关详细信息,请参阅 System V ABI at OSDev Wiki

这意味着堆栈向下增长;较小的地址在堆栈中 "higher up"。典型的 C 函数被编译为

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

        ; ... function ...

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

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

(注意return地址和之前入栈的%rbp地址都是8字节,一共16字节。)

%rbp指向当前栈帧,%rsp指向栈顶。因为编译器在函数内的任何一点都知道 %rbp%rsp 之间的区别,所以可以随意使用其中任何一个作为局部变量的基础。

堆栈帧只是局部函数的游乐场:当前函数使用的堆栈区域。

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

虽然相同的 ABI 适用于所有二进制文件,但无论它们是用什么语言编写的,某些其他语言确实需要 "unwinding" 的堆栈帧(例如,"throw exceptions" 到祖先调用者当前功能);即 "unwind" 一个或多个函数可以中止并将控制权传递给某个祖先函数的堆栈帧,而不会在堆栈上留下不需要的东西。

当省略堆栈帧时——-fomit-frame-pointer 对于 GCC——,函数实现本质上变为

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

        ; ... function ...

        addq    , %rsp
        ret

因为没有栈帧(%rbp用于其他用途,它的值永远不会压栈),每次函数调用只会将return地址压栈,这是一个8字节的量,所以我们需要用%rsp减8来保持它是16的倍数。(一般来说,%rsp减去和加上的值都是8的奇数倍。 )

函数参数通常在寄存器中传递。有关详细信息,请参阅此答案开头的 ABI link,但简而言之,整型和指针在寄存器中传递 %rdi%rsi%rdx%rcx%r8%r9,在 %xmm0%xmm7 寄存器中有 floating-point 个参数。

在某些情况下,您会看到 rep ret 而不是 rep。不要混淆:rep retret 的含义完全相同; rep 前缀虽然通常与字符串指令(重复指令)一起使用,但在应用于 ret 指令时什么也不做。只是某些 AMD 处理器的分支预测器不喜欢跳转到 ret 指令,建议的解决方法是在那里使用 rep ret

最后,我省略了栈顶上方的red zone(地址小于%rsp的128字节)。这是因为它对于典型的函数并不是真正有用:在正常的 have-stack-frame 情况下,您会希望本地内容位于堆栈帧内,以便进行调试。在 omit-stack-frame 的情况下,堆栈对齐要求已经意味着我们需要从 %rsp 中减去 8,因此在该减法中包括局部变量所需的内存不需要任何成本。