98

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

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

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

我有两个问题:

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

我在虚拟机(4 GB RAM)上使用 Manjaro 64 位,英特尔 64 位处理器

4

2 回答 2

103

rbp是 x86_64 上的帧指针。rsp在您生成的代码中,它获取堆栈指针rsppushrbp.

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

在 GCC 的情况下,我猜你是从 AT&T 汇编器语法中使用的,那个开关是-fomit-frame-pointer. 试着用那个开关编译你的代码,看看你得到了什么汇编代码。您可能会注意到,当访问相对于rsp而不是的值时rbp,指针的偏移量在整个函数中会有所不同。

于 2017-01-28T17:35:08.850 回答
76

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

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

        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或类似的指令。

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

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

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

每当使用优化时,当前版本的 GCC 都会禁用堆栈帧。这是有道理的,因为对于用 C 编写的程序,堆栈帧对调试最有用,但除此之外没有多大用处。(但是,您可以使用 eg-O2 -fno-omit-frame-pointer来保持堆栈帧,同时启用优化。)

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

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

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

        ; ... function ...

        addq    $8, %rsp
        ret

因为没有栈帧(%rbp用于其他目的,它的值从不压栈),所以每次函数调用只压栈返回地址,也就是一个8字节的数量,所以我们需要减去8%rsp保持它是 16 的倍数。(通常,减去和添加的值%rsp是 8 的奇数倍。)

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

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

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

于 2017-01-28T19:46:14.477 回答