天天看點

『C』棧幀什麼是棧幀函數調用過程總結

什麼是棧幀

棧幀也叫過程活動記錄,是編譯器用來實作過程/函數調用的一種資料結構。C語言中,每個棧幀對應着一個未運作完的函數。棧幀中儲存了該函數的傳回位址和局部變量。從邏輯上講,棧幀就是一個函數執行的環境:函數參數、函數的局部變量、函數執行完後傳回到哪裡等等。

棧是從高位址向低位址延伸的。每個函數的每次調用,都有它自己獨立的一個棧幀,這個棧幀中維持着所需要的各種資訊。寄存器ebp指向目前棧幀的底部(高位址),寄存器esp指向目前的棧幀的頂部(低位址)。

注意:ebp指向目前位于系統棧最上邊一個棧幀的底部,而不是系統棧的底部。嚴格說來,“棧幀底部”和“棧底”是不同的概念;esp所指的棧幀頂部和系統棧的頂部是同一個位置。

函數調用過程

示例代碼

#include <stdio.h>
#include <stdlib.h>

int Add(int left, int right){

	int sum = 0;

	sum = left + right;

	return sum;

}

int main(){

	int num1 = 1;
	int num2 = 2;
	int result = 0;

	result = Add(num1, num2);

	printf("result: %d\n", result);
	
	system("pause");
	return 0;
}
           

_tmainCRTStartup函數的調用

我們對該代碼進行調試,檢視調用堆棧如下:

『C』棧幀什麼是棧幀函數調用過程總結

我們可以發現,main函數是在_tmainCRTStartup函數中調用的。而_tmainCRTStartup是在mainCRTStartup中調用的。

下面看一下_tmainCRTStartup函數的棧幀。

『C』棧幀什麼是棧幀函數調用過程總結

main函數的調用

既然main函數被調用的,那麼這個程式第一步應該會為main函數配置設定棧空間。調試中檢視反彙編。

int main(){
/* 将ebp壓棧處理(友善函數傳回之後的現場恢複) */
00C51420  push        ebp  

/* 這裡使esp的值賦給ebp,産生新的ebp */
00C51421  mov         ebp,esp  

/* 給esp減去一個16進制數字0E4H,産生新的esp */
00C51423  sub         esp,0E4h  

00C51429  push        ebx  
00C5142A  push        esi  
00C5142B  push        edi  

/* 将棧幀與開辟的空間全部初始化為0xCCCCCCCC */
00C5142C  lea         edi,[ebp-0E4h]  
00C51432  mov         ecx,39h  
00C51437  mov         eax,0CCCCCCCCh  

00C5143C  rep stos    dword ptr es:[edi] 

/* 建立局部變量num1、num2、result */
	int num1 = 1;
00C5143E  mov         dword ptr [num1],1  
	int num2 = 2;
00C51445  mov         dword ptr [num2],2  
	int result = 0;
00C5144C  mov         dword ptr [result],0  
           

圖解如下

『C』棧幀什麼是棧幀函數調用過程總結

過程分析

  • 首先_tmainCRTStartup函數調用main函數。
  • 将ebp壓棧處理,儲存指向棧底的ebp的位址(友善函數傳回之後的現場恢複)。
  • 将esp的值賦給ebp,産生新的ebp。
  • 給esp減去一個16進制數0E4H(為main函數預開辟空間)。
  • push ebx、esi、edi。
  • lea指令,加載有效位址。
  • 初始化預開辟的空間為0xCCCCCCCC。
  • 建立變量num1、num2和result。

Add函數的調用

先檢視main函數中調用Add之前的反彙編:

result = Add(num1, num2);
	
/* 參數壓棧,先壓num2 */
00C51453  mov         eax,dword ptr [num2]  
00C51456  push        eax  

/* 參數壓棧,壓num1 */
00C51457  mov         ecx,dword ptr [num1]  
00C5145A  push        ecx  

/* call指令調用Add函數 */
00C5145B  call        _Add (0C510E6h)  
00C51460  add         esp,8  
00C51463  mov         dword ptr [result],eax  
           

逐語句執行到call指令,如下,跳轉到Add函數。

『C』棧幀什麼是棧幀函數調用過程總結

再來看Add函數的反彙編:

int Add(int left, int right){
/* 将ebp壓棧處理(友善函數傳回之後的現場恢複) */
00C513D0  push        ebp  

/* 這裡使esp的值賦給ebp,産生新的ebp */
00C513D1  mov         ebp,esp 

/* 給esp減去一個16進制數字0CCH,産生新的esp */ 
00C513D3  sub         esp,0CCh  

00C513D9  push        ebx  
00C513DA  push        esi  
00C513DB  push        edi  

/* 将棧幀與開辟的空間全部初始化為0xCCCCCCCC */
00C513DC  lea         edi,[ebp-0CCh]  
00C513E2  mov         ecx,33h  
00C513E7  mov         eax,0CCCCCCCCh 
 
00C513EC  rep stos    dword ptr es:[edi]  

/* 建立局部變量sum */
	int sum = 0;
00C513EE  mov         dword ptr [sum],0  

/* 擷取形參left和right的值,相加之後存到sum中 */
	sum = left + right;
00C513F5  mov         eax,dword ptr [left]  
00C513F8  add         eax,dword ptr [right]  
00C513FB  mov         dword ptr [sum],eax  

/* 将結果存到寄存器,通過寄存器帶回函數的傳回值 */
	return sum;
00C513FE  mov         eax,dword ptr [sum]  

}

/* 寄存器edi、esi、ebx出棧 */
00C51401  pop         edi  
00C51402  pop         esi  
00C51403  pop         ebx 

/* 将ebp賦給esp,使esp指向ebp指向的地方,釋放Add函數的棧幀 */ 
00C51404  mov         esp,ebp  

/* ebp出棧,将出棧的内容給ebp(即main函數的ebp),回到main函數的棧幀 */
00C51406  pop         ebp  

/* 出棧一次,将出棧的内容當做位址,并跳轉到該位址處(call指令的下一條) */
00C51407  ret  
           

圖解如下

『C』棧幀什麼是棧幀函數調用過程總結

注意:C語言函數調用參數的壓棧順序為從右向左,上述例子中就是num2先壓棧,num1後壓棧。

過程分析

  • 将num2存入eax寄存器,将eax壓棧。
  • 将num1存入ecx寄存器,将ecx壓棧。
  • call指令的調用,先将call指令的下一條指令的位址壓棧。然後跳轉到Add函數。
  • 将main函數的ebp壓棧,友善函數傳回之後的現場恢複。
  • 将esp的值賦給ebp,産生新的ebp,即Add函數的ebp。
  • 給esp減去一個16進制的數0CCH,為Add函數預開辟空間。
  • push ebx、esi、edi。
  • lea指令,加載有效位址。
  • 初始化預開辟的空間為0xCCCCCCCC。
  • 建立變量sum。
  • 擷取形參left和right的值再相加,将結果存到sum中。
  • 将結果存儲到eax寄存器,通過寄存器帶回函數的傳回值。
  • pop edi、esi、ebx。
  • 将ebp的值賦給esp,是esp指向ebp指向的地方。釋放Add函數的棧幀。
  • ebp出棧,将出棧的内容給ebp(即main函數的ebp),回到main函數的棧幀。
  • ret指令,出棧一次,将出棧的内容當做位址,并跳轉到該位址處(call指令的下一條指令的位址)。

總結

  • ebp寄存器:擴充基址指針寄存器(extended base pointer)其記憶體放一個指針,該指針指向系統棧最上面一個棧幀的底部。
  • esp寄存器:擴充棧指針寄存器(extended stack pointer),是指針寄存器的一種,用于存放函數棧頂指針。esp為棧指針,用于指向棧的棧頂(下一個壓入棧的活動記錄的頂部)。