PE文件结构:PE文件也叫做可移植执行体文件,其结构如下所示:
1.PE文件由程序集组成。
2.程序集由托管模块和资源文件以及元数据清单表组成。
3.托管模块由PE32或者PE32+头,CLR头,元数据,IL组成。
4.PE32或者PE32+头是windows要求的标准信息。负责创建对应位数的进程,并在进程中加载MSCorEE.dll。
5.CLR头是由CorHdr.h中定义的IMAGE_COR20_HEADER结构体组成。负责由MSCorEE.dll初始化CLR。
6.元数据是由元数据定义表和元数据引用表组成。负责描述托管模块中定义了什么以及引用了什么。
7.IL是由编译器将源码编译成的中间语言代码,运行时由CLR编译成机器码。
8.元数据清单表描述了程序集的版本信息以及有哪些文件,导出了哪些公有类型等。
PE文件类型:PE文件可以是DLL,EXE,netmodule。类型区别如下所示:
1.netmodule的PE文件不包含元数据清单表,通常被添加到其他的程序集中。
2.EXE的PE文件必须包含主入口。
元数据特性:如下所示:
1.元数据定义表用来记录托管模块中定义的数据信息。常用的定义表如下所示:
名称 | 说明 |
---|---|
ModuleDef | 模块定义表。该表会为托管模块生成一条记录项。记录项中包含托管模块的文件名和扩展名以及模块版本标识 |
TypeDef | 类型定义表。托管模块中的每一个类型在该表中都有一条记录项。每条记录项中包含类型的名称,基类型,标志 (如:访问权限)以及一些索引值(如:方法,属性,字段,事件等地方使用该类型的索引值) |
MethodDef | 方法定义表。托管模块中的每一个方法在该表中都有一条记录项。每条记录项中包含方法的名称,标志 (如:访问权限),签名(参数(参数定义表中的记录项)和返回类型)以及方法对应的IL在托管模块中的偏移值 |
FieldDef | 字段定义表。托管模块中的每一个字段在该表中都有一条记录项。每条记录项中包含字段的名称,类型和标志 (如:访问权限) |
ParamDef | 参数定义表。托管模块中的每一个参数在该表中都有一条记录项。每条记录项中包含参数的标志(如:in,out,retval),类型和名称 |
PropertyDef | 属性定义表。托管模块中的每一个属性在该表中都有一条记录项。每条记录项中包含属性的标志(如:访问权限),类型和名称 |
EventDef | 事件定义表。托管模块中的每一个事件在该表中都有一条记录项。每条记录项中包含事件的标志(如:访问权限)和名称 |
2.元数据引用表用来记录托管模块中引用的数据信息。常用的引用表如下所示:
名称 | 说明 |
---|---|
AssemblyRef | 程序集引用表。托管模块中引用的每一个程序集在该表中都有一条记录项。每条记录项中包含程序集的名称,版本号,语言文化,公钥,标志以及校验哈希值 |
ModuleRef | 模块引用表。托管模块中引用的每一个其他托管模块在该表中都有一条记录项。每条记录项中包含引用托管模块的文件名和扩展名 |
TypeRef | 类型引用表。托管模块中引用的每一个类型在该表中都有一条记录项。每条记录项中包含类型的名称和指向类型位置的引用(引用的类型在不同程序集中实现时,引用就等于程序集引用表记录项;引用的类型在不同类型中实现时,引用就等于类型引用表记录项;其他情况下引用就等于模块引用表记录项。) |
MemberRef | 成员引用表。托管模块中引用的每一个成员(字段和方法)在该表中都有一条记录项。每条记录项中包含成员的名称和签名以及类型引用表记录项 |
3.元数据清单表用来记录程序集中文件集的表。常用的清单表如下所示:
名称 | 说明 |
---|---|
Assembly | 程序集清单表。该表会为程序集生成一条记录项。记录项中包含程序集的名称,版本号,语言文化,公钥,标志以及校验哈希值 |
File | 文件清单表。除了文件清单表外,其他的资源文件和托管模块中的文件在该表中都有一条记录项。每条记录项中包含文件名和扩展名,哈希值和一些标志。 |
ManifestResource | 资源清单表。程序集中的每一个资源在该表中都有一条记录项。每条记录项中包含资源名称,一些标志(如:访问权限),以及文件清单表中该资源文件的索引或者程序集中该资源流的偏移位置。 |
ExportedType | 导出类型清单表。程序集中导出的每一个公有类型在该表中都有一条记录项。每条记录项中包含类型名称,文件清单表中的索引(类型在哪个文件中定义)以及类型定义表中的索引(具体类型定义信息)。 |
4.元数据token是一个四字节的值。其中高位字节是由CorHdr.h文件中的CorTokenType枚举指明的元数据表类型;低位三字节指明对应元数据表中的行。
编译&链接PE文件:如下所示:
1.net framework在安装时,会在%SystemRoot%\MicroSoft.NET\Framework(64)\vX.X.X目录下生成csc.exe和csc.rsp文件。这些文件的描述如下所示:
1>.csc.exe是编译器执行文件,负责将c#源文件编译成程序集,并由该程序集来生成PE文件。常见的编译参数如下表所示:
参数 | 描述 |
---|---|
/out | PE文件名。如:源文件名.exe,源文件名.dll |
/t | PE文件类型。如:exe(控制台程序),library(动态链接库),winexe(图形程序), appcontainerexe(windows store程序) ,module(生成的PE文件不包含元数据清单表且后缀名为.netmodule) |
/r | 引用程序集,如:mscorlib.dll(包含所有核心类型动态链接库)。csc.exe会尝试在以下目录查找程序集:1.指定的文件名是否为完整路径;2.工作目录;3.csc.exe所在的目录(包含CLR的各种DLL文件);4.使用/lib编译器开关指定的任何目录;5.使用LIB环境变量指定的任何目录。 |
/nostdlib | 不自动引用标准动态链接库 |
/noconfig | 忽略全局和本地响应文件 |
/addmodule | 将PE文件(如.netmodule后缀的文件)添加到程序集。编译器会将该PE文件添加到程序集的文件清单表以及把该PE文件中公共导出类型添加到导出类型清单表。 |
/resource | 将资源文件嵌入到程序集中,并在资源清单表中添加该资源记录项信息。 |
/linkresource | 在资源清单表和文件清单表中添加该资源记录项信息。不必将资源文件嵌入到程序集中,因为通过该资源在文件清单表的记录项就可以知道位置信息。 |
/win32icon | 将资源类型为ico的文件嵌入到程序集中。 |
/win32res | 将资源类型为res的文件嵌入到程序集中。 |
/nowin32manifest | 不生成win32资源清单表。 |
/keyfile | 使用签名文件中的私钥对程序集进行签名并将签名文件中的公钥写入到元数据清单表中。 |
/keycontainer | 使用CSP容器中的签名文件中的私钥对程序集进行签名并将签名文件中的公钥写入到元数据清单表中。 |
/delaysign | 使用延迟签名技术。 |
2>.csc.rsp是全局响应文件,负责将编译参数传递给csc.exe。当csc.exe在执行编译操作时会优先查找全局的csc.rsp响应文件;然后再查找编译命令中@后面的本地响应文件;最后将两个响应文件的编译参数进行合并,如果有相同的编译参数时就以本地响应文件的配置为准。
2.net framework在安装时,会在C:\Program Files(x86)\MicroSoft SDKs\Windows\vX.X\bin\NETFX X.X.X Tools目录下生成al.exe文件。该文件就是用来将托管模块和资源文件链接成程序集,并由该程序集来生成PE文件。具有以下特性:
1>.只对资源文件进行链接生成的程序集叫做附属程序集,通常用于本地化。
2>.常见的链接参数如下表所示:
参数 | 描述 |
---|---|
/out | PE文件名。如:源文件名.exe,源文件名.dll |
/t | PE文件类型。如:exe(控制台程序),library(动态链接库),winexe(图形程序), appcontainerexe(windows store程序) |
/main | 在生成exe的PE文件时,需要指定主入口函数 |
/embed | 将资源文件嵌入到程序集中,并在资源清单表中添加该资源记录项信息。 |
/link | 在资源清单表和文件清单表中添加该资源记录项信息。不必将资源文件嵌入到程序集中,因为通过该资源在文件清单表的记录项就可以知道位置信息。 |
/win32icon | 将资源类型为ico的文件嵌入到程序集中。 |
/win32res | 将资源类型为res的文件嵌入到程序集中。 |
/algid | 将文件名加入到字段定义表中时使用的哈希算法,默认是SHA-1。当然也可以使用AlgorithmIdAttribute定制特性来进行设置。 |
/keyfile | 使用签名文件中的私钥对程序集进行签名并将签名文件中的公钥写入到元数据清单表中。 |
/keyname | 使用CSP容器中的签名文件中的私钥对程序集进行签名并将签名文件中的公钥写入到元数据清单表中。 |
/delay[sign] | 使用延迟签名技术。 |
程序集特性:如下所示:
1.程序集的版本资源字段可以使用AL.exe的命令开关(如:/version:“3.0.0.0”)来进行设置,也可以在源文中使用定制特性(如:[assembly: AssemblyFileVersion(“3.0.0.0”)])进行设置。对应关系如下表所示:
版本资源字段 | AL.exe命令开关 | 定制特性或者说明 |
---|---|---|
FILEVERSION | /fileversion | System.Reflection.AssemblyFileVersionAttribute |
PRODUCTVERSION | /productversion | System.Reflection.AssemblyInformationalVersionAttribute |
FILEFLAGSMASK | 无 | 总是设为VS_FFI_FILEFLAGSMASK,其值为WinVer.h中定义的0x0000003F |
FILEFLAGS | 无 | 总是设为0 |
FILEOS | 无 | 总是设为VOS_WINDOWS32 |
FILETYPE | /target | 如果指定了/target:exe或者/target:winexe,就设置为VFT_APP;如果指定了/target:library,就设置为VFT_DLL |
FILESUBTYPE | 无 | 总是设为VFT2_UNKNOWN |
AssemblyVersion | /version | System.Reflection.AssemblyVersionAttribute |
Comments | /description | System.Reflection.AssemblyDescriptionAttribute |
CompanyName | /company | System.Reflection.AssemblyCompanyAttribute |
FileDescription | /title | System.Reflection.AssemblyTitleAttribute |
FileVersion | /version | System.Reflection.AssemblyFileVersionAttribute |
InternalName | /out | 无扩展名的输出文件名称 |
LegalCopyright | /copyright | System.Reflection.AssemblyCopyrightAttribute |
LegalTrademarks | /trademark | System.Reflection.AssemblyTrademarkAttribute |
OriginalFilename | /out | 无路径的输出文件名称 |
PrivateBuild | 无 | 总是空白 |
ProductName | /product | System.Reflection.AssemblyProductAttribute |
ProductVersion | /productversion | System.Reflection.AssemblyInformationalVersionAttribute |
SpecialBuild | 无 | 总是空白 |
2.程序集版本号格式为"major(主版本号).minor(次版本号).build(内部版本号).revision(修订号)",且存在三种版本号,分别如下所示:
1>.AssemblyFileVersion存储在win32资源文件中。
2>.AssemblyInformationalVersion存储在win32资源文件中,主要用来指出包含该程序集的产品的版本。
3>.AssemblyVersion存储在元数据定义表中,主要用来唯一性的标识程序集。
3.程序集语言文化是由主标记和副标记组成。如下表所示:
主标记 | 副标记 | 语言文化 |
---|---|---|
de | 无 | 德语 |
de | AT | 奥地利德语 |
de | CH | 瑞士德语 |
en | 无 | 英语 |
en | GB | 英国英语 |
en | US | 美国英语 |
其中指定语言文化的程序集叫做附属程序集,而未指定语言文化的程序集叫做语言文化中性。通常使用AL.exe的/c[ulture]:text(就是主标记-副标记字符串,如:en-US)命令或者[assembly:AssemblyCulture(主标记-副标记字符串,如:en-US)]定制特性来生成附属程序集。一般而言也不要引用附属程序集,而是通过反射技术来加载附属程序集。
4.部署程序集时通常有以下方案:
1>.使用vs生成.appx文件并发布到windows store上;然后下载安装时会将.appx的所有程序集存放在对应的的版本号目录中。
2>.使用vs生成msi文件并发布到网站,FTP服务器等地方;然后下载安装时会按需安装并且利用clickonce技术自动检查更新等。
3>.使用side-load技术或者批处理文件将所有程序集文件安装到设备上。
5.CLR查找语言文化中性的程序集流程如下:
1>.首先在应用程序根目录下查找程序集。
2>.然后在跟程序集同名的应用程序子目录中查找程序集。
3>.最后在配置文件(xxx.config文件)中的probing元素的privatepath属性值(用分号分割的相对于应用程序根目录的路径名)下面进行查找。其中exe的配置文件为"exe文件全名.config"并存储在应用程序根目录中,而web的配置文件为Web.config并存储在Web应用程序虚拟根目录中。
4>.从头开始查找同名的其他类型程序集。
5>.找不到就抛出FileNotFoundException异常。
6.CLR查找附属程序集流程如下:
1>.首先在应用程序根目录/主标记-副标记目录下查找程序集。
2>.然后在跟程序集同名的主标记-副标记/应用程序子目录中查找程序集。
3>.最后在配置文件(xxx.config文件)中的probing元素的privatepath属性值(用分号分割的相对于应用程序根目录的路径名)/主标记-副标记目录下面进行查找。其中exe的配置文件为"exe文件全名.config"并存储在应用程序根目录中,而web的配置文件为Web.config并存储在Web应用程序虚拟根目录中。
4>.从头开始查找同名的其他类型程序集。
5>.从头开始将上述步骤中的主标记-副标记换成主标记来查找程序集。
6>.找不到就抛出FileNotFoundException异常。
7.在编译程序集时需要程序集和引用程序集的文件清单表中的所有文件都存在,否则就会编译不过。
8.在加载程序集中的一个文件时,CLR检查设备的下载缓存中存在该文件时就直接加载该文件;否则就从程序集配置文件中的codeBase元素中指定的URL下载该文件并存储在设备的下载缓存中,然后加载该文件。