2016-09-17 76 views
2

在功能输入,标准的序言如何引用堆栈正确

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

堆栈指针*通常可用于确定堆栈上变量的地址。但是,如果函数使用可变长度数组或等价于'alloca()',则使用堆栈指针的偏移量可能不再可行。 – EOF

+0

@EOF,所以应该在所有情况下使用相对于rsp的寻址,除了两个? –

回答

3

看看gcc的输出。优化时默认为,只有当函数使用可变长度数组或者需要将堆栈对齐到16B以上时才创建堆栈帧。

该wiki页面基本上是错误的。没有可怕的怪物让它“不可靠”。唯一不能做到这一点的是当你需要修改RSP的数量不是汇编时间常数时。


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

使用RSP-相对寻址方式的一点是,你可以避免浪费指示进行堆栈帧,所以RBP是另一个调用保存的寄存器(如RBX),您可以保存/恢复和使用任何你要。


的另一大优点是RBP相对寻址是,从RBP停留整个函数恒定的偏移给定的变量。与编译器不同的是,我们小小的人类很容易被推送和弹出混淆,这会改变函数中的RSP。当然,64位代码几乎不会在序言和结尾之间的函数内改变RSP,因为两个ABI都通过寄存器中的参数。在序言/结尾中保存/恢复一些保存呼叫的寄存器(比如RBX或R12-R15)往往比在功能内部使用push/pop更好(并且肯定比循环内更好)。当你需要泄漏/重新加载时,mov随机访问通常是最好的。

在32位代码中,在手写代码中制作堆栈帧通常更有意义,特别是,为了可维护性。在64位代码中,通常不是什么问题。虽然用一对额外的push/pop保存/恢复一个额外的寄存器确实会改变堆栈布局,如果任何参数在堆栈上传递的话(例如,一个大的struct按值,但是编写你的函数来获取一个const指针arg代替!)。

+0

非常有用,特别是关于效率的提示。所以在编译优化(帧指针省略)的情况下,会得到更大的代码,因为需要更多的字节来编码rsp寻址? –

+1

@BulatM .:有时省去了每个功能3或4条指令,可以平衡它。这是1 + 3个字节的序言,1 + 0或3个字节的尾声。 (可选的'mov rsp,rbp',然后'pop rbp'或者一些编译器使用LEAVE,它是1个字节,这只是3 uops IIRC,所以如果RSP没有指向保存的RBP值,那么这是一个不错的选择。函数的结尾) –

+2

当你手写程序集时,栈指针可能会非常棘手,因为本地变量可能没有固定的栈指针偏移量。 – fuz