方法描述符: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 代码:
|
编译代码:
|
|
运行 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,方法入口:00afc079IFun1 方法描述符地址:
00af31a0
其内容:0004000a 20000006
依旧是没jit编译,方法入口在方法表后,token:000a,非内联,slot number:0006,方法入口:00afc07dIFun2 方法描述符地址:
00af31a8
其内容:0006000b 20000007
依旧是没jit编译,方法入口在方法表后,token:000b,非内联,slot number:0007,方法入口:00afc081InstanceFun1 方法描述符地址:
00af31b0
其内容:4008000c 2000000a 00afc085
‘40’这位(bit)代表方法入口(slot)是跟在方法描述符(MD)后面的并非在方法表(MT)后面,依旧是没jit编译,方法入口在方法表后,token:000c,非内联,slot number:000a(这里的slot number依然有值,但值是大于等方法表的slot长度的),方法入口:00afc085InstanceFun2 方法描述符地址:
00af31bc
其内容:400b000d 2000000b 00afc089
依旧是没jit编译,方法入口在方法描述符(MD)后,token:000d,非内联,slot number:000b,方法入口:00afc089StaticFun1 方法描述符地址:
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
|
可以通过
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跨平台或扫描二维码关注