CLR是如何找到托管代码的入口方法并对其Jit的呢?Jit的发生过程是怎么样的呢?Jit编译器和Metadata表又有什么关系呢?本文试图寻找出答案,在此之前,不妨先了解一下CLR Header的大致结构。
    以如下代码为例:
Example
using System;

namespace CLRTesing
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Console.WriteLine("Hello World!");
            Console.ReadKey();

new P().Display();

}

Program()
        {
            Console.WriteLine("Constructor.");
            Console.ReadKey();
        }

static Program()
        {
            Console.WriteLine("Static constructor.");
            Console.ReadKey();
        }
    }

class P
    {
        public void Display()
        {
            System.Console.WriteLine("P!");
            Console.ReadKey();

new Q().Display();
            Console.ReadKey();
        }
    }

class Q
    {
        public void Display()
        {
            System.Console.WriteLine("Q!");
            Console.ReadKey();
        }
    }
}

编译后通过dumpbin工具的到其CLR Header,如图所示:

从图中可以看到,CLR Header由以下几个部分组成:
      1、 CB:表示CLR Header的大小,单位是byte;
      2、 Run time version:运行时版本,包含两部分MajorRuntimeVersion和MinorRuntimeVersion;
      3、 Metadata Directory:指出Metadata table的RVA和其大小;
      4、 Flag:这个标识主要是供加载器使用,flag值为0x00000001表示当前runtime image仅由IL代码组成并且对CPU没有特殊要求;值为0x00000002表示image只能被加载到32位机中,值为0x00010000表示运行时和jit编译器需要追踪方法的调试信息;
      5、 EntryPointToken:Metadata 表中标记为EntryPoint的方法的MethodDef;
      6、 Resources Directory:CLR的资源,也就是托管资源的RVA和大小,注意与PE文件中存储Win32资源的section不同;
      7、 StrongNameSignature Directory:PE文件中供CLR加载器使用的哈希值所处RVA和大小;
      8、 CodeManagerTable Directory:Code Manager 表的RVA和其大小;
      9、 VTableFixups Directory:由非托管C++类型中虚方法的指针组成的数组;
      10、ExportAddressTableJumps Directory:跳转地址表的RVA和大小;
      11、ManagedNativeHeader Directory:一般情况下为0。
      以上结构可以从CorHdr.h文件中看出,如果装的是vs2005,这个文件在\Microsoft Visual Studio 8\SDK\v2.0\include\。
      查看托管PE文件的工具有很多,不用很复杂的,就园子里的大牛Anders Liu写的CliPeViwer就很好用,用Reflector可以偷窥其代码哦。

那么在上面这个结构中我最关心的是Metadata directory和EntryPointToken,Metadata directory存提供了原数据所在内存地址的范围,EntryPointToken告诉我们在原数据表中哪个token标识的方法是入口方法,这里一定是方法,所以这个token是以6开头的一个数。

回到主题,我们从CLR已经被载入内存、mscorwks.dll中的_CorExeMain2方法接管主线程开始说起:

1、_CorExeMain2方法会调用System Domain中的SystemDomain::ExecuteMainMethod方法,然后由此方法再去调用其它方法(具体什么方法参见深入了解CLR的加载过程一文中的第8步), 通过MetaData提供的接口查找包含.entrypoint的类型,接着返回入口方法(在C#中这个入口方法一定是Main方法)的一个MethodDesc类型的实例;获取MethodDesc类型实例的这个过程我认为是:CLR通过读取MetaData,定位入口方法所属的类型,将包含该类型的Module载入,然后建立这个类型的EECLASS(EECLASS结构中包含重要信息有:指向当前类型父类的指针、指向方法表的指针、实例字段和静态字段等)和这个类型所包含方法的Method Table(方法表由一个个Method Descripter组成,具体到内存中就是指向若干MethodDesc类型实例的地址),通过EEClass::FindMethod方法找到并返回入口方法的MethodDesc类型实例。

MethodDesc这个类型很有意思,它有两个重要的部分,一个部分叫做m_CodeOrIL,用来存储编译好的MSIL在内存中的地址,初值为ffffffffffffffff,另一个部分叫做Stub,如果当前代码没有被编译为本地CPU指令,那么通过这个Stub会触发对Jit编译器的调用。

   执行上述代码,

用Windbg 查看,如下:

Windbg1
0:000> !name2ee *!CLRTesing.Program
Module: 790c2000 (mscorlib.dll)
--------------------------------------
Module: 00a72c3c (Hello.exe)
Token: 0x02000002
MethodTable: 00a73048
EEClass: 00a7129c
Name: CLRTesing.Program

0:000> !name2ee *!CLRTesing.P
Module: 790c2000 (mscorlib.dll)
--------------------------------------
Module: 00a72c3c (Hello.exe)
Token: 0x02000003
MethodTable: <not loaded yet>
EEClass: <not loaded yet>
Name: CLRTesing.P

0:000> !dumpmt -md 00a73048
EEClass: 00a7129c
Module: 00a72c3c
Name: CLRTesing.Program
mdToken: 02000002  (D:\test\Hello\bin\Debug\Hello.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
79371278   7914b928   PreJIT System.Object.ToString()
7936b3b0   7914b930   PreJIT System.Object.Equals(System.Object)
7936b3d0   7914b948   PreJIT System.Object.GetHashCode()
793624d0   7914b950   PreJIT System.Object.Finalize()
00a7c011   00a73030     NONE CLRTesing.Program.Main(System.String[])
00a7c015   00a73038     NONE CLRTesing.Program..ctor()
00da0070   00a73040      JIT CLRTesing.Program..cctor()

CLRTesing.Program类型的静态构造函数执行时,入口方法Main和CLRTesing.Program的实例构造函数还没有被Jit,Main方法中引用到的CLRTesing.P类型也没有被加载,所以它的Method Table和EEClass结构也没有建立起来。

2、在Windbg中detach debuggee,随便敲一个字符让程序继续运行;接着,入口方法Main开始执行,

因为Main方法第一次执行,所以通过Stub,Jit编译器会被唤起,由于Main方法引用了CLRTesing.P类型,那么在执行前会将CLRTesing.P类型载入,并建立Method Table和其EEClass结构,当然这个建立过程也要去查找MetaData表,我认为这个过程是这样的:

Main方法被调用,由于它没有被Jit过,CLR会通过Main方法的MethodDesc结构的Stub对Jit编译器进行调用,CLR通过MetaData表的接口找到Main方法对应的Token,如下:

我们可以看到Main方法的RVA是0x00002050,于是去PE文件的.Text section中的Raw Data中查找image base+RVA这个位置处的IL代码,接着Jit编译器会对这段IL代码进行验证,验证过程通过调用CheckIL方法来实现,这个方法的签名可以是这样的:

CHECK CheckIL(RVA il);
CHECK CheckIL(RVA il, COUNT_T size);

验证结束后把这段IL代码编译成本地CPU指令,将编译后后的CPU指令存到内存并修改Main方法的MethodDesc结构中m_CodeOrIL和Stub的值,让它们指向这个新的内存地址,当这个方法被再次调用的时候就会直接通过这个地址访问到本地CPU指令而不会触发第二次编译。对于这个过程大家的看法呢?用Windbg查看各对象情况:

Windbg2
0:000> !name2ee *!CLRTesing.Program
Module: 790c2000 (mscorlib.dll)
--------------------------------------
Module: 00a72c3c (Hello.exe)
Token: 0x02000002
MethodTable: 00a73048
EEClass: 00a7129c
Name: CLRTesing.Program

0:000> !name2ee *!CLRTesing.P
Module: 790c2000 (mscorlib.dll)
--------------------------------------
Module: 00a72c3c (Hello.exe)
Token: 0x02000003
MethodTable: 00a730b8
EEClass: 00a71730
Name: CLRTesing.P

0:000> !name2ee *!CLRTesing.Q
Module: 790c2000 (mscorlib.dll)
--------------------------------------
Module: 00a72c3c (Hello.exe)
Token: 0x02000004
MethodTable: <not loaded yet>
EEClass: <not loaded yet>
Name: CLRTesing.Q

0:000> !dumpmt -md 00a73048
EEClass: 00a7129c
Module: 00a72c3c
Name: CLRTesing.Program
mdToken: 02000002  (D:\test\Hello\bin\Debug\Hello.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
79371278   7914b928   PreJIT System.Object.ToString()
7936b3b0   7914b930   PreJIT System.Object.Equals(System.Object)
7936b3d0   7914b948   PreJIT System.Object.GetHashCode()
793624d0   7914b950   PreJIT System.Object.Finalize()
00da00b0   00a73030      JIT CLRTesing.Program.Main(System.String[])
00a7c015   00a73038     NONE CLRTesing.Program..ctor()
00da0070   00a73040      JIT CLRTesing.Program..cctor()

0:000> !dumpmt -md 00a730b8
EEClass: 00a71730
Module: 00a72c3c
Name: CLRTesing.P
mdToken: 02000003  (D:\test\Hello\bin\Debug\Hello.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
79371278   7914b928   PreJIT System.Object.ToString()
7936b3b0   7914b930   PreJIT System.Object.Equals(System.Object)
7936b3d0   7914b948   PreJIT System.Object.GetHashCode()
793624d0   7914b950   PreJIT System.Object.Finalize()
00a7c04c   00a730a8     NONE CLRTesing.P.Display()
00a7c058   00a730b0     NONE CLRTesing.P..ctor()

我们可以发现Main方法已经被Jit,且它引用的CLRTesing.P类型的相关结构也已经建立起来了,而CLRTesing.P类型的Display方法所引用的CLRTesing.Q类型没有被载入。

总结一下,Jit编译针对的对象总是方法,不论是入口方法还是其他方法的Jit过程都类似上述过程,Metadata这这里的作用不言而喻,可以说没有Metadata的支持就无法进行Jit,我觉得Meatadata在Jit编译期间的作用至少有三个:

1、Jit编译器通过查找Metadata来找到入口方法;

2、Jit编译器通过查找Metadata来定位待编译方法并利用其RVA找到存储于PE文件中的IL代码在内存中的实际地址;

3、Jit编译器在找到IL代码并准备编译为本地CPU指令前所进行的IL代码验证同样会用到Metadata,例如,验证方法的合法性需要去核实方法参数数量是正确的、传给方法的每个参数是否都有正确的类型、方法返回值是否正确等等。

文中是一些我通过Shared Source Common Language Infrastructure(SSCLI)看到的和感觉到的东西,希望能给大家理解Jit提供一点帮助,如果有错误的地方也请大家指出,大家一起学习。

最后要说明的是,SSCLI里东西仅作为理解CLR使用,与MS真正实现CLR的过程可能不一样。最后,大家在看SSCLI的时候可以使用Source Insight,个人感觉还挺好用。

SSCLI的下载地址是:http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en

转载于:https://www.cnblogs.com/vivounicorn/archive/2009/09/03/1559488.html

深入了解Jit编译发生的过程相关推荐

  1. 你的Java代码对JIT编译友好么?

    JIT编译器是Java虚拟机(以下简称JVM)中效率最高并且最重要的组成部分之一.但是很多的程序并没有充分利用JIT的高性能优化能力,很多开发者甚至也并不清楚他们的程序有效利用JIT的程度. 在本文中 ...

  2. 你的Java代码对JIT编译友好么?(转)

    JIT编译器是Java虚拟机(以下简称JVM)中效率最高并且最重要的组成部分之一.但是很多的程序并没有充分利用JIT的高性能优化能力,很多开发者甚至也并不清楚他们的程序有效利用JIT的程度. 在本文中 ...

  3. Java编译过程、JIT编译详解、类加载过程

    文章目录 Java编译执行过程 类加载过程 即时编译JIT JIT编译优化中的常见技术 方法内联 逃逸分析 栈上分配 锁消除 小总结 Java编译执行过程 提到编译,可能大多数人想到的就是将**.ja ...

  4. 66.javac 编译与 JIT 编译\编译过程\javac 编译\词法、语法分析\填充符号表\语义分析\字节码生成\JIT 编译

    66.javac 编译与 JIT 编译 66.1.编译过程 66.2.javac 编译 66.2.1.词法.语法分析 66.2.2.填充符号表 66.2.3.语义分析 66.2.4.字节码生成 66. ...

  5. java全jit编译_JVM即时编译(JIT)(转载)

    原文出处:https://blog.csdn.net/sunxianghuang/article/details/52094859 什么是JIT 1.动态编译(dynamic compilation) ...

  6. 理解JIT编译与优化

    对Oracle JRockit JVM如何生成.编译.执行代码进行了介绍. 不仅仅是"黑匣子" 从用户的角度来看,JRockit JVM只是一个黑盒子,它将Java代码" ...

  7. JVM-服务器预热-JIT编译

    如下图所示,编译器可以分为:前端编译器.JIT 编译器和AOT编译器.下面我们逐个讲解. 前端编译器:源代码到字节码 之前我们说到:对于 Java 虚拟机来说,其实际输入的是字节码文件,而不是 Jav ...

  8. javac 编译与 JIT 编译

    javac 编译与 JIT 编译 编译过程 不论是物理机还是虚拟机,大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行: 其中绿色的模块 ...

  9. VC如何在编译链接程序过程中在输出窗口看到链接的顺序

    VC如何在编译链接程序过程中在输出窗口看到链接的顺序 具体操作:选择VC菜单Project->Settings->Link页,然后在Project Options的Edit栏中输入/ver ...

最新文章

  1. 从粗放到精细,如何用AI技术实现信息流广告投放的降本增效
  2. AJAX初探,XMLHttpRequest介绍
  3. 采用IpIq控制方法和电流空间和电压空间的PWM方法控制
  4. HashMap源码浅析
  5. ubuntu18.04的ifconfig输出没有ip地址
  6. c++ primer 第14章 习题解答
  7. 5行代码解决——L1-042 日期格式化 (5分)
  8. 图像处理/255.0 和/127.5 -1
  9. Cell Research | 单细胞测序技术揭示派杰氏病的致病机制
  10. html5 页面加载缓慢,html5体验优化页面加载的14条建议
  11. RSYNC及其算法简单介绍
  12. 使用jquery获取父元素或父节点的方法
  13. 系统运维方案_传统运维 VS 互联网运维 框架体系大观
  14. python论文排版格式_Latex论文排版工具使用教程
  15. apk反编译教程(2022win11亲测)
  16. 使用好压(HaoZip)软件打包EverEdit制作安装程序
  17. 年仅 28 岁就宣布从字节跳动退休?
  18. cpu飙高1000,几近崩溃
  19. Python简易图片批量压缩程序
  20. Triplet-Graph Reasoning Network for few-shot Metal Generic Surface Defect Segmentation论文理解

热门文章

  1. 怎么推广引流?利用B站实现精准引流
  2. 邮件服务器过滤,Winmail 邮件服务器软件
  3. 【微课制作软件】Focusky教程 | 设置鼠标单击不进入下一页面
  4. android rom打包失败,安卓编译完成打包时出现栈溢出,大伙帮帮忙
  5. 超2TB缓存 Radware进军中国云安全市场
  6. Scratch(三十四):古诗大作战
  7. (arxiv-2018) 重新审视基于视频的 Person ReID 的时间建模
  8. #用turtle画奸笑脸##Python入门(二)
  9. 1.1 airtest初识
  10. 藏不住了!来 WICC,你会看懂今天的融云