自己给自己网站做seo,做电影网站多少钱,今天的三个新闻,泰安网络信息公司最近在看 C 的虚方法调用实现原理#xff0c;大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针#xff0c;而 vtable array 中的每一个指针元素指向的就是各自的 虚方法#xff0c;实现方式很有意思#xff0c;哈哈#xff0c;现在我很好奇 C# 中… 最近在看 C 的虚方法调用实现原理大概就是说在 class 的首位置存放着一个指向 vtable array 指针数组 的指针而 vtable array 中的每一个指针元素指向的就是各自的 虚方法实现方式很有意思哈哈现在我很好奇 C# 中如何实现的。一C# 中的多态玩法 1. 一个简单的 C# 例子为了方便说明我就定义一个 Person 类和一个 Chinese 类详细代码如下internal class Program{static void Main(string[] args){Person person new Chinese();person.SayHello();Console.ReadLine();}}public class Person{public virtual void SayHello(){Console.WriteLine(sayhello);}}public class Chinese: Person{public override void SayHello(){Console.WriteLine(chinese);}}
}2. 汇编代码分析接下来用 windbg 在 person.SayHello() 处下一个断点观察一下它的反汇编代码D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs 9:
05cf21b3 b93c5dce05 mov ecx,5CE5D3Ch (MT: ConsoleApp1.Chinese)
05cf21b8 e8030f89fa call 005830c0 (JitHelp: CORINFO_HELP_NEWSFAST)
05cf21bd 8945f4 mov dword ptr [ebp-0Ch],eax
05cf21c0 8b4df4 mov ecx,dword ptr [ebp-0Ch]
05cf21c3 e820fbffff call 05cf1ce8 (ConsoleApp1.Chinese..ctor(), mdToken: 0600000A)
05cf21c8 8b4df4 mov ecx,dword ptr [ebp-0Ch]
05cf21cb 894df8 mov dword ptr [ebp-8],ecxD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs 11:05cf21ce 8b4df8 mov ecx,dword ptr [ebp-8]
05cf21d1 8b45f8 mov eax,dword ptr [ebp-8]
05cf21d4 8b00 mov eax,dword ptr [eax]
05cf21d6 8b4028 mov eax,dword ptr [eax28h]
05cf21d9 ff5010 call dword ptr [eax10h]
05cf21dc 90 nop从汇编代码看逻辑非常清晰大体步骤如下eax,dword ptr [ebp-8]从栈上(ebp-8)处获取 person 在堆上的首地址如果不相信的话可以用 !do 027ea88c 试试看。0:000 dp ebp-8 L1
0057f300 027ea88c0:000 !do 027ea88c
Name: ConsoleApp1.Chinese
MethodTable: 05ce5d3c
EEClass: 05cd3380
Size: 12(0xc) bytes
File: D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
Fields:
Noneeax,dword ptr [eax]如果大家了解 实例 在堆上的内存布局的话应该知道这个首地址存放的就是 methodtable 指针,我们可以用 !dumpmt 05ce5d3c 来验证下。0:000 dp 027ea88c L1
027ea88c 05ce5d3c0:000 !dumpmt 05ce5d3c
EEClass: 05cd3380
Module: 05addb14
Name: ConsoleApp1.Chinese
mdToken: 02000007
File: D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize: 0xc
ComponentSize: 0x0
DynamicStatics: false
ContainsPointers false
Slots in VTable: 6
Number of IFaces in IFaceMap: 0eax,dword ptr [eax28h]那这句话是什么意思呢如果你了解 CoreCLR 的话你应该知道 methedtable 是由一个 class MethodTable 类来承载的所以它取了 methodtable 偏移 0x28 位置的一个字段那这个偏移字段是什么呢我们先用 dt 把 methodtable 结构给导出来。0:000 dt 05ce5d3c MethodTable
coreclr!MethodTable7ad96bc8 s_pMethodDataCache : 0x00639ec8 MethodDataCache7ad96bc4 s_fUseParentMethodData : 0n17ad96bcc s_fUseMethodDataCache : 0n10x000 m_dwFlags : 0xc0x004 m_BaseSize : 0x740880x008 m_wFlags2 : 50x00a m_wToken : 00x00c m_wNumVirtuals : 0x5ccc0x00e m_wNumInterfaces : 0x5ce0x010 m_pParentMethodTable : IndirectPointerMethodTable *0x014 m_pLoaderModule : PlainPointerModule *0x018 m_pWriteableData : PlainPointerMethodTableWriteableData *0x01c m_pEEClass : PlainPointerEEClass *0x01c m_pCanonMT : PlainPointerunsigned long0x020 m_pPerInstInfo : PlainPointerPlainPointerDictionary * *0x020 m_ElementTypeHnd : 00x020 m_pMultipurposeSlot1 : 00x024 m_pInterfaceMap : PlainPointerInterfaceInfo_t *0x024 m_pMultipurposeSlot2 : 0x5ce5d687ad04c78 c_DispatchMapSlotOffsets : [0] $ (System.Private.CoreLib.dll7ad04c70 c_NonVirtualSlotsOffsets : [0] $ ($((, $ (System.Private.CoreLib.dll7ad04c60 c_ModuleOverrideOffsets : [0] $ ($((,$((,(,,0 $ ($((, $ (System.Private.CoreLib.dll7ad12838 c_OptionalMembersStartOffsets : [0] (((((((,(((,(,,0(((,(,,0(,,0,004从 methodtable 的布局图来看, eax28h 是 m_pMultipurposeSlot2 结构的第二个字段了因为第一个字段是 虚方法表指针如果要验证的话也很简单用 !dumpmt -md 05ce5d3c 把所有的方法给导出来然后结合 dp 05ce5d3c 看下 0x5ce5d68 之后是不是许多的方法。0:000 !dumpmt -md 05ce5d3c
EEClass: 05cd3380
Module: 05addb14
Name: ConsoleApp1.Chinese
mdToken: 02000007
File: D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize: 0xc
ComponentSize: 0x0
DynamicStatics: false
ContainsPointers false
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc TableEntry MethodDe JIT Name
02610028 02605568 NONE System.Object.Finalize()
02610030 02605574 NONE System.Object.ToString()
02610038 02605580 NONE System.Object.Equals(System.Object)
02610050 026055ac NONE System.Object.GetHashCode()
05CF1CE0 05ce5d24 NONE ConsoleApp1.Chinese.SayHello()
05CF1CE8 05ce5d30 JIT ConsoleApp1.Chinese..ctor()
0:000 dp 05ce5d3c L10
05ce5d3c 00000200 0000000c 00074088 00000005
05ce5d4c 05ce5ccc 05addb14 05ce5d7c 05cd3380
05ce5d5c 05cf1ce8 00000000 05ce5d68 02610028
05ce5d6c 02610030 02610038 02610050 05cf1ce0仔细看输出上面的 05ce5d68 后面的 02610028 就是 System.Object.Finalize() 方法02610030 对应着 System.Object.ToString() 方法。call dword ptr [eax10h]有了前面的基础这句话就好理解了它是从 m_pMultipurposeSlot2 结构中找 SayHello 所在的单元指针位置然后做 call 调用。0:000 !U 05cf1ce0
Unmanaged code
05cf1ce0 e88f9dde74 call coreclr!PrecodeFixupThunk (7aadba74)
05cf1ce5 5e pop esi
05cf1ce6 0001 add byte ptr [ecx],al
05cf1ce8 e913050000 jmp 05cf2200
05cf1ced 5f pop edi
05cf1cee 0300 add eax,dword ptr [eax]
05cf1cf0 245d and al,5Dh
05cf1cf2 ce into
05cf1cf3 0500000000 add eax,0
05cf1cf8 0000 add byte ptr [eax],al从汇编看它还是一段 桩代码言外之意就是该方法没有被 JIT 编译如果编译完了这里的 05CF1CE0 05ce5d24 NONE ConsoleApp1.Chinese.SayHello() 的 Entry (05CF1CE0) 也会被同步修改验证一下很简单我们继续 go 代码让其编译完成然后再 dumpmt 。0:008 !dumpmt -md 05ce5d3c
EEClass: 05cd3380
Module: 05addb14
Name: ConsoleApp1.Chinese
mdToken: 02000007
File: D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dll
BaseSize: 0xc
ComponentSize: 0x0
DynamicStatics: false
ContainsPointers false
Slots in VTable: 6
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc TableEntry MethodDe JIT Name
02610028 02605568 NONE System.Object.Finalize()
02610030 02605574 NONE System.Object.ToString()
02610038 02605580 NONE System.Object.Equals(System.Object)
02610050 026055ac NONE System.Object.GetHashCode()
05CF2270 05ce5d24 JIT ConsoleApp1.Chinese.SayHello()
05CF1CE8 05ce5d30 JIT ConsoleApp1.Chinese..ctor()0:008 dp 05ce5d3c L10
05ce5d3c 00000200 0000000c 00074088 00000005
05ce5d4c 05ce5ccc 05addb14 05ce5d7c 05cd3380
05ce5d5c 05cf1ce8 00000000 05ce5d68 02610028
05ce5d6c 02610030 02610038 02610050 05cf2270此时可以看到它由 05cf1ce0 变成了 05cf2270 这个就是 JIT 编译后的方法代码我们用 !U 反编译下。0:008 !U 05cf2270
Normal JIT generated code
ConsoleApp1.Chinese.SayHello()
ilAddr is 05E720D5 pImport is 008F6E88
Begin 05CF2270, size 27D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs 28:05cf2270 55 push ebp
05cf2271 8bec mov ebp,esp
05cf2273 50 push eax
05cf2274 894dfc mov dword ptr [ebp-4],ecx
05cf2277 833d74dcad0500 cmp dword ptr ds:[5ADDC74h],0
05cf227e 7405 je 05cf2285
05cf2280 e8cb2bf174 call coreclr!JIT_DbgIsJustMyCode (7ac04e50)
05cf2285 90 nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs 29:
05cf2286 8b0d74207e04 mov ecx,dword ptr ds:[47E2074h] (chinese)
05cf228c e8dffbffff call 05cf1e70
05cf2291 90 nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs 30:
05cf2292 90 nop
05cf2293 8be5 mov esp,ebp
05cf2295 5d pop ebp
05cf2296 c3 ret终于这就是多态下的 ConsoleApp1.Chinese.SayHello 方法啦。3. 总结本质上来说CoreCLR 也是 C 写的所以也逃不过用 虚表 来实现多态的玩法 不过玩法也稍微复杂了一些希望本篇对大家有帮助。