<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C++函數(shù)調(diào)用過(guò)程深入分析

          共 3242字,需瀏覽 7分鐘

           ·

          2021-07-09 16:44

          置頂/星標(biāo)公眾號(hào)??,硬核文章第一時(shí)間送達(dá)!

          鏈接 | https://blog.csdn.net/dongtingzhizi/article/details/6680050


          函數(shù)調(diào)用的過(guò)程實(shí)際上也就是一個(gè)中斷的過(guò)程,那么C++中到底是怎樣實(shí)現(xiàn)一個(gè)函數(shù)的調(diào)用的呢?參數(shù)入棧、函數(shù)跳轉(zhuǎn)、保護(hù)現(xiàn)場(chǎng)、回復(fù)現(xiàn)場(chǎng)等又是怎樣實(shí)現(xiàn)的呢?本文將對(duì)函數(shù)調(diào)用的過(guò)程進(jìn)行深入的分析和詳細(xì)解釋,并在VC 6.0環(huán)境下進(jìn)行演示。分析不到位或者存在錯(cuò)誤的地方請(qǐng)批評(píng)指正,請(qǐng)與作者聯(lián)系。

          首先對(duì)三個(gè)常用的寄存器做一下說(shuō)明,EIP是指令指針,即指向下一條即將執(zhí)行的指令的地址;EBP為基址指針,常用來(lái)指向棧底;ESP為棧指針,常用來(lái)指向棧頂。

          看下面這個(gè)簡(jiǎn)單的程序并在VC 6.0中查看并分析匯編代碼。

          圖1


          1. 函數(shù)調(diào)用

          g_func函數(shù)調(diào)用的匯編代碼如圖2:

          圖2

          首先是三條push指令,分別將三個(gè)參數(shù)壓入棧中,可以發(fā)現(xiàn)參數(shù)的壓棧順序是從右向左的。這時(shí)我們可以查看棧中的數(shù)據(jù)驗(yàn)證一下。如圖3所示,從右邊的實(shí)時(shí)寄存器表中我們可以看到ESP(棧頂指針)值為0x0012FEF0,然后從中間的內(nèi)存表中找到內(nèi)存地址0x0012FEF0處,我們可以看到內(nèi)存中依次存儲(chǔ)了0x00000001(即參數(shù)1),0x00000002(即參數(shù)2),0x00000003(即參數(shù)3),即此時(shí)棧頂存儲(chǔ)的是三個(gè)參數(shù)值,說(shuō)明壓棧成功。

          圖3

          然后可以看到call指令跳轉(zhuǎn)到地址0x00401005,那么該地址處是什么呢?我們繼續(xù)跟蹤一下,在圖4中我們看到這里又是一條跳轉(zhuǎn)指令,跳轉(zhuǎn)到0x00401030。我們?cè)倏匆幌碌刂?x00401030處,在圖5中可以看到這才是真正的g_func函數(shù),0x00401030是該函數(shù)的起始地址,這樣就實(shí)現(xiàn)了到g_func函數(shù)的跳轉(zhuǎn)。

          圖4

          圖5

          2. 保存現(xiàn)場(chǎng)

          此時(shí)我們?cè)賮?lái)查看一下棧中的數(shù)據(jù),如圖6所示,此時(shí)的ESP(棧頂)值為0x0012FEEC,在內(nèi)存表中我們可以看到棧頂存放的是0x00401093,下面還是前面壓棧的參數(shù)1,2,3,也就是執(zhí)行了call指令后,系統(tǒng)默認(rèn)的往棧中壓入了一個(gè)數(shù)據(jù)(0x00401093),那么它究竟是什么呢?我們?cè)倏吹綀D3,call指令后面一條指令的地址就是0x00401093,實(shí)際上就是函數(shù)調(diào)用結(jié)束后需要繼續(xù)執(zhí)行的指令地址,函數(shù)返回后會(huì)跳轉(zhuǎn)到該地址。這也就是我們常說(shuō)的函數(shù)中斷前的“保護(hù)現(xiàn)場(chǎng)”。這一過(guò)程是編譯器隱含完成的,實(shí)際上是將EIP(指令指針)壓棧,即隱含執(zhí)行了一條push eip指令,在中斷函數(shù)返回時(shí)再?gòu)臈V袕棾鲈撝档紼IP,程序繼續(xù)往下執(zhí)行。

          圖6

          繼續(xù)往下看,進(jìn)入g_func函數(shù)后的第一條指令是push ebp,即將ebp入棧。因?yàn)槊恳粋€(gè)函數(shù)都有自己的棧區(qū)域,所以棧基址也是不一樣的。現(xiàn)在進(jìn)入了一個(gè)中斷函數(shù),函數(shù)執(zhí)行過(guò)程中也需要ebp寄存器,而在進(jìn)入函數(shù)之前的main函數(shù)的ebp值怎么辦呢?為了不被覆蓋,將它壓入棧中保存。

          下一條mov ebp, esp 將此時(shí)的棧頂?shù)刂纷鳛樵摵瘮?shù)的棧基址,確定g_func函數(shù)的棧區(qū)域(ebp為棧底,esp為棧頂)。

          再往下的指令是sub esp, 48h,指令的字面意思是將棧頂指針往上移動(dòng)48h Byte。那為什么要移動(dòng)呢?這中間的內(nèi)存區(qū)域用來(lái)做什么呢?這個(gè)區(qū)域?yàn)殚g隔空間,將兩個(gè)函數(shù)的棧區(qū)域隔開一段距離,如圖7所示。而該間隔區(qū)域的大小固定為40h,即64Byte,然后還要預(yù)留出存儲(chǔ)局部變量的內(nèi)存區(qū)域。g_func函數(shù)有兩個(gè)局部變量x和y,所以esp需移動(dòng)的長(zhǎng)度為40h+8=48h。

          圖7

          接下來(lái)的幾行指令(如下)是將剛才留出的48h的內(nèi)存區(qū)域賦值為0CCCCCCCCh。

          00401039 lea edi,[ebp-48h]

          0040103C mov ecx,12h

          00401041 mov eax,0CCCCCCCCh

          00401046 rep stos dword ptr [edi] 。

          接下來(lái)三條壓棧指令,分別將EBX,ESI,EDI壓入棧中,這也是屬于“保護(hù)現(xiàn)場(chǎng)”的一部分,這些是屬于main函數(shù)執(zhí)行的一些數(shù)據(jù)。EBX,ESI,EDI分別為基址寄存器,源變址寄存器,目的變址寄存器。

          3. 執(zhí)行子函數(shù)

          繼續(xù)往下看,接下來(lái)是局部變量的x和y的賦值,看匯編指令中是怎樣去計(jì)算x和y的內(nèi)存地址的呢?如圖8所示,是基于ebp去計(jì)算的,分別是[ebp-4]和[ebp-8]。我們查看內(nèi)存表可以看到相應(yīng)的內(nèi)存區(qū)域已經(jīng)存入了0x11111111和0x22222222。

          圖8

          此時(shí)我們對(duì)整個(gè)內(nèi)存區(qū)域中存儲(chǔ)的內(nèi)容應(yīng)該非常清晰了(如圖9所示)。

          圖9

          4. 恢復(fù)現(xiàn)場(chǎng)

          這時(shí)子函數(shù)部分的代碼已經(jīng)執(zhí)行完,繼續(xù)往下看,編譯器將會(huì)做一些事后處理的工作(如圖10所示)。首先是三條出棧指令,分別從棧頂讀取EDI,ESI和EBX的值。從圖9的內(nèi)存數(shù)據(jù)分布我們可以得知此時(shí)棧頂?shù)臄?shù)據(jù)確實(shí)是EDI,ESI和EBX,這樣就恢復(fù)了調(diào)用前的EDI,ESI和EBX值,這是“恢復(fù)現(xiàn)場(chǎng)”的一部分。

          圖10

          第四條指令是mov esp, ebp 即將ebp的值賦給esp。那這是什么意思呢?看看圖9的內(nèi)存數(shù)據(jù)分布,我們就能很明白了,這條語(yǔ)句是讓ESP指向EBP所指的內(nèi)存單元,也就是讓ESP跳過(guò)了一段區(qū)域,很明顯跳過(guò)的恰好是間隔區(qū)域和局部數(shù)據(jù)區(qū)域,因?yàn)楹瘮?shù)已經(jīng)退出了,這兩個(gè)區(qū)域都已經(jīng)沒(méi)有用處了。實(shí)際上這條語(yǔ)句是進(jìn)入函數(shù)時(shí)創(chuàng)建間隔區(qū)域的語(yǔ)句 sub esp, 48h的相反操作。

          再往下是pop ebp,我們從圖9的內(nèi)存數(shù)據(jù)分布可以看出此時(shí)棧頂確實(shí)是存儲(chǔ)的前EBP值,這樣就恢復(fù)了調(diào)用前的EBP值,這也是“恢復(fù)現(xiàn)場(chǎng)”的一部分。該指令執(zhí)行完后,內(nèi)存數(shù)據(jù)分布如圖11所示。

          圖11

          再往下是一條ret指令,即返回指令,他會(huì)怎么處理呢?注意在執(zhí)行ret指令前的ESP值和EIP值(如圖12所示),ESP指向棧頂?shù)?x00401093,EIP的值是0x0040105C(即ret指令的地址)。

          圖12

          執(zhí)行ret指令后我們來(lái)查看ESP和EIP值(如圖13所示),此時(shí)ESP為0012FEF0,即往下移動(dòng)了4Byte。顯然此處編譯器隱含的執(zhí)行了一條pop指令。再來(lái)看一下EIP的值,變?yōu)榱?x00401093,這個(gè)值怎么這么熟悉呢!它實(shí)際上就是棧頂?shù)?Byte數(shù)據(jù),所以這里隱含執(zhí)行的指令應(yīng)該是pop eip。而這個(gè)值就是前面講到過(guò)的,在調(diào)用call指令前壓棧的call的下一條指令的地址。從圖13中可以看出,正是因?yàn)镋IP的值變成了0x00401093,所以程序跳轉(zhuǎn)到了call指令后面的一條指令,又回到了中斷前的地方,這就是所謂的恢復(fù)斷點(diǎn)。

          圖13

          還沒(méi)有完全結(jié)束,此時(shí)還有最后一條指令add esp, 0Ch。這個(gè)就很簡(jiǎn)單了,從圖13中可以看出現(xiàn)在棧頂?shù)臄?shù)據(jù)是1,2,3,也就是函數(shù)調(diào)用前壓入的三個(gè)實(shí)參。這是函數(shù)已經(jīng)執(zhí)行完了,顯然這三個(gè)參數(shù)沒(méi)有用處了。所以add esp, 0Ch就是讓棧頂指針往下移動(dòng)12Byte的位置。為什么是12Byte呢,很簡(jiǎn)單,因?yàn)槿霔5氖?個(gè)int數(shù)據(jù)。這樣由于函數(shù)調(diào)用在棧中添加的所有數(shù)據(jù)都已清除,棧頂指針(ESP)真正回到了函數(shù)調(diào)用前的位置,所有寄存器的值也恢復(fù)到了函數(shù)調(diào)用之前。

          結(jié)束!


          往期推薦




          專輯 | 趣味設(shè)計(jì)模式
          專輯 | 音視頻開發(fā)
          專輯 | C++ 進(jìn)階
          專輯 | 超硬核 Qt
          專輯 | 玩轉(zhuǎn) Linux
          專輯 | GitHub 開源推薦
          專輯 | 程序人生


          關(guān)注公眾號(hào)「高效程序員」??一起優(yōu)秀!

          回復(fù)“1024”,送你一份程序員大禮包。
          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  夜色AV8888 | AAA片网站 | 色在线视频福利 | 国产狼人综合 | 北条麻妃被操 |