Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

http://www.blogcn.com/user8/flier_lu/index.html?id=1745407&run=.0F766FA

再回头看前面那个 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 和对虚函数的 callvirt 指令外,这里又多出一种对接口虚函数进行调用的操作。
 

以下为引用:

 06d900bb 8bcf             mov     ecx,edi               // stloc.2
 06d900bd 8b01             mov     eax,[ecx]             // 载入对象地址指向对象结构头部(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 动态跟踪到上述指令处
 
以下为引用:

 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 类型的对象实例(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)。这个地址是直接指向 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() 函数的入口地址。因此对于接口进行调用的 callvirt 指令,实际上是遵循以下的 dispatch 路线完成调用的:

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

     具体的结构图请参考《本质论》167面的图 (6.5 - 0.1), -_-b

     至此,CLR 中最常见的三种函数调用方式就大致分析完毕,以后有机会在继续分析其他的如jmp、间接调用和 tail call等方式的实现。

 btw: BLogCN限制一篇帖子只能12K实在太过分了,害得我一篇文章拆成三盘发 :( 
         这也就算了,居然还说我的文章里面有敏感词语,真是 $#@%$#