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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41912684/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 10:35:41  来源:igfitidea点击:

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

cassemblyx86-64stack-pointer

提问by Govind Parmar

So I'm trying to learn a little bit of assembly, because I need it for Computer Architecture class. I wrote a few programs, like printing the Fibonacci sequence.

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

I recognized that whenever I write a function I use those 3 lines (as I learned from comparing assembly code generated from gccto its Cequivalent):

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

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

I have 2 questions about it:

我有两个问题:

  1. First of all, why do I need to use %rbp? Isn't it simpler to use %rsp, as its contents are moved to %rbpon the 2nd line?
  2. Why do I have to subtract anything from %rsp? I mean it's not always 16, when I was printfing like 7 or 8 variables, then I would subtract 24or 28.
  1. 首先,为什么我需要使用%rbp?使用起来不是更简单%rsp,因为它的内容被移到%rbp了第 2 行吗?
  2. 为什么我必须从中减去任何东西%rsp?我的意思是它并不总是16,当我printf像 7 或 8 个变量时,然后我会减去2428

I use Manjaro 64 bit on a Virtual Machine (4 GB RAM), Intel 64 bit processor

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

回答by Govind Parmar

rbpis the frame pointer on x86_64. In your generated code, it gets a snapshot of the stack pointer (rsp) so that when adjustments are made to rsp(i.e. reserving space for local variables or pushing values on to the stack), local variables and function parameters are still accessible from a constant offset from rbp.

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

A lot of compilers offer frame pointer omission as an optimization option; this will make the generated assembly code access variables relative to rspinstead and free up rbpas another general purpose register for use in functions.

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

In the case of GCC, which I'm guessing you're using from the AT&T assembler syntax, that switch is -fomit-frame-pointer. Try compiling your code with that switch and see what assembly code you get. You will probably notice that when accessing values relative to rspinstead of rbp, the offset from the pointer varies throughout the function.

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

回答by Nominal Animal

Linux uses the System V ABI for x86-64 (AMD64) architecture; see System V ABI at OSDev Wikifor details.

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

This means the stack grows down; smaller addresses are "higher up" in the stack. Typical C functions are compiled to

这意味着堆栈向下增长;较小的地址在堆栈中“更高”。典型的 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

The amount of memory reserved for the local variables is always a multiple of 16 bytes, to keep the stack aligned to 16 bytes. If no stack space is needed for local variables, there is no subq $16, %rspor similar instruction.

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

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

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

While %rbppoints to the current stack frame, %rsppoints to the top of the stack. Because the compiler knows the difference between %rbpand %rspat any point within the function, it is free to use either one as the base for the local variables.

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

A stack frame is just the local function's playground: the region of stack the current function uses.

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

Current versions of GCC disable the stack frame whenever optimizations are used. This makes sense, because for programs written in C, the stack frames are most useful for debugging, but not much else. (You can use e.g. -O2 -fno-omit-frame-pointerto keep stack frames while enabling optimizations otherwise, however.)

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

Although the same ABI applies to all binaries, no matter what language they are written in, certain other languages do need stack frames for "unwinding" (for example, to "throw exceptions" to an ancestor caller of the current function); i.e. to "unwind" stack frames that one or more functions can be aborted and control passed to some ancestor function, without leaving unneeded stuff on the stack.

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

When stack frames are omitted -- -fomit-frame-pointerfor GCC --, the function implementation changes essentially to

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

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

        ; ... function ...

        addq    , %rsp
        ret

Because there is no stack frame (%rbpis used for other purposes, and its value is never pushed to stack), each function call pushes only the return address to the stack, which is an 8-byte quantity, so we need to subtract 8 from %rspto keep it a multiple of 16. (In general, the value subtracted from and added to %rspis an odd multiple of 8.)

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

Function parameters are typically passed in registers. See the ABI link at the beginning of this answer for details, but in short, integral types and pointers are passed in registers %rdi, %rsi, %rdx, %rcx, %r8, and %r9, with floating-point arguments in the %xmm0to %xmm7registers.

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

In some cases you'll see rep retinstead of rep. Don't be confused: the rep retmeans the exact same thing as ret; the repprefix, although normally used with string instructions (repeated instructions), does nothing when applied to the retinstruction. It's just that certain AMD processors' branch predictors don't like jumping to a retinstruction, and the recommended workaround is to use a rep retthere instead.

在某些情况下,您会看到rep ret而不是rep. 不要混淆:rep ret意思与ret;完全相同。的rep前缀,尽管通常与字符串指令(重复指令)用的,当施加到不执行任何ret指令。只是某些 AMD 处理器的分支预测器不喜欢跳转到ret指令,推荐的解决方法是使用rep retthere 来代替。

Finally, I've omitted the red zoneabove the top of the stack (the 128 bytes at addresses less than %rsp). This is because it is not really useful for typical functions: In the normal have-stack-frame case, you'll want your local stuff to be within the stack frame, to make debugging possible. In the omit-stack-frame case, stack alignment requirements already mean we need to subtract 8 from %rsp, so including the memory needed by the local variables in that subtraction costs nothing.

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