CLR运行时细节 - Method Descriptor
方法描述符:MethodDesc
運行時用來描述類型的托管方法,它保存在方法描述桶(MethodDescChunk)內;
方法描述符保存了方法在運行時的一些重要信息:
是否JIT編譯;
是否有方法表槽(決定了方法入口是跟在方法描述符(MethodDesc)后還是在方法表(MethodTable)后面);
距離MethodDescChunk的索引(chunkIndex);
Token的末位(這個在編譯期確定了);
方法的一些標識比如是否靜態 非內聯等;
方法表槽(slot number);
以及最重要的方法入口(entrypoint);
官方的描述:
MethodDesc (method descriptor) is the internal representation of a managed method. It serves several purposes:
Provides a unique method handle, usable throughout the runtime. For normal methods, the MethodDesc is a unique handle for a triplet.
Caches frequently used information that is expensive to compute from metadata (e.g. whether the method is static).
Captures the runtime state of the method (e.g. whether the code has been generated for the method already).
Owns the entry point of the method.
先看下Demo C# MethodDesc.cs 代碼:
using System; using System.Runtime.CompilerServices; public class Program { public static void Main(string[] args) { Console.WriteLine("MethodDesc demo"); Console.ReadLine(); MDChlidClass cc = new MDChlidClass(); cc.VirtualFun1(); cc.VirtualFun2(); cc.IFun1(); cc.IFun2(); cc.InstanceFun1(); cc.InstanceFun2(); MDChlidClass.StaticFun1(); Console.ReadLine(); } } public class MDBaseClass { [MethodImpl(MethodImplOptions.NoInlining)] public virtual void VirtualFun1() { Console.WriteLine("MDBaseClass VirtualFun1"); } [MethodImpl(MethodImplOptions.NoInlining)] public virtual void VirtualFun2() { Console.WriteLine("MDBaseClass VirtualFun2"); } } public class MDChlidClass : MDBaseClass, IFoo { public static int TempInt = 0; [MethodImpl(MethodImplOptions.NoInlining)] public override void VirtualFun1() { Console.WriteLine("MDChlidClass VirtualFun1"); } [MethodImpl(MethodImplOptions.NoInlining)] public override void VirtualFun2() { Console.WriteLine("MDChlidClass VirtualFun2"); } [MethodImpl(MethodImplOptions.NoInlining)] public void IFun1() { Console.WriteLine("MDChlidClass IFun1"); } [MethodImpl(MethodImplOptions.NoInlining)] public void IFun2() { Console.WriteLine("MDChlidClass IFun2"); } [MethodImpl(MethodImplOptions.NoInlining)] public void InstanceFun1() { Console.WriteLine("MDChlidClass InstanceFun1"); } [MethodImpl(MethodImplOptions.NoInlining)] public void InstanceFun2() { Console.WriteLine("MDChlidClass InstanceFun2"); } [MethodImpl(MethodImplOptions.NoInlining)] public static void StaticFun1() { Console.WriteLine("MDChlidClass StaticFun1"); } } public interface IFoo { void IFun1(); void IFun2(); } |
編譯代碼:
1 2 | %windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /debug /target:exe /out:e:\temp\MethodDesc_2.0.exe e:\temp\MethodDesc.cs pause |
運行 MethodDesc_2.0.exe
啟動windbg 加載SOS
查找對應的模塊:
!Name2EE *!MethodDesc_2.0.exe
根據模塊查找方法表:
!DumpModule -mt 00af2c5c
通過MDChlidClass方法表地址查看其EEClass 查找方法描述桶在EEClass偏移40h的位置(64位的話偏移60h 因為標記位使用不變 所有地址類型由4字節變成8字節):
!DumpMT -md 00af31e4
通過方法桶的地址00af3180觀察其下方法描述符:
可以看到方法描述桶的第一個4字節(64位8字節)是方法表(MethodTable)的地址
可以看到MDChlidClass的方法描述符(MD)VirtualFun1?方法描述符地址:00af3190?其內容:00000008 20000004?第一個00代表方法入口在方法表(MT)后面以及還沒jit編譯,第二個00代表距方法描述桶(MethodDescChunk)的索引(便于找到桶的起始位置),后面的0008是方法的token末位 在編譯成IL時確定,可以通過ildasm查看 MethodDesc_2.0.exe 文件,這個token是在編譯期程序集內自增的,也就是在運行時并不是唯一的接下來的2000代表方法非內聯,0004代表方法表槽slot number 也就是方法入口(entrypoint)在方法表(MT)后的索引(索引從0開始 一般來說前4個方法都是從Object繼承下來的4個虛方法 除了接口類型),方法入口:00afc075
VirtualFun2?方法描述符地址:00af3198?其內容:00020009 20000005?依舊是沒jit編譯,方法入口在方法表后,token:0009,非內聯,slot number:0005,方法入口:00afc079
IFun1?方法描述符地址:00af31a0?其內容:0004000a 20000006?依舊是沒jit編譯,方法入口在方法表后,token:000a,非內聯,slot number:0006,方法入口:00afc07d
IFun2?方法描述符地址:00af31a8?其內容:0006000b 20000007?依舊是沒jit編譯,方法入口在方法表后,token:000b,非內聯,slot number:0007,方法入口:00afc081
InstanceFun1?方法描述符地址:00af31b0?其內容:4008000c 2000000a 00afc085?‘40’這位(bit)代表方法入口(slot)是跟在方法描述符(MD)后面的并非在方法表(MT)后面,依舊是沒jit編譯,方法入口在方法表后,token:000c,非內聯,slot number:000a(這里的slot number依然有值,但值是大于等方法表的slot長度的),方法入口:00afc085
InstanceFun2?方法描述符地址:00af31bc?其內容:400b000d 2000000b 00afc089?依舊是沒jit編譯,方法入口在方法描述符(MD)后,token:000d,非內聯,slot number:000b,方法入口:00afc089
StaticFun1?方法描述符地址:00af31c8?其內容:400b000e 2020000c 00afc08d?依舊是沒jit編譯,方法入口在方法描述符(MD)后,token:000e,2020非內聯 并且靜態,slot number:000c,方法入口:00afc08d
.ctor?實例構造方法 方法描述符地址:00af31d4?其內容:0011000f 00000008?依舊是沒jit編譯,方法入口在方法表后,token:000f,slot number:0008,方法入口:00afc091
.cctor?靜態構造方法 方法描述符地址:00af31dc?其內容:00130010 00200009?依舊是沒jit編譯,方法入口在方法表后,token:0010,靜態的:0020,slot number:0009,方法入口:00afc095
可以看到所有的虛方法(繼承或者實現接口)以及構造器方法(實例或者靜態)的方法入口(slot)都是在方法表后面的,而其他實例方法和靜態方法的方法入口(slot)是跟在方法描述符(MD)后面的
這里引用下CLR文檔的一段:
Each MethodDesc has a slot, which contains the entry point of the method. The slot and entry point must exist for all methods, even the ones that never run like abstract methods. There are multiple places in the runtime that depend on the 1:1 mapping between entry points and MethodDescs, making this relationship an invariant.
The slot is either in MethodTable or in MethodDesc itself. The location of the slot is determined by mdcHasNonVtableSlot bit on MethodDesc.
The slot is stored in MethodTable for methods that require efficient lookup via slot index, e.g. virtual methods or methods on generic types. The MethodDesc contains the slot index to allow fast lookup of the entry point in this case.
接下來讓 MethodDesc_2.0.exe 繼續執行,并回車跳過第一個ReadLine(),再中斷到調試器,觀察MDChlidClass的方法表00af31e4(MT)和其方法描述桶00af3180(MDC)
可以看到所有Jit編譯過的方法,其方法描述符的 00h或者40h 會 邏輯?或?31h,都是按位的,其中30h是安全描述符先忽略,01h代表是否Jit編譯過,同時所有Jit編譯過的方法其方法入口(entrypoint)會更新
更新安全描述符:
先更新方法入口,再更新是否Jit編譯標記位:
https://github.com/dotnet/coreclr/blob/master/src/vm/method.cpp#L5099
0:000> uf mscorwks!MethodDesc::SetStableEntryPointInterlocked mscorwks!MethodDesc::SetStableEntryPointInterlocked: 79ed9a27 55 ? ? ? ? ? ? ?push ? ?ebp 79ed9a28 8bec ? ? ? ? ? ?mov ? ? ebp,esp 79ed9a2a 53 ? ? ? ? ? ? ?push ? ?ebx 79ed9a2b 56 ? ? ? ? ? ? ?push ? ?esi 79ed9a2c 57 ? ? ? ? ? ? ?push ? ?edi 79ed9a2d 8bf9 ? ? ? ? ? ?mov ? ? edi,ecx 79ed9a2f e8b96afbff ? ? ?call ? ?mscorwks!MethodDesc::GetTemporaryEntryPoint (79e904ed) 79ed9a34 8bcf ? ? ? ? ? ?mov ? ? ecx,edi 79ed9a36 8bd8 ? ? ? ? ? ?mov ? ? ebx,eax 79ed9a38 e8156bfbff ? ? ?call ? ?mscorwks!MethodDesc::GetAddrOfSlot (79e90552) 79ed9a3d 8b5508 ? ? ? ? ?mov ? ? edx,dword ptr [ebp+8] 79ed9a40 53 ? ? ? ? ? ? ?push ? ?ebx 79ed9a41 8bc8 ? ? ? ? ? ?mov ? ? ecx,eax 79ed9a43 ff15d0c33c7a ? ?call ? ?dword ptr [mscorwks!FastInterlockCompareExchange (7a3cc3d0)] 79ed9a49 8bf0 ? ? ? ? ? ?mov ? ? esi,eax 79ed9a4b 2bf3 ? ? ? ? ? ?sub ? ? esi,ebx 79ed9a4d f7de ? ? ? ? ? ?neg ? ? esi 79ed9a4f 1bf6 ? ? ? ? ? ?sbb ? ? esi,esi 79ed9a51 46 ? ? ? ? ? ? ?inc ? ? esi 79ed9a52 ba00000001 ? ? ?mov ? ? edx,1000000h 79ed9a57 8bcf ? ? ? ? ? ?mov ? ? ecx,edi 79ed9a59 ff1574c23c7a ? ?call ? ?dword ptr [mscorwks!FastInterlockOr (7a3cc274)] 79ed9a5f 5f ? ? ? ? ? ? ?pop ? ? edi 79ed9a60 8bc6 ? ? ? ? ? ?mov ? ? eax,esi 79ed9a62 5e ? ? ? ? ? ? ?pop ? ? esi 79ed9a63 5b ? ? ? ? ? ? ?pop ? ? ebx 79ed9a64 5d ? ? ? ? ? ? ?pop ? ? ebp 79ed9a65 c20400 ? ? ? ? ?ret ? ? 4 |
可以通過?DumpMD?SOS擴展命令觀察方法描述符:
為毛要研究方法描述符這個東西?
方法描述符在CLR運行時作為方法的最基礎服務,繼承多態在運行時的實現依賴方法描述符,接口多態的運行時DispatchToken以及實現也依賴.
參考文檔:
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/method-descriptor.md
https://github.com/dotnet/coreclr/blob/master/src/vm/methodtablebuilder.cpp
https://github.com/dotnet/coreclr/blob/master/src/vm/method.hpp
http://blogs.microsoft.co.il/sasha/2009/09/27/how-are-methods-compiled-just-in-time-and-only-then/
原文地址:https://espider.github.io/CLR/method-descriptor/
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
贊賞
人贊賞
總結
以上是生活随笔為你收集整理的CLR运行时细节 - Method Descriptor的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【南京】.Net 开源基础服务线下技术交
- 下一篇: 数据库权限分配探讨