• <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 在線詞典, 英語學習, 在線翻譯

            學海苦作舟,書山勤為徑

            留下點回憶

            常用鏈接

            統(tǒng)計

            積分與排名

            Denoise

            English study

            Web技術

            數(shù)據(jù)壓縮

            一些連接

            最新評論

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

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

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

            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 函數(shù)調用幾個被測試的函數(shù),分別是:

            1.? 沒有參數(shù)

            2.? 有一個參數(shù)

            3.? 3 個參數(shù)

            4.? 有返回值

            5.? 有兩個臨時變量

            6.? 有多個臨時變量

            ?

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

            Main 函數(shù)變成這樣了:

            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 // 直接將數(shù)據(jù) 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?????????????

            ?

            函數(shù)入口部分:

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

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

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

            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]// 復制

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

            這里需要說明幾點:

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

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

            3.? 堆棧棧頂指針隨數(shù)據(jù)的進入逐漸減小。因此 sub esp 0CCh 實際上是留出了 CC 個自己的堆棧空間。

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

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

            ?

            ?

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

            由于已經知道 i 的地址了,對 i 的賦值就很簡單了。這里看調用第一個沒有參數(shù)沒有返回值的 test1 函數(shù);僅僅一條語句,將 test1 的函數(shù)地址給 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 的地址;同時將返回地址入棧;可以看到當前棧頂?shù)闹凳?/span> 0040114A ,實際上是 test1 的下條指令。

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

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

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

            ?

            現(xiàn)在進入 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????? ??????

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

            ?

            繼續(xù)回到主程序:

            ??? test(10);

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

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

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

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

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

            ?

            對于 test3 的調用:

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

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

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

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

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

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

            ?

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

            ?

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

            ??? i=test2();

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

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

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

            ?

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

            ?

            mian 函數(shù)的返回值,有點特別:

            ??? return 0;

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

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

            ?

            mian 函數(shù)退出的時候有這段代碼:

            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 是將保留的堆??臻g釋放,同時比較 ebp 是否與 esp 相等,如果不相等就提示相應的錯誤,說明有內存泄露等。最后將 ebp 彈出然后返回。

            ?

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

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

            評論

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

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

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

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

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

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

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

            久久99精品久久只有精品| 亚洲а∨天堂久久精品9966| 久久久精品国产免大香伊| 综合人妻久久一区二区精品| 国内精品久久久久久99蜜桃| 国产叼嘿久久精品久久| 伊人色综合久久天天网| 久久99亚洲网美利坚合众国| 日本欧美国产精品第一页久久| 国产精品99久久久精品无码| 国产精品久久国产精麻豆99网站| 久久青青国产| 欧美一区二区精品久久| 久久久久亚洲AV无码专区首JN| 国产成人综合久久久久久| 日韩精品久久久肉伦网站| 久久久精品国产Sm最大网站| 久久久久AV综合网成人| 久久久青草青青国产亚洲免观| 久久青青草原亚洲av无码app| 国产高潮国产高潮久久久91 | 香蕉aa三级久久毛片| 久久99国产乱子伦精品免费| 一本大道久久香蕉成人网| 日本道色综合久久影院| 亚洲色欲久久久综合网东京热| 精品久久人人爽天天玩人人妻| 久久无码人妻一区二区三区| 久久久久av无码免费网| 久久99热这里只有精品66| 久久无码一区二区三区少妇| 久久―日本道色综合久久| 99久久无码一区人妻a黑| 久久亚洲精品无码AV红樱桃| 亚洲国产另类久久久精品| 无码国内精品久久人妻蜜桃| 亚洲AV日韩AV永久无码久久| 久久棈精品久久久久久噜噜| 久久精品国产久精国产思思 | 亚洲午夜久久久影院伊人| 亚洲中文久久精品无码ww16|