方法描述符: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();}
  • 编译代码:

12
%windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /debug /target:exe /out:e:\temp\MethodDesc_2.0.exe e:\temp\MethodDesc.cspause
  • 运行 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::SetStableEntryPointInterlockedmscorwks!MethodDesc::SetStableEntryPointInterlocked:79ed9a27 55              push    ebp79ed9a28 8bec            mov     ebp,esp79ed9a2a 53              push    ebx79ed9a2b 56              push    esi79ed9a2c 57              push    edi79ed9a2d 8bf9            mov     edi,ecx79ed9a2f e8b96afbff      call    mscorwks!MethodDesc::GetTemporaryEntryPoint (79e904ed)79ed9a34 8bcf            mov     ecx,edi79ed9a36 8bd8            mov     ebx,eax79ed9a38 e8156bfbff      call    mscorwks!MethodDesc::GetAddrOfSlot (79e90552)79ed9a3d 8b5508          mov     edx,dword ptr [ebp+8]79ed9a40 53              push    ebx79ed9a41 8bc8            mov     ecx,eax79ed9a43 ff15d0c33c7a    call    dword ptr [mscorwks!FastInterlockCompareExchange (7a3cc3d0)]79ed9a49 8bf0            mov     esi,eax79ed9a4b 2bf3            sub     esi,ebx79ed9a4d f7de            neg     esi79ed9a4f 1bf6            sbb     esi,esi79ed9a51 46              inc     esi79ed9a52 ba00000001      mov     edx,1000000h79ed9a57 8bcf            mov     ecx,edi79ed9a59 ff1574c23c7a    call    dword ptr [mscorwks!FastInterlockOr (7a3cc274)]79ed9a5f 5f              pop     edi79ed9a60 8bc6            mov     eax,esi79ed9a62 5e              pop     esi79ed9a63 5b              pop     ebx79ed9a64 5d              pop     ebp79ed9a65 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相关推荐

  1. CLR运行时细节 - 继承多态的实现

    关于多态不多解释了,在运行时决定和调用具体的实现,是面向对象的基础 设计模式的基础. 准备把继承多态和接口多态分开,因为从CLR实现的角度继承多态相比于接口多态要简单得多,也更容易理解,本篇只讨论继承 ...

  2. .Net CLR运行时是如何编译函数的

    首先了解几个概念和内存结构 一: 1.函数描述符MethodDesc,包含了函数是否被编译标志,函数当前在函数描述符块的索引,以及函数在函数表的索引,以及Token 2.函数描述符块MethodDes ...

  3. ABAP SOAMANAGER暴露的函数function module,以web service方式执行的运行时细节

    要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  4. 快速搭建本地 .NET Core 运行时调试环境

    需要的软件环境: Oracle VM VirtualBox CentOS 7 llvm lldb 3.6.0 (3.5.0我试过 dumpobj时候一直报无效参数 Invalid parameter ...

  5. CLR基础,CLR运行过程,使用dos命令创建、编译、运行C#文件,查看IL代码

    CLR是Common Language Runtime的缩写,是.NET程序集或可执行程序运行的一个虚拟环境.CLR用于管理托管代码,但是它本身是由非托管代码编写的,并不是一个包含了托管代码的程序集, ...

  6. 深入探索.NET内部了解CLR如何创建运行时对象

    前言 SystemDomain, SharedDomain, and DefaultDomain. 对象布局和内存细节. 方法表布局. 方法分派(Method dispatching). 因为公共语言 ...

  7. [转载]深入探索.NET框架内部了解CLR如何创建运行时对象

    深入探索.NET框架内部了解CLR如何创建运行时对象 发布日期: 9/19/2005 | 更新日期: 9/19/2005 Hanu Kommalapati Tom Christian 本文讨论: • ...

  8. 深入探索.NET框架内部了解CLR如何创建运行时对象

    为什么80%的码农都做不了架构师?>>>    本文讨论: • SystemDomain, SharedDomain, and DefaultDomain • 对象布局和内存细节. ...

  9. 四.运行时数据区-本地方法栈(Native Method Stack)-堆-方法区

    1. 前言:本地方法接口 1.1 本地方法 简单来讲,一个Native Method就是一个java调用非java代码的接口,一个Native Method 是这样一个java方法:该方法的实现由非J ...

最新文章

  1. Android桌面组件App Widget开发三步走
  2. js页面跳转常用的几种方式
  3. python 网络爬虫(一)
  4. 【PAT乙级】1089 狼人杀-简单版 (20 分)
  5. java方法重载_Java方法的重载
  6. RedHat 5.4下构建postfix全功能电子邮(七)-extmail extman平台-(下集)
  7. Git中如何利用生成SSH个人公钥访问git仓库
  8. Java实验8 T5.使用键盘控制界面上图片的移动
  9. 在VB中调用API函数
  10. IDEA打包的两种方式及注意事项
  11. 码力十足学量化|如何获取指数成分股及权重数据
  12. [南阳OJ-No.33]蛇形填数|在n*n方陈里填入1,2,...,n*n,要求填成蛇形。
  13. Python绘制二元函数图像
  14. qt实现扫雷游戏一:算法实现
  15. Docker部署微服务应用笔记(三)
  16. 这100 个网络基础知识,看完成半个网络高手
  17. 《数论概论》读书笔记(第二章)勾股数组
  18. 基于电力大数据的空气污染预测
  19. 天池训练营——基于人脸的常见表情识别(3)——模型搭建、训练与测试
  20. jeecg-boot前后端部署在本地实操

热门文章

  1. uva705--slash maze
  2. 使用JavaScript实现页面选项自动添加行以及删除行 javaweb
  3. PHP访问连接MYSQL数据库
  4. About the windchill Command -
  5. C#对象映射器之Mapster
  6. ASP.NET Core 自动刷新JWT Token
  7. C#的dapper使用
  8. ASP.Net Core Web API 如何返回 File。
  9. 如何为 .NET Core 3.0 中 WPF 配置依赖注入 ?
  10. 如何在 .NET 程序万种死法中有效的生成 Dump (上)