当我们有一个红色区域时,为啥我们需要堆栈分配?
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
的偏移量,这看起来不是很有趣。有趣的事实:选择红色区域的大小是因为-128
是rsp
的最大一字节位移。
我不知道这已成为默认值。我比我看起来老:)
@Peter,为什么不是 255 字节,因为无符号字节可以取最大值 255?
@BulatM.:显然因为 x86 disp8 位移是 8 位有符号的,就像操作数大小大于 8 位的指令的 imm8 立即操作数一样。以上是关于当我们有一个红色区域时,为啥我们需要堆栈分配?的主要内容,如果未能解决你的问题,请参考以下文章
CSS 定位:相对构建独立的“堆栈”;或者:为啥蓝色害怕红色和黄色?