摘要:這里分塊講解六函數(shù)棧幀的銷毀過程一解析的作用是將棧頂?shù)臄?shù)據(jù)彈出,彈出數(shù)據(jù)儲(chǔ)存到相應(yīng)寄存器中。
讀完這篇博客,你可以明白什么?
①局部變量到底是怎么在棧上創(chuàng)建的?
②為什么局部變量不初始化為隨機(jī)值?
③函數(shù)是怎么傳參的?傳參的先后順序是什么?
④形參和實(shí)參是什么關(guān)系?
⑤函數(shù)調(diào)用是怎么實(shí)現(xiàn)的?
⑥函數(shù)調(diào)用后是怎么返回的?? ?
?在這篇博客里,我將帶領(lǐng)大家利用反匯編從底層上理解,不用擔(dān)心,都是零基礎(chǔ)入門的。當(dāng)你學(xué)完這篇博客去面試,面試官會(huì)非常高興,覺得這小伙子真??。所以學(xué)起來吧!
???作者概況:? 就讀南京郵電大學(xué)努力學(xué)習(xí)的大一小伙
???聯(lián)系方式:2879377052(QQ小號)? ? ? ? ? ? ?
???資源推薦:C語言從入門到進(jìn)階
???今日書籍分享:??《深入理解計(jì)算機(jī)系統(tǒng)》? ? ? ? ? ? ? ? ???
目錄
在C語言中我們可以把寄存器當(dāng)成指針來看待,他可以指向一塊空間,也可以用來存儲(chǔ)數(shù)據(jù)。現(xiàn)在向大家介紹以下幾種基本寄存器
參考博客:函數(shù)棧幀的創(chuàng)建和銷毀(圖解)
寄存器名稱 | 功能 |
①eax | "累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。 |
②ebx | "基地址"(base)寄存器, 在內(nèi)存尋址時(shí)存放基地址。 |
③ecx | 計(jì)數(shù)器(counter),計(jì)數(shù)寄存器,用于循環(huán)操作,比如重復(fù)的字符存儲(chǔ)操作,或者數(shù)字統(tǒng)計(jì)。 |
④edx | 作為EAX的溢出寄存器,總是被用來放整數(shù)除法產(chǎn)生的余數(shù)。 |
⑤esi | 源變址寄存器,主要用于存放存儲(chǔ)單元在段內(nèi)的偏移量。通常在內(nèi)存操作指令中作為“源地址指針”使用。 |
⑥edi | 目的變址寄存器,主要用于存放存儲(chǔ)單元在段內(nèi)的偏移量。 |
⑦esp? | 棧指針寄存器(extended stack pointer),其內(nèi)存放著一個(gè)指針,該指針永遠(yuǎn)指向棧最上面一個(gè)棧幀的棧頂。esp用于堆棧操作,被形象地稱為棧頂指針。堆棧的頂部是地址小的區(qū)域,壓入堆棧的數(shù)據(jù)越多,esp也就越來越小。在32位平臺(tái)上,esp每次減少4字節(jié)。 |
⑧ebp? | 基址指針寄存器(extended base pointer),其內(nèi)存放著一個(gè)基址指針,該指針永遠(yuǎn)指向系統(tǒng)棧最上面一個(gè)棧幀的底部?;分羔槪直恍螀⒎Q作棧底指針,一般與esp 配合使用,可以存取某時(shí)刻的esp ,這個(gè)時(shí)刻就是進(jìn)入一個(gè)函數(shù)內(nèi)后,CPU會(huì)將esp 的值賦給ebp ,此時(shí)就可以通過ebp 對棧進(jìn)行操作。 |
main函數(shù)其實(shí)也是被其他函數(shù)調(diào)用的,函數(shù)調(diào)用關(guān)系如下:
mainCRTStartup? →? ?__tmainCRTStartup? →? main?
?
(在調(diào)用堆棧中即可觀察到)?
①首先我們寫一段簡單的代碼,將代碼的每個(gè)過程拆分的盡可能簡單以便于我們對過程的觀察
int add(int x,int y){ int z = 0; z = x + y; return z;}int main(){ int a = 10; int b = 20; int c = add(a,b); return 0;}
?②在調(diào)試狀態(tài)下轉(zhuǎn)入反匯編模式
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)?
?看不懂沒關(guān)系,現(xiàn)在逐一解釋
我們知道,函數(shù)的調(diào)用都需要在棧區(qū)上開辟空間,那么我們先來解答幾個(gè)問題:
1.什么是棧?
【答】棧是一種數(shù)據(jù)結(jié)構(gòu),它按照后進(jìn)先出的原則存儲(chǔ)數(shù)據(jù),先進(jìn)入的數(shù)據(jù)被壓入棧底,最后的數(shù)據(jù)在棧頂,需要讀數(shù)據(jù)的時(shí)候從棧頂開始彈出數(shù)據(jù)。棧區(qū)內(nèi)存空間的使用是從高地址向低地址處使用的。
2.什么是壓棧?什么是出棧?
【答】一個(gè)形象的比喻就是機(jī)槍彈夾。壓棧的過程就是壓入一個(gè)元素,相當(dāng)于向機(jī)槍彈夾壓入子彈;出棧的過程就是彈出一個(gè)元素,相當(dāng)于子彈彈出來的過程。這正好對應(yīng)了棧的結(jié)構(gòu)特點(diǎn)——先進(jìn)入的數(shù)據(jù)被壓在棧底,后進(jìn)入的數(shù)據(jù)在棧頂。
3.為什么一個(gè)數(shù)據(jù)在內(nèi)存中是“倒著”存放的(注意顯示的是16進(jìn)制)?
【答】數(shù)據(jù)的存儲(chǔ)涉及大小端問題:
①什么是大小端
>大端(存儲(chǔ))模式是指數(shù)據(jù)的低位保存在內(nèi)存的高地址中,而數(shù)據(jù)的高位,保存在內(nèi)存的低地址中。
>小端(存儲(chǔ))模式是指數(shù)據(jù)的低位保存在內(nèi)存的低地址中,而數(shù)據(jù)的高位,,保存在內(nèi)存的高地址中。
?
?②為什么有大小端
存放的內(nèi)容大于1個(gè)字節(jié),必然存在著如何將多字節(jié)安排的問題。因此就出現(xiàn)了大小端。具體大小端取決于編譯器,一般為小端存儲(chǔ)。?
1.我們之前提到,main函數(shù)是由__tmianCRTStartup函數(shù)調(diào)用的,所以在創(chuàng)建main函數(shù)棧幀前,ebp和esp寄存器維護(hù)--tmainCRTStartup的棧區(qū),分別存放指向棧幀的棧頂和棧底。
2.同時(shí)注意,棧區(qū)上內(nèi)存的使用是從高地址向低地址處使用的
?
【過程一:??push? ?ebp?】
?【解釋】push指令的作用:它首先減小esp的值,再將源操作數(shù)復(fù)制到棧地址,每次esp地址減去四字節(jié)。最終效果就是在棧頂壓入一個(gè)元素,元素的值為ebp的地址。(4個(gè)字節(jié))
【補(bǔ)充】內(nèi)容不隨指令執(zhí)行而變化的操作數(shù)為源操作數(shù),內(nèi)容隨執(zhí)行指令而改變的操作數(shù)為目標(biāo)操作數(shù)。
?
?
?
?【過程二:? mov? ? ebp,esp?】
?【解釋】mov指令作用:將一個(gè)數(shù)據(jù)從源地址傳送到目標(biāo)地址,源操作地址的內(nèi)容不變。最終效果是將esp的地址賦值給ebp。寄存器指向的空間發(fā)生改變。
?
?
?【過程三:? ?sub? ? esp,0E4h】
?【解釋】sub指令的作用:減操作指令,地址減去相應(yīng)的數(shù)值。最終效果就是esp的地址減去0E4h。
?
?
?【過程四:push ebx,esi,edi】
?【解釋】前面我們提到過push的過程就壓入元素的過程。那壓入這三個(gè)元素有什么用呢?等會(huì)就明白。
?
??【過程五:lea? edi,[ebp- 0E4hh]? /? mov ? ecx,9? /? mov ? eax, 0CCCCCCCCh】
【解釋】Load Effective Address,即裝入有效地址的意思,它的操作數(shù)就是地址。在這里的效果就是將ebp+FFFFFF1Ch的值賦給edi。勾選“顯示符號名后可以發(fā)現(xiàn):
?
?ebp - 0E4h不正是當(dāng)初esp - 0E4h時(shí)的地址嗎?
?
?執(zhí)行mov指令后,我們可以發(fā)現(xiàn)寄存器eax,ecx的值發(fā)生變化。
?
?【過程六:?rep stos ? ?dword ptr es:[edi]?】???
?【解釋】rep指令的作用是:重復(fù)后面的指令。stos指令的作用是將eax中的值拷貝到es:edi指向的地址。ecx表示重復(fù)操作的次數(shù)。dword表示4個(gè)字節(jié)。所以整句指令的作用是:從edi開始,向高地址方向,將ecx個(gè)4字節(jié)內(nèi)存全部修改為eax的值。
?
?(可以看到,執(zhí)行操作后edi上相應(yīng)數(shù)量的四字節(jié)內(nèi)存被賦值為cc cc cc cc。這也解釋了為什么我們不初始化,變量默認(rèn)的初始值為cc cc cc cc)
?
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)??
【過程七:mov? ? ?dword? ptr? [ebp - 14h] ,14h】?
【解析】將ebp - 14地址處的四字節(jié)內(nèi)容修改為14h(10進(jìn)制中的20),也就是完成了給b賦值為20的動(dòng)作。下一條語句同理。
?
?
??至此main函數(shù)的棧幀創(chuàng)建的準(zhǔn)備階段完成。我們準(zhǔn)備進(jìn)入add函數(shù)
【過程八:mov? ?eax,dword ptr [ebp -?8h]? ?/? ?push ? ?eax】
【解析】將ebp - 14地址處的數(shù)值儲(chǔ)存到eax處;壓棧,將一個(gè)數(shù)值與eax相等的元素壓入棧中。ebp - 14這個(gè)地址好像似曾相識(shí),沒錯(cuò),這正是b的地址,所以這條語句的作用實(shí)際上即使將b的值傳到eax中。同理,a的值被存儲(chǔ)到ecx中去。
(實(shí)際上,上述過程解釋了函數(shù)究竟是如何傳參的。傳參的順序和變量創(chuàng)建的順序恰好相反,先傳b再傳a。同時(shí)注意函數(shù)傳參并不是在add函數(shù)棧幀內(nèi)完成的,而是在main函數(shù)的棧幀內(nèi)完成,通過寄存器eax和ecx實(shí)現(xiàn)變量的傳遞)
?
?【過程九:call? ? 00AD1023】?
?【解析】call指令的作用是:將下一條的指令的ip壓入棧中,并轉(zhuǎn)移到即將被調(diào)用的子程序。相當(dāng)于push ip +? jmp near ptr 標(biāo)號。我們現(xiàn)在來觀察棧頂?shù)淖兓?/p>
??在call指令一行們按F11觀察call的作用
?
我們驚奇的發(fā)現(xiàn)棧頂自動(dòng)壓入了一個(gè)元素,元素的值為——call指令的下一條指令的地址?。那壓入這個(gè)元素有什么用呢?試想,當(dāng)call指令調(diào)用add函數(shù)后,我們跳轉(zhuǎn)到add函數(shù)內(nèi)部,那函數(shù)結(jié)束后如何保證我們從add函數(shù)后面的語句繼續(xù)執(zhí)行呢?靠的就是棧頂壓入的地址,根據(jù)這個(gè)地址我們可以回到call指令的下一條語句。
?
再次按下F11后我們就跳轉(zhuǎn)到add函數(shù)內(nèi)部。
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)??
?這一部分的操作和main函數(shù)內(nèi)完全一致,都是為棧幀的創(chuàng)建做準(zhǔn)備。畫一個(gè)動(dòng)圖,不再贅述。
?
?我們現(xiàn)在研究接下來指令。?
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)??
?【過程一:mov? ? dword ptr [ebp-8],0】
?【解析】將ebp - 8 的地址賦值為0。也就代表著將Z初始化為0
?
?
【過程二:mov? ? eax, dword ptr [ebp+8]】
?【解析】將 ebp + 8 (從上圖觀察)處的值賦值給eax。此時(shí)eax儲(chǔ)存著形參a的值
【過程三:add ? ? eax, dword ptr [ebp+0Ch]】
【解析】將eax加上 ebp + 12 處的值。此時(shí)eax表示這a+b的值
【過程四:mov? ?? dword ptr [ebp-8], eax】
【解析】將eax儲(chǔ)存的a+b的值傳送到ebp - 8的地址處,也就是賦值給變量Z
【總結(jié)】從上面我們也可以加深這樣的認(rèn)識(shí):形參只是實(shí)參的一個(gè)臨時(shí)拷貝,所以修改形參當(dāng)然不會(huì)影響實(shí)參。?
?
?
【過程五:mov? ? ?eax, dword ptr [ebp-8]】
【解析】我們知道函數(shù)內(nèi)創(chuàng)建的臨時(shí)變量出函數(shù)后被銷毀,那返回值是如何被帶回主函數(shù)的呢?靠的就是寄存器eax。這一步的操作就是 return返回 值的過程。
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)??
【過程一:pop ? ?edi / esi / ebx】
【解析】pop的作用是將棧頂?shù)臄?shù)據(jù)彈出,彈出數(shù)據(jù)儲(chǔ)存到相應(yīng)寄存器中。每次pop過程中esp的地址自動(dòng)加4字節(jié)。
?
【過程二:mov? ? esp, ebp?】
?【解析】一句話就回收了為add函數(shù)開辟的內(nèi)存
?
【過程三:pop ebp】
【解析】彈出棧頂?shù)脑?,并將彈出的?shù)據(jù)儲(chǔ)存到ebp寄存器中。由于此時(shí)的棧頂元素事先存入main函數(shù)中ebp的地址,所以pop ebp時(shí),將main函數(shù)中的ebp元素的地址存入ebp寄存器中
?
?【過程四:ret】
?【解析】ret指令的作用實(shí)際相當(dāng)于 pop IP。在這里實(shí)際就是彈出了棧頂事先存儲(chǔ)的add下一條指令的地址,并跳轉(zhuǎn)到該地址處。
?
?
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)??
?【過程五:mov ? ? dword ptr [ebp-20h],eax】
?
?【解析】監(jiān)視我們可以發(fā)現(xiàn)ebp - 20所指向的對象就是c,所以這條語句的作用就是將返回值存儲(chǔ)到c中
?
?
(完整連續(xù)反匯編代碼請看文章結(jié)尾。這里分塊講解)??
?如何理解之后的語句呢,其實(shí)和add函數(shù)棧幀的銷毀基本是一致的,因?yàn)槲覀兦懊嫣岬?,main函數(shù)也是被其他函數(shù)調(diào)用的。以此類推。
int main(){00031410 push ebp 00031411 mov ebp,esp 00031413 sub esp,0E4h 00031419 push ebx 0003141A push esi 0003141B push edi 0003141C lea edi,[ebp-0E4h] 00031422 mov ecx,39h 00031427 mov eax,0CCCCCCCCh 0003142C rep stos dword ptr es:[edi] int a = 10;0003142E mov dword ptr [a],0Ah int b = 20;00031435 mov dword ptr [b],14h int c = add(a,b);0003143C mov eax,dword ptr [b] 0003143F push eax 00031440 mov ecx,dword ptr [a] 00031443 push ecx 00031444 call _add (0310E6h) 00031449 add esp,8 0003144C mov dword ptr [c],eax return 0;0003144F xor eax,eax }00031451 pop edi 00031452 pop esi 00031453 pop ebx 00031454 add esp,0E4h 0003145A cmp ebp,esp 0003145C call __RTC_CheckEsp (03113Bh) 00031461 mov esp,ebp 00031463 pop ebp 00031464 ret
int add(int x, int y){000313C0 push ebp 000313C1 mov ebp,esp 000313C3 sub esp,0CCh 000313C9 push ebx 000313CA push esi 000313CB push edi 000313CC lea edi,[ebp-0CCh] 000313D2 mov ecx,33h 000313D7 mov eax,0CCCCCCCCh 000313DC rep stos dword ptr es:[edi] int z = 0;000313DE mov dword ptr [z],0 z = x + y;000313E5 mov eax,dword ptr [x] 000313E8 add eax,dword ptr [y] 000313EB mov dword ptr [z],eax return z;000313EE mov eax,dword ptr [z] }000313F1 pop edi 000313F2 pop esi 000313F3 pop ebx 000313F4 mov esp,ebp 000313F6 pop ebp 000313F7 ret
?【注】這是后面補(bǔ)充的便于大家對知識(shí)的掌握有一個(gè)連貫性,所以在地址上有些不同。關(guān)注重要的指令即可。
? ? ? ? 恭喜你學(xué)完了函數(shù)棧幀的創(chuàng)建和銷毀!覺得不錯(cuò)可以點(diǎn)個(gè)贊。大家最近是不是有一大波考試呢?祝大家考的都會(huì),蒙的全對。
?
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/125080.html
摘要:語言深層理解函數(shù)中棧幀的創(chuàng)建與銷毀引言引言問題一引言問題二引言問題三一棧的簡單認(rèn)識(shí)內(nèi)存的簡單了解棧的簡單了解棧的定義棧的結(jié)構(gòu)二寄存器與簡單的匯編指令寄存器的定義寄存器的分類簡單的匯編指令三棧幀的創(chuàng)建于銷毀調(diào)試調(diào)用堆棧調(diào) ...
摘要:目錄前言由于作者水平有限,文章難免存在謬誤之處,敬請讀者斧正,俚語成篇,懇望指教前言由于作者水平有限,文章難免存在謬誤之處,敬請讀者斧正,俚語成篇,懇望指教作者新曉故知作者新曉故知那些代碼背后的故事那些代碼背后的故事通過 目錄 前言:●由于作者水平有限,文章難免存在謬誤之處,敬請讀者斧正,俚...
摘要:函數(shù)棧幀的銷毀匯編語言了解函數(shù)傳參函數(shù)返回值如何返回函數(shù)中變量如何初始化和賦值函數(shù)執(zhí)行結(jié)束后系統(tǒng)進(jìn)行了什么操作 文章目錄 一、什么是函數(shù)棧幀 1.寄存器2.函數(shù)棧幀3.棧幀的作用和維護(hù)4.棧幀結(jié)構(gòu)二、函數(shù)棧幀的創(chuàng)建? 1.匯編2.main函數(shù)3.Add函數(shù)的創(chuàng)建三、函數(shù)...
摘要:同時(shí),和所指示的位置會(huì)隨著函數(shù)棧幀的創(chuàng)建和銷毀而不斷的發(fā)生改變。再次執(zhí)行函數(shù)棧幀的創(chuàng)建操作。函數(shù)的返回值會(huì)存在一個(gè)寄存器中當(dāng)函數(shù)棧幀釋放后,返回值不會(huì)隨之消失。二函數(shù)棧幀的銷毀將一些函數(shù)調(diào)用中使用的寄存器彈出棧。 ...
摘要:在位機(jī)器上,如果有個(gè)地址線,那一個(gè)指針變量的大小是個(gè)字節(jié),才能存放一個(gè)地址。就是一個(gè)指針變量,也有自己的類型,指針變量的類型我們可以發(fā)現(xiàn)指針的定義方式是類型星號。也就是說存儲(chǔ)什么變量類型就用什么指針變量類型。 ...
閱讀 3258·2021-11-25 09:43
閱讀 2457·2021-09-07 10:28
閱讀 4003·2021-08-11 11:14
閱讀 2868·2019-08-30 13:49
閱讀 3644·2019-08-29 18:41
閱讀 1273·2019-08-29 11:26
閱讀 2076·2019-08-26 13:23
閱讀 3479·2019-08-26 10:43