• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Dict.CN 在線詞典, 英語學習, 在線翻譯

            學海苦作舟,書山勤為徑

            留下點回憶

            常用鏈接

            統計

            積分與排名

            Denoise

            English study

            Web技術

            數據壓縮

            一些連接

            最新評論

            函數是如何被調用的?-探索代碼背后的故事

            C/C++ 語言中,函數是如何被調用的呢?本文就實際的例子,走進匯編代碼來看下函數調用的過程。

            首先看一個簡單的代碼例子:

            void test(int i)

            {

            ??? int j = i;

            }

            ?

            void test1()

            {

            ?

            }

            ?

            int test2()

            {

            ??? return 1;

            }

            ?

            void test3(int a,int b,int c)

            {

            }

            ?

            void test4()

            {

            ??? int i,j;

            }

            ?

            void test5()

            {

            ??? int i,j,k,l;

            }

            ?

            int main()

            {??

            ??? int i =0;

            ??? test1();

            ???

            ??? test(10);

            ???

            ??? test3(1,2,3);

            ?

            ??? i=test2();

            ???

            ??? test4();

            ???

            ??? test5();

            ?

            ??? return 0;

            }

            ?

            這段代碼很簡單, mian 函數調用幾個被測試的函數,分別是:

            1.? 沒有參數

            2.? 有一個參數

            3.? 3 個參數

            4.? 有返回值

            5.? 有兩個臨時變量

            6.? 有多個臨時變量

            ?

            VC7 中,我們將斷點設置到 main 函數入口的地方;然后 F5 運行程序。再按 ALT+8 反匯編,我們看到下面的代碼:

            Main 函數變成這樣了:

            int main()

            {??

            00401120? push??????? ebp?

            00401121? mov???????? ebp,esp

            00401123? sub???????? esp,0CCh

            00401129? push??????? ebx?

            0040112A ? push??????? esi?

            0040112B? push??????? edi?

            0040112C ? lea???????? edi,[ebp-0CCh]

            00401132? mov???????? ecx,33h

            00401137? mov?? ??????eax,0CCCCCCCCh

            0040113C ? rep stos??? dword ptr [edi]

            ??? int i =0;

            0040113E? mov???????? dword ptr [i],0 // 直接將數據 0 放到指定地址中

            ??? test1();

            00401145? call??????? test1 (401030h)

            ???

            ??? test(10);

            0040114A ? push??????? 0Ah?

            0040114C ? call??????? test (401000h)

            00401151? add???????? esp,4

            ???

            ??? test3(1,2,3);

            00401154? push??????? 3???

            00401156? push??????? 2???

            00401158? push??????? 1???

            0040115A ? call??????? test3 (401090h)

            0040115F ? add???????? esp,0Ch

            ?

            ??? i=test2();

            00401162? call??????? test2 (401060h)

            00401167? mov???????? dword ptr [i],eax

            ?

            ??? test4();

            0040116A ? call??????? test4 (4010C0h)

            ???

            ??? test5();

            0040116F ? call ???????test5 (4010F0h)

            ?

            ??? return 0;

            00401174? xor???????? eax,eax

            }

            00401176? pop???????? edi?

            00401177? pop???????? esi?

            00401178? pop???????? ebx?

            00401179? add???????? esp,0CCh

            0040117F ? cmp???????? ebp,esp

            00401181? call??????? _RTC_CheckEsp (4011E0h)

            00401186? mov???????? esp,ebp

            00401188? pop???????? ebp?

            00401189? ret?????????????

            ?

            函數入口部分:

            00401120? push??????? ebp? // 保存 ebp 的值

            00401121? mov???????? ebp,esp // 將當前棧頂指針送到 ebp

            00401123? sub???????? esp,0CCh // 將棧頂指針下移 0XCC 個字節,為臨時變量留出空間

            00401129? push??????? ebx? // 保存 ebx

            0040112A ? push??????? esi? // 保存 esi

            0040112B? push??????? edi? // 保存 edi

            0040112C ? lea???????? edi,[ebp-0CCh] // edp-0CC 地址送 EAX

            00401132? mov???????? ecx,33h //CC/4 得到的

            00401137? mov???????? eax,0CCCCCCCCh // 初始化為 0XCCCCCCCCH

            0040113C ? rep stos??? dword ptr [edi]// 復制

            這寫匯編是編譯器為我們生成的函數入口部分,基本的含義是為臨時變量分配空間,并且初始化臨時變量。

            這里需要說明幾點:

            1.? 函數調用是通過堆棧來完成的。

            2.? 函數入口的地方必須為臨時變量分配一定空間;實際上如果沒有臨時變量,也要留出 C0 個字節。

            3.? 堆棧棧頂指針隨數據的進入逐漸減小。因此 sub esp , 0CCh 實際上是留出了 CC 個自己的堆??臻g。

            我們看到實現將棧頂指針保存在 ebp 中,然后對該段空間設置初始值。而 0XCCCCCCH 是由堆棧的性質決定,可以看 MSDN 。

            如果開始的時候假設 ESP 等于 0X12FEE0 ,那么在保存 EBP 之后, ESP 變成 0X12FEDC ,那么后來 EBP 中的值就是這個值,在保存的空間(從 0X12FE10 0X12FEDC )上將所有的內存都初始化為 0XCC 。而 i 被分配在 0X12FED4 處,也就是第一個預留的位置)。

            ?

            ?

            call??????? test1 (401030h)

            由于已經知道 i 的地址了,對 i 的賦值就很簡單了。這里看調用第一個沒有參數沒有返回值的 test1 函數;僅僅一條語句,將 test1 的函數地址給 call 指令。

            EAX = CCCCCCCC EBX = 7FFDE000 ECX = 00000000 EDX = 00000001

            ESI = 00000040 EDI = 0012FEDC EIP = 00401145 ESP = 0012FE04

            EBP = 0012FEDC EFL = 00000202

            上面是 Call 指令調用前各寄存器的值;下面是調用后的值:

            EAX = CCCCCCCC EBX = 7FFD7000 ECX = 00000000 EDX = 00000001

            ESI = 00000040 EDI = 0012FEDC EIP = 00401030 ESP = 0012FE00

            EBP = 0012FEDC EFL = 00000202

            主要變化在于 EIP ESP ;前者是指令指針寄存器,而后者是堆棧指針寄存器。調用前指令的位置在 00401145 位置,而 call 指定將 EIP 改為 test1 的地址;同時將返回地址入棧;可以看到當前棧頂的值是 0040114A ,實際上是 test1 的下條指令。

            因此我們說 Call 指定做了兩件事情:

            1.? EIP 從當前值改為被調用函數的值。

            2.? 將返回地址,也就是當前地址的下條指令放入堆棧。

            ?

            現在進入 test1 中看個究竟。

            void test1()

            {

            00401030? push??????? ebp?

            00401031? mov???????? ebp,esp

            00401033? sub???????? esp,0C0h

            00401039? push??????? ebx?

            0040103A ? push??????? esi?

            0040103B? push??????? edi?

            0040103C ? lea???????? edi,[ebp-0C0h]

            00401042? mov??? ?????ecx,30h

            00401047? mov???????? eax,0CCCCCCCCh

            0040104C ? rep stos??? dword ptr [edi]

            ?

            }

            0040104E? pop???????? edi?

            0040104F ? pop???????? esi?

            00401050? pop???????? ebx?

            00401051? mov???????? esp,ebp

            00401053? pop???????? ebp?

            00401054? ret????? ??????

            上面的命令基本相同,主要區別在于 test1 內部沒有臨時變量,因此這里只保留了 C0 個自己的空間。

            ?

            繼續回到主程序:

            ??? test(10);

            0040114A ? push??????? 0Ah?

            0040114C ? call??????? test (401000h)

            00401151? add???????? esp,4

            由于 test 函數有一個參數,因此需要首先將參數壓入堆棧中,然后執行與前面相似的操作。

            這里有一點需要注意:函數返回之后需要將壓入的參數彈出;可以使用 pop 命令,也可以使用 add 命令來執行。

            ?

            對于 test3 的調用:

            ??? test3(1,2,3);

            00401154? push??????? 3???

            00401156? push??????? 2???

            00401158? push??????? 1???

            0040115A ? call??????? test3 (401090h)

            0040115F ? add???????? esp,0Ch

            ?

            由于它需要三個參數,因此都必須壓入棧,返回的時候一次性彈出。

            ?

            下面看如何調用帶有返回值的參數:

            ??? i=test2();

            00401162? call??????? test2 (401060h)

            00401167? mov???????? dword ptr [i],eax

            其他的相同,但重要的一點是函數的返回值是通過 eax 寄存器來返回的。

            ?

            其他幾個函數的調用不同的是臨時變量數目的不同,僅僅在初始化預留空間的時候不同,基本上是每增加一個變量多出 12 個字節的堆??臻g。

            ?

            mian 函數的返回值,有點特別:

            ??? return 0;

            00401174? xor???????? eax,eax

            特別的不在于通過 eax 返回,而是自己和自己異或,大部分返回 0 的函數都這么做。

            ?

            mian 函數退出的時候有這段代碼:

            00401176? pop???????? edi?

            00401177? pop???????? esi?

            00401178? pop???????? ebx?

            00401179? add???????? esp,0CCh

            0040117F ? cmp???????? ebp,esp

            00401181? call??????? _RTC_CheckEsp (4011E0h)

            00401186? mov???????? esp,ebp

            00401188? pop???????? ebp?

            00401189? ret?????????????

            前面幾行是將寄存器的值恢復,而 add esp , 0CCh 是將保留的堆棧空間釋放,同時比較 ebp 是否與 esp 相等,如果不相等就提示相應的錯誤,說明有內存泄露等。最后將 ebp 彈出然后返回。

            ?

            從上面的分析我們可以看到編譯器為我們做了很多事情,包括:堆棧空間分配和釋放、寄存器狀態保存、參數傳遞等。當然這些事情也可以完全由我們自己來完成,那么需要做的是使用關鍵字 naked 來聲明函數。

            posted on 2007-01-18 15:08 笨笨 閱讀(2497) 評論(3)  編輯 收藏 引用 所屬分類: 編碼

            評論

            # re: 函數是如何被調用的?-探索代碼背后的故事 2007-01-18 15:09 笨笨

            終于又找回密碼了,痛恨木馬編寫的人,痛恨病毒!同時感謝論壇斑竹的熱心幫助!  回復  更多評論   

            # re: 函數是如何被調用的?-探索代碼背后的故事 2007-01-27 01:04 SonicLing

            很多時候并不一定會用ebp來備份esp。包含ret的分支很少的函數用release編譯之后,直接會在ret之前將esp加回到原來的值,ebp用來干其他的事。

            xor eax,eax是因為該指令比mov eax,0無論是在指令長度還是在執行效率上都更優秀。  回復  更多評論   

            # re: 函數是如何被調用的?-探索代碼背后的故事 2007-01-29 08:45 笨笨

            你說的很有道理,這里僅僅是將一段代碼再VC中反匯編的到的。當然,這里的代碼并非唯一的寫法。
            所以,謝謝你的補充
              回復  更多評論   

            国产成人久久精品麻豆一区| 久久精品国产亚洲av麻豆蜜芽| 东京热TOKYO综合久久精品| 国产V综合V亚洲欧美久久| 狠狠色婷婷综合天天久久丁香| 久久99国产一区二区三区| 狠狠色丁香婷婷久久综合五月 | 品成人欧美大片久久国产欧美| 久久精品国产一区二区| 亚洲综合熟女久久久30p| 久久激情亚洲精品无码?V| 色欲久久久天天天综合网| 久久久久九九精品影院| 久久婷婷五月综合97色| 亚洲国产成人久久综合区| 青青青伊人色综合久久| 日韩人妻无码一区二区三区久久 | 久久综合久久综合亚洲| 久久中文字幕一区二区| 人妻无码久久一区二区三区免费 | 久久久久国产精品熟女影院| 日本欧美国产精品第一页久久| 996久久国产精品线观看| 国内精品综合久久久40p| 日本久久久久久久久久| 国产巨作麻豆欧美亚洲综合久久| 蜜臀av性久久久久蜜臀aⅴ麻豆 | 久久丫精品国产亚洲av不卡| 欧美精品九九99久久在观看| 久久精品国产亚洲AV不卡| 国产精品久久久99| 九九久久精品国产| 久久国产影院| 亚洲一级Av无码毛片久久精品| 三级韩国一区久久二区综合| 久久久艹| 精品久久久久久中文字幕大豆网| 久久笫一福利免费导航| 无码专区久久综合久中文字幕 | 久久亚洲AV无码精品色午夜| 无码国内精品久久综合88 |