前几天看System V AMD64 ABI标准的时候发现栈帧的顶部后面有一块“red zone”,在学cs:app3e/深入理解操作系统的时候并没有遇到这个,总结一下。
引用标准中的话:
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字节是被保留的——它不能被信号和终端处理程序使用。因此,函数可以在这个区域放一些临时的数据。特别地,叶子函数可能会将这128字节的区域作为它的整个栈帧,而不是像往常一样在进入函数和离开时靠移动栈指针获取栈帧和释放栈帧。这128字节被称作红色区域。
简单点说,这个红色区域(red zone)就是一个优化。因为这个区域不会被信号或者中断侵占,函数可以在不移动栈指针的情况下使用它存取一些临时数据——于是两个移动rsp的指令就被节省下来了。
然而,标准只说了不会被信号和终端处理程序侵占,red zone还是会被接下来的函数调用使用的,这也是为什么大多数情况下都是叶子函数(不会再调用别的函数)使用这种优化。下面我举一个例子:
/*test.c*/
long test2(long a, long b, long c) /* 叶子函数 */
{
return a*b + c;
}
long test1(long a, long b)
{
return test2(b, a, 3);
}
int main(int argc, char const *argv[])
{
return test1(1, 2);
}
编译汇编与反汇编:
frank@under:~/tmp$ gcc test.c && objdump -d a.out
得到test1
和test2
对应的指令:
00000000004004d6 <test2>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: 48 89 7d f8 mov %rdi,-0x8(%rbp)
4004de: 48 89 75 f0 mov %rsi,-0x10(%rbp)
4004e2: 48 89 55 e8 mov %rdx,-0x18(%rbp)
4004e6: 48 8b 45 f8 mov -0x8(%rbp),%rax
4004ea: 48 0f af 45 f0 imul -0x10(%rbp),%rax
4004ef: 48 89 c2 mov %rax,%rdx
4004f2: 48 8b 45 e8 mov -0x18(%rbp),%rax
4004f6: 48 01 d0 add %rdx,%rax
4004f9: 5d pop %rbp
4004fa: c3 retq
00000000004004fb <test1>:
4004fb: 55 push %rbp
4004fc: 48 89 e5 mov %rsp,%rbp
4004ff: 48 83 ec 10 sub $0x10,%rsp
400503: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400507: 48 89 75 f0 mov %rsi,-0x10(%rbp)
40050b: 48 8b 4d f8 mov -0x8(%rbp),%rcx
40050f: 48 8b 45 f0 mov -0x10(%rbp),%rax
400513: ba 03 00 00 00 mov $0x3,%edx
400518: 48 89 ce mov %rcx,%rsi
40051b: 48 89 c7 mov %rax,%rdi
40051e: e8 b3 ff ff ff callq 4004d6 <test2>
400523: c9 leaveq
400524: c3 retq
可以看到test1
移动了栈顶指针来获取栈帧空间,即sub $xxx, %rsp + leaveq的组合。但是test2
并没有移动栈顶指针,而是直接使用ebp/esp(此时它们两个相等,由于是叶子也不用考虑内存对齐的问题)存放要使用的数据。栈帧布局如下:
最后提一点,Windows x64 ABI中并没有“red zone”这个概念,栈顶指针rsp的低地址处被认为是“volatile”和“unsafe”的——操作系统、调试器、终端处理程序等等都可能侵占该区域。
参考: