[你必须知道的.NET]第二十六回:认识元数据和IL(下)
[你必须知道的.NET]第二十六回:认识元数据和IL(下)
发布日期:2009.03.04 作者:Anytao
© 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处。
说在,开篇之前 |
书接上回: 第二十四回:认识元数据和IL(上), 第二十五回:认识元数据和IL(中) 我们继续。 终于到了,说说元数据和IL在JIT编译时的角色了,虽然两个回合的铺垫未免铺张,但是却丝毫不为过,因为只有充分的认知才有足够的体会,技术也是如此。那么,我们就开始沿着方法调用的轨迹,追随元数据和IL在那个神秘瞬间所贡献的力量吧。 www.anytao.com |
5 元数据和IL在JIT编译时
CLR最终执行的只有本地机器码,所以JIT编译的作用是在运行时将IL代码解析为机器码执行。对于JIT编译,我们会以专门的篇幅来全面了解,本文只将目光关注于元数据和IL在程序执行时的作用和参与细节。首先,IL是基于栈执行的,执行方法调用时,方法参数、局部变量还有返回值等被分配于栈上,并执行其调用过程,既然是关注JIT编译时,因此我们自然而然将关注方法的执行,因为JIT编译是以执行方法调用而触发的。
首先,对本文开始的代码加点新料:
// Release : code04, 2009/02/24 // Author : Anytao, http://www.anytao.com // List : Base.cs public class Base { public void M() { Console.WriteLine("M in Base"); } public virtual void N() { Console.WriteLine("N in Base"); } }
还有:
// Release : code05, 2009/02/24 // Author : Anytao, http://www.anytao.com // List : Three.cs public class Three : Base { private static int ID { get; set; } public override void N() { //Something new in Three Console.WriteLine("N in Three"); } public void M() { Console.WriteLine("M in Three"); M1(); } public void M1() { Console.WriteLine("M1 in Three"); } }
还有执行代码:
static void Main(string[] args) { Base three = new Three(); three.M(); three.N(); }
小窥方法表
以该例而言,执行Main方法调用时,同时伴随着对于Three实例的创建,和相应类型信息的加载。我们先将类型信息创建的秘密放在以后的内容中,好留点悬念在未来发挥,哈哈。然而,类型加载一定是在实例创建之前完成的,也就是我们常常提起的方法表创建。类型加载是由class loader负责执行的,其过程简言之就是从元数据表中获取相应的类型信息,创建方法表(包含CORINFO_CLASS_STRUCT结构),其结构主要包括非虚方法表和虚方法表,按照继承的虚方法、新引入的虚方法、实例方法和静态方法的顺序排列,以类Three类型为例其CORINFO_CLASS_STRUCT结构可以表示为:
Note: 在本例中Three没有定义任何静态方法,其方法表中父类方法N已有子类覆写,同时因为有静态成员存在的原因,CLR会自动创建类型构造器,详细情况可参考《你必须知道的.NET》1.2节 “什么是继承”。
我们可以同过加载SOS调试来了解相应的方法表信息:
- 在three.N()调用处打好断点,来查看该时刻的dump信息,就像一个内存快照,发现多少东西就看摄影师的水准。
- 然后,通过dumpheap加载类型信息,获取方法表地址(0x002a354c),
!dumpheap -type Three Address MT Size 01d332c4 002a354c 12 total 1 objects Statistics: MT Count TotalSize Class Name 002a354c 1 12 Anytao.Insidenet.MetadataIL.Three
- 并根据MT地址,以dumpmt查看相关的MethodDesc信息,
!dumpmt -md 002a354c EEClass: 002a15b4 Module: 002a2f2c Name: Anytao.Insidenet.MetadataIL.Three mdToken: 02000003 (E:\anytao\Today\OnWriting\MetadataIL\Anytao.Insidenet.MetadataIL\Anytao.Insidenet.MetadataIL\bin\Debug\Anytao.Insidenet.MetadataIL.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 10 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 6f756a70 6f5d1328 PreJIT System.Object.ToString() 6f756a90 6f5d1330 PreJIT System.Object.Equals(System.Object) 6f756b00 6f5d1360 PreJIT System.Object.GetHashCode() 6f7c7460 6f5d1384 PreJIT System.Object.Finalize() 002ac0b8 002a3514 NONE Anytao.Insidenet.MetadataIL.Three.N() 002ac0d0 002a3540 JIT Anytao.Insidenet.MetadataIL.Three..ctor() 002ac0a8 002a34f4 NONE Anytao.Insidenet.MetadataIL.Three.get_ID() 002ac0b0 002a3504 NONE Anytao.Insidenet.MetadataIL.Three.set_ID(Int32) 002ac0c0 002a3520 NONE Anytao.Insidenet.MetadataIL.Three.M() 002ac0c8 002a3530 NONE Anytao.Insidenet.MetadataIL.Three.M1()
经过简单的Dump,方法表的信息和我们图示的信息相差无几,细心的观众可能会发现Dump信息中并不包含Three::cctor(),那么你答对了。图示的cctor是我基于为Three实现了类型构造器(静态构造函数)而特别加入的,而代码中dump的方法表并没有把类型构造器包含在内,这是个小粗心,希望细心的您看得够透。
执行细则
具体的执行过程为为:
- class loader从TypeDef元数据表加载相关元数据信息,包括当前类型,继承层次的所有父类和实现的接口元数据,根据这些信息建立CORINFO_CLASS_STRUCT结构:
当然,对class loader,我们可以进行一点知识救急:
上课啦:class loader |
Classic Loader是CLR提供的基本组件之一,作用正像其名称所宣扬的那样,load一个Class给CLR,class loader将Metadata和IL从PE文件中取出,并加载到运行时内存,简单的说就是我们下面要介绍的全过程缩影。 当然,如果你总是对CLR的Classic Loader耿耿于怀,不能释然。那么,我们也可以参考MSDN的资料来实现自定义的Classic Loader[How to: Write a Class Loader],希望其中能提供灵光一现的思考。 |
- 加载之后,方法执行之前的CORINFO_CLASS_STRUCT中所有的方法表槽都保存了方法应该执行的行为逻辑,这些信息保存在被称为方法描述(MethodsDesc)的结构中,而MethodDesc则被初始化为指向IL代码,同时还包含一个指向触发JIT编译的PreJitStub地址,如下:
上述所有方法描述都指向各自的IL代码地址和JIT编译器,在此我们仅仅以N()方法为例来进行说明,详细的情况可以参考MSDN相关内容。
- 简单的说,任何方法第一次执行时都会首先触发执行JIT编译,JIT的主要工作就是将IL代码翻译为Native Code,并插入指向Native Code的jmp指令地址覆盖原来的Call JIT Compiler指令:
- 当该方法再次被执行时,因为MethodDesc中保存了机器码地址,以后的执行将不会执行JIT编译过程而直接执行x86(X64)机器码,实现整个执行过程。
纵观整个JIT编译的全过程,其细节的实现远比我们这里呈现的复杂,在粗略的步骤中我们大致了解了元数据和IL在整个过程中的作用、角色和关系,对了解CLR运行机制而言,适当的选择是明智的,如果有更多的心思探索,那么就在以后的岁月中由简及繁吧,但是相信这一定是一次美妙的旅程。
6 结论
Metadata描述了静态的结构,而IL阐释了动态的执行,这一静一动承载了太多的技术奥秘。
当这篇文章行将结束的时候,我发现牵一发而动全身,由此引入的新问题接踵而至,方法调用、程序集、程序域、CLR加载过程在元数据和IL的分析中若隐若现,也驱使我投入注意在后面的《你必须知道的.NET》中,将这些内容一一过招。由此才能在复杂的概念和本质之余,由点及面的对所有内容综合把握,形成全面的了解和一条线贯穿的认识,那么未来Anytao将要继续分享还有:
系列预告 |
|
2009/03/04 | http://anytao.cnblogs.com/ | http://www.cnblogs.com/anytao/archive/2009/03/04/must_net_26.html
本文以“现状”提供且没有任何担保,同时也没有授予任何权利。 | This posting is provided "AS IS" with no warranties, and confers no rights.
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
参考文献
- 《你必须知道的.NET》第3章 “一切从IL开始”
- DonBox,《.NET本质论》
- http://www.sloppycode.net/articles/inside-net-assemblies-and-metadata.aspx
- http://www.codeproject.com/KB/dotnet/dotnetformat.aspx
温故知新
[开篇有益]
[第一回:恩怨情仇:is和as]
[第二回:对抽象编程:接口和抽象类]
[第三回:历史纠葛:特性和属性]
[第四回:后来居上:class和struct]
[第五回:深入浅出关键字---把new说透]
[第六回:深入浅出关键字---base和this]
[第七回:品味类型---从通用类型系统开始]
[第八回:品味类型---值类型与引用类型(上)-内存有理]
[第九回:品味类型---值类型与引用类型(中)-规则无边]
[第十回:品味类型---值类型与引用类型(下)-应用征途]
[第十一回:参数之惑---传递的艺术(上)]
[第十二回:参数之惑---传递的艺术(下)]
[第十三回:从Hello, world开始认识IL]
[第十四回:认识IL代码---从开始到现在]
[第十五回:继承本质论]
[第十六回:深入浅出关键字---using全接触]
[第十七回:貌合神离:覆写和重载]
[第十八回:对象创建始末(上)]
[第十九回:对象创建始末(下)]
[第二十回:学习方法论]
[第二十一回:认识全面的null]
[第二十二回:字符串驻留(上)---带着问题思考]
[第二十三回:品味细节,深入.NET的类型构造器]
[第二十四回:认识元数据和IL(上)]
[第二十五回:认识元数据和IL(中)]
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com