现在我们知道了我们创建的是什么类型的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, 它会影响显示的文件大小信息. 特别地, 你不要相信未作说明的信息.