GCC为何不使用red zone存储局部变量?汇编栈操作疑问
被调用函数可以使用red zone存储局部变量,无需承担修改栈指针的额外开销。System V所使用的x86-64 ABI规定了一个大小为128字节的red zone,其起始位置紧邻返回地址之后,范围覆盖函数参数所在区域。
对应测试代码与编译生成的汇编如下:
void test(int x, int b) { char ab[60]; strncpy(ab, "Hello World!\n", strlen("Hello World!\n") + 1); }
// gcc (9) -g 0x114d <+4>: push rbp 0x114e <+5>: mov rbp, rsp 0x1151 <+8>: sub rsp, 0x60 ... 0x1196 <+77>: call 0x1050 <__stack_chk_fail@plt> 0x119b <+82>: leave 0x119c <+83>: ret
问题解答
为什么GCC没有使用red zone,反而分配了0x60的栈空间?
128字节的red zone确实从大小上足够存下60字节的ab数组,但GCC不使用red zone是编译选项、函数属性、ABI约束共同导致的,和空间大小无关:
- 你的函数不是叶子函数,不满足red zone的安全使用前提。
test()内部调用了strlen和strncpy两个外部函数:x86-64栈向低地址增长,执行call指令时硬件会自动将返回地址压栈(rsp减8,写入返回地址),此时被调用函数的red zone范围是新rsp值往下128字节,会直接覆盖当前函数如果放在red zone里的局部变量。被调用函数可以随意修改自己的red zone,当前函数存放在这里的局部变量会被直接破坏。只有不调用任何其他函数的叶子函数,才有可能安全使用red zone。 - 你使用的编译选项关闭了优化,同时默认开启栈保护:
- 你仅使用了
-g生成调试信息,默认优化等级是-O0。该等级下GCC代码生成策略极度保守,优先保证调试信息的一致性,会生成标准的帧指针栈帧结构,不会做red zone这类省略栈指针调整的优化,哪怕编译叶子函数也不会使用red zone。 - GCC9默认开启
-fstack-protector-strong栈保护机制,会在栈帧中插入栈金丝雀值检测栈溢出,再加上x86-64要求函数调用时栈必须保持16字节对齐,预留的栈空间除了存放60字节的ab数组,还要存放金丝雀、满足对齐要求,最终分配0x60(96字节)是完全正常的。汇编中的call __stack_chk_fail@plt就是栈检查失败的出口,也印证了栈保护处于开启状态。
- 你仅使用了
- 补充纠正描述偏差:System V x86-64 ABI定义的red zone范围是当前rsp值往低地址128字节(地址区间
rsp-128 ~ rsp-1),并不覆盖栈上传递的函数参数——栈上参数位于返回地址往高地址的区域(rsp+16及以上位置),和red zone完全不重叠。
如何判断一个函数是否使用了red zone?
不能仅通过栈指针是否增减判断,准确判断标准如下:
- 通读函数全量汇编代码,如果存在访问低于当前rsp值的内存地址的操作,访问范围在
rsp-128以内,且这部分地址没有被之前的sub rsp操作纳入正式分配的栈帧范围,就说明函数在使用red zone。 - 实际生成的代码中,使用red zone的函数几乎都是开优化编译的叶子函数:这类函数通常不会调整rsp(没有
sub rsp/add rsp操作,也不会随意执行push/pop修改rsp),直接通过[rsp - 固定偏移]的寻址方式访问red zone存放局部变量。
为什么汇编里没有add rsp, 0x60的栈释放指令?
你看到的是标准的帧指针(rbp)栈帧结构,不需要单独的add rsp指令释放栈:
- 函数开头的
push rbp; mov rbp, rsp会先保存旧的rbp值,再把rbp设置为当前栈帧的基地址,后续sub rsp, 0x60分配的栈空间全部位于rbp的低地址方向。 - 函数结尾的
leave指令等价于两步操作:mov rsp, rbp; pop rbp,可以直接把rsp重置为分配栈空间之前的栈顶地址,一步完成栈空间释放和旧rbp恢复,不需要再单独执行add rsp, 0x60,最后执行ret即可返回,这是帧指针栈帧的标准收尾逻辑。
你可以通过编译叶子函数验证red zone的生成,参考代码如下,使用gcc -O2 -fno-stack-protector -c test.c编译后,就能看到不调整rsp、直接使用red zone的汇编代码:
int leaf_test() { char buf[100]; buf[0] = 'a'; buf[99] = 'z'; return buf[0] + buf[99]; }
内容的提问来源于stack exchange,提问作者yokiyaa J
相关产品推荐
相关产品推荐

