天天看點

棧幀結構的細化(C語言)

C語言中關于棧幀:由于在函數調用時往往會形成棧幀結構,為此我們經常有以下幾個疑問:

1.隻要給函數傳遞參數就會形成臨時變量,這些臨時變量會存在棧上,具體怎樣存的?

2.函數内部定義的變量叫局部變量(自動變量),這些變量調用時建立,調用完成後自動釋放,為什麼?

3.函數調用完成之後應該傳回到原來調用的地方,那之前要做什麼?

4.函數傳回時的臨時變量存在哪裡?

關于了解棧幀,我們需要知道一些預備知識:

1.寄存器:具有存儲功能的離cpu最近的存儲單元,速度最快

(1)ebp:基址寄存器(棧底)寄存器

(2)esp:棧頂寄存器

(3)pc指針(程式計數器):指向目前正在執行指令的下一條指令

(4)eax,ebx...通用寄存器

2.cpu的任務:(1)取指令(從記憶體取到cpu内部) (2)分析指令(根據指令集、操作數)  (3)執行指令

3.指令集:精簡指令集、複雜指令集

4.任何一個函數都有屬于自己的ebp(棧底)、esp(棧頂);但是記憶體中的ebp、esp隻有一份;為了保證一個函數不會覆寫另一個函數的ebp、esp,調用時必須對原來函數

的ebp(棧底)、esp(棧頂)進行儲存;因為ebp(棧底)、esp(棧頂)永遠指向目前函數最新的棧底、棧頂;棧是從(位址減小)的方向生長

6.函數調用時,相關寄存器内容必須儲存,目的是調用完之後恢複(程式切換也要儲存,隻是os的問題)

7.call指令的任務: 

(1)跳轉到被調用函數的入口處(通過調整pc指針完成)

(2)儲存目前正在執行指令的下一條指令的位址到棧中

8.push:把資料壓到棧頂;pop把棧頂資料拉出來放在指定寄存器裡

9.形參執行個體化時從右往左順序進行的 

10.将資料壓入棧頂時,先移動指針再放資料,保證棧頂指針永遠指向一個有效的資料

11.調用函數時,先形成實參的臨時變量,再執行call指令

12.第一個參數與函數傳回位址相鄰;

13.先将棧底位址儲存,再将棧頂内容指派給棧底,(棧底指向現在的棧頂)

為了講解清楚棧幀,我們以下面一個小小的程式為例:

#include <stdio.h>
#include <windows.h>
int fun(int x,int y)
{
	int c=0xcccccccc;
}
int main()
{
	int a=0xaaaaaaaa;
	int b=0xbbbbbbbb;
	int ret=fun(a, b);
	printf("you should running here\n");
	system("pause");
	return 0;
}
           

具體執行過程,我們以指令在計算機内部過程來剖析:

開始,寄存器ebp、esp分别指向main函數的棧底、棧頂(main函數是入口),pc指針指向main函數的代碼區;

(1)定義變量a,b;反彙編:dword ptr [a],0AAAAAAAAh ,dword ptr [b],0BBBBBBBBh 

(2)開始調用:但是調用之前要形參執行個體化形成臨時變量:(注意形參從右往左進行執行個體化)

     mov         eax,dword ptr [b] :把變量b存到通用寄存器eax中 

     push        eax  :把eax中(b)壓入堆棧中

     mov         ecx,dword ptr [a]  :把變量b存到通用寄存器ecx中

     push        ecx  :把ecx中(b)壓入堆棧中

     使用call指令開始調用函數:call        @ILT+295(_fun) (0F9112Ch) ;注意call指令完成兩個任務:(1)跳轉到被調用函數的入口處(通過調整pc指針完成)

(3)儲存目前正在執行指令的下一條指令的位址到棧中

    add         esp,8  :儲存目前正在執行指令的下一條指令的位址到棧中

    mov         dword ptr [ret],eax  :跳轉到被調用函數的入口處

(4)這時已經來到fun函數:但是現在系統的ebp、esp還指向原來main函數的棧底和棧頂,顯然我們應該馬上修改二者的值:

      push        ebp  :把ebp(main函數的棧底内容)中的内容壓入棧頂

      mov         ebp,esp :把esp内容放到ebp中,是以上面先儲存ebp内容;此時顯示的棧底指向棧頂 

      sub         esp,0CCh :棧頂向下移動

  現在的棧底、棧頂所組成的部分為被調用函數的棧幀結構

(4)恢複棧幀結構:

     mov         esp,ebp  :把ebp裡内容複原給esp,恢複棧底

     pop         ebp  :然後把棧頂内容傳回給ebp,恢複棧頂

     ret :恢複pc指針的指向(指向main函數的函數傳回位址)

     add         esp,8  :棧頂向上移動,釋放實參執行個體化的臨時變量

以上的執行過程用簡單的圖示描述如下:

棧幀結構的細化(C語言)

繼續閱讀