前期學(xué)習(xí)的時候,我們可能會有很多困惑:
1. 局部變量是怎么創(chuàng)建的?創(chuàng)建之后初始化又是怎么做的?
2.為什么局部變量的值是隨機值?這個隨機值是怎么得來的?
3.函數(shù)到底是怎么傳參的?參數(shù)傳遞的順序是怎樣的?
4.函數(shù)的形參和實參到底是什么關(guān)系?
5.函數(shù)的調(diào)用整個過程到底是怎么樣的?
6.函數(shù)調(diào)用完成之后又是怎么返回的?
只要知道函數(shù)棧幀的創(chuàng)建和銷毀就懂了這些知識了。
1.寄存器。寄存器有eax,ebx,ecx,edx,還有ebp,esp........函數(shù)棧幀要了解清楚,esp,ebp這兩個寄存器必須了解清楚,這兩個寄存器里面存放的是地址。里面存放的這兩個地址是用來維護函數(shù)棧幀的。什么意思呢?籠統(tǒng)的來講,那么這兩個寄存器是怎么來維護函數(shù)棧幀的呢??
2. 內(nèi)存分為三個區(qū)域,棧區(qū),堆區(qū)與靜態(tài)區(qū)。每一個函數(shù)調(diào)用(不管是相同函數(shù)還是不同的函數(shù)),都需要在內(nèi)存里面的棧區(qū)創(chuàng)建一個空間,比如說main()函數(shù),棧區(qū)里面會開辟一塊空間給main()函數(shù)(在內(nèi)存里面,地址的話有高低之分,棧區(qū)也不用說),這一塊空間就被稱為為main()函數(shù)開辟的函數(shù)棧幀。bty,棧區(qū)使用習(xí)慣是:先使用高地址,在使用低地址
3.?對于內(nèi)存當(dāng)中的棧區(qū)來講,這是一個線性數(shù)據(jù)結(jié)構(gòu),有棧頂與棧底,棧頂由寄存器esp維護,棧底由寄存器ebp維護。同時,棧區(qū)里面也有高地址和低地址。內(nèi)存棧區(qū)的使用習(xí)慣是先使用高地址,在使用低地址(由高向低蔓延)
4. (接第二個)那么這塊空間到底該怎么維護呢?不能你說是你的就是你的吧。這塊main()函數(shù)的函數(shù)棧幀是由兩個寄存器來進行維護,一個是ebp,一個是esp。作為一個寄存器,其實也就是一塊存儲空間(與內(nèi)存之間完全獨立,可在代碼中使用)就是來存儲地址的?。
5.?這兩個寄存器一上一下就來維護main()函數(shù)的函數(shù)棧幀。在程序當(dāng)中,正在調(diào)用哪個函數(shù),esp與ebp就正在維護哪個函數(shù)的函數(shù)棧幀。它們兩個之間的空間就是為這次函數(shù)調(diào)用所開辟的空間,也就是說是這次函數(shù)的函數(shù)棧幀。
6.?這邊得強調(diào)一下:棧區(qū)的使用習(xí)慣是先使用高地址,在使用低地址。也就是說,是從高地址到低地址這樣子按樣的順序棧區(qū)空間在不斷的被消耗。通常稱,esp為棧頂指針,ebp為棧底指針(從這邊也可以清晰的明白,寄存器用來存放地址,說白了,跟指針變量沒啥兩樣,指針變量不也是用來存放地址的嘛,但是但是,差別還是蠻大的,這兩個完全不是一個性質(zhì)的東西,寄存器是電腦中獨立于內(nèi)存的一種存儲器,其可在代碼中使用)
開始解析匯編之旅(為main()函數(shù)創(chuàng)建函數(shù)棧幀)我以下面這個簡單明晰的例子開看:
1.?當(dāng)你進行調(diào)試的時候,從調(diào)用堆棧里面可能看到函數(shù)此時此刻的調(diào)用情況。比如說我們發(fā)現(xiàn)main()函數(shù)誒被調(diào)用了,但我們特別困惑的一點:main()函數(shù)這玩意兒又被誰調(diào)用了呢?事實上你會發(fā)現(xiàn),main()函數(shù)也是被別人給調(diào)用的,在函數(shù)__tmainCRTStartup()函數(shù)里面調(diào)用了我們的main()函數(shù)。而這個函數(shù)__tmainCRTStartup()又是被mainCRTStartup()調(diào)用的。然后你會發(fā)現(xiàn)main()函數(shù)的return 0又返回到哪里去了,返回到mainret里面去了。反正就是說這個調(diào)用邏輯得明白。?
這個不同編譯器下可能會不一樣。但反正要知道的一點就是:main()函數(shù)也是被其他函數(shù)給調(diào)用的?
2.?在棧區(qū)上使用內(nèi)存的時候,每一次函數(shù)調(diào)用都要在棧區(qū)為其分配內(nèi)存空間。比如說我main()函數(shù)里面有一個add()函數(shù),當(dāng)程序運行到add()函數(shù)的時候,就會在棧區(qū)下面低地址處為add()函數(shù)開辟它的內(nèi)存空間(函數(shù)棧幀)。同理,在main()上面也應(yīng)該有那兩個函數(shù)的函數(shù)棧幀 (main()函數(shù)也是被其他函數(shù)調(diào)用的)
接下來進行具體匯編代碼一行一行讀下去1.?這時候就需要轉(zhuǎn)到匯編代碼(轉(zhuǎn)到反匯編),這時候就會看到C語言對應(yīng)的匯編代碼了。然后把“顯示符號名”這個去掉。(注意注意:ebp與esp雖然這兩個東西叫寄存器,ebp,esp這兩個東西里面就是存放的是地址)?
2.?在調(diào)用main()函數(shù)之前,就已經(jīng)調(diào)用過了一個函數(shù)__tmainCRTStartup(),那現(xiàn)在我既然已經(jīng)走到了main()里頭來了。在這個之前,esp,ebp維護的是__tmainCRTStartup()的函數(shù)棧幀,這是此時此刻的情景。接下來就是進入main()函數(shù)了。(這邊得再次強調(diào)一下:棧區(qū)使用習(xí)慣是先使用高地址,在使用低地址,由高向低蔓延)
原先是這樣子:
3. 接下來
這個叫做壓棧(也就是往棧里面放了一個元素),這句代碼的意思就是說把寄存器ebp里面的數(shù)據(jù)拿出來給壓到這個棧頂上,注意:是把寄存器里面的數(shù)據(jù)壓到棧上,不是寄存器ebp拿過來壓在上面,寄存器與內(nèi)存獨立的,你怎么把寄存器拿過來?是把寄存器里面的數(shù)據(jù)壓到棧頂上。這樣完了之后,由于esp是維護棧頂?shù)?,這個esp指向的位置就也隨之往低地址處奔走了。
結(jié)果也不出意外
4 .接下來
mov ? ebp,esp? ?mov是把右邊的東西給左邊,在這邊是把esp寄存器里的值給ebp,注意這時候ebp寄存器里面的值已經(jīng)被改掉了,ebp指向的位置也就發(fā)生了變化!!變成了esp??!
搜索ebp的值,果然
5. 接下來
接下來執(zhí)行下一條語句:sub ? ?esp,0E4h? ? sub是減的意思,也就是說把esp的值減去0E4h,于是乎,esp指向的位置也就向著低地址奔走了0E4h。?
esp值(吻合預(yù)期)
你就會發(fā)現(xiàn)在esp與ebp中間已經(jīng)有了一塊內(nèi)存空間,這塊空間就是為main()函數(shù)預(yù)開辟好的空間
6. 接下來
這個就是壓棧壓了三個值進去,(雖然ebx,esi,edi都是寄存器,但你不要認為是壓了寄存器進去,寄存器與內(nèi)存完全獨立?。。?,至于這三個值干啥用,你不用去管(但是要注意,1. 每壓完一次棧,esp都要向前更新一下。因為它總是去維護棧頂?shù)模?. 此時隨著棧區(qū)使用向低地址處蔓延,這個main()函數(shù)的函數(shù)棧幀是在不斷擴大的)?
7. 接下來
接下來再執(zhí)行一條語句: lea ? ?edi , [ebp - ?0E4h]? ? ?什么叫做lea,也就是load effective address ,就是說把后面的有效地址加載(放)到edi這個寄存器里面去,此時這個edi寄存器指向的是ebp-24h這個地址了?
edi的值
8. 接下來
接下來在執(zhí)行兩條語句:
mov ? ?ecx,9
mov ? ?eax,0CCCCCCCCh
這兩個執(zhí)行完后并不會出現(xiàn)效果,反正這兩個語句執(zhí)行的意義就在于把逗號后面的值給逗號前面的ecx,eax這兩個寄存器,使得寄存器里面的值發(fā)生了變化。真正會起到作用的是下一條語句。
我搜一下ecx與eax,不出意外,吻合預(yù)期
9. 接下來
接下來執(zhí)行下一條語句:
rep ?stos ? ? dword ?ptr ?es:[edi]
首先要知道,word一個字也就是兩個字節(jié),然后dword也就是雙字,就是四個字節(jié)。這句語句的意義在于從edi這個位置開始(此時,edi里面放的是什么你自己心里要清楚)一直向后(高地址方向)的ecx個雙字(也就是四個字節(jié))數(shù)據(jù)全部改成eax的內(nèi)容即:0xCCCCCCCC。
內(nèi)存中一目了然
至此,為main()函數(shù)開辟棧幀已經(jīng)全部ok了。當(dāng)main()函數(shù)的棧幀開辟好了之后,接下來我要執(zhí)行正式有效的代碼了,剛才前面搞了半天,竟然沒有執(zhí)行一條有效的代碼。接下來才開始真正執(zhí)行C語音的代碼。
開始解析匯編之旅(具體執(zhí)行C語言代碼,創(chuàng)建局部變量)1.?
接下來執(zhí)行c語言代碼:int a = 10
這句c語言代碼翻譯到匯編代碼就是
mov ? dword ?ptr ?[ebp-8] , 0Ah
順便說一下,在看匯編代碼的時候,一定要把顯示符號名去掉,這時候顯示的就是地址。這條匯編指令的意義就在于:把0Ah這個數(shù)字放到ebp-8這個地址里面去。這時候相當(dāng)于已經(jīng)有四個字節(jié)的內(nèi)存棧區(qū)空間已經(jīng)被放進去a了。然后假設(shè)如果創(chuàng)建變量的時候沒有給它賦值,那我里面放的就是CCCCCCCC,這里面放的這玩意兒不同的編譯器五花八門,就是隨機值了。因此一定要創(chuàng)建變量的時候初始化。?
2.?
接下來執(zhí)行c語言代碼: ? ?int? b = 20.
與它相對應(yīng)的匯編指令就是mov ? ?dword ?ptr ?[ebp-14h],14h
相當(dāng)于就是說把14h放到ebp-14h這個地址里面去。放進去之后,這時候相當(dāng)于在內(nèi)存棧區(qū)上,又有四個字節(jié)的空間已經(jīng)被放入數(shù)據(jù)14h然后相當(dāng)于這四個字節(jié)的空間就相當(dāng)于分配給了b
3.?
接下來執(zhí)行c語言代碼: ? ?int c = 10
與它相對應(yīng)的匯編指令就是mov ? dword ?ptr ?[ebp-20h],0
相當(dāng)于就是說把0放到ebp-20h這個地址里面去。放進去之后,這時候相當(dāng)于在內(nèi)存棧區(qū)上,又有四個字節(jié)的空間已經(jīng)被放入數(shù)據(jù)0然后相當(dāng)于這四個字節(jié)的空間就分配給了c
1. 接下來就是函數(shù)調(diào)用,函數(shù)調(diào)用需要傳參,那到底是怎么傳參的呢?
接下來執(zhí)行匯編語句:
mov ? eax,dword ?ptr ?[ebp-14h]。也就是說把ebp-14h這個地址及其后面總共雙字(四個字節(jié))的數(shù)據(jù)放到eax這個寄存器里面去。然后你去看一下ebp-14h,誒,現(xiàn)這不正就是b的值嗎??
搜了一下eax
2.?
然后就是push ? eax。這時候就是壓棧,但我們事實上已經(jīng)知道,eax這個寄存器此時里面放的就是b的數(shù)據(jù),現(xiàn)在把b的數(shù)據(jù)給壓棧壓上去
3.?
接下來執(zhí)行匯編語句:
mov ? ecx,dword ?ptr ?[ebp-8]。也就是說把ebp-8這個地址及其后面總共雙字(四個字節(jié))的數(shù)據(jù)放到ecx里面去。然后你去看一下ebp-8,誒,現(xiàn)這不正就是a的值嗎?
4.?然后就是push ? ecx。這時候就是壓棧,但我們事實上已經(jīng)知道,ecx這個寄存器此時里面放的就是a的數(shù)據(jù),現(xiàn)在把a的數(shù)據(jù)給壓棧壓上去
那么剛剛這兩個動作到底是在干什么?這兩個動作是在給我傳參嗎?答案是確實就是在傳參,但是你可能現(xiàn)在還感受不到。?
開始解析匯編之旅(進入調(diào)用函數(shù)內(nèi)部)1. 接下來的匯編指令是: call,F(xiàn)11按一下后
call就是說我要調(diào)用函數(shù)去。但是調(diào)用函數(shù)的時候一定要注意一個東西。事實上這個call的動作又把call指令的下一條指令的地址壓上去了。那么為什么要記住這個地址呢?可以想象一下,一旦我執(zhí)行call這個命令,相當(dāng)于我馬上去調(diào)用add這個函數(shù)去了,那么你這個函數(shù)調(diào)用完之后不是還要回來嗎?那么回來的時候,你到底要回哪去?你要回到call指令的下一條指令,等你從add函數(shù)里面跳出來,你還是要執(zhí)行call指令的下一條指令。而我這里恰好把call指令的下一條指令的地址記住,然后給它放在棧頂上面。然后等會兒從函數(shù)調(diào)用里面出來,我就會用得上這個地址?;貋淼臅r候就會找到這個地址,然后從這個地址往下執(zhí)行。
2. 真正的走到函數(shù)里面去。然后你會發(fā)現(xiàn)前面那一堆匯編指令與main()函數(shù)的極為類似。其實就是在為add函數(shù)準(zhǔn)備函數(shù)棧幀,當(dāng)然,順便提一下:現(xiàn)在main()函數(shù)的函數(shù)棧幀已經(jīng)增長到了很多了,你自己應(yīng)該有感覺的,看看上面壓棧了多少東西進去??纯茨愕膃sp往前移了多少,反正總而言之就是說main()函數(shù)的函數(shù)棧幀已經(jīng)增長到了很多了。??
3.?
這個時候把ebp此時此刻里面的值給壓棧壓進去了,這一步具有決定性意義,因為此時此刻ebp的值是指向main()函數(shù)的函數(shù)棧底,非常非常關(guān)鍵,后面會用到
4. 接下里的匯編指令是類同于main()函數(shù)的,懂得都懂
5.?
接下來匯編指令要執(zhí)行我的計算任務(wù)了。首先是創(chuàng)建臨時變量z,C語言代碼為int z = 0,匯編指令為:mov ? dword ?ptr ? [ebp-8],0 ? ?也就是說把0放到ebp-8這個地址里面去,這事實上相當(dāng)于已經(jīng)在add函數(shù)棧幀里面劃分了四個字節(jié)的空間給z?
6.?
接下來執(zhí)行c語言代碼z=x+y,匯編指令為:mov ? ?eax,dword ?ptr ?[ebp+8],這時候相當(dāng)于它又回去找參數(shù)去了?。∠劝裡bp+8這個地址及其后的四個字節(jié)的數(shù)據(jù)放到eax里面去。然后 add ? ? eax,dword ? ?ptr ? ?[ebp+0Ch],把ebp+12這個地址及其后的四個字節(jié)的數(shù)據(jù)加到eax里面去。這時候eax變成了30了。然后:mov? ? ??dword ??ptr? [ebp-8],eax,也就是說把eax寄存器里面已經(jīng)加好的數(shù)據(jù)在放到ebp-8里面去,也就是放到z里面去。?
我對z進行取地址(吻合預(yù)期)
注:
1.這時候你會發(fā)現(xiàn)形參根本就不是我add里面創(chuàng)建的,而是我回來去找原先壓棧進去的那幾個空間。
2.?那函數(shù)傳參是怎么傳的?也就是說事實上我函數(shù)還沒有調(diào)用的時候,我參數(shù)已經(jīng)壓棧壓進去了,壓進去之后,等我真正進入函數(shù)內(nèi)部,其實我又是找回了之前壓進去的數(shù)據(jù)進行計算。那然后我們之前有講過:如果是傳值調(diào)用,形參是實參的一份臨時拷貝,這句話完全正確。因為我壓棧壓進去的a,b是不是就是我實參的一份臨時拷貝?很顯然是的。那我到時候我對形參改來改去,壓根兒與我實參半毛錢關(guān)系都沒有。
開始解析匯編之旅(函數(shù)返回)1.?
那么上面我只是讓函數(shù)調(diào)用了,我還沒有返回呢。那我該怎么返回呢?
C語言代碼為return z
mov ? ? eax,dword ptr [ebp-8],這時候需要注意(寄存器是不會程序退出然后銷毀的),這匯編語言意思就在于把ebp-8的值(z)放到eax這個寄存器里面,相當(dāng)于我用了一個全局一樣的寄存器先把這個值保存起來這樣就安全了,等我再回到主函數(shù)的時候,再把eax里面的值拿起來用就可以了。
對eax取地址(吻合預(yù)期)
2.?
接下來pop ? ? edi, pop ? ?esi, ?pop ? ? ebx。就是說把棧頂?shù)闹等恳来螐棾鋈シ诺郊拇嫫鱡di, esi, ?ebx里面去,這時候esp都會+4+4+4這樣子。?然后mov? ? esp,ebp? ?這時候兩者指向同一個地方了
我這個add函數(shù)已經(jīng)調(diào)用完了的,我的結(jié)果都已經(jīng)放在eax里面了,那么原先add的函數(shù)棧幀已經(jīng)沒有必要存在了,該回收了。只需要一個匯編指令就可以搞定,你看:mov ? ? ? esp,ebp。把ebp賦給我的esp這意味著什么?意味著我的esp已經(jīng)不指向原先那個位置了。
3.
接下來是pop ? ? ebp,pop就是在棧頂彈出一個元素嘛,就是相當(dāng)于說從棧頂彈出一個元素放到ebp寄存器里面去,這時候,絕妙的是這個pop出來的東西正是main函數(shù)棧底的地址。于是ebp又指向棧底去了
因為main函數(shù)的棧頂是很容易找到的,當(dāng)我pop ebp也就是相當(dāng)于從棧頂彈出一個元素然后放到ebp這個寄存器里面,然后由于彈出的那個元素里面放的是main函數(shù)的棧底地址,于是這個ebp又回去了,這個時候esp也不指向原先的地方了,朝著高地址的地方移動了四個字節(jié),于是,這就又回到了main函數(shù)的函數(shù)棧幀。
這個時候main函數(shù)的函數(shù)棧幀就又開始由ebp與esp開始維護了。4.?pop出去的時候只是讓我找到了main函數(shù)的ebp與esp。我回到main函數(shù)里面去的時候,我還是得從call指令的下一條指令地址開始執(zhí)行,1. 而我恰好此時在棧頂上面放著call指令的下一條指令地址。2 .然后接下來還有一條ret語句-->就是從棧頂上彈出那個地址,然后就跑到那邊去
總算渡劫從add()函數(shù)里面出來了,
5.
渡劫出來后,我的esp這時候就來到了形參的位置,但這個時候,形參已經(jīng)沒有任何用了。這時候有一條新的語句:add ???esp,8 ?這個時候,esp又朝高地址處移動八個字節(jié),恰好也跳過了那兩個形參,這時候兩個形參的內(nèi)存空間也已經(jīng)還給操作系統(tǒng)。
我搜一下esp的地址(吻合預(yù)期)
6.
然后之前走出add函數(shù)的時候,把值委托給了eax,然后現(xiàn)在我要把這個結(jié)果給到c里面去,于是
mov ? ? ?dword ??ptr ? [ebp-20h],eax?
看一下ebp-20h的數(shù)據(jù)
吻合!
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧