当我们有一个红色区域时,为什么我们需要堆栈分配?

Why do we need stack allocation when we have a red zone?

我有以下疑惑:

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

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

我知道有些指令使用 rsp 值(例如 ret),但当时并不关心它。

问题的症结在于: 我们没有 redzone,我们已经完成了:

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

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

以下是 AMD64 ABI 对“红色区域”的描述:

The 128-byte area beyond the location pointed to by %rsp is considered to be reserved and shall not be modified by signal or interrupt handlers. Therefore, functions may use this area for temporary data that is not needed across function calls. In particular, leaf functions may use this area for their entire stack frame, rather than adjusting the stack pointer in the prologue and epilogue. This area is known as the red zone.

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

所以是的,虽然您可以使用 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 允许它)也可以让编译器省略序言和结尾部分,具有相同的好处。但是,没有红色区域,需要调整堆栈指针以保留 space 不会改变。)

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


最后一点:堆栈上的 Windows x64 ABI deviates slightly from the AMD64 ABI used on other operating systems. In particular, it has no concept of a "red zone". The area beyond rsp is considered volatile and subject to be overwritten at any time. Instead, it requires that the caller allocate a home address space,当被调用者需要溢出任何寄存器传递的参数时,它可供被调用者使用。

你的例子中的偏移量是错误的,这就是为什么它没有意义。代码不应访问堆栈指针下方 区域 - 它未定义。红色区域用于保护堆栈指针下方 的前 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

如果函数需要的暂存量 space 最多为 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)。