函式私底下都在干什么?圖解函式堆疊幀的創建與銷毀
- 前言(什么是函式堆疊幀?)
- 暫存器的介紹
- 資料暫存器(EAX EBX ECX EDX)
- 指標暫存器(ESP EBP)
- 變址暫存器(ESI EDI)
- 函式堆疊幀的創建與銷毀程序詳解
- 1.push指令
- esp和ebp
- 2.mov指令
- 3.sub指令和add指令
- 3.lea指令
- 4.rep stos指令
- 4.call指令
- 傳參
- 呼叫Add函式并記錄下一行代碼地址
- 5.pop指令
- 6.ret指令
- 總結
前言(什么是函式堆疊幀?)
函式堆疊幀是什么?相信很多人第一次聽到時都一臉懵逼,我們不妨百度一下,
C語言中,每個堆疊幀對應著一個未運行完的函式,堆疊幀中保存了該函式的回傳地址和區域變數,
堆疊幀也叫程序活動記錄,也就是函式的活動記錄,函式執行的環境,函式引數、函式的區域變數、函式執行完后回傳到哪里等等,
首先應該明白,堆疊是從高地址向低地址延伸的,每個函式的每次呼叫,都有它自己獨立的一個堆疊幀,這個堆疊幀中維持著所需要的各種資訊,暫存器ebp指向當前的堆疊幀的底部(高地址),暫存器esp指向當前的堆疊幀的頂部(低地址),
想要了解函式堆疊幀到底是什么一個東西,那么就必須要進入反匯編來觀察,我們會發現反匯編界面有著很多CPU指令,想要弄清楚函式堆疊幀那么就必須搞懂這些指令,這對我們理解函式堆疊幀有著很大的幫助,本文按照順序來介紹這些指令并梳理創建與銷毀函式堆疊幀的程序,
首先我們寫好一段代碼:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
}
在編譯器下按F10進入除錯界面,然后滑鼠右擊進入反匯編界面:

我們會發現在反匯編界面有著許多的cpu指令,那么下面就簡單介紹一下cpu指令和暫存器,
暫存器的介紹
資料暫存器(EAX EBX ECX EDX)
- 暫存器EAX通常稱為累加器(Accumulator),用累加器進行的操作可能需要更少時間,累加器可用于乘、除、輸入/輸出等操作,它們的使用頻率很高;
- 暫存器EBX稱為基地址暫存器(Base Register),它可作為存盤器指標來使用;
- 暫存器ECX稱為計數暫存器(Count Register),在回圈和字串操作時,要用它來控制回圈次數;在位操作中,當移多位時,要用CL來指明移位的位數;
- 暫存器EDX稱為資料暫存器(Data Register),在進行乘、除運算時,它可作為默認的運算元參與運算,也可用于存放I/O的埠地址,
指標暫存器(ESP EBP)
- 暫存器EBP稱為基址指標暫存器(堆疊底指標),
- 暫存器ESP稱為堆疊指標暫存器(堆疊頂指標),
變址暫存器(ESI EDI)
- 暫存器ESI稱為源變址暫存器 (Source Index);
- 暫存器EDI稱為目的變址暫存器(Destination Index),
函式堆疊幀的創建與銷毀程序詳解
1.push指令
push指令是壓堆疊的操作,就是將一塊記憶體地址壓入堆疊中(從高地址開始壓入):

在呼叫main函式之前,main函式還被其他函式所呼叫,這里不做贅述,很明顯我們看到,ebp被壓入堆疊中,push指令就是這么簡單,
esp和ebp
這里的esp和ebp分別是兩個暫存器,里面存放的是地址,這兩個地址用用來維護函式堆疊幀的,esp維護堆疊頂,ebp維護堆疊底,當每次開辟新的記憶體空間時,esp會自動跳到堆疊頂,所以esp為堆疊頂指標而ebp是堆疊底指標,
2.mov指令
move指令是將地址中的資料賦給其他的地址例如:
009818B1 mov ebp,esp
就是將esp所存盤的值也就是esp的地址賦給ebp,那么此時ebp的位置如下

3.sub指令和add指令
sub指令是減值操作,例如:
009818B3 sub esp,0E4h
這里就是esp的地址減去0E4h這么多的地址,那么此時esp指標應該上移:
相反add是加的指令

那么此時esp堆疊頂指標和ebp堆疊底指標之間所維護的空間就是main函式的堆疊幀,至于地址空間大小,一定是符合要求的,編譯器是為我們考慮好的,所以我們不必擔心開辟出來的空間不夠啊之類的問題,

再次壓入三個地址(暫存器地址),
3.lea指令
lea指令和mov有著些許相似的地方,mov是賦記憶體中的值,而lea是賦地址,
例如:
lea edi,[ebp-24h]
就是將[ebp-24h]這個地址值直接賦給edi,而不是把[ebp-24h]處的記憶體地址里的資料賦給eax,
mov指令則恰恰相反
例如:
mov ebp,esp
則是把記憶體地址為esp處的資料賦給eax,

4.rep stos指令
009818BC lea edi,[ebp-24h]
009818BF mov ecx,9
009818C4 mov eax,0CCCCCCCCh
009818C9 rep stos dword ptr es:[edi]
rep指令的目的是重復其上面的指令.ECX的值是重復的次數.
STOS指令的作用是將eax中的值拷貝到ES:EDI指向的地址.
這段代碼的意思也就是將ebp-24h向上的地址,執行回圈9次,雙字(一個字等于兩個位元組)也就是四個位元組的內容全部初始化為0CCCCCCCCh ,


這里也就是將main函式的堆疊幀初始化為cccccccc
4.call指令
call指令用來呼叫函式,并且記錄下行代碼地址,這里開始進入main函式
開始執行有效代碼:
- 將a初始化為10:十六進制0a就是10
- 將b初始化為20:
- 將c初始化為0:

b和c的位置都間隔2個地址空間(8位元組),這是編譯器所決定的

傳參
下面執行c=Add(a,b),傳參

ebp-14h也就是b的位置,也就代表編譯器是從右往左邊傳參的

呼叫Add函式并記錄下一行代碼地址

那么指令執行!

進行運算,我們可以看出其實引數加減是在暫存器中運行的,最后eax暫存器的值就是30,是把值都放在eax累加器的,那么為什么形參是實參的臨時拷貝也就解釋的通了,改變形參不會改變實參,

5.pop指令
pop指令就是出堆疊,銷毀函式堆疊幀
此時esp和ebp指向同一位置,Add函式堆疊幀被銷毀
ebp記憶體地址彈出,那么此時ebp堆疊底指標也就要回傳,指向在main函式中的ebp

此時,esp和ebp回傳到main函式里面!
6.ret指令
回傳到記錄的call指令記錄的下一條代碼的位置

回傳,那么就會彈出call指令記錄的位置且執行esp+8,那esp的位置就是:

ebp-20h就是c的位置,eax的值就是30,將30賦值給c,那么c的值就是30了
至此,函式堆疊幀的創建與銷毀邏輯大概就介紹完畢,
總結
想要解釋好函式堆疊幀畫圖是必不可少的環節,圖文相結合才能更好地理解函式堆疊幀,還要對于cpu的指令進行一定了解,
函式堆疊幀算是比較底層的東西,希望本文對你以后的學習能有所幫助!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292775.html
標籤:其他
上一篇:C語言中static的用法(修飾區域變數,修飾全域變數,修飾函式)
下一篇:函式堆疊幀詳解
