认识元数据和IL(中)<第四篇>

书接上回[第二十四回:认识元数据和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(中)<第四篇>,布布扣,bubuko.com

时间: 2024-10-22 15:07:20

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

认识元数据和IL(下)&lt;第五篇&gt;

书接上回: 第二十四回:认识元数据和IL(上) , 第二十五回:认识元数据和IL(中) 我们继续. 终于到了,说说元数据和IL在JIT编译时的角色了,虽然两个回合的铺垫未免铺张,但是却丝毫不为过,因为只有充分的认知才有足够的体会,技术也是如此.那么,我们就开始沿着方法调用的轨迹,追随元数据和IL在那个神秘瞬间所贡献的力量吧 5 元数据和IL在JIT编译时 CLR最终执行的只有本地机器码,所以JIT编译的作用是在运行时将IL代码解析为机器码执行.对于JIT编译,我们会以专门的篇幅来全面了解,本文只

认识元数据和IL(上) &lt;第三篇&gt;

说在,开篇之前 很早就有说说Metadata(元数据)和IL(中间语言)的想法了,一直在这篇开始才算脚踏实地的对这两个阶级兄弟投去些细关怀,虽然来得没有<第一回:恩怨情仇:is和as>那么迅速,但是Metadata和IL却是绝对重量级的内容,值得我们在任何时间关注,本文就是开始. 1 引言 你可曾想到,我们的C#代码,编译之后究竟为何物?你可曾认知,我们的可执行程序,运行之时的轨迹究竟为哪般?那么,本文通过对Metadata(元数据)和IL(Intermediate Language, 中间语

C语言中容易被忽略的细节(第四篇)

前言:本文的目的是记录C语言中那些容易被忽略的细节.我打算每天抽出一点时间看书整理,坚持下去,今天是第一篇,也许下个月的今天是第二篇,明年的今天又是第几篇呢?--我坚信,好记性不如烂笔头.第四篇了,fight~... 第一篇链接:C语言中容易被忽略的细节(第一篇) 第二篇链接:C语言中容易被忽略的细节(第二篇) 第三篇链接:C语言中容易被忽略的细节(第三篇) 1.void*类型的指针不能参与算术运算,只能进行赋值.比较和sizeof操作的原因? 指针的算术运算还要包含指针所指对象的字节数信息.

iOS开发中常用的轮子 第四篇 收集齐7个轮子,准备高仿部分微博APP页面

产品原因有几张页面会参考微博APP来做,先收集齐轮子:计划这周完成,然后放到github上开源. 1,微博流刷新: 2,浏览微博中的图片: 3,发布微博: 4,发微博时选择照片: ============分割线:具体如下 ============= 选择相册中图片: 1,UzysAssetsPickerController 链接:https://github.com/uzysjung/UzysAssetsPickerController 介绍:用于替换UIImagePickerControlle

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

是时候开始写个前端了,Flask中默认的模板语言是Jinja2 现在我们来一步一步的学习一下 Jinja2 捎带手把 render_template 中留下的疑问解决一下 首先我们要在后端定义几个字符串,用于传递到前端 STUDENT = {'name': 'Old', 'age': 38, 'gender': '中'}, STUDENT_LIST = [ {'name': 'Old', 'age': 38, 'gender': '中'}, {'name': 'Boy', 'age': 73,

Egret入门学习日记 --- 第二十四篇(书中 9.12~9.15 节 内容)

第二十四篇(书中 9.12~9.15 节 内容) 开始 9.12节 内容. 重点: 1.TextInput的使用,以及如何设置加密属性. 操作: 1.TextInput的使用,以及如何设置加密属性. 创建exml文件,拖入组件,设置好id. 这是显示密码星号处理的属性. 创建绑定类. 实例化,并运行. 但是焦点在密码输入框时,密码是显示的. 暂时不知道怎么设置 “焦点在密码框上时,还是显示为 * 号” 的方法. 至此,9.12节 内容结束. 开始 9.13节 . 这个,和TextInput的使用

Egret入门学习日记 --- 第六十四篇(书中 19.4 节 内容)

第六十四篇(书中 19.4 节 内容) 昨天的问题,是 images 库自己本身的问题. 我单独使用都报错. 这是main.js文件代码: let images = require("images"); console.log(images); 这是cmd运行命令历史: Microsoft Windows [版本 10.0.16299.15] (c) 2017 Microsoft Corporation.保留所有权利. C:\Users\Administrator\Desktop\a&

第四篇 SQL Server安全权限

本篇文章是SQL Server安全系列的第四篇,详细内容请参考原文. 权限授予主体访问对象,以执行某些操作.SQL Server有大量你可以授予给主体的权限,你甚至可以拒绝或回收权限.这听起来有点复杂,但在这一系列,你将知道SQL Server权限是如何工作的,你可以非常精细地控制对象创建.数据访问.以及其他类型操作在数据库和服务器对象上.权限权限像一个签证允许你访问外国,通常有一些基本条件.比如,你只有六个月的期限,你被限制在3/7的地区旅行.类似的,SQL Server权限给主体访问数据库对

[老老实实学WCF] 第四篇 初探通信--ChannelFactory

原文:[老老实实学WCF] 第四篇 初探通信--ChannelFactory 老老实实学WCF 第四篇 初探通信--ChannelFactory 通过前几篇的学习,我们简单了解了WCF的服务端-客户端模型,可以建立一个简单的WCF通信程序,并且可以把我们的服务寄宿在IIS中了.我们不禁感叹WCF模型的简单,寥寥数行代码和配置,就可以把通信建立起来.然而,仔细品味一下,这里面仍有许多疑点:服务器是如何建起服务的?我们在客户端调用一个操作后发生了什么?元数据到底是什么东西?等等.我们现在对WCF的理