zoukankan      html  css  js  c++  java
  • (翻译)《Expert .NET 2.0 IL Assembler》 第四章 托管可执行体文件的结构 4.1 PE/COFF头(二)

    返回目录

    PE

           PE头,紧跟在COFF头的后面,提供了OS加载器的信息。虽然这个头被称为可选择的头(optional header),它只是可选择的,在某种意义上是说,对象文件通常不包括它。对于PE文件而言,这个头是强制性的。

           PE文件的大小是不固定的。它取决于定义在头中的数据目录的数量,并由COFF头中的SizeOfOptionalHeader字段详细指明。定义在Winnt.h中的PE头的结构如下:

    typedef struct _IMAGE_OPTIONAL_HEADER {
         // Standard fields
         WORD    Magic;
         BYTE    MajorLinkerVersion;
         BYTE    MinorLinkerVersion;
         DWORD   SizeOfCode;
         DWORD   SizeOfInitializedData;
         DWORD   SizeOfUninitializedData;
         DWORD   AddressOfEntryPoint;
         DWORD   BaseOfCode;
         DWORD   BaseOfData;
         // NT additional fields
         DWORD   ImageBase;
         DWORD   SectionAlignment;
         DWORD   FileAlignment;
         WORD    MajorOperatingSystemVersion;
         WORD    MinorOperatingSystemVersion;
         WORD    MajorImageVersion;
         WORD    MinorImageVersion;
         WORD    MajorSubsystemVersion;
         WORD    MinorSubsystemVersion;
         DWORD   Win32VersionValue;
         DWORD   SizeOfImage;
         DWORD   SizeOfHeaders;
         DWORD   CheckSum;
         WORD    Subsystem;
         WORD    DllCharacteristics;
         DWORD   SizeOfStackReserve;
         DWORD   SizeOfStackCommit;
         DWORD   SizeOfHeapReserve;
         DWORD   SizeOfHeapCommit;
         DWORD   LoaderFlags;
         DWORD   NumberOfRvaAndSizes;
         IMAGE_DATA_DIRECTORY 
         DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] ;
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

    4-4 描述了PE头的字段

    4-4 PE头字段

    偏移量 32/64

    大小 32/64

    字段名

    描述

    0

    2

    Magic

    “魔数(Magic number)”,识别了映像文件的状态。对于32PE文件而言,可采用的值是0x010B;对于64PE文件而言,则是0x020B;对于一个ROM映像文件而言,则是0x107。托管的PE文件必须有设置为0x010B0x020B的字段(只适于64位映像文件的2.0或更后期的版本)。

    2

    1

    MajorLinkerVersion

    连接器的主版本。VC++连接器设置这个字段为8;被其它编译器使用的纯IL文件生成器做着同样的事情。在早期的版本中,这个字段被相应的设置67

    3

    1

    MinorLinkerVersion

    连接器的次版本

    4

    4

    SizeOfCode

    代码区段(.text)的大小,或者如果这些多样的代码区段存在的话,就是所有这些代码区段的总合。IL编译器总是发布一个简单的代码区段。

    8

    4

    SizeOfInitializedData

    初始化数据区段的大小(保存在相应的section头的SizeOfRawData字段中)或者所有这些section的总合。这个初始化的数据定义为一个特定的数值,存储在磁盘的映像文件中。

    12

    4

    SizeOfUninitializedData

    未初始化的数据区段(.bss)的大小或所有这样的区段的总合。这个数据并不是磁盘文件的一部分并且不具备明确的值,但是在这个文件被加载时,OS加载器会为这个数据提交内存空间。

    16

    4

    AddressOfEntryPoint

    RVA的入口点方法。对于非托管的PE文件,这个值可以是0。对于托管的PE文件,这个值总是指向CLR调用的stub

    20

    4

    BaseOfCode

    文件代码区段的开始部分的RVA

    24/-

    4/-

    BaseOfData

    文件数据区段的开始部分的RVA。这个入口不存在于64位可选择的头中。

    28/24

    4/8

    ImageBase

    映像的首选起始虚拟地址;必须排列在64KB的边界上(0x10000)。在ILAsm中,这个字段可以通过指令.imagebase<integer value>/或命令行选项/BASE=<integer value>显示的指定。这个命令行选项优先于指令。

    32

    4

    SectionAlignment

    区段加载到内存后的对齐值。。这个设置必须大于等于FileAlignment字段的值。默认为内存页的大小。

    36

    4

    FileAlignment

    在磁盘映像 区段的对齐值。这个值应该是2的幂,从51264000(从0x2000x10000)。如果SectionAlignment被设置为小于内存页的大小,FileAlignment必须与SectionAlignment相匹配。在ILAsm中,这个字段由指令.file alignment <integer value>/或命令行选项/ALIGNMENT=<integer value>显示指明。这个命令行选项优先于指令。

    40

    2

    MajorOperatingSystemVersion

    所需操作系统的主版本号。

    42

    2

    MinorOperatingSystemVersion

    所需操作系统的次版本号。

    44

    2

    MajorImageVersion

    应用程序的主版本号。

    46

    2

    MinorImageVersion

    应用程序的次版本号。

    48

    2

    MajorSubsystemVersion

    子系统的主版本号。

    50

    2

    MinorSubsystemVersion

    子系统的次版本号。

    52

    4

    Win32VersionValue

    保留的。

    56

    4

    SizeOfImage

    映像文件的大小(按字节),包括了所有的头。这个字段必须被设置为SectionAlignment值的若干倍。

    60

    4

    SizeOfHeaders

    MS-DOS头和stubCOFF头、PE头和section头的大小总合,成上舍入为FileAlignment值的若干倍。

    64

    4

    CheckSum

    磁盘映像文件的Checksum

    68

    2

    Subsystem

    需要运行这个映像文件的用户接口子系统。这个值定义在Winnt.h中,如下:

    NATIVE(1):不需要子系统(例如,一个设备驱动)。

    WINDOWS_GUI(2) :运行在Windows GUI子系统上。

    WINDOWS_CUI(3):运行在Windows控制台模式。

    OS2_CUI (5):运行在OS/2 1.x控制台模式。

    POSIX_CUI (7):运行在POSIX控制台模式。

    NATIVE_WINDOWS (8):映像文件是一个本地的Win9x驱动。

    WINDOWS_CE_GUI (9):运行在Windows CE GUI子系统上。

    ILAsm中,这个字段由指令.subsystem<integer value>/或命令行选项/SUBSYSTEM=<integer value>显示指明。这个命令行选项优先于指令。

    70

    2

    DllCharacteristics

    在映像文件的1.0版本中,总是被设置为0。在托管文件的1.1或更新版本中,总是被设置为0x400:没有非托管的Windows结构化异常处理。

    72

    4/8

    SizeOfStackReserve

    用于初始线程栈的虚拟内存大小。只有SizeOfStackCommit字段(所指定大小的内存)被提交了,余下部分会随着使用逐页增加。对于32位映像的默认值为1MB,对于64位映像的则为4MB。在ILAsm中,这个字段由指令.stackreserve <integer value>/或命令行选项/STACK=<integer value>显示指明。这个命令行选项优先于指令。

    76/80

    4/8

    SizeOfStackCommit

    初始提交于初始线程栈的虚拟内存大小。对于32位映像默认为一页(4KB),对于64位映像默认为16KB

    80/88

    4/8

    SizeOfHeapReserve

    用于初始进程堆的虚拟内存大小。只有SizeOfHeapCommit字段被提交;剩下的在一页的增加中是有效的。对于32位和64位映像默认值都是1MB

    84/96

    4/8

    SizeOfHeapCommit

    初始提交给进程堆的虚拟内存大小。对于32位映像默认为4KB(一个操作系统的内存页),对于64位映像默认为2KB

    88/104

    4

    LoaderFlags

    已经被废弃,设置为0

    92/108

    4

    NumberOfRvaAndSizes

    DataDirectory数组中的入口数量,至少为16。虽然理论上发布大于16个数据目录是可能的,但是所有现有的托管编译器严格地发布16个数据目录,伴随着第16个(最后一个)数据目录从来不使用(保留的)。

    包包译注:魔数(Magic number),一个不能提供任何额外信息的数字,当你看到它时,会感到“莫名其妙”。因此,判定一个数字是否是magic number的依据,是它的出现是否能提供足够的信息让你理解其所指代的行为和场景。

    数据目录表

    数据目录表开始于一个32PE头的96个偏移量处和64PE头的112个偏移量处。数据目录表中的每一个入口都包括了RVA,以及这个独特的数据目录表所描述的表或字符串的大小;这些信息由操作系统使用。数据目录表入口是一个定义在Winnt.h中的8字节结构,如下:

    typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD   VirtualAddress;

    DWORD   Size;

    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

    然而,第一个字段,命名为VirtualAddress的那个,不是一个虚拟地址,而是一个RVA。在这个表给出的RVA并没有必要指向一个section的起始部分,而这个包含特殊表的section也没有必要有特殊的名称。第二个字段是字节大小。

    在数据目录表中定义了16种数据目录表:

    1. 导出型目录表地址和大小。这个表包括了4种其它的表的信息,这些表保存了描述PE文件的非托管导出型数据。在这些托管的编译器中,只有VC++链接器和ILAsm是可以暴露托管方法的,它们被托管的PE文件导出为非托管的导出,由非托管的调用器使用。参见第18章获取更多细节。
    2. 导入型目录表地址和大小:这个表包括了由PE文件使用的非托管导入型数据。在这些托管的编译器中,只有VC++链接器才很需要这个表,导入这个非托管的外部方法,这个方法使用在非托管本地代码中,而这些代码则内嵌在当前的托管PE文件中。其他编译器——包括IL编译器,并不会内嵌这些非托管的本地代码到托管的PE文件中,因此由这些编译器生成的文件的导入表包含着一个单一的入口,而这就是CLR的入口函数。
    3. 资源表地址和大小:这个表包含内嵌在PE文件中的非托管资源,托管资源并不是这个数据的一部分。
    4. 异常表地址和大小:这个表只包含非托管的异常信息。
    5. 证书表地址和大小:这个地址入口指向一个由属性证书(attribute certificate)组成的表(用于文件验证),这个表不会作为映像文件的一部分被加载到内存。同样的,这个入口的第一个字段是一个文件指针而不是一个RVA。这个表的每个入口包括了一个4字节的文件指针,指向各自的属性证书,并具有4字节的大小。
    6. 基本重定位(Base Relocation)地址和大小:这将在本章后面一些详细讨论,参见“重定位”章节。
    7. 调试数据地址和大小:一个托管的PE文件并没有携带内嵌的调试数据;调试数据发布在一个PDB文件中。因此这种数据目录也是全都为0的,或者指向一个类型为2IMAGE_DEBUG_TYPE_CODEVIEW)的单一的30字节调试目录的入口,这将依次指向一个CodeView样式的头,包括着指向PDB文件的路径。IL编译器和C#VB.NET编译器将这些数据发布到.text区段中。
    8. 架构数据地址和大小:特定于架构的数据。这个数据目录(全都设置为0)没有用于I386IA64AMD64架构。
    9. 全局指针:存储在全局指针寄存器的RVA值。这个大小必须被设置为0。如果目标架构没有使用全局指针的概念,这个数据目录就全都被设置为0(例如I386AMD64)。
    10. TLS地址和大小:在托管编译器中,只有VC++链接器和IL编译器能够生成这些使用了TLSthread storage data)数据的代码。
    11. 加载配置表地址和大小:特定于Window NT家族操作系统的数据。(例如,GlobalFlag值)
    12. 绑定导入型表地址和大小:这个表是一个由绑定导入型描述符组成的数组,它们中的每一个都描述了一个DLL。这个映像与DLL在创建映像的时候密切相关。描述符还携带了绑定的时间戳,同时如果这种绑定是现时的,这个OS加载器将这些绑定设置为API导入的一个捷径。否则,加载器忽视这些绑定并通过这个导入表解决这些输入型的API
    13. 导入型地址表地址和大小:这个表(IAT)会在导出目录表中被引用到(数据目录1)。
    14. 延迟导入型描述符地址和大小:包括一个32ImgDelayDescr结构的数组,每个结构描述了一个延迟加载的导入。延迟加载的导入是这样一些DLL,它们被描述为隐式的导入,而被加载为显示的导入(通过对LoadLibrary这个API的调用)。动态库的延迟加载是按需执行的——在第一次调用这个DLL的时候。这就不同于隐式的导入,后者在导入的可执行体初始化的时候就立即被加载。
    15. CLR头地址和大小:CLR头结构将会在这一章的后面详细描述(参见“CLR头”)。
    16. 保留的:全都设置为0

    Section

    Section头的表必须紧跟在PE头后面。由于没有文件头直接地指向这个Section表,这个表的位置被计算为这些文件头的全部大小加上1

    COFF头的NumberOfSections字段,定义了section头这个表中入口的数量。Section头在这个表中的索引是从0开始的,伴随着由链接器定义的section顺序。这些Section按照Section头表中定义的顺序,一个接一个地连续地存放,(正如你所知道的那样)起始RVA对齐到PE头的SectionAlignment字段所指定的值。

    Section头是定义在Winnt.h中的一个40字节的结构体,如下:

    typedef struct _IMAGE_SECTION_HEADER {
         BYTE    Name[
    8];
         union {
         DWORD   PhysicalAddress;
         DWORD   VirtualSize;
    } Misc;
         DWORD   VirtualAddress;
         DWORD   SizeOfRawData;
         DWORD   PointerToRawData;
         DWORD   PointerToRelocations;
         DWORD   PointerToLinenumbers;
         WORD    NumberOfRelocations;
         WORD    NumberOfLinenumbers;
         DWORD   Characteristics;
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

        这些字段包含在IMAGE_SECTION_HEADER结构中,如下:

    Name8字节ASCII字符串):表示section的名称。Section名称开始于一个点(例如,.reloc)。如果Section名称包含正好8个字符,这个空的休止符就会被省略。如果Section名称少于8个字符,这个名称的数组就会以空字符来填充。映像文件不能有多于8个字符的section名称。然而,在对象文件中,Section名称可以再长一些。(想象一个冗长的文件生成器发布一个名为.myownsectionnobodyyelsecouldevergrokSection。)在这种情形中,名称被放置在字符串表中,而字段包括了字符“/”在第一个字节中,紧跟着是一个ASCII字符串——包括了在字符串表中相应偏移量的一个十进制表示值。

    PhysicalAddress/ VirtualSize4字节无符号整型):在映像文件中,这个字段保存了这一section中的代码或数据的准确(未对齐的)字节大小。

    VirtualAddress4字节无符号整型):不管它的名称是什么,这个字段保存了section开始部分的RVA

    SizeOfRawData4字节无符号整型):在一个映像文件中,这个字段保存了磁盘上初始数据的字节大小,将大量在PE头中详细指出的FileAlignment值聚拢起来。如果SizeOfRawData小于VirtualSize,这个section的剩余部分被空字节填充——当它在内存上展开的时候。

    PointerToRawData4字节无符号整型):这个字段保存了指向Section的第一页的一个文件指针。在映像文件中,这个值应该是大量在PE头文件中详细指定的FileAlignment值。

    PointerToRelocations4字节无符号整型):这是一个指向Section的重定位入口起始位置的文件指针。在映像文件中,这个字段不再使用并应该被设置为0

    PointerToLinenumbers4字节无符号整型):这个字段保存了指向Section的行号入口起始位置的一个文件指针。在托管PE文件中,COFF行号被剥去了,而这个字段必须被设置为0

    NumberOfRelocations2字节无符号整型):在托管的映像文件中,这个字段应该被设置为0

    NumberOfLinenumbers2字节无符号整型):在托管的映像文件中,这个字段应该被设置为0

    Characteristics4字节无符号整型):这个字段详细指明了映像文件的特性,并保存了这些二进制标记的位或运算值,如表4-5所描述。

    这些section的特性标记定义在Winnt.h中。其中一些标记是保留的,而一些是只相对于对象文件的。表4-5列出了这些对PE文件有效的标记。所有标记的名称以IMAGE_SCN开始,我将会和通常一样将其忽略;换句话说,IMAGE_SCN_SCALE_INDEX将会变成_SCALE_INDEX

    4-5 PE文件中Section的特性标记

    标记

    描述

    _SCALE_INDEX

    0x00000001

    TLS描述符的表索引是依比例决定的。

    _CNT_CODE

    0x00000020

    Section包括了可执行的代码。在IL编译器生成的PE文件中,只有.text这个section能够携带这种标记。

    _CNT_INITIALIZED_DATA

    0x00000040

    Section包括了初始化的数据。

    _CNT_UNINITIALIZED_DATA

    0x00000080

    Section包括了未初始化的数据。

    _LNK_INFO

    0x00000200

    Section包括了评论或一些其它类型的辅助信息。

    _NO_DEFER_SPEC_EXC

    0x00004000

    在这个sectionTLBtranslation look aside buffer)入口中,重新设置暂定的异常处理位。

    _LNK_NRELOC_OVFL

    0x01000000

    Section包括扩展的重定向。

    _MEM_DISCARDABLE

    0x02000000

    Section可以按需被废弃

    _MEM_NOT_CACHED

    0x04000000

    Section能够被缓存。

    _MEM_NOT_PAGED

    0x08000000

    Section不能被分页。

    _MEM_SHARED

    0x10000000

    Section可以在内存中共享。

    _MEM_EXECUTE

    0x20000000

    Section可以作为代码被执行。在IL编译器生成的PE文件中,只有.text这个section能够携带这种标记。

    _MEM_READ

    0x40000000

    Section可以被读取。

    _MEM_WRITE

    0x80000000

    Section可以被写入。在由IL编译器生成的PE文件中,只有.sdata.tls 这些section能够携带这种标记。

    下面的标记是不允许出现在section的托管文件中的:IMAGE_SCN_SCALE_INDEXIMAGE_SCN_NO_DEFER_SPEC_EXCIMAGE_SCN_LNK_NRELOC_OVFLIMAGE_SCN_MEM_SHARED

    IL编译器在PE文件中生成下面的section

    .text:一个只读的section,包括了CLR头、元数据、IL代码、托管异常处理信息以及资源

    .sdata:一个可读写的section,包括了数据

    .reloc:一个只读的section,包括了重定位

    .rsrc:一个只读的section,包括了非托管的资源

    .tls:一个可读写的section,包括了TLS数据

     

  • 相关阅读:
    用 ArcMap 发布 ArcGIS Server FeatureServer Feature Access 服务 PostgreSQL 版本
    ArcMap 发布 ArcGIS Server OGC(WMSServer,MapServer)服务
    ArcScene 创建三维模型数据
    ArcMap 导入自定义样式Symbols
    ArcMap 导入 CGCS2000 线段数据
    ArcMap 导入 CGCS2000 点坐标数据
    ArcGis Server manager 忘记用户名和密码
    The view or its master was not found or no view engine supports the searched locations
    python小记(3)操作文件
    pytest(2) pytest与unittest的区别
  • 原文地址:https://www.cnblogs.com/Jax/p/1259492.html
Copyright © 2011-2022 走看看