天天看點

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
           

得到

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(此時它們兩個相等,由于是葉子也不用考慮記憶體對齊的問題)存放要使用的資料。棧幀布局如下:

x86-64棧幀中的“紅色區域” red zone of stack frame on x86-64

最後提一點,Windows x64 ABI中并沒有“red zone”這個概念,棧頂指針rsp的低位址處被認為是“volatile”和“unsafe”的——作業系統、調試器、終端處理程式等等都可能侵占該區域。

參考:

Stack frame layout on x86-64