您的问题似乎不完整,您是想询问关于C语言编程的某个具体问题吗?比如C语言的语法、编程技巧、项目开发等。请提供更具体的信息,这样我才能给出更准确的回答。
- 内容介绍
- 文章标签
- 相关推荐
本文共计1576个文字,预计阅读时间需要7分钟。
目录
一、引言
二、创新概述
三、创新案例分析
四、创新发展趋势
五、结论
目录
- 一、C# 中的多态玩法
- 1. 一个简单的 C# 例子
- 2. 汇编代码分析
- (1)eax,dword ptr [ebp-8]
- (2)eax,dword ptr [eax]
- (3)eax,dword ptr [eax+28h]
- (4)call dword ptr [eax+10h]
- 三、总结
前言:
本质上来说,CoreCLR 也是 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()处下一个断点,观察一下它的反汇编代码:
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"); } } }
从汇编代码看,逻辑非常清晰,大体步骤如下:
(1)eax,dword ptr [ebp-8]
从栈上(ebp-8)处获取 person 在堆上的首地址,如果不相信的话,可以用!do 027ea88c试试看。
0:000> dp ebp-8 L1 0057f300 027ea88c 0: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: None
(2)eax,dword ptr [eax]
如果大家了解实例在堆上的内存布局的话,应该知道,这个首地址存放的就是methodtable指针,我们可以用!dumpmt 05ce5d3c来验证下。
0:000> dp 027ea88c L1 027ea88c 05ce5d3c 0: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: 0
(3)eax,dword ptr [eax+28h]
那这句话是什么意思呢?如果你了解 CoreCLR 的话,你应该知道 methedtable 是由一个class MethodTable类来承载的,所以它取了 methodtable 偏移0x28位置的一个字段,那这个偏移字段是什么呢?我们先用dt把 methodtable 结构给导出来。
0:000> dt 05ce5d3c MethodTable coreclr!MethodTable =7ad96bc8 s_pMethodDataCache : 0x00639ec8 MethodDataCache =7ad96bc4 s_fUseParentMethodData : 0n1 =7ad96bcc s_fUseMethodDataCache : 0n1 +0x000 m_dwFlags : 0xc +0x004 m_BaseSize : 0x74088 +0x008 m_wFlags2 : 5 +0x00a m_wToken : 0 +0x00c m_wNumVirtuals : 0x5ccc +0x00e m_wNumInterfaces : 0x5ce +0x010 m_pParentMethodTable : IndirectPointer<MethodTable *> +0x014 m_pLoaderModule : PlainPointer<Module *> +0x018 m_pWriteableData : PlainPointer<MethodTableWriteableData *> +0x01c m_pEEClass : PlainPointer<EEClass *> +0x01c m_pCanonMT : PlainPointer<unsigned long> +0x020 m_pPerInstInfo : PlainPointer<PlainPointer<Dictionary *> *> +0x020 m_ElementTypeHnd : 0 +0x020 m_pMultipurposeSlot1 : 0 +0x024 m_pInterfaceMap : PlainPointer<InterfaceInfo_t *> +0x024 m_pMultipurposeSlot2 : 0x5ce5d68 =7ad04c78 c_DispatchMapSlotOffsets : [0] " $ (System.Private.CoreLib.dll" =7ad04c70 c_NonVirtualSlotsOffsets : [0] " $ ($((, $ (System.Private.CoreLib.dll" =7ad04c60 c_ModuleOverrideOffsets : [0] " $ ($((,$((,(,,0 $ ($((, $ (System.Private.CoreLib.dll" =7ad12838 c_OptionalMembersStartOffsets : [0] "(((((((,(((,(,,0(((,(,,0(,,0,004"
从 methodtable 的布局图来看,eax+28h是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 Table Entry 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()方法。
(4)call dword ptr [eax+10h]
有了前面的基础,这句话就好理解了,它是从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 Table Entry 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 27 D:\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 nop D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 29: 05cf2286 8b0d74207e04 mov ecx,dword ptr ds:[47E2074h] ("chinese") 05cf228c e8dffbffff call 05cf1e70 05cf2291 90 nop D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 30: 05cf2292 90 nop 05cf2293 8be5 mov esp,ebp 05cf2295 5d pop ebp 05cf2296 c3 ret
终于这就是多态下的ConsoleApp1.Chinese.SayHello方法啦。
三、总结
到此这篇关于C#中的多态底层虚方法调用详情的文章就介绍到这了,更多相关C#多态内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!
本文共计1576个文字,预计阅读时间需要7分钟。
目录
一、引言
二、创新概述
三、创新案例分析
四、创新发展趋势
五、结论
目录
- 一、C# 中的多态玩法
- 1. 一个简单的 C# 例子
- 2. 汇编代码分析
- (1)eax,dword ptr [ebp-8]
- (2)eax,dword ptr [eax]
- (3)eax,dword ptr [eax+28h]
- (4)call dword ptr [eax+10h]
- 三、总结
前言:
本质上来说,CoreCLR 也是 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()处下一个断点,观察一下它的反汇编代码:
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"); } } }
从汇编代码看,逻辑非常清晰,大体步骤如下:
(1)eax,dword ptr [ebp-8]
从栈上(ebp-8)处获取 person 在堆上的首地址,如果不相信的话,可以用!do 027ea88c试试看。
0:000> dp ebp-8 L1 0057f300 027ea88c 0: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: None
(2)eax,dword ptr [eax]
如果大家了解实例在堆上的内存布局的话,应该知道,这个首地址存放的就是methodtable指针,我们可以用!dumpmt 05ce5d3c来验证下。
0:000> dp 027ea88c L1 027ea88c 05ce5d3c 0: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: 0
(3)eax,dword ptr [eax+28h]
那这句话是什么意思呢?如果你了解 CoreCLR 的话,你应该知道 methedtable 是由一个class MethodTable类来承载的,所以它取了 methodtable 偏移0x28位置的一个字段,那这个偏移字段是什么呢?我们先用dt把 methodtable 结构给导出来。
0:000> dt 05ce5d3c MethodTable coreclr!MethodTable =7ad96bc8 s_pMethodDataCache : 0x00639ec8 MethodDataCache =7ad96bc4 s_fUseParentMethodData : 0n1 =7ad96bcc s_fUseMethodDataCache : 0n1 +0x000 m_dwFlags : 0xc +0x004 m_BaseSize : 0x74088 +0x008 m_wFlags2 : 5 +0x00a m_wToken : 0 +0x00c m_wNumVirtuals : 0x5ccc +0x00e m_wNumInterfaces : 0x5ce +0x010 m_pParentMethodTable : IndirectPointer<MethodTable *> +0x014 m_pLoaderModule : PlainPointer<Module *> +0x018 m_pWriteableData : PlainPointer<MethodTableWriteableData *> +0x01c m_pEEClass : PlainPointer<EEClass *> +0x01c m_pCanonMT : PlainPointer<unsigned long> +0x020 m_pPerInstInfo : PlainPointer<PlainPointer<Dictionary *> *> +0x020 m_ElementTypeHnd : 0 +0x020 m_pMultipurposeSlot1 : 0 +0x024 m_pInterfaceMap : PlainPointer<InterfaceInfo_t *> +0x024 m_pMultipurposeSlot2 : 0x5ce5d68 =7ad04c78 c_DispatchMapSlotOffsets : [0] " $ (System.Private.CoreLib.dll" =7ad04c70 c_NonVirtualSlotsOffsets : [0] " $ ($((, $ (System.Private.CoreLib.dll" =7ad04c60 c_ModuleOverrideOffsets : [0] " $ ($((,$((,(,,0 $ ($((, $ (System.Private.CoreLib.dll" =7ad12838 c_OptionalMembersStartOffsets : [0] "(((((((,(((,(,,0(((,(,,0(,,0,004"
从 methodtable 的布局图来看,eax+28h是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 Table Entry 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()方法。
(4)call dword ptr [eax+10h]
有了前面的基础,这句话就好理解了,它是从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 Table Entry 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 27 D:\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 nop D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 29: 05cf2286 8b0d74207e04 mov ecx,dword ptr ds:[47E2074h] ("chinese") 05cf228c e8dffbffff call 05cf1e70 05cf2291 90 nop D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs @ 30: 05cf2292 90 nop 05cf2293 8be5 mov esp,ebp 05cf2295 5d pop ebp 05cf2296 c3 ret
终于这就是多态下的ConsoleApp1.Chinese.SayHello方法啦。
三、总结
到此这篇关于C#中的多态底层虚方法调用详情的文章就介绍到这了,更多相关C#多态内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!

