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

Posted

技术标签:

【中文标题】当我们有一个红色区域时,为啥我们需要堆栈分配?【英文标题】:Why do we need stack allocation when we have a red zone?当我们有一个红色区域时,为什么我们需要堆栈分配? 【发布时间】:2016-10-22 20:18:01 【问题描述】:

我有以下疑惑:

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

但我无法理解这一点。为什么这有关系?没有redzone有必要sub rsp, 12吗?毕竟,堆栈大小在一开始是有限的,那么为什么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

【问题讨论】:

您在此处显示的第二个 sn-p 代码是错误的。如果您减少堆栈指针,您必须在从函数返回之前恢复它。因此,您需要在ret 之前添加add rsp, 1024 那是哪个 ABI?我假设是 Linux,但还有其他的,例如适用于 Windows 64、Mac OS X 64 位等的一种。 @rudy 据我了解,只有两个 x86-64 ABI:System V AMD64 ABI(用于 Linux、Solaris、OS X 和其他兼容 POSIX 的操作系统)和微软在 Windows 上使用的实现。问题似乎与前者有关。 这些是主要的,但我相信还有更多。这就是为什么我喜欢人们说出他们的意思。不是每个人都使用符合 POSIX 标准的操作系统。 @RudyVelthuis:我同意,这个问题错误地暗示只有一个 ABI,所以我修复了它。顺便说一句,如果有除 System V 或 Win64(旧式或 __vectorcall)以外的任何 x86-64 ABI,它们可能只是对其中之一的细微修改。我还没有听说过,但是我还没有去寻找。 【参考方案1】:

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

AMD64 ABI 对“红色区域”的评价如下:

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

红色区域的真正目的是优化。它的存在允许代码假设rsp 下方的 128 个字节不会被信号或中断处理程序异步破坏,这使得可以将其用作暂存空间。这使得无需通过在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 ABIdeviates slightly from the AMD64 ABI used on other operating systems。特别是,它没有“红区”的概念。 rsp 以外的区域被认为是易失的,随时可能被覆盖。相反,它要求调用者在堆栈上分配一个home address space,以便被调用者在需要溢出任何寄存器传递的参数时使用它。

【讨论】:

好的,现在很清楚了。所以,我知道我们进程的信号/中断处理程序只需要(实际上是操作系统给它)rsp 并使用它。而且,当我们有低于rsp 的东西时,确实会出现问题。显然这是没有红区的情况。是吗? 信号或中断处理程序不能使用红色区域。这是由 ABI 保证的。【参考方案2】:

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

【讨论】:

-fomit-frame-pointer 是针对 Linux 的 gcc 的默认设置,-O1 及更高版本,并且已经使用了多年。您通常只能在-O0 输出中看到本地人与rbp 的偏移量,这看起来不是很有趣。有趣的事实:选择红色区域的大小是因为-128rsp 的最大一字节位移。 我不知道这已成为默认值。我比我看起来老:) @Peter,为什么不是 255 字节,因为无符号字节可以取最大值 255? @BulatM.:显然因为 x86 disp8 位移是 8 位有符号的,就像操作数大小大于 8 位的指令的 imm8 立即操作数一样。

以上是关于当我们有一个红色区域时,为啥我们需要堆栈分配?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不能在堆栈上分配动态内存?

CSS 定位:相对构建独立的“堆栈”;或者:为啥蓝色害怕红色和黄色?

Grails:堆栈红色区域的无效访问

当我们有常规数组时,为啥我们需要指向数组的指针?

Data Vault 2.0 - 当我们有信息集市时,为啥我们需要业务保险库?

当我们已经有了一阶逻辑时,为啥还需要 PDDL?