• <label id="pxtpz"><meter id="pxtpz"></meter></label>
      1. <span id="pxtpz"><optgroup id="pxtpz"></optgroup></span>

        當(dāng)前位置:雨林木風(fēng)下載站 > 技術(shù)開發(fā)教程 > 詳細(xì)頁面

        用WinDbg探索CLR世界 [4] 辦法的調(diào)用機(jī)制

        用WinDbg探索CLR世界 [4] 辦法的調(diào)用機(jī)制

        更新時(shí)間:2022-05-14 文章作者:未知 信息來源:網(wǎng)絡(luò) 閱讀次數(shù):

        Don Box在《.NET本質(zhì)論 第1卷:公共語言運(yùn)行庫》的第6章里,詳細(xì)地解說了 CLR 中方法地調(diào)用機(jī)制的原理;qqchen在其 BLog 上也有一篇不錯(cuò)的介紹 CLR 中方法調(diào)用分類的文章《CLR Drilling Down: The Overhead of Method Calls 》。但因?yàn)樗麄兾恼碌哪康牟煌识鴽]有足夠深入到讓我滿足的內(nèi)部細(xì)節(jié),呵呵,只好自己接著分析。:D


        我在《用WinDbg探索CLR世界 [3] 跟蹤方法的 JIT 過程》一文中介紹了如何使用 WinDbg 跟蹤 Don Box 所描述的 JIT 過程。本文中將使用前文所介紹的 WinDbg 功能進(jìn)一步分析 CLR 中方法的調(diào)用機(jī)制。

        首先我們來看一個(gè)簡單的例子,其中有兩個(gè)類和一個(gè)接口的定義,并使用了幾種不同的調(diào)用類型進(jìn)行方法調(diào)用:


        以下為引用:

        using System;

        namespace flier
        {
        public interface IFoo
        {
        void CallFromIntfBase();
        void CallFromIntfDerived();
        }

        public class Base : IFoo
        {
        public void CallFromObjBase()
        {
        System.Console.WriteLine("Base.CallFromObjBase");
        }

        public virtual void CallFromObjDerived()
        {
        System.Console.WriteLine("Base.CallFromObjDerived");
        }

        public void CallFromIntfBase()
        {
        System.Console.WriteLine("Base.IFoo.CallFromIntfBase");
        }
        public virtual void CallFromIntfDerived()
        {
        System.Console.WriteLine("Base.IFoo.CallFromIntfDerived");
        }
        }

        public class Derived : Base, IFoo
        {
        public new void CallFromObjBase()
        {
        System.Console.WriteLine("Derived.CallFromObjBase");
        }

        public override void CallFromObjDerived()
        {
        System.Console.WriteLine("Derived.CallFromObjDerived");
        }

        public override void CallFromIntfDerived()
        {
        System.Console.WriteLine("Derived.IFoo.CallFromIntfDerived");
        }
        }

        class EntryPoint
        {
        [STAThread]
        static void Main(string[] args)
        {
        Base b = new Base(),
        d = new Derived();

        b.CallFromObjBase();

        d.CallFromObjBase();
        d.CallFromObjDerived();

        IFoo i = (IFoo) b;

        i.CallFromIntfBase();

        i = (IFoo)d;

        i.CallFromIntfDerived();
        }
        }
        }





        將之編譯成 CallIt.exe 后用 WinDbg 啟動調(diào)試之。進(jìn)入調(diào)試后,可以使用 sos 的 !name2ee 命令查看指定類型的當(dāng)前狀態(tài),如:

        以下為引用:

        0:000> !name2ee CallIt.exe flier.Derived
        --------------------------------------
        MethodTable: 00975288
        EEClass: 06c63414
        Name: flier.Derived




        使用 !dumpclass 命令進(jìn)一步查看類型詳細(xì)信息:

        以下為引用:

        0:000> !dumpclass 06c63414
        Class Name : flier.Derived
        mdToken : 02000004 ()
        Parent Class : 06c6334c
        ClassLoader : 0015ee08
        Method Table : 00975288
        Vtable Slots : 9
        Total Method Slots : b
        Class Attributes : 100001 :
        Flags : 1000003
        NumInstanceFields: 0
        NumStaticFields: 0
        ThreadStaticOffset: 0
        ThreadStaticsSize: 0
        ContextStaticOffset: 0
        ContextStaticsSize: 0




        可以發(fā)現(xiàn) Derived 類型有 11 個(gè) Method Slot,但只有 9 個(gè) Vtable Slot。使用 !dumpmt 進(jìn)一步查看之:

        以下為引用:

        0:000> !dumpmt -md 00975288
        EEClass : 06c63414
        Module : 00167d98
        Name: flier.Derived
        mdToken: 02000004 (D:TempCallItCallItinDebugCallIt.exe)
        MethodTable Flags : 80000
        Number of IFaces in IFaceMap : 1
        Interface Map : 009752e0
        Slots in VTable : 11
        --------------------------------------
        MethodDesc Table
        Entry MethodDesc JIT Name
        79b7c4eb 79b7c4f0 None [DEFAULT] [hasThis] String System.Object.ToString()
        79b7c473 79b7c478 None [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
        79b7c48b 79b7c490 None [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
        79b7c52b 79b7c530 None [DEFAULT] [hasThis] Void System.Object.Finalize()
        0097525b 00975260 None [DEFAULT] [hasThis] Void flier.Derived.CallFromObjDerived()
        009751ab 009751b0 None [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
        0097526b 00975270 None [DEFAULT] [hasThis] Void flier.Derived.CallFromIntfDerived()
        // 以下開始為 IFoo 接口方法表
        009751ab 009751b0 None [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
        0097526b 00975270 None [DEFAULT] [hasThis] Void flier.Derived.CallFromIntfDerived()
        // 以下開始為非虛方法表
        0097524b 00975250 None [DEFAULT] [hasThis] Void flier.Derived.CallFromObjBase()
        0097527b 00975280 None [DEFAULT] [hasThis] Void flier.Derived..ctor()




        可以看到正如 Don Box 在書中所說,類型的方法表是分為虛方法表和非虛方法表兩部分的。前面 9 個(gè) Method Slot 組成 Derived 的 VTable,后兩個(gè) Slot 保存非虛方法。檢查 Base 類的情況也是類似:

        以下為引用:

        0:000> !name2ee CallIt.exe flier.Base
        --------------------------------------
        MethodTable: 009751d8
        EEClass: 06c6334c
        Name: flier.Base

        0:000> !dumpclass 06c6334c
        Class Name : flier.Base
        mdToken : 02000003 ()
        Parent Class : 79b7c3c8
        ClassLoader : 0015ee08
        Method Table : 009751d8
        Vtable Slots : 7
        Total Method Slots : 9
        Class Attributes : 100001 :
        Flags : 1000003
        NumInstanceFields: 0
        NumStaticFields: 0
        ThreadStaticOffset: 0
        ThreadStaticsSize: 0
        ContextStaticOffset: 0
        ContextStaticsSize: 0

        0:000> !dumpmt -md 009751d8
        EEClass : 06c6334c
        Module : 00167d98
        Name: flier.Base
        mdToken: 02000003 (D:TempCallItCallItinDebugCallIt.exe)
        MethodTable Flags : 80000
        Number of IFaces in IFaceMap : 1
        Interface Map : 00975228
        Slots in VTable : 9
        --------------------------------------
        MethodDesc Table
        Entry MethodDesc JIT Name
        79b7c4eb 79b7c4f0 None [DEFAULT] [hasThis] String System.Object.ToString()
        79b7c473 79b7c478 None [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
        79b7c48b 79b7c490 None [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
        79b7c52b 79b7c530 None [DEFAULT] [hasThis] Void System.Object.Finalize()
        0097519b 009751a0 None [DEFAULT] [hasThis] Void flier.Base.CallFromObjDerived()
        // 以下開始為 IFoo 接口方法表
        009751ab 009751b0 None [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
        009751bb 009751c0 None [DEFAULT] [hasThis] Void flier.Base.CallFromIntfDerived()
        // 以下開始為非虛方法表
        0097518b 00975190 None [DEFAULT] [hasThis] Void flier.Base.CallFromObjBase()
        009751cb 009751d0 None [DEFAULT] [hasThis] Void flier.Base..ctor()





        而對于每個(gè)接口,實(shí)際上 CLR 是單獨(dú)維護(hù)了一個(gè)方法表的。如 Base 類的方法表中指出,地址 0x009752e0 處有一個(gè)接口方法映射表,查看其內(nèi)容如下:

        以下為引用:

        0:000> dd 0x009752e0
        009752e0 00975138 00070001 00000000 00000000




        每個(gè)接口映射表表項(xiàng)由2個(gè)DWORD組成,頭一個(gè)DWORD就是接口方法表的地址。

        以下為引用:

        0:000> !dumpmt -md 00975138
        EEClass : 06c633b0
        Module : 00167d98
        Name: flier.IFoo
        mdToken: 02000002 (D:TempCallItCallItinDebugCallIt.exe)
        MethodTable Flags : 80000
        Number of IFaces in IFaceMap : 0
        Interface Map : 0097516c
        Slots in VTable : 2
        --------------------------------------
        MethodDesc Table
        Entry MethodDesc JIT Name
        009750eb 009750f0 None [DEFAULT] [hasThis] Void flier.IFoo.CallFromIntfBase()
        00975113 00975118 None [DEFAULT] [hasThis] Void flier.IFoo.CallFromIntfDerived()




        比較一下就會發(fā)現(xiàn),Base 和 Derived 類的接口映射表指向的接口方法表都是一樣的。

        以下為引用:

        0:000> dd 009752e0
        009752e0 00975138 00070001 00000000 00000000

        0:000> dd 00975228
        00975228 00975138 00050001 00000000 00000000





        只是接口映射表表項(xiàng)第2個(gè) DWORD 的高 WORD 指名此接口在原方法表中的起始索引(Base 為 5,Derived 為 7)不同。這正符合《本質(zhì)論》中167頁那張圖所示的接口映射表結(jié)構(gòu)。

        在了解了方法表的物理結(jié)構(gòu)后,我們接著分析方法的動態(tài)調(diào)用機(jī)制。


        從方法的調(diào)用類型來分,CLR支持直接調(diào)用、間接調(diào)用和很少見的 tail call 模式。

        直接調(diào)用最為常見,又可分為使用虛方法表的 callvirt 指令和不使用虛方法表的 call 和 jmp 指令。
        間接調(diào)用稍微少見,通過 ldftn/calli 和 ldvirtftn/calli 兩組指令,從棧中獲取方法描述 (Method Desc),語義上等同于 call/callvirt 指令。
        tail call 調(diào)用更為少見,類似于 jmp,但是作為前綴指令附加在 call/calli/callvirt 指令上的。

        下面我們對最常見的直接調(diào)用方式做一個(gè)簡單的分析,首先看看一個(gè)例子程序 Virt_not.il:


        以下為引用:

        .assembly extern mscorlib { }
        .assembly virt_not { }
        .module virt_not.exe

        .class public A
        {
        .method public specialname void .ctor() { ret }
        .method public void Foo()
        {
        ldstr "A::Foo"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
        }
        .method public virtual void Bar()
        {
        ldstr "A::Bar"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
        }
        .method public virtual void Baz()
        {
        ldstr "A::Baz"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
        }
        }

        .class public B extends A
        {
        .method public specialname void .ctor() { ret }
        .method public void Foo()
        {
        ldstr "B::Foo"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
        }
        .method public virtual void Bar()
        {
        ldstr "B::Bar"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
        }
        .method public virtual newslot void Baz()
        {
        ldstr "B::Baz"
        call void [mscorlib]System.Console::WriteLine(string)
        ret
        }
        }

        .method public static void Exec()
        {
        .entrypoint
        newobj instance void B::.ctor() // create instance of derived class
        castclass class A // cast it to base class

        dup // we need 3 instance pointers
        dup // on stack for 3 calls

        call instance void A::Foo()
        callvirt instance void A::Bar()
        callvirt instance void A::Baz()

        ret
        }





        上述代碼是使用 IL 匯編直接編寫,其 Exec 函數(shù)將被編譯成 IL 代碼如下:


        以下為引用:

        .method public static void Exec() cil managed
        // SIG: 00 00 01
        {
        .entrypoint
        // Method begins at RVA 0x209c
        // Code size 28 (0x1c)
        .maxstack 8
        IL_0000: /* 73 | (06)000006 */ newobj instance void B::.ctor()
        IL_0005: /* 74 | (1B)000001 */ castclass class A
        IL_000a: /* 25 | */ dup
        IL_000b: /* 25 | */ dup
        IL_000c: /* 28 | (06)000003 */ call instance void A::Foo()
        IL_0011: /* 6F | (06)000004 */ callvirt instance void A::Bar()
        IL_0016: /* 6F | (06)000005 */ callvirt instance void A::Baz()
        IL_001b: /* 2A | */ ret
        } // end of method 'Global Functions'::Exec




        可以看到直接調(diào)用時(shí) call 和 callvirt 指令,都是以方法的 Token 為參數(shù)的。但不同之處在于實(shí)現(xiàn)上,call指令使用類型的方法表,而 callvirt 使用對象的方法表。
        在 WinDbg 載入 Virt_not.exe 后,可以在 Exec 被 JIT 編譯后,使用 !ip2md 命令查看其方法描述信息,如


        以下為引用:

        0:000> g; !clrstack
        Breakpoint 0 hit
        Thread 0
        ESP EIP
        0012f694 791d6a4a [FRAME: PrestubMethodFrame] [DEFAULT] [hasThis] Void A.Foo()
        0012f6a4 06d90088 [DEFAULT] Void Exec()
        0012f9b0 791da717 [FRAME: GCFrame]
        0012fa94 791da717 [FRAME: GCFrame]

        0:000> !ip2md 06d90088
        MethodDesc: 0x00975070
        Jitted by normal JIT
        Method Name : [DEFAULT] Void Exec()
        MethodTable 975078
        Module: 15cd20
        mdToken: 06000001 (C:DevelopMS.NetBooksInside Microsoft .NET IL Assembler CodeVirt_not.EXE)
        Flags : 10
        Method VA : 06d90058





        反匯編 Exec 方法的代碼如下:


        以下為引用:

        0:000> u 06d90058
        06d90058 55 push ebp
        06d90059 8bec mov ebp,esp

        // newobj instance void B::.ctor()
        06d9005b 56 push esi
        06d9005c b9a8519700 mov ecx,0x9751a8 // 類 B 的方法表地址
        06d90061 e8b21fbdf9 call 00962018
        06d90066 8bf0 mov esi,eax

        06d90068 8bce mov ecx,esi
        06d9006a ff15ec519700 call dword ptr [009751ec]

        // castclass class A
        06d90070 8bd6 mov edx,esi
        06d90072 b900519700 mov ecx,0x975100 // 類 A 的方法表地址
        06d90077 e8a00b4672 call mscorwks!JIT_ChkCastClass (791f0c1c)

        06d9007c 8bf0 mov esi,eax // 對象地址
        06d9007e 90 nop
        06d9007f 90 nop

        // call instance void A::Foo()
        06d90080 8bce mov ecx,esi
        06d90082 ff1544519700 call dword ptr [00975144]

        // callvirt instance void A::Bar()
        06d90088 8bce mov ecx,esi
        06d9008a 8b01 mov eax,[ecx]
        06d9008c ff5038 call dword ptr [eax+0x38]

        // callvirt instance void A::Baz()
        06d9008f 8bce mov ecx,esi
        06d90091 8b01 mov eax,[ecx]
        06d90093 ff503c call dword ptr [eax+0x3c]

        06d90096 90 nop
        06d90097 5e pop esi
        06d90098 5d pop ebp
        06d90099 c3 ret





        可以看到 call 指令是通過一個(gè)絕對地址的間接尋址調(diào)用函數(shù)的,此調(diào)用指向代碼如下:


        以下為引用:

        0:000> dd 00975144
        00975144 009750d3 00000000 00000000 00000000

        0:000> u 009750d3
        009750d3 e808857dff call 0014d5e0

        0:000> u 0014d5e0
        0014d5e0 52 push edx
        0014d5e1 68f0301b79 push 0x791b30f0
        0014d5e6 55 push ebp
        0014d5e7 53 push ebx
        0014d5e8 56 push esi
        0014d5e9 57 push edi
        0014d5ea 8d742410 lea esi,[esp+0x10]
        0014d5ee 51 push ecx
        0014d5ef 52 push edx
        0014d5f0 648b1d2c0e0000 mov ebx,fs:[00000e2c]
        0014d5f7 8b7b08 mov edi,[ebx+0x8]
        0014d5fa 897e04 mov [esi+0x4],edi
        0014d5fd 897308 mov [ebx+0x8],esi
        0014d600 56 push esi
        0014d601 e844940879 call mscorwks!PreStubWorker (791d6a4a)
        0014d606 897b08 mov [ebx+0x8],edi





        呵呵,這不正是上次分析的調(diào)用JIT的包裝代碼嗎?


        在進(jìn)行了 JIT 之后,上面的 Exec 代碼調(diào)用 A::Foo 方法體被JIT修改為:


        以下為引用:

        0:000> dd 975144
        00975144 009750d3 00000000 00000000 00000000

        0:000> u 009750d3
        009750d3 e9f8af4106 jmp 06d900d0

        0:000> !ip2md 06d900d0
        MethodDesc: 0x009750d8
        Jitted by normal JIT
        Method Name : [DEFAULT] [hasThis] Void A.Foo()
        MethodTable 975100
        Module: 15cd20
        mdToken: 06000003 (C:DevelopMS.NetBooksInside Microsoft .NET IL Assembler CodeVirt_not.EXE)
        Flags : 0
        Method VA : 06d900d0





        也就是說 call 指令實(shí)際上是直接對 JIT 后的 A::Foo 方法體的代碼進(jìn)行了調(diào)用。


        而 callvirt 指令則使用兩段的間接尋址來調(diào)用方法。


        以下為引用:

        // callvirt instance void A::Bar()
        06d90088 8bce mov ecx,esi
        06d9008a 8b01 mov eax,[ecx]
        06d9008c ff5038 call dword ptr [eax+0x38]




        這里的 esi 是指向?qū)ο蟮闹羔槪鴮ο蠼Y(jié)構(gòu)的第一個(gè) DWORD 保存指向?qū)嶋H類型方法表的指針,也就是《本質(zhì)論》中所說的 RuntimeTypeHandle (具體分析請參看我以前的一篇文章《Type, RuntimeType and RuntimeTypeHandle 》)。而方法表的 0x38 偏移處內(nèi)容如下:


        以下為引用:

        0:000> !dumpmt -md 00975100
        EEClass : 06c63344
        Module : 0015cd20
        Name: A
        mdToken: 02000002 (C:DevelopMS.NetBooksInside Microsoft .NET IL Assembler CodeVirt_not.EXE)
        MethodTable Flags : 80000
        Number of IFaces in IFaceMap : 0
        Interface Map : 0097514c
        Slots in VTable : 8
        --------------------------------------
        MethodDesc Table
        Entry MethodDesc JIT Name
        79b7c4eb 79b7c4f0 None [DEFAULT] [hasThis] String System.Object.ToString()
        79b7c473 79b7c478 None [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
        79b7c48b 79b7c490 None [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
        79b7c52b 79b7c530 None [DEFAULT] [hasThis] Void System.Object.Finalize()
        009750e3 009750e8 None [DEFAULT] [hasThis] Void A.Bar()
        009750f3 009750f8 None [DEFAULT] [hasThis] Void A.Baz()
        009750c3 009750c8 None [DEFAULT] [hasThis] Void A..ctor()
        009750d3 009750d8 None [DEFAULT] [hasThis] Void A.Foo()

        0:000> dd 00975100
        00975100 00080000 0000000c 06c63344 00000000
        00975110 00120000 0015cd20 0006ffff 0097514c
        00975120 00000000 00000008 79b7c4eb 79b7c473
        00975130 79b7c48b 79b7c52b 009750e3 009750f3
        00975140 009750c3 009750d3 00000000 00000000





        可以看到 00975100+0x38 正好是 A.Bar() 方法的入口地址


        以下為引用:

        0:000> u 009750e3
        009750e3 e8f8847dff call 0014d5e0

        0:000> u 14d5e0
        0014d5e0 52 push edx
        ...
        0014d600 56 push esi
        0014d601 e844940879 call mscorwks!PreStubWorker (791d6a4a)
        0014d606 897b08 mov [ebx+0x8],edi

        0:000> !dumpmd 009750e8
        Method Name : [DEFAULT] [hasThis] Void A.Bar()
        MethodTable 975100
        Module: 15cd20
        mdToken: 06000004 (C:DevelopMS.NetBooksInside Microsoft .NET IL Assembler CodeVirt_not.EXE)
        Flags : 0
        IL RVA : 0000205e





        因此 callvirt 指令實(shí)際上是使用變量實(shí)際保存對象的類型的方法表在進(jìn)行調(diào)用,也就是我們所說的虛函數(shù)語義。




        再回頭看前面那個(gè) C# 代碼的例子,在 JIT 完成之后:


        以下為引用:

        .method private hidebysig static void Main(string[] args) cil managed
        // SIG: 00 01 01 1D 0E
        {
        .entrypoint
        .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
        // Method begins at RVA 0x2120
        // Code size 47 (0x2f)
        .maxstack 1
        .locals init ([0] class flier.Base b,
        [1] class flier.Base d,
        [2] class flier.IFoo i)
        IL_0000: /* 73 | (06)000007 */ newobj instance void flier.Base::.ctor()
        IL_0005: /* 0A | */ stloc.0
        IL_0006: /* 73 | (06)00000B */ newobj instance void flier.Derived::.ctor()
        IL_000b: /* 0B | */ stloc.1
        IL_000c: /* 06 | */ ldloc.0
        IL_000d: /* 6F | (06)000003 */ callvirt instance void flier.Base::CallFromObjBase()
        IL_0012: /* 07 | */ ldloc.1
        IL_0013: /* 6F | (06)000003 */ callvirt instance void flier.Base::CallFromObjBase()
        IL_0018: /* 07 | */ ldloc.1
        IL_0019: /* 6F | (06)000004 */ callvirt instance void flier.Base::CallFromObjDerived()
        IL_001e: /* 06 | */ ldloc.0
        IL_001f: /* 0C | */ stloc.2
        IL_0020: /* 08 | */ ldloc.2
        IL_0021: /* 6F | (06)000001 */ callvirt instance void flier.IFoo::CallFromIntfBase()
        IL_0026: /* 07 | */ ldloc.1
        IL_0027: /* 0C | */ stloc.2
        IL_0028: /* 08 | */ ldloc.2
        IL_0029: /* 6F | (06)000002 */ callvirt instance void flier.IFoo::CallFromIntfDerived()
        IL_002e: /* 2A | */ ret
        } // end of method EntryPoint::Main

        0:000> !ip2md 06d900a7
        MethodDesc: 0x00975070
        Jitted by normal JIT
        Method Name : [DEFAULT] Void flier.EntryPoint.Main(SZArray String)
        MethodTable 975088
        Module: 167d98
        mdToken: 0600000c (D:TempCallItCallItinDebugCallIt.exe)
        Flags : 10
        Method VA : 06d90058

        0:000> u 06d90058
        06d90058 55 push ebp
        06d90059 8bec mov ebp,esp
        06d9005b 83ec10 sub esp,0x10
        06d9005e 57 push edi
        06d9005f 56 push esi
        06d90060 53 push ebx
        06d90061 894dfc mov [ebp-0x4],ecx
        06d90064 c745f800000000 mov dword ptr [ebp-0x8],0x0
        06d9006b 33f6 xor esi,esi
        06d9006d 33ff xor edi,edi

        // newobj instance void flier.Base::.ctor()
        06d9006f b9d8519700 mov ecx,0x9751d8 // 類 flier.Base 的方法表
        06d90074 e89f1fbdf9 call 00962018
        06d90079 8bd8 mov ebx,eax
        06d9007b 8bcb mov ecx,ebx
        06d9007d ff1520529700 call dword ptr [00975220] // call flier.Base::.ctor()
        06d90083 895df8 mov [ebp-0x8],ebx // stloc.0

        // newobj instance void flier.Derived::.ctor()
        06d90086 b988529700 mov ecx,0x975288 // 類 flier.Derived 的方法表
        06d9008b e8881fbdf9 call 00962018
        06d90090 8bd8 mov ebx,eax
        06d90092 8bcb mov ecx,ebx
        06d90094 ff15d8529700 call dword ptr [009752d8] // call flier.Derived::.ctor()
        06d9009a 8bf3 mov esi,ebx // stloc.1

        06d9009c 8b4df8 mov ecx,[ebp-0x8] // ldloc.0
        06d9009f 3909 cmp [ecx],ecx
        06d900a1 ff151c529700 call dword ptr [0097521c] // callvirt instance void flier.Base::CallFromObjBase()

        06d900a7 8bce mov ecx,esi // ldloc.1
        06d900a9 3909 cmp [ecx],ecx
        06d900ab ff151c529700 call dword ptr [0097521c] // callvirt instance void flier.Base::CallFromObjBase()

        06d900b1 8bce mov ecx,esi // ldloc.1
        06d900b3 8b01 mov eax,[ecx]
        06d900b5 ff5038 call dword ptr [eax+0x38] // callvirt instance void flier.Base::CallFromObjDerived()

        06d900b8 8b7df8 mov edi,[ebp-0x8] // ldloc.0
        06d900bb 8bcf mov ecx,edi // stloc.2
        06d900bd 8b01 mov eax,[ecx]
        06d900bf 8b400c mov eax,[eax+0xc]
        06d900c2 8b402c mov eax,[eax+0x2c]
        06d900c5 ff10 call dword ptr [eax] // callvirt instance void flier.IFoo::CallFromIntfBase()

        06d900c7 8bfe mov edi,esi // ldloc.1
        06d900c9 8bcf mov ecx,edi // stloc.2
        06d900cb 8b01 mov eax,[ecx]
        06d900cd 8b400c mov eax,[eax+0xc]
        06d900d0 8b402c mov eax,[eax+0x2c]
        06d900d3 ff5004 call dword ptr [eax+0x4] // callvirt instance void flier.IFoo::CallFromIntfDerived()

        06d900d6 90 nop
        06d900d7 5b pop ebx
        06d900d8 5e pop esi
        06d900d9 5f pop edi
        06d900da 8be5 mov esp,ebp
        06d900dc 5d pop ebp
        06d900dd c3 ret







        除了剛剛分析過的 call 和對虛函數(shù)的 callvirt 指令外,這里又多出一種對接口虛函數(shù)進(jìn)行調(diào)用的操作。


        以下為引用:

        06d900bb 8bcf mov ecx,edi // stloc.2
        06d900bd 8b01 mov eax,[ecx] // 載入對象地址指向?qū)ο蠼Y(jié)構(gòu)頭部(04aa1b4c)字段指向的類型信息地址
        06d900bf 8b400c mov eax,[eax+0xc] // 載入全局接口偏移量表基址
        06d900c2 8b402c mov eax,[eax+0x2c] // 獲取 IFoo 接口映射表偏移量
        06d900c5 ff10 call dword ptr [eax] // callvirt instance void flier.IFoo::CallFromIntfBase()




        使用 WinDbg 動態(tài)跟蹤到上述指令處


        以下為引用:

        0:000> !dumpstackobjects
        ESP/REG Object Name
        ebx 04aa1b74 flier.Derived
        ecx 04aa2804 System.IO.TextWriter/SyncTextWriter
        esi 04aa1b74 flier.Derived
        edi 04aa1b68 flier.Base
        0012f6a0 04aa1b68 flier.Base
        0012f6a4 04aa1b4c System.Object[]
        0012f6d8 04aa1b4c System.Object[]
        0012f928 04aa1b4c System.Object[]
        0012f92c 04aa1b4c System.Object[]




        edi 指向 flier.Base 類型的對象實(shí)例(0x04aa1b68)


        以下為引用:

        0:000> !dumpobj 04aa1b68
        Name: flier.Base
        MethodTable 0x009751d8
        EEClass 0x06c6334c
        Size 12(0xc) bytes
        mdToken: 02000003 (D:TempCallItCallItinDebugCallIt.exe)

        0:000> dd 04aa1b68
        04aa1b68 009751d8 00000000 00000000 00975288
        04aa1b78 00000000 80000000 79b7daf8 00000015





        而此對象的偏移 0 處保存著此對象的類型信息地址(0x009751d8)


        以下為引用:

        0:000> !dumpmt 009751d8
        EEClass : 06c6334c
        Module : 00167d98
        Name: flier.Base
        mdToken: 02000003 (D:TempCallItCallItinDebugCallIt.exe)
        MethodTable Flags : 80000
        Number of IFaces in IFaceMap : 1
        Interface Map : 00975228
        Slots in VTable : 9

        0:000> dd 009751d8
        009751d8 00080000 0000000c 06c6334c 0097bff0
        009751e8 00120001 00167d98 0008ffff 00975228







        類型信息的 0xC 偏移處是全局接口偏移量表的入口基址 (0x0097bff0)


        以下為引用:

        0:000> dd 0097bff0
        0097bff0 ???????? ???????? ???????? ????????
        0097c000 00000000 0097c000 00004000 00000000
        0097c010 00000000 000003e8 00000001 00975214
        0097c020 009752cc 00000000 00000000 00000000




        而 IFoo 接口的物理地址就在此偏移量表的 0x2C 偏移處(0x00975214)。這個(gè)地址是直接指向 flier.Base 類的虛方法表。


        以下為引用:

        0:000> !dumpmt -md 009751d8
        EEClass : 06c6334c
        Module : 00167d98
        Name: flier.Base
        mdToken: 02000003 (D:TempCallItCallItinDebugCallIt.exe)
        MethodTable Flags : 80000
        Number of IFaces in IFaceMap : 1
        Interface Map : 00975228
        Slots in VTable : 9
        --------------------------------------
        MethodDesc Table
        Entry MethodDesc JIT Name
        79b7c4eb 79b7c4f0 None [DEFAULT] [hasThis] String System.Object.ToString()
        79b7c473 79b7c478 None [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
        79b7c48b 79b7c490 None [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
        79b7c52b 79b7c530 None [DEFAULT] [hasThis] Void System.Object.Finalize()
        0097519b 009751a0 None [DEFAULT] [hasThis] Void flier.Base.CallFromObjDerived()
        009751ab 009751b0 None [DEFAULT] [hasThis] Void flier.Base.CallFromIntfBase()
        009751bb 009751c0 None [DEFAULT] [hasThis] Void flier.Base.CallFromIntfDerived()
        0097518b 00975190 None [DEFAULT] [hasThis] Void flier.Base.CallFromObjBase()
        009751cb 009751d0 None [DEFAULT] [hasThis] Void flier.Base..ctor()

        0:000> dd 009751d8
        009751d8 00080000 0000000c 06c6334c 0097bff0
        009751e8 00120001 00167d98 0008ffff 00975228
        009751f8 00000000 00000009 79b7c4eb 79b7c473
        00975208 79b7c48b 79b7c52b 0097519b 009751ab
        00975218 009751bb 0097518b 009751cb 00000000
        00975228 00975138 00050001 00000000 00000000
        00975238 00975288 00000000 00000003 00000000
        00975248 e8000008 ff7d9110 00000009 c00020c4







        0x0097519b 就是最后 flier.Base.CallFromObjDerived() 函數(shù)的入口地址。因此對于接口進(jìn)行調(diào)用的 callvirt 指令,實(shí)際上是遵循以下的 dispatch 路線完成調(diào)用的:

        ObjectPtr -> Object -> Class -> Global Interface Map Table -> Class Method Table

        具體的結(jié)構(gòu)圖請參考《本質(zhì)論》167面的圖 (6.5 - 0.1), -_-b

        至此,CLR 中最常見的三種函數(shù)調(diào)用方式就大致分析完畢,以后有機(jī)會在繼續(xù)分析其他的如jmp、間接調(diào)用和 tail call等方式的實(shí)現(xiàn)。

        溫馨提示:喜歡本站的話,請收藏一下本站!

        本類教程下載

        系統(tǒng)下載排行

        主站蜘蛛池模板: 国产成人亚洲综合一区| 337p欧洲亚洲大胆艺术| 成人婷婷网色偷偷亚洲男人的天堂| 1000部啪啪毛片免费看| 亚洲黄色网址大全| 日韩免费无码一区二区三区 | 99热精品在线免费观看| 国产亚洲精品自在久久| 久久免费公开视频| 亚洲美女激情视频| 国产一卡2卡3卡4卡2021免费观看| 亚洲综合综合在线| www.黄色免费网站| 亚洲精品乱码久久久久久V| 免费观看国产精品| 4hu四虎免费影院www| 亚洲国产精品无码久久久秋霞2 | 亚洲电影在线播放| 成年男女男精品免费视频网站 | a色毛片免费视频| 亚洲视屏在线观看| 成人免费视频一区| 一级毛片免费毛片毛片| 亚洲av无码一区二区三区乱子伦| 久久久精品2019免费观看| 亚洲1234区乱码| 免费大黄网站在线观| 成人久久免费网站| 亚洲香蕉久久一区二区| 免费人成在线观看视频播放| 色播在线永久免费视频网站| 亚洲乱码在线播放| 亚洲人成人网站在线观看| 日韩插啊免费视频在线观看| 亚洲精品天堂成人片AV在线播放 | 亚洲日韩精品射精日| 久草视频免费在线观看| 婷婷亚洲综合一区二区| 久久久久亚洲精品影视| 成人免费在线视频| 日韩精品无码免费专区网站 |