7

我有以下疑问:

正如我们所知,System V x86-64 ABI 在堆栈帧中为我们提供了大约一个固定大小的区域(128 字节),即所谓的 redzone。因此,我们不需要使用例如sub rsp, 12. 只需制作mov [rsp-12], X,仅此而已。

但我无法理解这一点。为什么这有关系?有没有必要sub rsp, 12没有redzone?毕竟,堆栈大小在一开始是有限的,那么为什么sub rsp, 12重要呢?我知道这使我们能够跟随堆栈的顶部,但在那一刻让我们忽略它。

我知道某些指令使用rsp值(例如ret),但在那一刻并不关心它。

问题的症结在于:我们没有红区,我们已经做到了:

function:
    mov [rsp-16], rcx
    mov [rsp-32], rcx
    mov [rsp-128], rcx
    mov [rsp-1024], rcx
    ret

有区别吗?

function:
    sub rsp, 1024
    mov [rsp-16], rcx
    mov [rsp-32], rcx
    mov [rsp-128], rcx
    mov [rsp-1024], rcx
    add rsp, 1024
    ret
4

2 回答 2

13

“红色区域”并不是绝对必要的。用你的话来说,它可以被认为是“毫无意义的”。您可以使用红色区域执行的所有操作,也可以执行针对 IA-32 ABI 的传统方式。

以下是AMD64 ABI关于“红色区域”的说明:

超出指向的位置的 128 字节区域%rsp被认为是保留的,不应被信号或中断处理程序修改。因此,函数可以将此区域用于函数调用之间不需要的临时数据。特别是,叶函数可以将这个区域用于它们的整个堆栈帧,而不是在序言和尾声中调整堆栈指针。这个区域被称为红色区域。

红色区域的真正目的是作为优化。它的存在允许代码假设下面的 128 字节rsp不会被信号或中断处理程序异步破坏,这使得可以将其用作暂存空间。这使得无需通过将堆栈指针移入rsp. rsp这是一种优化,因为现在可以省略递减和恢复指令,从而节省时间和空间。

所以是的,虽然你可以用 AMD64 做到这一点(并且需要用 IA-32 做到这一点):

function:
    push rbp                      ; standard "prologue" to save the
    mov  rbp, rsp                 ;   original value of rsp

    sub  rsp, 32                  ; reserve scratch area on stack
    mov  QWORD PTR [rsp],   rcx   ; copy rcx into our scratch area
    mov  QWORD PTR [rsp+8], rdx   ; copy rdx into our scratch area

    ; ...do something that clobbers rcx and rdx...

    mov  rcx, [rsp]               ; retrieve original value of rcx from our scratch area
    mov  rdx, [rsp+8]             ; retrieve original value of rdx from our scratch area
    add  rsp, 32                  ; give back the stack space we used as scratch area

    pop  rbp                      ; standard "epilogue" to restore rsp
    ret

在只需要 128 字节暂存区(或更小)的情况下,我们不需要这样做,因为这样我们就可以将红色区域用作暂存区。

另外,由于我们不再需要递减堆栈指针,我们可以将rsp其用作基指针(而不是rbp),从而无需保存和恢复rbp(在序言和尾声中),并且还可以腾出空间rbp用作另一个通用指针——目的登记!

(从技术上讲,打开帧指针省略(-fomit-frame-pointer,默认情况下启用,-O1因为 ABI 允许它)也可以使编译器省略序言和尾声部分,具有相同的好处。但是,如果没有红色区域,需要调整堆栈指针以保留空间不会改变。)

但是请注意,ABI 仅保证诸如信号和中断处理程序之类的异步事物不会修改红色区域。对其他函数的调用可能会破坏红色区域中的值,因此它不是特别有用,除了叶函数(那些不调用任何其他函数的函数,就好像它们位于函数调用树的“叶”) .


最后一点:Windows x64 ABI 与其他操作系统上使用的 AMD64 ABI 略有不同。特别是,它没有“红区”的概念。超出的区域rsp被认为是不稳定的,随时可能被覆盖。相反,它要求调用者在堆栈上分配一个本地地址空间,然后在它需要溢出任何寄存器传递的参数的情况下,它可供被调用者使用。

于 2016-06-21T11:00:13.063 回答
3

在您的示例中,您的偏移量错误,这就是它没有意义的原因。代码不应访问堆栈指针下方的区域——它是未定义的。红色区域用于保护堆栈指针下方的前 128 个字节。您的第二个示例应为:

function:
    sub rsp, 1024
    mov [rsp+16], rcx
    mov [rsp+32], rcx
    mov [rsp+128], rcx
    mov [rsp+1016], rcx
    add rsp, 1024
    ret

如果一个函数需要的暂存空间量高达 128 字节,那么它可以使用堆栈指针下方的地址,而无需调整堆栈:这就是优化。相比:

function:        // Not using red-zone.
    sub rsp, 128
    mov [rsp+120], rcx
    add rsp, 128
    ret

使用红色区域使用相同的代码:

function:        // Using the red-zone, no adjustment of stack
    mov [rsp-8], rcx
    ret

关于堆栈指针偏移量的混淆通常是因为编译器从帧(RBP)生成负偏移量,而不是从堆栈(RSP)生成正偏移量。

于 2016-06-21T11:21:38.180 回答