如何正确引用堆栈上的局部变量

时间:2016-09-17 13:31:33

标签: assembly stack x86-64

输入功能,标准序言

push rbp
mov rbp, rsp
sub rsp, 128 ; large space for storing doubles, for example

现在如何通过rsp +正偏移或rbp +负偏移来引用局部变量?

阅读https://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames,确实很容易理解。 它写了

... esp的值不能可靠地用于确定(使用适当的偏移量)特定局部变量的内存位置。为了解决这个问题,许多编译器使用来自ebp寄存器的负偏移来访问局部变量。

为什么不可靠?在那个问题之前,我通过rsp访问局部变量,如下所示:

mov rax, [rsp+.length] ; get length of array
mov [rsp+8], rax ; store sum at the stack

使用rsp进行堆栈引用一切都很顺利。

1 个答案:

答案 0 :(得分:3)

看看gcc输出。优化时默认为-fomit-frame-pointer,仅在函数使用可变长度数组时生成堆栈帧,或者需要将堆栈对齐超过16B。

那个维基页面基本上是错误的。没有可怕的奇怪的东西使它“不可靠”。唯一不能做的就是当你需要修改RSP的量不是装配时间常数时。

但是,如果执行使用push rbp / mov rbp, rsp创建堆栈帧,则应使用RBP相对寻址模式。它效率更高,因为[rsp + 8]需要额外的字节进行编码(与[rbp - 8]相比)。使用RSP作为基址寄存器寻址模式总是需要一个SIB字节,即使没有索引寄存器也是如此。

使用RSP相对寻址模式的关键是你可以避免浪费指令制作堆栈帧,所以RBP只是另一个可以保存/恢复的调用保存寄存器(如RBX)并用于任何你想要的东西。

RBP相对寻址的另一大优势是从RBP到给定变量的偏移对于整个函数保持不变。与编译器不同,我们微不足道的人很容易被推动和弹出混淆,后者会在功能中改变RSP。当然,64位代码几乎不会在序言和结尾之间的函数内更改RSP,因为两个ABI都在寄存器中传递args。在序言/结尾中保存/恢复一些调用保留的寄存器(如RBX或R12-R15)通常比在函数内部使用push / pop更好(并且肯定比在循环内更好)。当您需要溢出/重新加载时,mov用于随机访问通常是最佳的。

在32位代码中,在手写代码中创建堆栈帧通常更有意义,尤其是。为了可维护性。在64位代码中,它通常不是一个问题。虽然使用额外的push / pop保存/恢复额外的寄存器会改变堆栈布局,但是如果堆栈上传递了任何args(例如,按值传递大型结构,但是编写函数以获取const指针)反而代之以!)。