• <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ù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

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


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

            __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)用傳遞進(jìn)去的函數(shù)print_str,并將參數(shù)"a string"傳遞給目標(biāo)
            函數(shù)。

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

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

            了解了以上常識(shí)后,再看這段代碼,其本質(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ù)傳遞所占用的堆??臻g,維持堆棧平衡
            end_label //位于add后的指令,后面會(huì)提到

            call:
            // 此時(shí)堆棧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ù)的入口,此時(shí)堆棧
                
            // stack: end_label, a1, a2, 

            callee(print_str):

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

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

             

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

             

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

             

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

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

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

            評(píng)論

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

            哈哈 不錯(cuò)  回復(fù)  更多評(píng)論   

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

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

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

            圍觀,表示看不懂,哈哈哈哈哈~~~!  回復(fù)  更多評(píng)論   

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

            已閱  回復(fù)  更多評(píng)論   

            久久久久久亚洲AV无码专区| 91久久精品国产免费直播| 理论片午午伦夜理片久久| 亚州日韩精品专区久久久| 亚洲国产精品久久电影欧美| 国内精品久久久久久久97牛牛| 午夜不卡888久久| 中文字幕无码免费久久| 久久久精品一区二区三区| 亚洲精品NV久久久久久久久久| 日韩av无码久久精品免费| 久久久久综合中文字幕 | 品成人欧美大片久久国产欧美| 一本色道久久88综合日韩精品| 好久久免费视频高清| 久久久久久综合网天天| 久久www免费人成精品香蕉| 久久99国内精品自在现线| 亚洲第一永久AV网站久久精品男人的天堂AV | 久久精品国产一区二区| 久久精品九九亚洲精品| 久久精品中文无码资源站| 三级片免费观看久久| 国产AV影片久久久久久| 99久久人妻无码精品系列蜜桃| 一本色道久久综合狠狠躁| 日本亚洲色大成网站WWW久久| 99久久精品免费看国产免费| 精品国产91久久久久久久| 久久精品无码专区免费青青| 久久久久久综合网天天| 久久婷婷色综合一区二区| 热久久最新网站获取| 久久成人小视频| 久久亚洲AV成人无码| 精产国品久久一二三产区区别 | 国产免费久久精品99re丫y| 久久综合伊人77777| 亚洲国产视频久久| 综合人妻久久一区二区精品| 久久久久久国产精品无码超碰|