真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C語言學習筆記-從匯編代碼的角度觀察函數(shù)棧幀的創(chuàng)建和銷毀-創(chuàng)新互聯(lián)

目錄
  • 一、前置知識
    • 1.函數(shù)棧幀的概念
    • 2.基本的寄存器
    • 3.基本的匯編指令
  • 二、具體過程
    • 1.main函數(shù)的調用
    • 2.main函數(shù)棧幀的創(chuàng)建
    • 3.main函數(shù)中變量的創(chuàng)建
    • 4.SUB函數(shù)的調用以及棧幀創(chuàng)建
    • 5.SUB函數(shù)中變量的創(chuàng)建以及運算
    • 6.SUB函數(shù)棧幀的銷毀以及返回值的接收
    • 7.main函數(shù)棧幀的銷毀
  • 三、補充說明

10年積累的網(wǎng)站建設、成都網(wǎng)站設計經(jīng)驗,可以快速應對客戶對網(wǎng)站的新想法和需求。提供各種問題對應的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡服務。我雖然不認識你,你也不認識我。但先網(wǎng)站設計后付款的網(wǎng)站建設流程,更有浦江免費網(wǎng)站建設讓你可以放心的選擇與我們合作。
一、前置知識 1.函數(shù)棧幀的概念

函數(shù)在被調用的時候,操作系統(tǒng)會在內存棧區(qū)為這個函數(shù)開辟一塊空間,提供給這個函數(shù)使用,這個??臻g就是該函數(shù)的棧幀。函數(shù)的返回地址、函數(shù)中創(chuàng)建的局部變量、以及一些寄存器信息都會保存在這塊??臻g中。在函數(shù)運行完畢后,函數(shù)棧幀會銷毀,將內存空間釋放出來。

2.基本的寄存器

2.1 ebp、esp
esp即extended stack pointer,擴展棧指針寄存器,是指針寄存器的一種,用于存放函數(shù)棧頂指針。
ebp即extended base pointer,擴展基址指針寄存器,也屬于指針寄存器,與esp對應,ebp用于存放函數(shù)棧底指針。
函數(shù)的棧幀空間就是由這兩個寄存器來維護的。

2.2 edi、esi
這兩個屬于變址寄存器,用于存放存儲單元在段內的偏移量。
edi:源索引寄存器,一般用于在串操作中存放數(shù)據(jù)源的地址
esi:目標索引寄存器,一般用于在串操作中存放目標地址

2.3 ecx、ebx、eax
這三個屬于通用寄存器,在程序執(zhí)行的過程中,大部分時間都是通過操作這些寄存器來實現(xiàn)指令功能的。
ecx:計數(shù)器,用于存放重復、循環(huán)等指令的執(zhí)行次數(shù)計數(shù)
ebx:基地址寄存器,在內存尋址時存放基地址
eax:累加器,在進行加法運算時使用,也用于存放函數(shù)的返回值

2.4 psw
psw是標志寄存器,是存放標志信息的寄存器。標志信息的作用是為CPU執(zhí)行相關指令提供行為依據(jù),或者控制CPU的相關工作方式。

  • 標志寄存器與其它寄存器相比有一個明顯的特點,其他寄存器是用整個寄存器來存放某個特定的數(shù)據(jù),而標志寄存器卻是用寄存器中的某一位來記錄特定信息。因此標志寄存器的每一位都有不同的專門的含義,記錄不同的特定信息。
  • 這篇博客中只涉及了其中一個標志位:方向標志位(DF),它位于psw的第10位,決定了串操作中寄存器中的地址值是增加還是減少。
3.基本的匯編指令

3.0 操作數(shù)
大部分的匯編指令中,左邊的操作數(shù)都是目標操作數(shù),右邊的操作數(shù)都是源操作數(shù)

3.1 push、pop
push即壓棧,使一個寄存器中的數(shù)據(jù)入棧,然后使棧頂指針的值相應減小
pop即彈出、出棧,將棧頂?shù)臄?shù)據(jù)存到一個寄存器中,然后使棧頂指針的值相應增加,相當于從棧里面彈出了一個數(shù)據(jù)

push ebp  //將ebp中的值入棧
pop  edi  //將棧頂元素出棧,并儲存到寄存器edi中

3.2 mov
將源地址中的一個值賦到一個目標地址中,源地址中的值不受影響

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中存的值增加0E4h

3.4 lea
lea即load effective address,加載有效地址,將一個地址加載到一個寄存器中

lea edi,[ebp-04Eh]  //將[ebp-04Eh]這個地址加載到寄存器edi中

3.5 rep
rep即repeat,重復,這是一個前綴指令,指令的作用是重復執(zhí)行后面的指令
rep指令每次執(zhí)行的時候都從寄存器ecx里面讀取值,當ecx中的值大于0,就執(zhí)行后面語句,執(zhí)行完以后,會讓ecx - 1,然后再執(zhí)行rep后面的指令,直到減小到0,因此每次rep執(zhí)行之前,一般都會先將重復次數(shù)存到ecx中,執(zhí)行完后ecx中的值都會變?yōu)?

rep add esp,1  //將后面的add語句重復執(zhí)行,重復次數(shù)由寄存器ecx中的值決定

3.6 stos
stos即store string,串儲存,將寄存器eax中存的值賦值到目標地址(這個地址一般都是es:[edi])

stos dword ptr es:[edi]  //將寄存器eax中一個雙字長度的數(shù)據(jù)賦值到es:[edi]這個地址

賦值之后還會執(zhí)行一次使edi中存的值加4或減4,具體是執(zhí)行加還是減由標志寄存器中的方向標志位DF來決定,DF為0時執(zhí)行加,DF為1時執(zhí)行減,可以使用cld指令和std指令來對DF進行設置
cld指令:將標志寄存器的DF位設置為0
std指令:將標志寄存器的DF位設置為1

3.7 call、jmp
call是子程序調用指令,使程序跳轉到目標地址來執(zhí)行子程序,跳轉之前會先將call指令的下一條指令的地址進行壓棧,執(zhí)行完子程序之后會跳轉回到call指令的下一條指令的位置,然后再繼續(xù)按順序執(zhí)行指令
jmp是無條件轉移指令,使程序直接跳轉到目標地址執(zhí)行下一條指令,之后程序按順序執(zhí)行

call @ILT+215(_SUB) (0DF10DCh)  //首先將下一條語句的地址入棧,然后程序發(fā)生跳轉,跳轉的目標位置是0x0df10dc
jmp SUB (0DF1380h)  //使程序跳轉到地址0xdf1380處(SUB函數(shù)的第一條指令)

3.8 ret
ret即return,返回,彈出棧頂?shù)脑兀⑹钩绦蛱D到棧頂元素儲存的地址對應的指令

3.9 xor
xor即exclusive or,異或,將源操作數(shù)與目標操作數(shù)進行按位異或,得到的值保存到目標操作數(shù)中

xor eax,ebx  //將eax中存的值與ebx中存的值異或,所得結果存到eax中

二、具體過程

使用一段簡單的C語言代碼,通過VS2010中的反匯編功能,從匯編代碼的角度觀察這段代碼中的函數(shù)在內存中是怎么調用、怎么實現(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中對這段代碼進行調試,按F10進入調試模式后,可以看見反匯編、調用堆棧、監(jiān)視、內存這幾個窗口。

接下來通過這幾個窗口來觀察這段代碼在內存中是怎么執(zhí)行的。

1.main函數(shù)的調用

首先,在調用堆棧窗口可以看到函數(shù)的調用情況。容易發(fā)現(xiàn)main函數(shù)是被__tmainCRTstartup函數(shù)調用的,而__tmainCRTstartup函數(shù)又是被mainCRTstartup函數(shù)調用的。其中mainCRTstartup函數(shù)是啟動函數(shù),與C語言程序的啟動有關,功能大致是在程序啟動之前做一些準備工作。

這說明在調用main函數(shù)之前,內存中已經(jīng)為之前的__tmainCRTstartup函數(shù)開辟了棧幀空間,因此在程序剛開始運行的時候,ebp和esp寄存器正在維護的是__tmainCRTstartup函數(shù)的棧幀,此時內存棧區(qū)中的情況是這樣的:
寄存器中存的地址可以在監(jiān)視窗口和內存窗口看到

寄存器中存的地址可以在監(jiān)視窗口和內存窗口看到

此時程序還沒開始執(zhí)行第一條語句,處于準備進入main函數(shù)的狀態(tài)。
黃色箭頭指向的是當前要執(zhí)行的語句

黃色箭頭指向的是當前要執(zhí)行的語句
2.main函數(shù)棧幀的創(chuàng)建

在執(zhí)行第一條語句之前,先在內存中為main函數(shù)開辟一塊空間,即創(chuà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(作為重復次數(shù))
00DF13E7  mov         eax,0CCCCCCCCh        //將0CCCCCCCCh賦給eax(用作賦值內容)
00DF13EC  rep stos    dword ptr es:[edi]    //重復賦值,dword表示雙字(作為每次賦值的長度),es:[edi]為賦值的目標地址
//上面四個語句合起來的效果是:
//從es:[edi]這個地址開始,向高地址方向重復賦值,每次賦值的長度為雙字(四個字節(jié))
//每次賦值后edi中的地址的值會增加4,從而實現(xiàn)向高地址方向重復多次賦值
//賦值的內容為eax中的值,重復次數(shù)為ecx中的值(39h次)  
	int a = 1;
	......

這段過程中,內存中的情況是這樣的:
main函數(shù)棧幀創(chuàng)建的過程

main函數(shù)棧幀創(chuàng)建的過程
3.main函數(shù)中變量的創(chuàng)建

接下來在主函數(shù)中創(chuà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ù)在棧幀存放的位置是由編譯器決定的,不同的編譯器下存放的位置可能不同。
這段過程中,內存中的情況是這樣的:
main函數(shù)中變量創(chuàng)建的過程

main函數(shù)中變量創(chuàng)建的過程
4.SUB函數(shù)的調用以及棧幀創(chuàng)建

接下來調用SUB函數(shù),首先進行的是函數(shù)傳參以及程序的跳轉。這部分對應的匯編代碼如下:

......
	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指令調用SUB函數(shù)
00DF140B  call        @ILT+215(_SUB) (0DF10DCh) //子程序調用指令
//首先將下一條語句的地址(0x00df1410)入棧,然后程序發(fā)生跳轉,跳轉的目標位置是0x0df10dc,對應一條使程序跳轉到SUB函數(shù)的jmp語句
//執(zhí)行完SUB函數(shù)之后,程序會再跳轉回到此處,執(zhí)行下一條語句
00DF1410  add         esp,8  
00DF1413  mov         dword ptr [c],eax  
	return 0;
	......
	......
@ILT+215(_SUB):
00DF10DC  jmp         SUB (0DF1380h)             //無條件轉移指令
//前面的call指令會使程序跳轉到這條指令,而這條指令會使程序跳轉到SUB函數(shù)
//地址0x0df1380h對應的就是SUB函數(shù)中第一條指令的地址
	......

由此可見:
(1)SUB函數(shù)的形參在函數(shù)棧幀創(chuàng)建之前就已經(jīng)創(chuàng)建好了,而且形參是實參的一份臨時拷貝,對形參的修改不影響實參。
(2)call函數(shù)在調用子程序之前會先將其下一條指令的地址入棧,用于在結束調用之后使程序返回到原來的位置繼續(xù)執(zhí)行指令。
這段過程中,內存中的情況是這樣的:
函數(shù)傳參以及call指令將下一條指令的地址入棧的過程

函數(shù)傳參以及call指令將下一條指令的地址入棧的過程

jmp指令完成跳轉之后,就開始創(chuàng)建SUB函數(shù)的棧幀。這部分對應的匯編代碼如下:

......
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(作為重復次數(shù))
00DF1397  mov         eax,0CCCCCCCCh      //將0CCCCCCCCh賦給eax(用作賦值內容)
00DF139C  rep stos    dword ptr es:[edi]  //重復賦值,dword表示雙字(作為每次賦值的長度),es:[edi]為賦值的目標地址
//上面四個語句合起來的效果是:
//從es:[edi]這個地址開始,向高地址方向重復多次賦值,每次賦值的長度為雙字(四個字節(jié))
//每次賦值后edi中的地址的值會增加4,從而實現(xiàn)向高地址方向重復多次賦值
//賦值的內容為eax中的值,重復次數(shù)為ecx中的值(33h次)  
	int z = 0;
	......

這段過程中,內存中的情況是這樣的:
SUB函數(shù)棧幀的創(chuàng)建過程

SUB函數(shù)棧幀的創(chuàng)建過程

可以觀察到,SUB函數(shù)棧幀的創(chuàng)建過程與前面main函數(shù)棧幀的創(chuàng)建幾乎是完全一樣的。

5.SUB函數(shù)中變量的創(chuàng)建以及運算

接下來在SUB函數(shù)中創(chuàng)建變量,并與傳過來的形參進行運算,然后把返回值返回到主函數(shù)。這部分對應的匯編代碼如下:

......
	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中(相當于將變量z中的值返回)
//局部變量z會隨著SUB函數(shù)運行結束而銷毀,將z的值存到寄存器eax中就可以保存下來,并返回到主函數(shù)中
}
	......

這段過程中,內存中的情況是這樣的:

SUB函數(shù)中創(chuàng)建變量并與形參進行運算,最終將返回值存到寄存器eax的過程

SUB函數(shù)中創(chuàng)建變量并與形參進行運算,最終將返回值存到寄存器eax的過程

可以觀察到,SUB函數(shù)的返回值儲存在了寄存器eax中,如果主函數(shù)中需要接收返回值,就可以從eax中取出。

6.SUB函數(shù)棧幀的銷毀以及返回值的接收

接下來進行SUB函數(shù)棧幀的銷毀以及返回值的接收。這部分對應的匯編代碼如下:

......
	return z;
}
00DF13B1  pop         edi      //將棧頂元素出棧,并存到寄存器edi中,同時棧頂指針的值相應增加
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ù)脑?,并使程序跳轉到棧頂元素儲存的地址對應的指令
//此時的棧頂元素是0x00df1410,正好是call指令的下一條指令的地址,因此程序返回到call指令的下一條指令
	......
00DF140B  call        00DF10DC                 //子程序調用指令
//執(zhí)行ret后,程序返回到此處,往下接著執(zhí)行指令
00DF1410  add         esp,8                    //將esp中存的值增加8(相當于銷毀了形參x和y)
00DF1413  mov         dword ptr [ebp-20h],eax  //將eax中存的值存放到ebp-20h這個地址中(相當于變量c接收了返回值)
	return 0;

這段過程中,內存中的情況是這樣的:
SUB函數(shù)棧幀銷毀以及變量c接收返回值的過程

SUB函數(shù)棧幀銷毀以及變量c接收返回值的過程

由此可見,形參x和y的銷毀是在SUB函數(shù)棧幀銷毀之后進行的,變量c是通過寄存器eax來接收SUB函數(shù)的返回值的。

7.main函數(shù)棧幀的銷毀

接下來進行main函數(shù)棧幀的銷毀。這部分對應的匯編代碼如下:

......
	return 0;
00DF1416  xor         eax,eax  //將eax中存的值與eax中存的值異或,所得結果存到eax中(相當于把eax存的值置為0)
}
00DF1418  pop         edi      //棧頂元素出棧到edi中
00DF1419  pop         esi      //棧頂元素出棧到esi中
00DF141A  pop         ebx      //棧頂元素出棧到ebx中
00DF141B  add         esp,0E4h //將esp中存的值增加0E4h(相當于銷毀了main函數(shù)的棧幀)
	......

這段過程中,內存中的情況是這樣的:
main函數(shù)棧幀銷毀的過程

main函數(shù)棧幀銷毀的過程

可以觀察到,main函數(shù)棧幀的銷毀過程與SUB函數(shù)略有不同,但基本是一致的,都是通過將棧頂指針向高地址移動來實現(xiàn)的。

至此,雖然整個程序還沒有徹底運行結束,但是main函數(shù)和SUB函數(shù)的棧幀的創(chuàng)建和銷毀都已經(jīng)完成。


三、補充說明

這篇博客的主要目標是觀察函數(shù)棧幀的創(chuàng)建銷毀過程并記錄,通過觀察匯編代碼對內存的操作,可以加深對內存管理的理解,還可以清楚地感受到程序員前輩們設計邏輯的嚴密。

如果文章中有任何問題,歡迎來糾正我。這是我第一次寫博客,以后一定會更加細心。

你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


新聞名稱:C語言學習筆記-從匯編代碼的角度觀察函數(shù)棧幀的創(chuàng)建和銷毀-創(chuàng)新互聯(lián)
網(wǎng)站網(wǎng)址:http://weahome.cn/article/eccpe.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部