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

            S.l.e!ep.¢%

            像打了激速一樣,以四倍的速度運(yùn)轉(zhuǎn),開(kāi)心的工作
            簡(jiǎn)單、開(kāi)放、平等的公司文化;尊重個(gè)性、自由與個(gè)人價(jià)值;
            posts - 1098, comments - 335, trackbacks - 0, articles - 1
              C++博客 :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            在上一篇博客中小覽call stack(調(diào)用棧) (一)中,我展示了如何在windbg中觀(guān)察調(diào)用棧的相關(guān)信息:函數(shù)的返回地址,參數(shù),返回值。這些信息都按照一定的規(guī)則存儲(chǔ)在固定的地方。這個(gè)規(guī)則就是調(diào)用約定(calling convention)。

            ?

            調(diào)用約定在計(jì)算機(jī)界不是什么新鮮的概念,已經(jīng)有許多相關(guān)的文獻(xiàn)給予詳細(xì)的介紹。比較全面的介紹可以參見(jiàn)wikipedia上的相關(guān)頁(yè)面。然而,如果你和我一樣,在第一次接觸調(diào)用約定的時(shí)候,覺(jué)得這個(gè)概念是個(gè)高深神秘的冬冬,那么就請(qǐng)跟隨我一起,在這篇博客中看看他的由來(lái),他的范疇以及他的用途。

            ?

            為什么需要調(diào)用約定?

            在具體介紹調(diào)用約定的定義之前,我們先來(lái)看看為什么我們需要一個(gè)稱(chēng)之為調(diào)用約定的冬冬。如果各位了解匯編語(yǔ)言(不了解的話(huà),看下面的這段會(huì)稍微有些費(fèi)力,不過(guò)我盡可能把匯編的相關(guān)知識(shí)解釋的清楚一些),那么回憶一下我們是怎么來(lái)做一個(gè)函數(shù)調(diào)用的。

            ?

            匯編語(yǔ)言提供了一條指令,call ptr,其功能是把CS:IP (指令段:指令指針,決定著下一條執(zhí)行指令的地址)壓棧,并且修改CPU的指令指針,作一個(gè)跳轉(zhuǎn)。在函數(shù)結(jié)束的地方,我們使用另一條指令,ret,其功能是把棧中的返回地址取出,并且跳轉(zhuǎn)到那條指令。

            ?

            在這里匯編語(yǔ)言只提供了指令跳轉(zhuǎn)的命令,作為函數(shù)調(diào)用另一個(gè)重要組成部分的參數(shù)傳遞,其方式就很靈活,你可以通過(guò)寄存器傳值,可以通過(guò)調(diào)用棧傳值,可以通過(guò)某一塊具體的內(nèi)存?zhèn)髦?類(lèi)似全局變量)。然后在被調(diào)用函數(shù)中,從寄存器,棧或者是內(nèi)存中讀取這些信息。想象一下如果被調(diào)用函數(shù)是某一個(gè)程序員所編寫(xiě)的,調(diào)用者是另一個(gè)程序員,那么他倆之間對(duì)于參數(shù)的傳遞方式就有了一個(gè)約定。

            ?

            高級(jí)語(yǔ)言的出現(xiàn),把這個(gè)問(wèn)題隱藏了起來(lái)。我們?cè)诰帉?xiě)一般的c++程序的時(shí)候,通常不需要顧慮參數(shù)傳遞的底層實(shí)現(xiàn),但是,這并不意味著這一問(wèn)題不再出現(xiàn)——我們只是把責(zé)任推給了編譯器。編譯器作為一個(gè)計(jì)算機(jī)程序,總是遵照一定的規(guī)則工作,每一個(gè)規(guī)則對(duì)應(yīng)了一種調(diào)用約定。

            ?

            久而久之,那些經(jīng)典的規(guī)則所產(chǎn)生的調(diào)用約定,就成了耳熟能詳?shù)亩?/p>

            ?

            耳熟能詳?shù)恼{(diào)用約定

            在介紹這些調(diào)用規(guī)范之前,我想先說(shuō)明的是,下面所涉及的調(diào)用規(guī)范是在32位x86處理器windows平臺(tái)上的。把范疇限定在32位處理器的原因是:16位處理器已經(jīng)退出CPU的歷史舞臺(tái),64微處理器無(wú)論是IA64還是AMD64都只有一個(gè)調(diào)用規(guī)范——只有32位處理器呈現(xiàn)百家成名,百花齊放的景象。(對(duì)了,你當(dāng)然明白調(diào)用規(guī)范是綁定在處理器架構(gòu)上的概念,因?yàn)樗婕疤嗟闹T如寄存器之類(lèi)的處理器架構(gòu)細(xì)節(jié)。)聚焦于windows則是因?yàn)槲椰F(xiàn)在的工作只涉及這一平臺(tái)。

            下表的出處來(lái)自于The Old New Thing以及張羿的csdn專(zhuān)欄,并作了適當(dāng)修改。


            首先來(lái)看所有的調(diào)用規(guī)范都遵循的規(guī)定:返回值存儲(chǔ)在EDX:EAX中,EDI,ESI,EBP,EBX是保留的存儲(chǔ)器。(即函數(shù)可以任意使用這些寄存器,無(wú)需擔(dān)心破壞了調(diào)用者的寄存器狀態(tài))

            調(diào)用約定名稱(chēng)
            ?清理堆棧
            ?參數(shù)壓棧順序
            ?備注
            ?
            cdecl
            ?調(diào)用者 (Caller)
            ?從右往左?
            ?因?yàn)槭钦{(diào)用者清理Stack,因此允許變參 (如printf)
            ?
            stdcall
            ?被調(diào)用者 (Callee)
            ?從右往左?
            ?一般在Windows API和COM中使用,也是.NET和Native代碼調(diào)用的缺省Calling Convention。
            順便提一下,Windows中API的Calling Convention所使用到的WINAPI宏在PC機(jī)上是__stdcall,而在WinCE上則是__cdecl,并非一成不變。
            ?
            Thiscall (Microsoft)
            ?被調(diào)用者 (Callee)
            ?從右往左
            ?基本上等價(jià)stdcall, 除了this指針用ECX傳遞
            ?
            Fastcall (Microsoft)
            ?被調(diào)用者 (Callee)
            ?從右往左
            ?和Stdcall類(lèi)似,但是會(huì)選擇兩個(gè)從左往右數(shù)最先可以放在寄存器里面的參數(shù)放在ECX和EDX中
            ?


            大家可能對(duì)清理堆棧,參數(shù)壓棧順序這些概念不是很清楚,在這里我會(huì)通過(guò)一個(gè)具體的例子來(lái)說(shuō)明。下面列出了一小段程序和它的匯編代碼:

            view plaincopy to clipboardprint?
            #include <stdio.h>??
            int __stdcall Test(int a, char b, short c)??
            {??
            ??? printf("%d %c %d", a, b, c);??
            ??? return a+c;??
            }??
            void main()??
            {??
            ??? int a = Test(5, 'a', 10);??
            }?
            #include <stdio.h>
            int __stdcall Test(int a, char b, short c)
            {
            ??? printf("%d %c %d", a, b, c);
            ??? return a+c;
            }
            void main()
            {
            ??? int a = Test(5, 'a', 10);
            }

            在main中對(duì)Test的調(diào)用對(duì)應(yīng)了如下的匯編代碼:

            view plaincopy to clipboardprint?
            00412004 6a0a??????????? push??? 0Ah??
            00412006 6a61??????????? push??? 61h??
            00412008 6a05??????????? push??? 5??
            0041200a e800f0feff????? call??? test!ILT+10(?TestYGHHDFZ) (0040100f)??
            0041200f 8945fc????????? mov???? dword ptr [ebp-4],eax ss:002b:001?
            00412004 6a0a??????????? push??? 0Ah
            00412006 6a61??????????? push??? 61h
            00412008 6a05??????????? push??? 5
            0041200a e800f0feff????? call??? test!ILT+10(?TestYGHHDFZ) (0040100f)
            0041200f 8945fc????????? mov???? dword ptr [ebp-4],eax ss:002b:001

            ?

            在這個(gè)例子中,我們可以觀(guān)察到如下信息:

            1. 壓棧順序:棧中首先壓入的是0A(十進(jìn)制中的10),是最后一個(gè)參數(shù),其次是’a’,最后是5,所以說(shuō)__stdcall的壓棧順序是從右向左。

            2. 返回值存放在eax中:在call指令之后,把eax的值存入到[ebp-4]中,對(duì)應(yīng)了c++代碼中對(duì)a的賦值,可見(jiàn)eax是返回值的存放之所。

            3. 被調(diào)用函數(shù)清理?xiàng)#涸赾all指令和mov指令沒(méi)有額外的其他指令,可見(jiàn)之前放到棧里的參數(shù),都已經(jīng)被函數(shù)Test清理了(Test的最后一條指令是ret 0c),把棧的指針調(diào)整了三個(gè)變量的位置。

            4. 函數(shù)更名:細(xì)心的讀者會(huì)發(fā)現(xiàn)call指令后面跟的是如同亂碼般的test!ILT+10(?TestYGHHDFZ),這是編譯器做的手腳(name mangling),不同的調(diào)用規(guī)范下,編譯器會(huì)按照不同的規(guī)則對(duì)函數(shù)進(jìn)行更名。我不想細(xì)究的原因在于:一方便,函數(shù)更名的規(guī)則本身就在變化,我目前使用的編譯器,會(huì)按照以前__thiscall的規(guī)則來(lái)更名__stdcall的函數(shù)。另一方面,許多debuger比如windbg,會(huì)自動(dòng)的把命名調(diào)整回來(lái)。


            如何指定調(diào)用約定

            通常,我們真正需要考慮到調(diào)用約定的場(chǎng)景,是對(duì)一些外部類(lèi)庫(kù)的使用。舉例來(lái)說(shuō),如果我們要調(diào)用的函數(shù)由另外一個(gè)類(lèi)庫(kù)提供,那么,我們需要根據(jù)這個(gè)函數(shù)所聲明的調(diào)用約定來(lái)使用這個(gè)函數(shù)。也就是說(shuō),我們要告訴編譯器,請(qǐng)按照這個(gè)調(diào)用約定,生成相關(guān)的代碼,來(lái)使用那個(gè)來(lái)自于類(lèi)庫(kù)的函數(shù)。對(duì)于MSVC的編譯器來(lái)說(shuō),有下面的這些開(kāi)關(guān):

            編譯器開(kāi)關(guān)
            ?調(diào)用規(guī)范
            ?
            /Gd
            ?__cdecl
            ?
            /Gr
            ?__fastcall
            ?
            /Gz
            ?__stdcall
            ?

            其中/Gz是c++的默認(rèn)選項(xiàng)。

            ?

            另外一個(gè)例子是,提供給別人的回調(diào)函數(shù),需要根據(jù)調(diào)用者的要求,聲明調(diào)用約定,舉一個(gè)例子來(lái)說(shuō),在windows中開(kāi)始一個(gè)新的線(xiàn)程。

            這時(shí)候,可以在函數(shù)聲明的語(yǔ)句中,在返回值類(lèi)型后面插入相關(guān)的調(diào)用規(guī)范,如前面的例子中所示。

            view plaincopy to clipboardprint?
            int __stdcall Test(int a, char b, short c)?
            int __stdcall Test(int a, char b, short c)?


            如果你是一個(gè).NET用戶(hù)(終于,我可以談及一些我們的產(chǎn)品了),那么你在P/Invoke的時(shí)候仍然需要調(diào)用約定。DllImportAttibute中,有一個(gè)字段CallingConvention,就是對(duì)應(yīng)這個(gè)需求生成的。

            ?

            view plaincopy to clipboardprint?
            [DllImport("ole32.dll", EntryPoint="CoCreateInstance", CallingConvention=CallingConvention.StdCall)]??
            public static extern? int CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, uint dwClsContext, ref Guid riid, ref System.IntPtr ppv) ;?
            [DllImport("ole32.dll", EntryPoint="CoCreateInstance", CallingConvention=CallingConvention.StdCall)]
            public static extern? int CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, uint dwClsContext, ref Guid riid, ref System.IntPtr ppv) ;?


            調(diào)用約定的用武之地

            看了上面的介紹之后,你可能會(huì)想,我們只需要根據(jù)文檔上聲明的調(diào)用約定,在自己的代碼中指定相應(yīng)的調(diào)用約定就可以了。那么,了解清楚每一個(gè)調(diào)用約定的具體內(nèi)容對(duì)我們有什么幫助呢?

            我認(rèn)為,了解調(diào)用約定首先可以幫助我們深入了解函數(shù)調(diào)用部分的匯編代碼的原理。有很多時(shí)候,錯(cuò)誤的使用了調(diào)用規(guī)范是一個(gè)很難察覺(jué)的bug。

            其次,了解調(diào)用約定在只擁有公共符號(hào)(public symbol)進(jìn)行調(diào)試的時(shí)候?qū)ξ覀儙椭艽螅卜?hào)通常只能讓我們觀(guān)察到調(diào)用棧信息。那么了解了調(diào)用約定之后,我們至少能利用調(diào)用棧找到函數(shù)參數(shù),函數(shù)返回值等信息。

            ?

            總結(jié)以及下期預(yù)告

            今天我花費(fèi)了蠻多筆墨講解調(diào)用規(guī)范,對(duì)于這一系列的主題“調(diào)用棧”來(lái)說(shuō),調(diào)用規(guī)范是一個(gè)息息相關(guān)的概念。下一次,我將通過(guò)一個(gè)windbg調(diào)試腳本來(lái)觀(guān)察遵循stdcall的調(diào)用棧,作為這一系列的收尾,敬請(qǐng)期待。


            本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/mountaintaiII/archive/2009/03/12/3985729.aspx

            性高朝久久久久久久久久| 亚洲国产精品无码久久久久久曰| 久久人人爽人人人人爽AV| 丁香色欲久久久久久综合网| 婷婷五月深深久久精品| 国产午夜精品久久久久免费视| 国内精品久久久久影院免费| 久久精品无码av| 7777久久久国产精品消防器材| 久久国产成人精品麻豆| 久久久久99这里有精品10| 国内精品久久久久影院优| 久久精品国产色蜜蜜麻豆| 三上悠亚久久精品| 久久精品国产只有精品66| 久久99国产乱子伦精品免费| 亚洲а∨天堂久久精品9966| 青青草国产精品久久| 少妇高潮惨叫久久久久久| 久久99精品久久久久久齐齐| 久久久精品人妻一区二区三区蜜桃 | 99久久国产亚洲综合精品| 久久99精品综合国产首页| 久久午夜夜伦鲁鲁片免费无码影视| 久久精品视频网| 精品久久久久中文字幕日本| 国产精品久久久久免费a∨| 久久久久人妻一区精品 | 93精91精品国产综合久久香蕉 | 青春久久| 无码任你躁久久久久久老妇| 国产精自产拍久久久久久蜜 | 精品无码久久久久久国产| 久久精品成人免费网站| 久久人人爽人人爽人人片av高请| 亚洲国产综合久久天堂| 久久精品无码免费不卡| 999久久久国产精品| 国产精品一区二区久久精品无码 | 99久久精品国产一区二区蜜芽| 久久99中文字幕久久|