• <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>

            loop_in_codes

            低調(diào)做技術(shù)__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

            一段tricky codes:函數(shù)調(diào)用的那些底層細節(jié)


            有一天,被同事問到了下面這段代碼,就簡單分析了一下,發(fā)覺還有點意思:

            __declspec(naked)
            void call(void* pfn, 
            {
                __asm 
                
            {
                    pop eax;
                    add eax, 
            3;
                    xchg dword ptr[esp], eax;
                    push eax;
                    ret;
                }

            }

             

            再看它的用法:

             

            void print_str( const char *s )
            {
                printf( 
            "%s\n", s );
            }

            call( print_str, 
            "a string" );

             

            call函數(shù)的大致作用,就是調(diào)用傳遞進去的函數(shù)print_str,并將參數(shù)"a string"傳遞給目標
            函數(shù)。

            但是它是怎么做到的呢?雖然call只有簡單的幾句匯編代碼,但是卻包含了很多函數(shù)在編譯
            器中的匯編層實現(xiàn)。要了解這段代碼的意思,需要知道如下相關(guān)知識:

            0、函數(shù)調(diào)用的實現(xiàn)中,編譯器通過系統(tǒng)堆棧(ESP寄存器指向)傳遞參數(shù);
            1、C語言默認的函數(shù)調(diào)用規(guī)則(_cdecl)中,調(diào)用者從右往左將參數(shù)壓入堆棧,并且調(diào)用者負
            責堆棧平衡,也就是保證調(diào)用函數(shù)的前后,ESP不變;
            2、匯編指令call本質(zhì)上是先將返回地址,通常是該條指令的下一條指令壓入堆棧,然后直
            接跳轉(zhuǎn)到目標位置;
            3、匯編指令ret則是先從堆棧棧頂取出返回地址,然后跳轉(zhuǎn)過去;
            4、匯編指令add加上其操作數(shù),貌似占3個字節(jié)長度;
            5、在visual studio中,DEBUG模式下編譯器會在我們的代碼中插入各種檢測代碼,而
            __declspec(naked)則是告訴編譯器:別往這里添加代碼。

            了解了以上常識后,再看這段代碼,其本質(zhì)無非就是利用了這些規(guī)則,在代碼段跳來跳去。
            我們來逐步分析一下:

            在調(diào)用call函數(shù)的地方,大概的代碼為:

             

            caller:
            // 堆棧狀態(tài),從左往右分別表示棧頂至下
            // ret_addr是call后的地址,即add esp, 8的位置
            // a1, a2表示函數(shù)參數(shù),callee_addr是這里的print_str
            // stack: ret_addr, callee_addr, a1, a2, 
            call( print_str, "a string" ); 
            add esp, 
            8 //清除參數(shù)傳遞所占用的堆棧空間,維持堆棧平衡
            end_label //位于add后的指令,后面會提到

            call:
            // 此時堆棧stack: ret_addr, a1, a2
            pop eax // eax = ret_addr; stack: callee_addr, a1, a2, 
            add eax, 3 // eax = end_label; stack: callee_addr, a1, a2, 
            xchg dword ptr[esp], eax // eax = callee_addr; stack: end_label, a1, a2, 
            push eax // stack: callee_addr, end_label, a1, a2, 
            ret // 取出callee_addr并跳轉(zhuǎn),也就跳轉(zhuǎn)到print_str函數(shù)的入口,此時堆棧
                
            // stack: end_label, a1, a2, 

            callee(print_str):

             無視函數(shù)內(nèi)容

            ret 
            // print_str返回,此時正常情況下,堆棧stack: end_label, a1, a2, 
             
            // 取出end_label并跳轉(zhuǎn),stack: a1, a2, 

             

            那么當callee結(jié)束時,則跳轉(zhuǎn)回caller函數(shù)中。不過,如過你所見,此時堆棧中還保留著再
            調(diào)用call函數(shù)時傳入的參數(shù):stack: a1, a2, ...,所以,DEBUG模式下,VS就會提示你堆
            棧不平衡。這里簡單的處理就是手動來進行堆棧平衡:

             

                call( print_str, "a string" );
                __asm
                
            {
                    add esp, 
            4
                }

             

            傳入了多少個參數(shù),就得相應地改變esp的值。

            話說距離上篇博客都有半年了,自己都不知道時間晃得如此之快。最近業(yè)余折騰了下android開發(fā)
            一不小心就跨年了。
             

            posted on 2011-01-02 16:34 Kevin Lynx 閱讀(4897) 評論(4)  編輯 收藏 引用 所屬分類: c/c++

            評論

            # re: 一段tricky codes:函數(shù)調(diào)用的那些底層細節(jié) 2011-01-03 05:58 淘寶網(wǎng)

            哈哈 不錯  回復  更多評論   

            # re: 一段tricky codes:函數(shù)調(diào)用的那些底層細節(jié) 2011-01-06 12:30 miosys

            整個懸念就是放在 add eax, 3;
            這條指令就是為了在跳轉(zhuǎn)到最外層主調(diào)函數(shù)上時,留出一個指令空間來平棧。
            如果用 ADD + WORD,應該是 3。當然不會BT到加 DWORD。  回復  更多評論   

            # re: 一段tricky codes:函數(shù)調(diào)用的那些底層細節(jié) 2011-01-08 21:47 G++

            圍觀,表示看不懂,哈哈哈哈哈~~~!  回復  更多評論   

            # re: 一段tricky codes:函數(shù)調(diào)用的那些底層細節(jié)[未登錄] 2011-03-15 14:36 dophi

            已閱  回復  更多評論   

            51久久夜色精品国产| 国产高潮久久免费观看| 97精品伊人久久大香线蕉| 精品免费tv久久久久久久| 亚洲乱码日产精品a级毛片久久 | 人妻丰满?V无码久久不卡| 日日躁夜夜躁狠狠久久AV| 久久精品国产国产精品四凭 | 国色天香久久久久久久小说 | 欧美色综合久久久久久| 久久婷婷五月综合国产尤物app| 久久国产乱子伦精品免费午夜| 久久国产AVJUST麻豆| 99热热久久这里只有精品68| 亚洲综合伊人久久综合| 久久久久噜噜噜亚洲熟女综合| 久久久久亚洲Av无码专| 久久天天躁狠狠躁夜夜avapp| 久久久精品视频免费观看| 久久精品国产亚洲av高清漫画| 欧美伊人久久大香线蕉综合 | 国产精品无码久久久久久| 久久夜色精品国产网站| 久久夜色精品国产亚洲av| 亚洲精品乱码久久久久久蜜桃| 2021久久国自产拍精品| 国产精品99久久精品爆乳| 久久影院综合精品| 久久亚洲春色中文字幕久久久| 亚洲国产日韩欧美综合久久| 精品久久久久久无码人妻蜜桃| 欧美久久一区二区三区| 天天爽天天爽天天片a久久网| 久久五月精品中文字幕| 国产精品免费久久| 久久se精品一区二区| 久久线看观看精品香蕉国产| 77777亚洲午夜久久多喷| 精品久久一区二区三区| 久久久久免费精品国产| 久久本道久久综合伊人|