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
我有两个问题:
- 首先,为什么我需要使用
%rbp
?使用 %rsp
是不是更简单,因为它的内容被移动到第 2 行的 %rbp
?
- 为什么我必须从
%rsp
中减去任何东西?我的意思是它并不总是 16
,当我 printf
像 7 或 8 个变量时,我会减去 24
或 28
。
我在虚拟机 (4 GB RAM)、Intel 64 位处理器上使用 Manjaro 64 位
rbp
是 x86_64 上的帧指针。在您生成的代码中,它获取堆栈指针 (rsp
) 的快照,以便在对 rsp
进行调整时(即为局部变量保留 space 或 push
ing栈上的值),局部变量和函数参数仍然可以从 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 ret
与 ret
的含义完全相同; rep
前缀虽然通常与字符串指令(重复指令)一起使用,但在应用于 ret
指令时什么也不做。只是某些 AMD 处理器的分支预测器不喜欢跳转到 ret
指令,建议的解决方法是在那里使用 rep ret
。
最后,我省略了栈顶上方的red zone(地址小于%rsp
的128字节)。这是因为它对于典型的函数并不是真正有用:在正常的 have-stack-frame 情况下,您会希望本地内容位于堆栈帧内,以便进行调试。在 omit-stack-frame 的情况下,堆栈对齐要求已经意味着我们需要从 %rsp
中减去 8,因此在该减法中包括局部变量所需的内存不需要任何成本。
所以我正在尝试学习一点汇编,因为计算机体系结构需要它 class。我写了几个程序,比如打印斐波那契数列。
我认识到,每当我编写一个函数时,我都会使用这 3 行(正如我从 gcc
生成的汇编代码与其等效的 C
比较中了解到的):
pushq %rbp
movq %rsp, %rbp
subq , %rsp
我有两个问题:
- 首先,为什么我需要使用
%rbp
?使用%rsp
是不是更简单,因为它的内容被移动到第 2 行的%rbp
? - 为什么我必须从
%rsp
中减去任何东西?我的意思是它并不总是16
,当我printf
像 7 或 8 个变量时,我会减去24
或28
。
我在虚拟机 (4 GB RAM)、Intel 64 位处理器上使用 Manjaro 64 位
rbp
是 x86_64 上的帧指针。在您生成的代码中,它获取堆栈指针 (rsp
) 的快照,以便在对 rsp
进行调整时(即为局部变量保留 space 或 push
ing栈上的值),局部变量和函数参数仍然可以从 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 ret
与 ret
的含义完全相同; rep
前缀虽然通常与字符串指令(重复指令)一起使用,但在应用于 ret
指令时什么也不做。只是某些 AMD 处理器的分支预测器不喜欢跳转到 ret
指令,建议的解决方法是在那里使用 rep ret
。
最后,我省略了栈顶上方的red zone(地址小于%rsp
的128字节)。这是因为它对于典型的函数并不是真正有用:在正常的 have-stack-frame 情况下,您会希望本地内容位于堆栈帧内,以便进行调试。在 omit-stack-frame 的情况下,堆栈对齐要求已经意味着我们需要从 %rsp
中减去 8,因此在该减法中包括局部变量所需的内存不需要任何成本。