书接上回[第二十四回:认识元数据和IL(上)],我们对PE文件、程序集、托管模块,这些概念与元数据、IL的关系进行了必要的铺垫,同时顺便熟悉了以ILDASM工具进行反编译的基本方法认知,下面是时候来了解什么是元数据,什么是IL这个话题了,我们继续。

很早就有说说Metadata(元数据)和IL(中间语言)的想法了,一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀,虽然来得没有《第一回:恩怨情仇:is和as》那么迅速,但是Metadata和IL却是绝对重量级的内容,值得我们在任何时间关注,本文就是开始。

3 元数据是什么?

元数据,就是描述数据的数据。这一概念并非CLR之独创,Metadata存在于任何对数据和数据关系中,例如程序集清单信息也被称为程序集元数据。而不同系统的元数据也相应具有本身的特点,.NET元数据也是如此。那么,CLR元数据描述的是哪些内容呢?正如前文的描述一样,编译之后,类型信息将以元数据的形式保存在PE格式文件中。.NET是基于面向对象的,所以元数据描述的主要目标就是面向对象的基本元素:类、类型、属性、方法、字段、参数、特性等,主要包括:

定义表,描述了源代码中定义的类型和成员信息,主要包括:TypeDef、MehodDef、FieldDef、ModuleDef、PropertyDef等。

引用表,描述了源代码中引用的类型和成员信息,引用元素可以是同一程序集的其他模块,也可以是不同程序集的模块,主要包括:AssemblyRef、TypeRef、ModuleRef、MethodsRef等。

指针表,使用指针表引用未知代码,主要包括:MethodPtr、FieldPtr、ParamPtr等。

堆,以stream的形式保存的信息堆,主要包括:#String、#Blob、#US、#GUIDe等。

如前文所述,我们以ILDasm.exe可以通过反编译的方式,通过执行Ctrl+M快捷键来获取该程序集所使用的MetaData信息列表,在.NET中每个模块包含了44个CLR元数据表,如下:

表记录 元数据表 说明
0(0) ModuleDef 描述当前模块
1(0x1) TypeRef 描述引用Type,为每个引用到类型保存一条记录
2(0x2) TypeDef 描述Type定义,每个Type将在TypeDef表中保存一条记录
3(0x3) FieldPtr 描述字段指针,定义类的字段时的中间查找表
4(0x4) FieldDef 描述字段定义
5(0x5) MethodPtr 描述方法指针,定义类的方法时的中间查找表
6(0x6) MethodDef 描述方法定义
7(0x7) ParamPtr 描述参数指针,定义类的参数时的中间查找表
8(0x8) ParamDef 描述方法的参数定义
9(0x9) InterfaceImpl 描述有哪些类型实现了哪些接口
10(0xa) MemberRef 描述引用成员的情况,引用成员可以是方法、字段还有属性。
11(0xb) Constant 描述了参数、字段和属性的常数值
12(0xc) CustomAttribute 描述了特性的定义
13(0xd) FieldMarshal 描述了与非托管代码交互时,参数和字段的传递方式。
14(0xe) DeclSecurity 描述了对于类、方法和程序集的安全性
15(0xf) ClassLayout 描述类加载时的布局信息
16(0x10) FieldLayout 描述单个字段的偏移或序号
17(0x11) StandAloneSig 描述未被任何其他表引用的签名
18(0x12) EventMap 描述类的事件列表
19(0x13) EventPtr 描述了事件指针,定义事件时的中间查找表
20(0x14) Event 描述事件
21(0x15) PropertyMap 描述类的属性列表
22(0x16) PropertyPtr 描述了属性指针,定义类的属性时的中间查找表
23(0x17) Property 描述属性
24(0x18) MethodSemantics 描述事件、属性与方法的关联
25(0x19) MethodImpl 描述方法的实现
26(0x1a) ModuleRef 描述外部模块的引用
27(0x1b) TypeSpec 描述了对TypeDef或者TypeRef的说明
28(0x1c) ImplMap 描述了程序集使用的所有非托管代码的方法
29(0x1d) FieldRVA 字段表的扩展,RVA给出了一个字段的原始值位置
30(0x1e) ENCLog 描述在Edit-And-Continue模式中哪些元数据被修改过
31(0x1f) ENCMap 描述在Edit-And-Continue模式中的映射
32(0x20) Assembly 描述程序集定义
33(0x21) AssemblyProcessor 未使用
34(0x22) AssemblyOS 未使用
35(0x23) AssemblyRef 描述引用的程序集
36(0x24) AssemblyRefProcessor 未使用
37(0x25) AssemblyRefOS 未使用
38(0x26) File 描述外部文件
39(0x27) ExportedType 描述在同一程序集但不同模块,有哪些类型
40(0x28) ManifestResource 描述资源信息
41(0x29) NestedClass 描述嵌套类型定义
42(0x2a) GenericParam 描述了泛型类型定义或者泛型方法定义所使用的泛型参数
43(0x2b) MethodSpec 描述泛型方法的实例化
44(0x2c) GenericParamConstraint 描述了每个泛型参数的约束

然后是6个命名堆:

说明
#String 一个AscII string数组,被元数据表所引用,来表示方法名、字段名、类名、变量名以及资源相关字符串,但不包含string literals。
#Blob 包含元数据引用的二进制对象,但不包含用户定义对象
#US 一个unicode string数组,包含了定义在代码中的字符串(string literals),这些字符串可以直接由ldstr指令加载获取,还记得吗?我们在《第二十二回:字符串驻留(上)---带着问题思考》中对字符串创建过程的论述吗?
#GUID 保存了128byte的GUID值,由元数据表引用
#~ 一个特殊堆,包含了所有的元数据表,会引用其他的堆。
#- 一个未压缩的#~堆。除了#-堆,其他堆都是压缩的。

Note:对于#String和#US,一个简单的区别就是:

string hello = "Hello, World";

变量hello名,将保存在#String,而代码中字符串信息“Hello, World”则被保存在#US中。

关于元数据信息的详细描述,例如每个表包含哪些列,不同表间的关系,请参考[Standard ECMA-335]和[The .NET File Format]。

在PE文件格式中,Metadata有着复杂的结构,我试图以数据库管理数据的角度出发来理解元数据的结构和关系,所以表示元数据的逻辑结构被成为元数据表,类似于数据库表有主键和Sechema,元数据表以RID(表索引)和元-元数据表示类同的概念,以TypeDef表为例,通过数据引用关系同时与Field、Method、TypeRef等表发生关联,其他表间又有类似的关系,从而形成一个复杂的类数据库结构:

因此,元数据是保存了类型的编译后数据,是.NET程序运行的基础,我们可以在运行时动态的以反射的方式获取元数据信息,而这些信息在.NET Framework中以System.Type、MethodInfo等封装,例如截取MSDN中一个类间关系的简单示例:

对于每个CLR类型而言都可以通过Object.GetType方法返回其Type,从而任意的取到所有的运行时元数据信息:

// Release : code04, 2009/02/21
// Author : Anytao, http://www.anytao.com
// List  : Program.cs
private static void ShowMemberInfo()
{
   var assems = AppDomain.CurrentDomain.GetAssemblies();

   foreach (Assembly ass in assems)
   {
     foreach (Type t in ass.GetTypes())
     {
       foreach (MemberInfo mi in t.GetMembers())
       {
         Console.WriteLine("Name:{0}, Type:{1}", mi.Name, mi.MemberType.ToString());
       }
     }
   }
}

执行上述方法,将获取一个长长的列表,看到很多熟悉的符号:-)

4 IL是什么?

IL,又称为CIL或者MSIL,翻译为中文就是中间语言,由ECMA组织(Standard ECMA-335)提供完整的定义和规范。顾名思义,中间语言正如它的名称所言,任何与CLR兼容的编译器所生成的都是中间语言代码,这是实现CLR跨语言的基础结构之一。IL就像一座桥梁,其指令集独立于CPU指令而存在,可以由JIT编译器在运行时翻译为本地代码执行,连接了任何遵守CLS规范的高级语言,为.NET平台提供了最基本的支持。在[你必须知道的.NET]一书中,我用一整章(第3章 “一切从IL开始”)的篇幅对IL的基本内容进行了相应的介绍,所以关于IL的基础内容例如基本类型、IL分析方法、常见指令、基本运算等,就不在本文有所赘述,只对IL基本内容进行一点小结:

IL是一种面向对象的机器语言,因此具有面向对象语言的所有特性,类、对象、继承、多态等仍然是IL语言的基本概念。

IL指令独立于CPU指令,CLR通过JIT编译机制将其转换为本地代码。

IL和元数据是了解CLR运行机制的重要内容,对于我们打开CLR神秘面纱有着重要的意义。

如前文[初次接触]部分论述的一样,可以通过ILDasm.exe或者Reflector工具对托管代码执行反编译来查看其IL代码,对于很多情况下IL代码分析可以解决很多高级语言隐藏的语法糖游戏,例如C#3.0提出的自动属性、隐式类型、匿名类型、扩展方法等都可以很快从IL分析中找到答案,所以适当的了解IL是必要的。那么我们在下面JIT编译时的一个片段来了解IL代码对于托管程序执行的作用。

另外,Metadata描述了静态的结构,而IL阐释了动态的执行,而IL代码是通过一个4字节大小的地址引用元数据表的。该引用被称为元数据符号(Metadata Token,也就是记录元数据表的位置信息),在ILdasm.exe工具中选中“Show token values”,就可以在IL代码中看到IL代码通过Metadata Token引用元数据表的情况:

.method /*06000003*/ private hidebysig static
     void Main(string[] args) cil managed
{
  .entrypoint
  // Code size    36 (0x24)
  .maxstack 2
  .locals /*11000002*/ init ([0] int32 id,
       [1] class Anytao.Insidenet.MetadataIL.One/*02000004*/ one,
       [2] class Anytao.Insidenet.MetadataIL.Two/*02000002*/ two)
  IL_0000: nop
  IL_0001: ldc.i4.1
  IL_0002: stloc.0
  IL_0003: newobj   instance void Anytao.Insidenet.MetadataIL.One/*02000004*/::.ctor() /* 06000007 */
  IL_0008: stloc.1
  IL_0009: ldloc.1
  IL_000a: ldloc.0
  IL_000b: callvirt  instance void Anytao.Insidenet.MetadataIL.One/*02000004*/::set_ID(int32) /* 06000006 */
  IL_0010: nop
  IL_0011: newobj   instance void Anytao.Insidenet.MetadataIL.Two/*02000002*/::.ctor() /* 06000002 */
  IL_0016: stloc.2
  IL_0017: ldloc.2
  IL_0018: callvirt  instance string Anytao.Insidenet.MetadataIL.Two/*02000002*/::SayHello() /* 06000001 */
  IL_001d: call    void [mscorlib/*23000001*/]System.Console/*01000012*/::WriteLine(string) /* 0A000011 */
  IL_0022: nop
  IL_0023: ret
} // end of method Program::Main

其中,按照ECMA定义的规范,元数据第一个字节表示引用的元数据表,而其余三个字节则表示在相应元数据表中的记录,例如06000003表示了引用了MethodDef(06)表的000003项Main方法。

我们可以通过Type的MetadataToken属性在运行时反射获取类型的元数据符号,例如:

static void Main(string[] args)
{
   Console.WriteLine(typeof(One).MetadataToken);
}

有了上述所有的准备,我们就可以着手分析元数据和IL在程序执行时的角色和关联。

认识元数据和IL(中)<第四篇>的更多相关文章

  1. 认识元数据和IL(下)<第五篇>

    书接上回: 第二十四回:认识元数据和IL(上) , 第二十五回:认识元数据和IL(中) 我们继续. 终于到了,说说元数据和IL在JIT编译时的角色了,虽然两个回合的铺垫未免铺张,但是却丝毫不为过,因为 ...

  2. [你必须知道的.NET]第二十五回:认识元数据和IL(中)

    发布日期:2009.02.25 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 书接上回[第二十四回:认识元数据和IL(上)], ...

  3. 认识元数据和IL(上) <第三篇>

    说在,开篇之前 很早就有说说Metadata(元数据)和IL(中间语言)的想法了,一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀,虽然来得没有<第一回:恩怨情仇:is和as>那么 ...

  4. [你必须知道的.NET]第二十四回:认识元数据和IL(上)

    发布日期:2009.02.24 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 很早就有说说Metadata(元数据)和IL(中 ...

  5. Flask最强攻略 - 跟DragonFire学Flask - 第四篇 Flask 中的模板语言 Jinja2 及 render_template 的深度用法

    是时候开始写个前端了,Flask中默认的模板语言是Jinja2 现在我们来一步一步的学习一下 Jinja2 捎带手把 render_template 中留下的疑问解决一下 首先我们要在后端定义几个字符 ...

  6. Egret入门学习日记 --- 第十四篇(书中 5.4~5.6节 内容)

    第十四篇(书中 5.4~5.6节 内容) 书中内容: 总结 5.4节 内容重点: 1.如何编写自定义组件? 跟着做: 重点1:如何编写自定义组件? 文中提到了重要的两点. 好,我们来试试看. 第一步, ...

  7. [你必须知道的.NET]第二十六回:认识元数据和IL(下)

    发布日期:2009.03.04 作者:Anytao © 2009 Anytao.com ,Anytao原创作品,转贴请注明作者和出处. 说在,开篇之前 书接上回: 第二十四回:认识元数据和IL(上), ...

  8. 解剖SQLSERVER 第十四篇 Vardecimals 存储格式揭秘(译)

    解剖SQLSERVER 第十四篇    Vardecimals 存储格式揭秘(译) http://improve.dk/how-are-vardecimals-stored/ 在这篇文章,我将深入研究 ...

  9. 第四篇 Integration Services:增量加载-Updating Rows

    本篇文章是Integration Services系列的第四篇,详细内容请参考原文. 回顾增量加载记住,在SSIS增量加载有三个使用案例:1.New rows-add rows to the dest ...

随机推荐

  1. Eclipse代码提示功能设置(Java & Eclipse+CDT C/C++)

    http://developer.51cto.com/art/200907/136242.htm http://blog.chinaunix.net/u/21684/showart_462486.ht ...

  2. ASP.NET MVC3 系列教程 – 新的Layout布局系统

    原文地址:http://www.cnblogs.com/highend/archive/2011/04/18/asp_net_mvc3_layout.html I:回忆MVC2当中MasterPage ...

  3. mysqldump命令详解(转载)

    1.简介 mysqldump为MySQL逻辑备份工具,产生一系列SQL语句,之后重新执行以产生备份的库.表及数据.也可产生CSV.XML等格式的数据.适用于各类引擎的表. 运行mysqldump需一定 ...

  4. android滑动基础篇 - 触屏显示信息

    效果图: 代码部分: activity类代码: package com.TouchView; /* * android滑动基础篇 * */ import android.app.Activity; i ...

  5. pyqt 同时勾选多个items(网友提供学习)

    框选多个item之后,用空格键可以勾选/去选多个item,效果如下图所示: http://oglop.gitbooks.io/pyqt-pyside-cookbook/list/img/checkbo ...

  6. WinForm 控件不闪烁

    1: [DllImport("user32")] 2: public static extern int SendMessage(IntPtr hwnd, int wMsg, in ...

  7. Spark Streaming--实战篇

    摘要:      Sprak Streaming属于Saprk API的扩展,支持实时数据流(live data streams)的可扩展,高吞吐(hight-throughput) 容错(fault ...

  8. java与.net比较学习系列(2) 基础语言要素

    这一篇从最基础的开始对比总结,说起基础语言要素,故名思义,就是学习语言的基础,主要内容包括标识符,关键字和注释.我想从以下几点进行总结,有区别的地方有都使用红色粗体字进行了总结. 1,标识符 2,关键 ...

  9. 【Spark Core】任务运行机制和Task源代码浅析1

    引言 上一小节<TaskScheduler源代码与任务提交原理浅析2>介绍了Driver側将Stage进行划分.依据Executor闲置情况分发任务,终于通过DriverActor向exe ...

  10. android开发步步为营之65:解决ScrollView和ListView触摸事件onInterceptTouchEvent相互冲突问题

    近期项目里面有个需求,一个页面放了一个ScrollView,整个页面能够向上滚动,然后ScrollView里面又嵌套了一个ListView,ListView里面的数据也是能够上下滑动的,理论上List ...