CLR via C# 3 读书笔记(11):第2章 生成、打包、部署和管理应用程序与类型 — 2.3 元数据简介
2010-03-12 09:41
369 查看
我们已经知道了我们所创建的是哪种类型的PE文件。但Program.exe文件内部究竟是什么呢?一个托管的PE文件包括4个部分:PE32(+)头,CLR头,元数据和IL。PE32(+)头是Windows所要求的标准信息。CLR头是一个小的信息块,专门用于那些需要CLR才能运行的模块(托管模块)。包括和模块一起创建的CLR主次版本号,一些标记,如果模块是CUI或GUI可执行文件还会有一个MentodDef标记用来标识入口点方法,以及一个可选的强命名数字签名。最后,头还包含模块中元数据表的尺寸和偏移量(offset)。你可以查看CorHdr.h头文件中的IMAGE_COR20_HEADER来了解CLR头的具体格式。
元数据是一个由多个表组成的二进制数据块。表的种类有三种:定义表、引用表和清单(manifest)表。常见的定义表如下:
ModuleDef:总是包含一个标识模块的条目[1]。该条目包含模块的文件名和扩展名(不包含路径),以及模块版本ID(下面代码中的MVID,编译器创建的GUID)。这使得文件在重命名时可以保留原来的名称记录。但是,这样会在运行时妨碍CLR定位某个程序集,因此强烈不建议重命名PE文件。
TypeDef:模块中的每个类型在TypeDef表中都有一个对应的条目。每个条目都包含类型名、基类型、标记(public、private等)以及该类型所拥有的方法、字段、属性、事件分别到MethodDef表、FieldDef表、Property表、EventDef表的指针。
MethodDef:模块中的每个方法在MethodDef表中都有一个对应的条目。每个条目都包含方法名、标记(private、public、virtual、abstract、static、final等)、签名以及方法的IL代码在模块中的偏移量(下面代码中的RVA,Relative Virtual Addresses,相对虚地址)。每个条目还包含一个指向ParamDef表对应条目的指针,这样可以找到有关方法参数的更多信息。 (另,CallCnvntn即指CallingConventions,但DEFAULT对应的是哪个枚举,还没有找到相关的资料)
FieldDef:模块中的每个字段在FieldDef表中都有一个对应的条目。每个条目包含标记(private、public等)、类型和名称。
ParamDef:模块中的每个参数在ParamDef表中都有一个对应的条目。每个条目包含标记(in、out、retval等)、类型和名称。
PropertyDef:模块中的每个属性在PropertyDef表中都有一个对应的条目。每个条目包含标记、类型和名称。
EventDef:模块中的每个事件在EventDef表中都有一个对应的条目。每个条目包含标记和名称。
在编译器编译源代码时,代码所定义的所有东西都会导致在以上的某个表中创建一个条目。在编译器检测源代码引用的类型、字段、方法、属性和事件时,还会创建一个元数据表条目。所创建的元数据包含一组引用表,用来保存引用项。常见的引用表如下:
AssemblyRef:模块中的每个引用的程序集在AssemblyRef表中都有一个对应的条目。每个条目包含绑定程序集所必要的信息:程序集名称(不包含路径和扩展名)、版本号、语言文化(culture)和公共密钥标记(通常由程序集发布者提供的公共密钥(标识被引用的程序集的发布者)生成的小的散列值。该散列值是被引用的程序集中的位的校验和。CLR完全忽略该散列值,并在以后也将继续如此。
ModuleRef:该模块所引用的每个类型所在的PE模块在Module表中都有一个对应的条目。每个条目都包含模块的文件名和扩展名(不含路径)。该表用来绑定那些实现在相同程序集不同模块中的类型。
TypeRef:该模块所引用的每个类型都在TypeRef表中有一个对应的条目。每个条目包含了类型的名称和指向类型所在位置的指针(下面代码中的ResolutionScope)。如果类型实现在另一个类型内部,那么该指针指向一个TypeRef条目。如果该类型实现在同一个模块中或者实现在同一程序集的其他模块中,那么该指针指向一个ModuleDef条目。如果类型实现在不同的程序集中,那么该指针指向一个AssemblyRef条目(下面代码中所有TypeRef的ResolutionScope均为0x23000001,即代表程序集mscorlib)。
MemberRef:模块中所引用的每一个成员(字段、方法、属性方法(即get和set方法)和事件方法(即add和remove方法)(当托管模块引用一个属性或者事件时,它并不会引用属性或事件本身的元数据,而是直接引用相应的访问器方法元数据))都在MemberRef表中有一个对应的条目。每个条目都包含成员的名称、签名以及指向成员所在类型的TypeRef表中对应条目的指针。
实际的元数据表要远远多于以上所列出的。
可以使用不同的工具查看托管PE文件中的元数据,如ILDasm.exe。要查看元数据,可以执行以下命令行:
启动ILDasm.exe,加载Program.exe程序集(Program.exe的源代码在这里)。可以通过View—>MetaInfo—>Show!菜单项(或CTRL+M)以一种可读的方式打开元数据,将得到以下信息:
ILDasm处理元数据表并合并恰当的信息,这样我们就不必去解析原始表中的信息。例如上面的元数据中,我们可以看到当ILDasm显示一个TypeDef的条目,并且紧接着在第一个TypeRef条目之前,先显示了相关的成员定义信息。
你现在还没有必要完全理解这些信息,但要记住Program.exe包含了一个名为Program的TypeDef。该类型标识了一个公共的、封闭的(sealed)、继承自System.Object(一个引用自另一个程序集的类型)的类。Program类型同样定义了两个方法:Main和.ctor(构造函数)。
Main是一个公共的静态方法,其实现代码为IL(相对于本地CPU代码而言,如x86)。Main的返回类型为void,并且没有参数。构造函数(总是以.ctor的名字显示)是公共的,其实现代码也是IL。构造函数返回类型为void,没有参数,并且包含一个this指针(上面元数据中的hasThis),指向构造函数被调用时正在被构造的那个对象的内存。
强烈建议大家体验一下ILDasm。它可以显示大量信息,对这些信息理解得越多,就越能深入理解CLR及其功能。
Just for fun,我们来看一下有关Program.exe程序集的一些统计结果。选择ILDasm的View—>Statistic菜单项,可以看到一下信息:
我们可以看到文件的大小(字节)和组成文件的各个部分的大小(字节和百分比)。对于这个非常小的Program.cs应用程序来说,PE头和元数据占据了文件的大部分内容。事实上IL代码仅仅占用了18个字节。当然,随着程序的增长,它会重用大多数自己的类型和对其他程序集中类型的引用,这会极大地降低元数据和头信息在整个文件中所占的比例。
ILDasm.exe有个bug,影响了上面显示的文件大小的信息(Unaccounted为负数)。
MetaData - 1. .NET PE/COFF
MetaData - 2. .NET Directory (1)
MetaData - 3. .NET Directory (2)
MetaData - 4. Tables
MetaData - 5. Method (1)
MetaData - 6. Method (2)
元数据是一个由多个表组成的二进制数据块。表的种类有三种:定义表、引用表和清单(manifest)表。常见的定义表如下:
ModuleDef:总是包含一个标识模块的条目[1]。该条目包含模块的文件名和扩展名(不包含路径),以及模块版本ID(下面代码中的MVID,编译器创建的GUID)。这使得文件在重命名时可以保留原来的名称记录。但是,这样会在运行时妨碍CLR定位某个程序集,因此强烈不建议重命名PE文件。
TypeDef:模块中的每个类型在TypeDef表中都有一个对应的条目。每个条目都包含类型名、基类型、标记(public、private等)以及该类型所拥有的方法、字段、属性、事件分别到MethodDef表、FieldDef表、Property表、EventDef表的指针。
MethodDef:模块中的每个方法在MethodDef表中都有一个对应的条目。每个条目都包含方法名、标记(private、public、virtual、abstract、static、final等)、签名以及方法的IL代码在模块中的偏移量(下面代码中的RVA,Relative Virtual Addresses,相对虚地址)。每个条目还包含一个指向ParamDef表对应条目的指针,这样可以找到有关方法参数的更多信息。 (另,CallCnvntn即指CallingConventions,但DEFAULT对应的是哪个枚举,还没有找到相关的资料)
FieldDef:模块中的每个字段在FieldDef表中都有一个对应的条目。每个条目包含标记(private、public等)、类型和名称。
ParamDef:模块中的每个参数在ParamDef表中都有一个对应的条目。每个条目包含标记(in、out、retval等)、类型和名称。
PropertyDef:模块中的每个属性在PropertyDef表中都有一个对应的条目。每个条目包含标记、类型和名称。
EventDef:模块中的每个事件在EventDef表中都有一个对应的条目。每个条目包含标记和名称。
在编译器编译源代码时,代码所定义的所有东西都会导致在以上的某个表中创建一个条目。在编译器检测源代码引用的类型、字段、方法、属性和事件时,还会创建一个元数据表条目。所创建的元数据包含一组引用表,用来保存引用项。常见的引用表如下:
AssemblyRef:模块中的每个引用的程序集在AssemblyRef表中都有一个对应的条目。每个条目包含绑定程序集所必要的信息:程序集名称(不包含路径和扩展名)、版本号、语言文化(culture)和公共密钥标记(通常由程序集发布者提供的公共密钥(标识被引用的程序集的发布者)生成的小的散列值。该散列值是被引用的程序集中的位的校验和。CLR完全忽略该散列值,并在以后也将继续如此。
ModuleRef:该模块所引用的每个类型所在的PE模块在Module表中都有一个对应的条目。每个条目都包含模块的文件名和扩展名(不含路径)。该表用来绑定那些实现在相同程序集不同模块中的类型。
TypeRef:该模块所引用的每个类型都在TypeRef表中有一个对应的条目。每个条目包含了类型的名称和指向类型所在位置的指针(下面代码中的ResolutionScope)。如果类型实现在另一个类型内部,那么该指针指向一个TypeRef条目。如果该类型实现在同一个模块中或者实现在同一程序集的其他模块中,那么该指针指向一个ModuleDef条目。如果类型实现在不同的程序集中,那么该指针指向一个AssemblyRef条目(下面代码中所有TypeRef的ResolutionScope均为0x23000001,即代表程序集mscorlib)。
MemberRef:模块中所引用的每一个成员(字段、方法、属性方法(即get和set方法)和事件方法(即add和remove方法)(当托管模块引用一个属性或者事件时,它并不会引用属性或事件本身的元数据,而是直接引用相应的访问器方法元数据))都在MemberRef表中有一个对应的条目。每个条目都包含成员的名称、签名以及指向成员所在类型的TypeRef表中对应条目的指针。
实际的元数据表要远远多于以上所列出的。
可以使用不同的工具查看托管PE文件中的元数据,如ILDasm.exe。要查看元数据,可以执行以下命令行:
ILDasm Program.exe
启动ILDasm.exe,加载Program.exe程序集(Program.exe的源代码在这里)。可以通过View—>MetaInfo—>Show!菜单项(或CTRL+M)以一种可读的方式打开元数据,将得到以下信息:
=========================================================== ScopeName : Program.exe MVID : {CA73FFE8-0D42-4610-A8D3-9276195C35AA} =========================================================== Global functions ------------------------------------------------------- Global fields ------------------------------------------------------- Global MemberRefs ------------------------------------------------------- TypeDef #1 (02000002) ------------------------------------------------------- TypDefName: Program (02000002) Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit] (00100101) Extends : 01000001 [TypeRef] System.Object Method #1 (06000001) [ENTRYPOINT] ------------------------------------------------------- MethodName: Main (06000001) Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096) RVA : 0x00002050 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] ReturnType: Void No arguments. Method #2 (06000002) ------------------------------------------------------- MethodName: .ctor (06000002) Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886) RVA : 0x0000205c ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #1 (01000001) ------------------------------------------------------- Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object MemberRef #1 (0a000004) ------------------------------------------------------- Member: (0a000004) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #2 (01000002) ------------------------------------------------------- Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute MemberRef #1 (0a000001) ------------------------------------------------------- Member: (0a000001) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: I4 TypeRef #3 (01000003) ------------------------------------------------------- Token: 0x01000003 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute MemberRef #1 (0a000002) ------------------------------------------------------- Member: (0a000002) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #4 (01000004) ------------------------------------------------------- Token: 0x01000004 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1 (0a000003) ------------------------------------------------------- Member: (0a000003) WriteLine: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String Assembly ------------------------------------------------------- Token: 0x20000001 Name : Program Public Key : Hash Algorithm : 0x00008004 Version: 0.0.0.0 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [none] (00000000) CustomAttribute #1 (0c000001) ------------------------------------------------------- CustomAttribute Type: 0a000001 CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32) Length: 8 Value : 01 00 08 00 00 00 00 00 > < ctor args: (8) CustomAttribute #2 (0c000002) ------------------------------------------------------- CustomAttribute Type: 0a000002 CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor() Length: 30 Value : 01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78 > T WrapNonEx< : 63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01 >ceptionThrows < ctor args: () AssemblyRef #1 (23000001) ------------------------------------------------------- Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Version: 4.0.0.0 Major Version: 0x00000004 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) User Strings ------------------------------------------------------- 70000001 : ( 2) L"Hi" Coff symbol name overhead: 0 =========================================================== =========================================================== ===========================================================
ILDasm处理元数据表并合并恰当的信息,这样我们就不必去解析原始表中的信息。例如上面的元数据中,我们可以看到当ILDasm显示一个TypeDef的条目,并且紧接着在第一个TypeRef条目之前,先显示了相关的成员定义信息。
你现在还没有必要完全理解这些信息,但要记住Program.exe包含了一个名为Program的TypeDef。该类型标识了一个公共的、封闭的(sealed)、继承自System.Object(一个引用自另一个程序集的类型)的类。Program类型同样定义了两个方法:Main和.ctor(构造函数)。
Main是一个公共的静态方法,其实现代码为IL(相对于本地CPU代码而言,如x86)。Main的返回类型为void,并且没有参数。构造函数(总是以.ctor的名字显示)是公共的,其实现代码也是IL。构造函数返回类型为void,没有参数,并且包含一个this指针(上面元数据中的hasThis),指向构造函数被调用时正在被构造的那个对象的内存。
强烈建议大家体验一下ILDasm。它可以显示大量信息,对这些信息理解得越多,就越能深入理解CLR及其功能。
Just for fun,我们来看一下有关Program.exe程序集的一些统计结果。选择ILDasm的View—>Statistic菜单项,可以看到一下信息:
File size : 3584 PE header size : 512 (496 used) (14.29%) PE additional info : 1415 (39.48%) Num.of PE sections : 3 CLR header size : 72 ( 2.01%) CLR meta-data size : 612 (17.08%) CLR additional info : 0 ( 0.00%) CLR method headers : 2 ( 0.06%) Managed code : 18 ( 0.50%) Data : 2048 (57.14%) Unaccounted : -1095 (-30.55%) Num.of PE sections : 3 .text – 1024 .rsrc – 1536 .reloc - 512 CLR meta-data size : 612 Module - 1 (10 bytes) TypeDef - 2 (28 bytes) 0 interfaces, 0 explicit layout TypeRef - 4 (24 bytes) MethodDef - 2 (28 bytes) 0 abstract, 0 native, 2 bodies MemberRef - 4 (24 bytes) ParamDef - 2 (12 bytes) CustomAttribute- 2 (12 bytes) Assembly - 1 (22 bytes) AssemblyRef - 1 (20 bytes) Strings - 184 bytes Blobs - 68 bytes UserStrings - 8 bytes Guids - 16 bytes Uncategorized - 168 bytes CLR method headers : 2 Num.of method bodies – 2 Num.of fat headers – 0 Num.of tiny headers – 2 Managed code : 18 Ave method size - 9
我们可以看到文件的大小(字节)和组成文件的各个部分的大小(字节和百分比)。对于这个非常小的Program.cs应用程序来说,PE头和元数据占据了文件的大部分内容。事实上IL代码仅仅占用了18个字节。当然,随着程序的增长,它会重用大多数自己的类型和对其他程序集中类型的引用,这会极大地降低元数据和头信息在整个文件中所占的比例。
ILDasm.exe有个bug,影响了上面显示的文件大小的信息(Unaccounted为负数)。
扩展阅读
推荐雨痕大牛的几篇关于元数据的随笔:MetaData - 1. .NET PE/COFF
MetaData - 2. .NET Directory (1)
MetaData - 3. .NET Directory (2)
MetaData - 4. Tables
MetaData - 5. Method (1)
MetaData - 6. Method (2)
注释
[1] 这里的“条目”实为entry,参照元数据代码中的ENTRYPOINT项,我本将其翻译为“入口”。但李建忠老师翻译的第一版将其翻译为“条目”,在此我沿用了他的翻译。其实这里的entry我认为是指针的意思。相关文章推荐
- CLR via C# 3 读书笔记(12):第2章 生成、打包、部署和管理应用程序与类型 — 2.4 将模块组合为程序集(上)
- CLR via C# 3 读书笔记(16):第2章 生成、打包、部署和管理应用程序与类型 — 2.8 简单管理控制(配置)
- CLR via C# 3 读书笔记(14):第2章 生成、打包、部署和管理应用程序与类型 — 2.5 程序集版本资源信息
- CLR via C# 3 读书笔记(13):第2章 生成、打包、部署和管理应用程序与类型 — 2.4 将模块组合为程序集(下)
- CLR via C# 3 读书笔记(10):第2章 生成、打包、部署和管理应用程序与类型 — 2.2 将类型生成为模块
- CLR via C# 3 读书笔记(15):第2章 生成、打包、部署和管理应用程序与类型 — 2.6 语言文化 & 2.7 简单应用程序部署
- 《CLR via C#》读书笔记 之 生成、打包、部署和管理应用程序及类型
- 《CLR via C#》Part1之Chapter2 生成、打包、部署及管理应用程序及类型(二)
- 《CLR via C#》Part1之Chapter2 生成、打包、部署及管理应用程序及类型
- CLR只记不看笔记(二) 生成、打包、部署和管理应用程序及类型
- CLR基础---生成、打包、部署和管理应用程序及类型(二)
- CLR基础---生成、打包、部署和管理应用程序及类型(一)
- CLR基础---生成、打包、部署和管理应用程序及类型(二)
- 第2章 生成、打包、部署和管理应用程序及类型
- CLR基础---生成、打包、部署和管理应用程序及类型(一)
- 第2章:生成、打包、部署和管理应用程序及类型
- [转]第2章 生成、打包、部署和管理应用程序及类型 part 2
- 02.生成、打包、部署和管理应用程序及类型
- (二)生成,打包,部署及管理应用程序与类型(随笔)
- .NET框架程序设计--生成,打包,部署及管理应用程序与类型(1:程序集的PE格式)