
函數(shù)在被調(diào)用的時候,操作系統(tǒng)會在內(nèi)存棧區(qū)為這個函數(shù)開辟一塊空間,提供給這個函數(shù)使用,這個棧空間就是該函數(shù)的棧幀。函數(shù)的返回地址、函數(shù)中創(chuàng)建的局部變量、以及一些寄存器信息都會保存在這塊棧空間中。在函數(shù)運行完畢后,函數(shù)棧幀會銷毀,將內(nèi)存空間釋放出來。
2.基本的寄存器2.1 ebp、esp
esp即extended stack pointer,擴展棧指針寄存器,是指針寄存器的一種,用于存放函數(shù)棧頂指針。
ebp即extended base pointer,擴展基址指針寄存器,也屬于指針寄存器,與esp對應(yīng),ebp用于存放函數(shù)棧底指針。
函數(shù)的棧幀空間就是由這兩個寄存器來維護的。
2.2 edi、esi
這兩個屬于變址寄存器,用于存放存儲單元在段內(nèi)的偏移量。
edi:源索引寄存器,一般用于在串操作中存放數(shù)據(jù)源的地址
esi:目標(biāo)索引寄存器,一般用于在串操作中存放目標(biāo)地址
2.3 ecx、ebx、eax
這三個屬于通用寄存器,在程序執(zhí)行的過程中,大部分時間都是通過操作這些寄存器來實現(xiàn)指令功能的。
ecx:計數(shù)器,用于存放重復(fù)、循環(huán)等指令的執(zhí)行次數(shù)計數(shù)
ebx:基地址寄存器,在內(nèi)存尋址時存放基地址
eax:累加器,在進行加法運算時使用,也用于存放函數(shù)的返回值
2.4 psw
psw是標(biāo)志寄存器,是存放標(biāo)志信息的寄存器。標(biāo)志信息的作用是為CPU執(zhí)行相關(guān)指令提供行為依據(jù),或者控制CPU的相關(guān)工作方式。
3.0 操作數(shù)
大部分的匯編指令中,左邊的操作數(shù)都是目標(biāo)操作數(shù),右邊的操作數(shù)都是源操作數(shù)
3.1 push、pop
push即壓棧,使一個寄存器中的數(shù)據(jù)入棧,然后使棧頂指針的值相應(yīng)減小
pop即彈出、出棧,將棧頂?shù)臄?shù)據(jù)存到一個寄存器中,然后使棧頂指針的值相應(yīng)增加,相當(dāng)于從棧里面彈出了一個數(shù)據(jù)
push ebp //將ebp中的值入棧
pop edi //將棧頂元素出棧,并儲存到寄存器edi中3.2 mov
將源地址中的一個值賦到一個目標(biāo)地址中,源地址中的值不受影響
mov ebp,esp //將esp中存的值賦給ebp,即esp中的值不變,ebp中的值變?yōu)閑sp中的值3.3 sub、add
sub即減法,將一個數(shù)據(jù)減小一定的值
add即加法,將一個數(shù)據(jù)增加一定的值
sub esp,0E4h //將esp中存的值減小0E4h
add esp,0E4h //將esp中存的值增加0E4h3.4 lea
lea即load effective address,加載有效地址,將一個地址加載到一個寄存器中
lea edi,[ebp-04Eh] //將[ebp-04Eh]這個地址加載到寄存器edi中3.5 rep
rep即repeat,重復(fù),這是一個前綴指令,指令的作用是重復(fù)執(zhí)行后面的指令
rep指令每次執(zhí)行的時候都從寄存器ecx里面讀取值,當(dāng)ecx中的值大于0,就執(zhí)行后面語句,執(zhí)行完以后,會讓ecx - 1,然后再執(zhí)行rep后面的指令,直到減小到0,因此每次rep執(zhí)行之前,一般都會先將重復(fù)次數(shù)存到ecx中,執(zhí)行完后ecx中的值都會變?yōu)?
rep add esp,1 //將后面的add語句重復(fù)執(zhí)行,重復(fù)次數(shù)由寄存器ecx中的值決定3.6 stos
stos即store string,串儲存,將寄存器eax中存的值賦值到目標(biāo)地址(這個地址一般都是es:[edi])
stos dword ptr es:[edi] //將寄存器eax中一個雙字長度的數(shù)據(jù)賦值到es:[edi]這個地址賦值之后還會執(zhí)行一次使edi中存的值加4或減4,具體是執(zhí)行加還是減由標(biāo)志寄存器中的方向標(biāo)志位DF來決定,DF為0時執(zhí)行加,DF為1時執(zhí)行減,可以使用cld指令和std指令來對DF進行設(shè)置
cld指令:將標(biāo)志寄存器的DF位設(shè)置為0
std指令:將標(biāo)志寄存器的DF位設(shè)置為1
3.7 call、jmp
call是子程序調(diào)用指令,使程序跳轉(zhuǎn)到目標(biāo)地址來執(zhí)行子程序,跳轉(zhuǎn)之前會先將call指令的下一條指令的地址進行壓棧,執(zhí)行完子程序之后會跳轉(zhuǎn)回到call指令的下一條指令的位置,然后再繼續(xù)按順序執(zhí)行指令
jmp是無條件轉(zhuǎn)移指令,使程序直接跳轉(zhuǎn)到目標(biāo)地址執(zhí)行下一條指令,之后程序按順序執(zhí)行
call @ILT+215(_SUB) (0DF10DCh) //首先將下一條語句的地址入棧,然后程序發(fā)生跳轉(zhuǎn),跳轉(zhuǎn)的目標(biāo)位置是0x0df10dc
jmp SUB (0DF1380h) //使程序跳轉(zhuǎn)到地址0xdf1380處(SUB函數(shù)的第一條指令)3.8 ret
ret即return,返回,彈出棧頂?shù)脑兀⑹钩绦蛱D(zhuǎn)到棧頂元素儲存的地址對應(yīng)的指令
3.9 xor
xor即exclusive or,異或,將源操作數(shù)與目標(biāo)操作數(shù)進行按位異或,得到的值保存到目標(biāo)操作數(shù)中
xor eax,ebx //將eax中存的值與ebx中存的值異或,所得結(jié)果存到eax中使用一段簡單的C語言代碼,通過VS2010中的反匯編功能,從匯編代碼的角度觀察這段代碼中的函數(shù)在內(nèi)存中是怎么調(diào)用、怎么實現(xiàn)功能的。
//非常簡單的一段C語言代碼,用作觀察對象
#includeint SUB(int x, int y)
{int z = 0;
z = x - y;
return z;
}
int main()
{int a = 1;
int b = 3;
int c = 0;
c = SUB(a, b);
return 0;
} 在VS2010中對這段代碼進行調(diào)試,按F10進入調(diào)試模式后,可以看見反匯編、調(diào)用堆棧、監(jiān)視、內(nèi)存這幾個窗口。
接下來通過這幾個窗口來觀察這段代碼在內(nèi)存中是怎么執(zhí)行的。
首先,在調(diào)用堆棧窗口可以看到函數(shù)的調(diào)用情況。容易發(fā)現(xiàn)main函數(shù)是被__tmainCRTstartup函數(shù)調(diào)用的,而__tmainCRTstartup函數(shù)又是被mainCRTstartup函數(shù)調(diào)用的。其中mainCRTstartup函數(shù)是啟動函數(shù),與C語言程序的啟動有關(guān),功能大致是在程序啟動之前做一些準(zhǔn)備工作。
這說明在調(diào)用main函數(shù)之前,內(nèi)存中已經(jīng)為之前的__tmainCRTstartup函數(shù)開辟了棧幀空間,因此在程序剛開始運行的時候,ebp和esp寄存器正在維護的是__tmainCRTstartup函數(shù)的棧幀,此時內(nèi)存棧區(qū)中的情況是這樣的:
此時程序還沒開始執(zhí)行第一條語句,處于準(zhǔn)備進入main函數(shù)的狀態(tài)。
在執(zhí)行第一條語句之前,先在內(nèi)存中為main函數(shù)開辟一塊空間,即創(chuàng)建棧幀。這部分對應(yīng)的匯編代碼如下:
int main()
{00DF13D0 push ebp //將ebp的值入棧,此時棧頂指針esp的值減小,因為棧中壓入了新元素
00DF13D1 mov ebp,esp //將esp的值賦給ebp,即令ebp指向esp指向的地址
00DF13D3 sub esp,0E4h //將esp的值減小0E4h
00DF13D9 push ebx //ebx入棧
00DF13DA push esi //esi入棧
00DF13DB push edi //edi入棧
00DF13DC lea edi,[ebp-0E4h] //將ebp-0E4h這個地址賦給edi(作為開始地址)
00DF13E2 mov ecx,39h //將39h賦給ecx(作為重復(fù)次數(shù))
00DF13E7 mov eax,0CCCCCCCCh //將0CCCCCCCCh賦給eax(用作賦值內(nèi)容)
00DF13EC rep stos dword ptr es:[edi] //重復(fù)賦值,dword表示雙字(作為每次賦值的長度),es:[edi]為賦值的目標(biāo)地址
//上面四個語句合起來的效果是:
//從es:[edi]這個地址開始,向高地址方向重復(fù)賦值,每次賦值的長度為雙字(四個字節(jié))
//每次賦值后edi中的地址的值會增加4,從而實現(xiàn)向高地址方向重復(fù)多次賦值
//賦值的內(nèi)容為eax中的值,重復(fù)次數(shù)為ecx中的值(39h次)
int a = 1;
......這段過程中,內(nèi)存中的情況是這樣的:
接下來在主函數(shù)中創(chuàng)建并初始化變量。這部分對應(yīng)的匯編代碼如下:
......
int a = 1;
00DF13EE mov dword ptr [ebp-8],1 //將1這個值存到ebp-8這個地址中
int b = 3;
00DF13F5 mov dword ptr [ebp-14h],3 //將3這個值存到ebp-14h這個地址中
int c = 0;
00DF13FC mov dword ptr [ebp-20h],0 //將0這個值存到ebp-20h這個地址中
c = SUB(a, b);
......由此可見這幾個int變量的存放位置恰好是從ebp-8開始,每隔8個字節(jié)存放一個數(shù)據(jù)。變量的數(shù)據(jù)在棧幀存放的位置是由編譯器決定的,不同的編譯器下存放的位置可能不同。
這段過程中,內(nèi)存中的情況是這樣的:
接下來調(diào)用SUB函數(shù),首先進行的是函數(shù)傳參以及程序的跳轉(zhuǎn)。這部分對應(yīng)的匯編代碼如下:
......
c = SUB(a, b);
//下面四條指令完成的是函數(shù)傳參
00DF1403 mov eax,dword ptr [ebp-14h] //將雙字指針ebp-14h中的值(即b的值)存入寄存器eax中
00DF1406 push eax //eax入棧
00DF1407 mov ecx,dword ptr [ebp-8] //將ebp-8中的值(即a的值)存入寄存器ecx中
00DF140A push ecx //ecx入棧
//call指令調(diào)用SUB函數(shù)
00DF140B call @ILT+215(_SUB) (0DF10DCh) //子程序調(diào)用指令
//首先將下一條語句的地址(0x00df1410)入棧,然后程序發(fā)生跳轉(zhuǎn),跳轉(zhuǎn)的目標(biāo)位置是0x0df10dc,對應(yīng)一條使程序跳轉(zhuǎn)到SUB函數(shù)的jmp語句
//執(zhí)行完SUB函數(shù)之后,程序會再跳轉(zhuǎn)回到此處,執(zhí)行下一條語句
00DF1410 add esp,8
00DF1413 mov dword ptr [c],eax
return 0;
......
......
@ILT+215(_SUB):
00DF10DC jmp SUB (0DF1380h) //無條件轉(zhuǎn)移指令
//前面的call指令會使程序跳轉(zhuǎn)到這條指令,而這條指令會使程序跳轉(zhuǎn)到SUB函數(shù)
//地址0x0df1380h對應(yīng)的就是SUB函數(shù)中第一條指令的地址
......由此可見:
(1)SUB函數(shù)的形參在函數(shù)棧幀創(chuàng)建之前就已經(jīng)創(chuàng)建好了,而且形參是實參的一份臨時拷貝,對形參的修改不影響實參。
(2)call函數(shù)在調(diào)用子程序之前會先將其下一條指令的地址入棧,用于在結(jié)束調(diào)用之后使程序返回到原來的位置繼續(xù)執(zhí)行指令。
這段過程中,內(nèi)存中的情況是這樣的:
jmp指令完成跳轉(zhuǎn)之后,就開始創(chuàng)建SUB函數(shù)的棧幀。這部分對應(yīng)的匯編代碼如下:
......
int SUB(int x, int y)
{00DF1380 push ebp //ebp入棧
00DF1381 mov ebp,esp //將esp中存的值賦給ebp
00DF1383 sub esp,0CCh //將esp的值減小0CCh
00DF1389 push ebx //ebx入棧
00DF138A push esi //esi入棧
00DF138B push edi //edi入棧
00DF138C lea edi,[ebp-0CCh] //將ebp-0CCh這個地址賦給edi(作為開始地址)
00DF1392 mov ecx,33h //將33h賦給ecx(作為重復(fù)次數(shù))
00DF1397 mov eax,0CCCCCCCCh //將0CCCCCCCCh賦給eax(用作賦值內(nèi)容)
00DF139C rep stos dword ptr es:[edi] //重復(fù)賦值,dword表示雙字(作為每次賦值的長度),es:[edi]為賦值的目標(biāo)地址
//上面四個語句合起來的效果是:
//從es:[edi]這個地址開始,向高地址方向重復(fù)多次賦值,每次賦值的長度為雙字(四個字節(jié))
//每次賦值后edi中的地址的值會增加4,從而實現(xiàn)向高地址方向重復(fù)多次賦值
//賦值的內(nèi)容為eax中的值,重復(fù)次數(shù)為ecx中的值(33h次)
int z = 0;
......這段過程中,內(nèi)存中的情況是這樣的:
可以觀察到,SUB函數(shù)棧幀的創(chuàng)建過程與前面main函數(shù)棧幀的創(chuàng)建幾乎是完全一樣的。
5.SUB函數(shù)中變量的創(chuàng)建以及運算接下來在SUB函數(shù)中創(chuàng)建變量,并與傳過來的形參進行運算,然后把返回值返回到主函數(shù)。這部分對應(yīng)的匯編代碼如下:
......
int z = 0;
00DF139E mov dword ptr [ebp-8],0 //將0這個值存到ebp-8這個地址中
z = x - y;
00DF13A5 mov eax,dword ptr [ebp+8] //將ebp+8這個地址中存的值存到寄存器eax中
//ebp+8這個地址是0x00d3fd38,存的是形參x的值
00DF13A8 sub eax,dword ptr [ebp+0Ch] //將eax中存的數(shù)據(jù)減小一定值,減小的值為ebp+0Ch中存的值
//ebp+0Ch這個地址是0x00d3fd3c,存的是形參y的值
00DF13AB mov dword ptr [ebp-8],eax //將eax中存的值存到ebp-8這個地址中(即存到變量z中)
return z;
00DF13AE mov eax,dword ptr [ebp-8] //將ebp-8中存的值存到寄存器eax中(相當(dāng)于將變量z中的值返回)
//局部變量z會隨著SUB函數(shù)運行結(jié)束而銷毀,將z的值存到寄存器eax中就可以保存下來,并返回到主函數(shù)中
}
......這段過程中,內(nèi)存中的情況是這樣的:

可以觀察到,SUB函數(shù)的返回值儲存在了寄存器eax中,如果主函數(shù)中需要接收返回值,就可以從eax中取出。
6.SUB函數(shù)棧幀的銷毀以及返回值的接收接下來進行SUB函數(shù)棧幀的銷毀以及返回值的接收。這部分對應(yīng)的匯編代碼如下:
......
return z;
}
00DF13B1 pop edi //將棧頂元素出棧,并存到寄存器edi中,同時棧頂指針的值相應(yīng)增加
00DF13B2 pop esi //將棧頂元素出棧,并存到寄存器esi中
00DF13B3 pop ebx //將棧頂元素出棧,并存到寄存器ebx中
00DF13B4 mov esp,ebp //將ebp的值賦給esp,即令esp指向ebp指向的地址,
00DF13B6 pop ebp //將棧頂元素出棧,并存到寄存器ebp中,即令ebp指向main函數(shù)的棧底
00DF13B7 ret //返回,彈出棧頂?shù)脑兀⑹钩绦蛱D(zhuǎn)到棧頂元素儲存的地址對應(yīng)的指令
//此時的棧頂元素是0x00df1410,正好是call指令的下一條指令的地址,因此程序返回到call指令的下一條指令
......
00DF140B call 00DF10DC //子程序調(diào)用指令
//執(zhí)行ret后,程序返回到此處,往下接著執(zhí)行指令
00DF1410 add esp,8 //將esp中存的值增加8(相當(dāng)于銷毀了形參x和y)
00DF1413 mov dword ptr [ebp-20h],eax //將eax中存的值存放到ebp-20h這個地址中(相當(dāng)于變量c接收了返回值)
return 0;這段過程中,內(nèi)存中的情況是這樣的:
由此可見,形參x和y的銷毀是在SUB函數(shù)棧幀銷毀之后進行的,變量c是通過寄存器eax來接收SUB函數(shù)的返回值的。
7.main函數(shù)棧幀的銷毀接下來進行main函數(shù)棧幀的銷毀。這部分對應(yīng)的匯編代碼如下:
......
return 0;
00DF1416 xor eax,eax //將eax中存的值與eax中存的值異或,所得結(jié)果存到eax中(相當(dāng)于把eax存的值置為0)
}
00DF1418 pop edi //棧頂元素出棧到edi中
00DF1419 pop esi //棧頂元素出棧到esi中
00DF141A pop ebx //棧頂元素出棧到ebx中
00DF141B add esp,0E4h //將esp中存的值增加0E4h(相當(dāng)于銷毀了main函數(shù)的棧幀)
......這段過程中,內(nèi)存中的情況是這樣的:
可以觀察到,main函數(shù)棧幀的銷毀過程與SUB函數(shù)略有不同,但基本是一致的,都是通過將棧頂指針向高地址移動來實現(xiàn)的。
至此,雖然整個程序還沒有徹底運行結(jié)束,但是main函數(shù)和SUB函數(shù)的棧幀的創(chuàng)建和銷毀都已經(jīng)完成。
這篇博客的主要目標(biāo)是觀察函數(shù)棧幀的創(chuàng)建銷毀過程并記錄,通過觀察匯編代碼對內(nèi)存的操作,可以加深對內(nèi)存管理的理解,還可以清楚地感受到程序員前輩們設(shè)計邏輯的嚴密。
如果文章中有任何問題,歡迎來糾正我。這是我第一次寫博客,以后一定會更加細心。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
新聞標(biāo)題:C語言學(xué)習(xí)筆記-從匯編代碼的角度觀察函數(shù)棧幀的創(chuàng)建和銷毀-創(chuàng)新互聯(lián)
本文路徑:http://chinadenli.net/article34/eccpe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計公司、虛擬主機、域名注冊、外貿(mào)網(wǎng)站建設(shè)、服務(wù)器托管、響應(yīng)式網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容