*/ 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等方式的实现。
……