x86-64栈帧中的“红色区域” red zone of stack frame on x86-64

前几天看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

得到test1test2对应的指令:

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”的——操作系统、调试器、终端处理程序等等都可能侵占该区域。

参考:

Stack frame layout on x86-64

posted @ 2017-11-03 13:13  QiuhaoLi  阅读(1564)  评论(0编辑  收藏  举报