所以我正在尝试学习一点装配,因为我需要它用于计算机体系结构类。我写了一些程序,比如打印Fibonacci序列。
我认识到每当我编写一个函数时,我都会使用这三行(正如我从gcc
生成的汇编代码到它的C
等价物的比较中所学到的):
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
我有2个问题:
%rbp
?是不是更简单使用%rsp
,因为它的内容被移动到第二行的%rbp
?%rsp
中减去任何东西?我的意思是它并不总是16
,当我像7或8变量printf
ing,然后我会减去24
或28
。我在虚拟机(4 GB RAM),Intel 64位处理器上使用Manjaro 64位
rbp
是x86_64上的帧指针。在生成的代码中,它获取堆栈指针(rsp
)的快照,以便在对rsp
进行调整时(即为局部变量保留空间或在堆栈上保留push
ing值),仍然可以从一个局部变量和函数参数访问从rbp
不断抵消。
许多编译器提供帧指针省略作为优化选项;这将使生成的汇编代码相对于rsp
访问变量,并将rbp
释放为另一个用于函数的通用寄存器。
在GCC的情况下,我猜你正在使用AT&T汇编语法,那个开关是-fomit-frame-pointer
。尝试使用该开关编译代码并查看您获得的汇编代码。你可能会注意到,当访问相对于rsp
而不是rbp
的值时,指针的偏移在整个函数中变化。
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字节的数量,所以我们需要减去8从%rsp
来保持它的16的倍数。(一般来说,从%rsp
中减去并加到的值是8的奇数倍。)
函数参数通常在寄存器中传递。有关详细信息,请参阅本答案开头的ABI链接,但简而言之,整数类型和指针在寄存器%rdi
,%rsi
,%rdx
,%rcx
,%r8
和%r9
中传递,并在%xmm0
到%xmm7
寄存器中使用浮点参数。
在某些情况下,你会看到rep ret
而不是rep
。不要混淆:rep ret
与ret
完全相同; rep
前缀虽然通常与字符串指令(重复指令)一起使用,但在应用于ret
指令时不执行任何操作。只是某些AMD处理器的分支预测器不喜欢跳到ret
指令,推荐的解决方法是在那里使用rep ret
。
最后,我省略了堆栈顶部上方的red zone(地址小于%rsp
的128个字节)。这是因为它对于典型函数并不真正有用:在普通的has-stack-frame情况下,您需要将本地内容放在堆栈框架内,以便进行调试。在omit-stack-frame的情况下,堆栈对齐要求已经意味着我们需要从%rsp
中减去8,因此在该减法中包含局部变量所需的内存不需要任何费用。