1.C#中的多态1.一个简单的C#例子。详细代码如下:internalclassProgram{staticvoidMain(string[]args){Personperson=newChinese();person.SayHello();控制台.ReadLine();}}publicclassPerson{publicvirtualvoidSayHello(){Console.WriteLine("chinese");}}publicclassChinese:Person{publicoverridevoidSayHello(){Console.WriteLine("chinese");}}}2.汇编代码分析接下来使用windbg在person.SayHello()下一个断点,观察其反汇编代码:internalclassProgram{staticvoidMain(string[]args){Personperson=newChinese();person.SayHello();控制台.ReadLine();}}publicclassPerson{publicvirtualvoidSayHello(){控制台。WriteLine("打个招呼");}}公共类Cchinese:Person{publicoverridevoidSayHello(){Console.WriteLine("chinese");}}}从汇编代码看,逻辑很清晰,大致步骤如下:(1)eax,dwordptr[ebp-8]从栈中获取人在堆上的首地址at(ebp-8)、不信可以用!do027ea88ctry0:000>dpebp-8L10057f300027ea88c0:000>!do027ea88cName:ConsoleApp1.ChineseMethodTable:05ce5d3cEEClass:05cd3380Size:12(0xc)bytesFile:D:\net6\ConsoleApplication2\ConsoleApp1\Debin\x86.0\ConsoleApp1.dllFields:None(2)eax,dwordptr[eax]如果知道实例在堆上如果想知道内存布局,应该知道首地址存放的是methodtable指针,我们可以使用!dumpmt05ce5d3c来验证。0:000>dp027ea88cL1027ea88c05ce5d3c0:000>!dumpmt05ce5d3cEEClass:05cd3380Module:05addb14Name:ConsoleApp1.ChinesemdToken:02000007File:D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dllBaseSize:0xcComponentSize:0x0DynamicStatics:falseContainsPointersfalseSlotsinVTable:6NumberofIFacesinIFaceMap:0(3)eax,dwordptr[eax+28h]这句话是什么意思?如果你了解CoreCLR,你应该知道methedtable是由一个类MethodTable承载的,所以它会在methodtable的偏移量0x28处取一个字段,那么这个偏移量字段是什么?我们首先使用dt导出methodtable结构。0:000>dt05ce5d3cMethodTablecoreclr!MethodTable=7ad96bc8s_pMethodDataCache:0x00639ec8MethodDataCache=7ad96bc4s_fUseParentMethodData:0n1=7ad96bccs_fUseMethodDataCache:0n1+0x000m_dwFlags:0xc+0x004m_BaseSize:0x74088+0x008m_wFlags2:5+0x00am_wToken:0+0x00cm_wNumVirtuals:0x5ccc+0x00em_wNumInterfaces:0x5ce+0x010m_pParentMethodTable:IndirectPointer+0x014m_pLoaderModule:PlainPointer+0x018m_pWriteableData:PlainPointer+0x01cm_pEEClass:PlainPointer+0x01cm_pCanonMT:PlainPointer+0x020m_pPerInstInfo:PlainPointer*>+0x020m_ElementTypeHnd:0+0x020m_pMultipurposeSlot1:0+0x024m_pInterfaceMap:PlainPointer+0x024m_pMultipurposeSlot2:0x5ce5d68=7ad04c78c_DispatchMapSlotOffsets:[0]“$(System.Private.CoreLib.dll”=7ad04c70c_NonVirtualSlotsOffsets:[0]“$((,$(System.Private.CoreLib.dll”=7ad04c60c_ModuleOverrideOffsets:[0]"$(((,$((,(,,0$((((,$(System.Private.CoreLib.dll"=7ad12838c_OptionalMembersStartOffsets:[0]]"((((((((((((,(,,0(((,(,,0(,,0,004"从methodtable布局图看,eax+28h是m_pMultipurposeSlot2结构体的第二个域,因为第一个域是虚方法表指针,如果要验证,也很简单,使用!dumpmt-md05ce5d3c导出所有方法,然后结合dp05ce5d3c看0x5ce5d68之后的方法是不是很多0:000>!dumpmt-md05ce5d3cEEClass:05cd3380Module:05addb14Name:ConsoleApp1.ChinesemdToken:02000007File:D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dllBaseSize:0xcComponent:falseContainstatics0xcComponent:falseContainstatics0中的文件:D:\net6\ConsoleApplication2\ConsoleApp1\bin\VTable:6NumberofIFacesinIFaceMap:0------------------------------------MethodDesc表项MethodDeJITName0261002802605568NONESystem.Object.Finalize()0261003002605574NONESystem.Object.ToString()0261003802605580NONESystem.Object.Equals(System.Object)02610050026055acNONESystem.Object.GetHashCode()05CEAppd12Console.05NONECF1CE14Chinese.SayHello()05CF1CE805ce5d30JITConsoleApp1.Chinese..ctor()0:000>dp05ce5d3cL1005ce5d3c000002000000000c000740880000000505ce5d4c05ce5ccc05addb1405ce5d7c05cd338005ce5d5c05cf1ce80000000005ce5d680261002805ce5d6c02610030026100380261005005cf1ce0仔细看输出,上面05ce5d68后面的02610028是System.Object.Finalize()方法,02610030对应System.Object.ToString()方法(4)calldwordptr[eax+10h]和前面的基础,这句话很容易理解,就是从m_pMultipurposeSlot2结构体中找到SayHello所在的单元指针位置,然后进行call调用。0:000>!U05cf1ce0Unmanagedcode05cf1ce0e88f9dde74callcoreclr!PrecodeFixupThunk(7aadba74)05cf1ce55epopesi05cf1ce60001addbyteptr[ecx],al05cf1ce8e913050000jmp05cf220005cf1ced5fpopedi05cf1cee0300addeax,dwordptr[eax]05cf1cf0245dandal,5Dh05cf1cf2ceinto05cf1cf30500000000addeax,005cf1cf80000addbyteptr[eax],al从汇编来看还是stub代码,言外之意是这个方法没有经过JIT编译,如果编译完成,这里05CF1CE005ce5d24NONECon??soleApp1.Chinese.SayHello()的Entry(05CF1CE0)也会同步修改。验证起来非常简单。我们继续走代码让它编译,然后dumpmt。0:008>!dumpmt-md05ce5d3cEEClass:05cd3380Module:05addb14Name:ConsoleApp1.ChinesemdToken:02000007File:D:\net6\ConsoleApplication2\ConsoleApp1\bin\x86\Debug\net6.0\ConsoleApp1.dllBaseSize:0xcComponent:falseContainstatics0xcComponentDllBaseSize:0xcComponent:falseContainstatics0中的VTable:6NumberofIFacesinIFaceMap:0------------------------------------MethodDesc表项MethodDeJITName0261002802605568NONESystem.Object.Finalize()0261003002605574NONESystem.Object.ToString()0261003802605580NONESystem.Object.Equals(System.Object)02610050026055acNONESystem.Object.GetHashCode()05CF222257005JITConsoled5140Chinese.SayHello()05CF1CE805ce5d30JITConsoleApp1.Chinese..ctor()0:008>dp05ce5d3cL1005ce5d3c000002000000000c000740880000000505ce5d4c05ce5ccc05addb1405ce5d7c05cd338005ce5d5c05cf1ce80000000005ce5d680261002805ce5d6c02610030026100380261005005cf2270这时候可以看到从05cf1ce0变成了05cf2270。这是JIT编译出来的方法代码。我们使用!U来反编译它0:008>!U05cf2270NormalJITgeneratedcodeConsoleApp1.Chinese.SayHello()ilAddris05E720D5pImportis008F6E88Begin05CF2270,size27D:\net6\ConsoleApplication2\ConsoleApp1\Program.cs@28:>>>05cf2270ebpc7552pushebp755推送e2270,esp05cf227350pusheax05cf2274894dfcmovdwordptr[ebp-4],ecx05cf2277833d74dcad0500cmpdwordptrds:[5ADDC74h],005cf227e7405je05cf228505cf2280e8cb2bf174callcoreclr!JIT_DbgIsJustMyCode(7ac04e50)05cf228590nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs@29:05cf22868b0d74207e04movecx,dwordptrds:[47E2074h]("chinese")05cf228ce8dffbffffcall05cf1e7005cf229190nopD:\net6\ConsoleApplication2\ConsoleApp1\Program.cs@30:05cf229290nop05cf22938be5movesp,ebp05cf22955dpopebp05cf2296c3ret终于这就是多态下的ConsoleApp1.Chinese.SayHello方法3.总结CoreCLR本质上也是用C++写的,所以逃不过使用虚表的多态玩法,只是玩法稍微复杂一些。希望这篇文章对大家有所帮助。