现在我们知道了我们创建的是什么类型的PE文件了, 但是在Program.exe中真正是什么? 一个托管PE文件有如下四个主要部分: PE32 (+) header, CLR header, metadata和IL. PE32(+) header是Windows需要的标准信息, CLR header是用于那些需要CLR才能运行的模块(托管模块)的一小块信息, 这个header包括CLR的主版本和次版本号: 一些标志, 一个MethodDef符号(后面会描述)用于表示模块的入口函数(如果这个模块是一个CUI或者GUI可执行程序), 和一个可选的强名字数字签名(第3章会讨论). 最后, header还包括metadata的大小和偏移量. 你可以通过检查定义在CorHdr,h中的IMAGE_Cor20_HEADER来看看CLR header的准确格式.
Metadata是一块二进制的数据, 包含了几个表格. 有3类表格: 定义表格, 引用表格, 和manifest表格. 表2-1描述了一些常用的定义表格:
表2-1 常用的metadata定义表格
Metadata定义表格的名称 |
描述 |
ModuleDef |
总是包含一个条目用来标识模块, 包括模块的文件名和扩展名(没有路径)和模块的版本ID(编译器创建的GUID). 它允许文件被重新命名, 而保持原名字的一个记录. 然而, 强烈不推荐重命名一个文件, 重命名会导致CLR在运行时不能定位程序集, 所以不要这么做. |
TypeDef |
对定义在模块中的每一个类型都包含一个条目, 每个条目包括类型的名称, 基类型, 标志(public, private, 等)和它所包含的函数在MethodDef表格中的索引, 它所包含的字段在FieldDef表格中的索引, 它所包含的属性在PropertyDef表格中的索引, 和它所包含的事件在EventDef表格中的索引. |
MethodDef |
对定义在模块中的每个方法都包含一个条目, 每个条目包括方法的名字, 标志(private, public, virtual, abstract, static, final, 等), 签名, 在模块中的偏移量(IL代码可在此处找到这个函数). 每个条目也指向ParamDef表格的一个条目, 后者记录着函数的参数信息. |
FieldDef |
对定义在模块中的每个字段都包含一个条目, 每个条目包括标志(private, public, 等), 类型和名字. |
ParamDef |
对定义在模块中的每个参数都包含一个条目, 每个条目包含标志(in, out, retval, 等), 类型, 和名字. |
PropertyDef |
对定义在模块中的每个属性都包含一个条目, 每个条目包含标志, 类型, 和名字. |
EventDef |
对定义在模块中的每个事件都包含一个条目, 每个条目包含标志和名字. |
当编译器编译你的源代码时, 你的代码中定义的任何东西都会在表2-1中描述的一个表格中创建一个条目. 编译器也会检测到的你的代码中引用的类型, 字段, 函数, 属性, 事件, 所以也会对它们创建Metadata表格条目. 创建的metadata包括一组引用表格, 其对每个引用保存一个条目. 表2-2给出了一些常见的引用metadata表格.
表2-2 常用的引用metadata表格
Metadata引用表格的名称 |
描述 |
ModuleRef |
对这个模块引用的类型, 都需要引用对应的实现模块PE, 都对应着这个表格中的一个条目, 每个条目包含模块的文件名和扩展名(没有路径). 这个表格被用于绑定在不同模块中实现的类型. |
TypeRef |
对这个模块引用的每个类型都包含一个条目, 每个条目包括类型的名字和一个能找到它的地方的引用. 如果类型是在另一个类型内实现的, 那么这个引用会指明一个TypeRef条目, 如果类型是在相同的模块中实现的, 那么引用会指明一个ModuleDef条目, 如果类型是在另一个模块中实现的, 那么引用会指明一个ModuleRef条目, 如果类型是在不同的程序集中实线的, 那么引用会指明一个AssemblyRef条目. |
MemberRef |
对这个模块引用的每个成员(字段和函数, 属性和事件方法)都包含一个条目, 每个条目包括成员的名字, 签名, 和指向TypeRef条目的指针(定义这个成员的那个type). |
除了我在表2-1和2-2中列出的, 还有很多表格, 但是我只是想让你知道编译器都产生了哪些metadata信息, 早些时候, 我提到还有一组manifest metadata表格, 我将在后面讲解这件事情.
各种不同的工具允许你检查一个托管PE文件的metadata, 就我个人而言, 我比较喜欢使用ILDasm.exe, 它是IL反汇编器. 为了看到metadata表格, 执行如下的命令: ILDasm Program.exe 这会运行ILDasm.exe, 从而载入Program.exe程序集, 为了能以可读的方式看到metadata, 选择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: 2.0.0.0 Major Version: 0x00000002 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) User Strings ------------------------------------------------ 70000001 : C 2) L"Hi" Coff symbol name overhead: 0 ================================================ ================================================ ================================================ 幸运的是, ILDasm处理了metadata表并适当地合并了信息, 你不用解析原始的表格信息. 例如, 在上面的导出信息中, 你会看到ILDasm显示了TypeDef条目, 对应的成员定义信息显示在第一个TypeDef条目之前. 你不必完全理解你看到的所有内容, 要记住的最重要的事情是Program.exe包含一个名字为Program的TypeDef, 这个类型标识着一个公开的密封类(public sealed class), 派生自System.Object(从另一个程序集引用的类型). Program类型还定义两个函数: Main和.ctor(一个构造函数). Main是公开的, 静态的函数, 它是IL代码(与native CPU代码相对). Main有一个void返回类型, 没有参数. 构造函数(总是显示成.ctor的名称)是公开的, 它也是IL代码. 构造函数的返回类型是void, 没有参数, 有一个this指针, 其指向对象的内存, 当这个函数被调用时, 对象会在这个位置创建. 我强烈鼓励你使用ILDasm进行试验, 它能为你展示丰富的信息, 你对你看到的内容理解得越多, 你就会更好地理解CLR和它的能力. 正如你看到的, 我在本书中会常常使用ILDasm. 只是为了好玩, 让我们看看有关Program.exe程序集的统计信息. 当你选择ILDasm的View|Statistics菜单项时, 下面的信息会显示出来: File size : 3072 PE header size : 512 (496 used) (16.67%) PE additional info : 839 (27.31%) Num.of PE sections : 3 CLR header size : 72 ( 2.34%) CLR meta-data size : 604 ( 19.66%) CLR additional info : 0 ( 0.00%) CLR method headers : 2 ( 0.07%) Managed code : 18 ( 0.59%) Data : 1536 ( 50.00%) Unaccounted : -511 (-16.63%) Num.of PE sections : 3 .text - 1024 .rsrc - 1024 .reloc – 512 CLR meta-data size : 604 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) CustomAttribute - 2 (12 bytes) Assembly - 1 (22 bytes) AssemblyRef - 1 (20 bytes) Strings - 176 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 header和metadata占了文件中很大一块, 实际上, IL代码只占了18个字节. 当然, 随着应用程序的增长, 它会重用很多类型, 应用工其他类型和程序集, 这会使得metadata和header的信息显著地减小(和整个文件的大小想比).
注意: ILDasm.exe有一个bug, 它会影响显示的文件大小信息. 特别地, 你不要相信未作说明的信息.