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

时间:2017-01-28 17:29:23

标签: c assembly x86-64 stack-pointer

所以我试图学习一点装配,因为我需要它用于计算机架构课程。我写了一些程序,比如打印Fibonacci序列。

我认识到每当我编写程序时,我都会使用这3行(正如我从将gcc生成的汇编代码与C等效的汇编代码进行比较所学到的):

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

我有两个问题:

  1. 首先,为什么我需要使用%rbp?使用%rsp是否更简单,因为其内容在第二行被移至%rbp
  2. 为什么我必须从%rsp中减去任何内容?我的意思是它并不总是16(当我printf第7行或第8行变量时,我会减去2428
  3. 我在虚拟机(4 GB RAM),Intel 64位处理器

    上使用Manjaro 64位

2 个答案:

答案 0 :(得分:35)

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

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

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

答案 1 :(得分:33)

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

这意味着堆栈向下增长;较小的地址是"更高的地址"在堆栈中。典型的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个字节。)

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

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

当使用优化时,当前版本的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中减去并添加到%rdi的值是8的奇数倍。)

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

在某些情况下,您会看到rep ret而不是rep。不要感到困惑:rep ret表示与ret完全相同的东西; rep前缀虽然通常与字符串指令(重复指令)一起使用,但在应用于ret指令时不执行任何操作。只是某些AMD处理器'分支预测器不喜欢跳转到ret指令,推荐的解决方法是在那里使用rep ret

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