可移植可执行

可移植性可执行文件（英语：Portable Executable，缩写为PE）是一种用于可执行文件、目标文件和动态链接库的文件格式，主要使用在32位和64位的Windows操作系统上。“可移植的”是指该文件格式的通用性，可用于许多种不同的操作系统和体系结构中。PE文件格式封装了Windows操作系统加载可执行程序代码时所必需的一些信息。这些信息包括动态链接库、API导入和导出表、资源管理数据和线程局部存储数据。在Windows NT操作系统中，PE文件格式主要用于EXE文件、DLL文件、.sys（驱动程序）和其他文件类型。可扩展固件接口（EFI）技术规范书中说明PE格式是EFI环境中的标准可执行文件格式。开头为DOS头部。

PE格式是由Unix中的COFF格式修改而来的。在Windows开发环境中，PE格式也称为PE/COFF格式。

在Windows NT操作系统中，PE格式目前支持IA-32、IA-64和x86-64（AMD64/Intel64）的指令系统。在Windows 2000之前，Windows NT还支持MIPS、Alpha和PowerPC的指令系统。由于Windows CE也在使用PE文件格式，因此PE仍然支持几种不同型号的MIPS、ARM（包括Thumb）和SuperH指令系统。

PE文件格式的主要竞争对手是可执行与可链接格式（ELF）（使用于Linux和大多数Unix版本中）和Mach-O（使用于Mac OS X中）。
布局结构
文件头部
MS-DOS头与MS-DOS stub
MS-DOS头和MS-DOS Stub只存在于映像文件中。在MS-DOS下运行该应用程序，默认的Stub会打印出消息"This program cannot be run in DOS mode"。 MS-DOS头的偏移位置0x3C处包括指向PE签名的文件指针，用于定位PE的开始位置。
COFF file header
PE签名是一个4字节的项：字符P和E，随后是2个空字节。

在Winnt.h中定义的标准COFF头的结构_IMAGE_FILE_HEADER
optional header
Object文件不含这部分，所以称为“可选头”。

在Winnt.h中定义的optional header的结构_IMAGE_OPTIONAL_HEADER
Standard COFF Fields 
Windows Specific Fields 
Data Directories
Export Table: .edata Section
Import Table: .idata Section.
Resource Table: .rsrc Section.
Exception Table: .pdata Section.
Certificate Table: 指向Attribute Certificate Table(由用于文件验证的属性证书组成的表). 属性证书不会作为映像文件的一部分加载到内存。同样，这个地址项的第一个字段是文件指针而不是RVA。属性证书表的每个项都包括了一个4字节的文件指针，指向各自的属性证书，并具有4字节的大小。
Base Relocation Table: .reloc Section
Debug: .debug Section. 调试数据输出到PDB文件中，因此这个Data directory要么全都是0，或者只指向一个类型为2（IMAGE_DEBUG_TYPE_CODEVIEW）、30个字节的调试目录项，而这个项又指向一个包括PDB文件路径在内的、CodeReview风格的头。 
Architecture: 保留,必须为0
Global Ptr: 在全局指针寄存器中存储的RVA值。该结构的大小必须设置为0。如果目标架构没有使用全局指针的概念，这个数据目录就全都设置为0（例如I386或AMD64）。
TLS Table: .tls Section.
Load Config Table: Load Configuration Structure. 特定于Window NT家族操作系统的数据（例如，GlobalFlag值）。 
Bound Import: 由绑定导入描述符组成的数组，其中的每个描述符都描述了一个DLL。在创建映像的时候，该映像与DLL绑定在一起。描述符中还携带这些绑定的时间戳，如果这些绑定是最新的，那么操作系统加载程序就会使用这些绑定以作为API导入的"快捷方式"。否则，加载程序就会忽视这些绑定并通过导入表解析这些导入API。 
IAT: Import Address Table. 这个表（IAT）会在导出目录表（第1个数据目录）中被引用。
Delay Import Descriptor: Delay-Load Import Tables. 包括一个由32位ImgDelayDescr结构体组成的数组，每个结构体都描述了延迟加载的导入。延迟加载（delay load）的导入是这样一些DLL，它们被描述为隐式的导入，而作为显式的导入进行加载（通过对LoadLibrary这个API的调用）。动态库的延迟加载是根据需要--在第一次调用这样一个DLL的时候执行的。这与隐式的导入不同，后者在导入的可执行体初始化的时候就立即被加载。 
CLR Runtime Header: .cormeta Section (Object Only).
保留: 必须全为0 

节
节（section）是PE文件中存储数据的划分。通常，加载到内存后，同一节的数据具有相同的内存访问属性（可读/可写/可执行等）。

节表紧随文件头部。由于没有文件头具有直接指向节表的指针，所以节表的位置被计算为文件头的大小再加上1。

COFF头的NumberOfSections字段，定义了节表中节的数量。在节头表中，节的索引是基于1，并且节的顺序是由链接器确定。节按照节表中定义的顺序连续存放，起始RVA根据PE头的SectionAlignment字段值进行对齐。

节头是一个定义在Winnt.h中的40字节的结构IMAGE_SECTION_HEADER:
Name: 8字节的ASCII字符串。表示节的名称。节名称开始于一个点号（例如，.reloc）。如果节名称正好包含8个字符，就会省略null终结符。如果节名称小于8个字符，就会使用null字符来填充数组Name。映像文件不能使用超过8个字符的节名称。然而，在对象文件中，节名称可以更长一些, 在这种情况下，名称被放置在字符串表中，并且字段的第一个字节中包括了字符"/"，随后是一个ASCII字符串，包含有字符串表中相应偏移量的十进制表示。
VirtualSize: 4字节无符号整型的Union。在映像文件中，这个字段保存了节中的代码或数据装入内存的实际（未对齐的）字节大小。如果改制大于本节的SizeOfRawData, 本节由0填充. 对于object文件该节为0. 
VirtualAddress: 4字节无符号整型。在映像文件中，为本节装入内存后相对于image base的偏移.
SizeOfRawData: 4字节无符号整型。在映像文件中，这个字段保存了磁盘上需要初始化的数据的字节大小，向上舍入为FileAlignment的倍数。如果SizeOfRawData小于VirtualSize，那么装入内存时使用0来填充节的剩余部分。对于不需要初始化的数据,这个字段的值为0.
PointerToRawData: 4字节无符号整型。保存了指向节的第一页的文件指针。在映像文件中，向上舍入为FileAlignment的倍数。 对于不需要初始化的数据,这个字段的值为0.
PointerToRelocations: 4字节无符号整型。这是一个文件指针，指向了节的重定位项的起始位置。在映像文件中，不使用这个字段，应该设置为0。 
PointerToLinenumbers: 4字节无符号整型。这个字段保存了一个文件指针，指向节的行号项的起始位置。在映像文件中, 该字段已经过时了,应设置为0
NumberOfRelocations: 2字节无符号整型。节的重定位项的数量. 在映像文件中，设置为0。 
NumberOfLinenumbers: 2字节无符号整型. 节的行号条目的数量. 在映像文件中, 该字段已经过时了,应设置为0
Characteristics: 4字节无符号整型。这个字段指定了映像文件的特征，并保存了这些二进制标志的位或运算值。

常见的节： 
.text: 只读的节，包括了CLR头、元数据、IL代码、托管异常处理信息以及资源。
.sdata: 可读写的节，与GP相关的已初始化数据
.reloc: 只读的节，基址重定位表包含了镜像中所有需要重定位的内容。NT头中的数据目录中的Base Relocation Table（基址重定位表）域给出了基址重定位表所占的字节数。基址重定位表被划分成许多块，每一块表示一个4K页面范围内的基址重定位信息，它必须从32位边界开始。
.rsrc: 只读的节，包括了非托管的资源目录。
.tls: 可读写的节，包括了TLS数据。
 .bss(Block Start with Symbol)：未初始化全局变量
 .textbss：未初始化的可执行代码节。也即这个节具有可执行属性，在PE文件中未实际占用硬盘文件空间，在加载到内存时未填充数据。这用于支持Visual Studio在Debug模式下动态编译代码功能，也即“Edit and Continue”功能。例如，一个函数在Visual Studio中设断点或单步调试，这时该函数在.text节中；修改源代码后继续执行这个函数，Visual Studio会重新编译这个函数并把它加载到.textbss节中的未利用地址空间（原为padding的部分），并修改对这个函数调用的跳转（jmp）表条目以及当前EIP寄存器值。
 .data 代码节
 .edata 导出表
 .idata 导入表
 .idlsym 包含已注册的SEH，它们用以支持IDL属性
 .pdata 64位程序的异常处理器的地址表  NT头中的Exception Table（异常表）域指向它。
 .rdata 只读的已初始化数据（用于常量）
 .sbss 与GP相关的未初始化数据 
 .srdata 与GP相关的只读数据
 .text 默认代码节
 符号表 
COFF File Header中的字段PointerToSymbolTable给出了符号表地址，字段NumberOfSymbols给出了符号表条目数量。对于映象文件，COFF调试信息是过时的，因此该字段为空。 
typedef struct {
  union {
    char e_name[E_SYMNMLEN];
    struct {
      unsigned long e_zeroes;
      unsigned long e_offset;
    } e;
  } e;
  unsigned long e_value;
  short e_scnum;
  unsigned short e_type;
  unsigned char e_sclass;
  unsigned char e_numaux;
} SYMENT;
符号表包含了所有符号与元符号的条目。

e.e_name - 内联的符号名字（小于等于8字节）。
e.e.e_zeroes - flag，用于判断是内联符号名字还是在字符串表中的符号名字
e.e.e_offset - 字符串表中的符号名字的偏移值
e_value - 符号的值。如表示函数的符号，值为函数的地址。还有相对于%ebp的变量地址、寄存器变量的寄存器号、结构成员相对偏移值、枚举成员值、struct/union/enum的尺寸
e_scnum - 符号所属的节的编号。节表中的节从1开始编号。节号0表示未定义（外部）符号；-1表示绝对符号（e_value是个常量而非地址）；-2表示调试符号。
e_type - 符号类型。由基类型与派生类型组成，如“指针到整型”。   
e_sclass - 存储类。C_FCN，值101，".bf"或".ef" - 函数的开始/结束。C_FILE，值103，表示函数名。
e_numaux - 辅助条目（18个字节长）的数量（通常为0或1）。符号表中的符号允许紧随其后有额外的辅助条目。

 字符串表 
字符串表保存长度大于8的符号。字符串表紧随符号表。首先读出4个字节，为符号表的字节长度。随后的4个字节总为0。到符号表的引用地址总是从这4个0字节开始。示例代码如下：
    int i;
    char *s;
    read(fd, &i, 4);
    s = (char *)malloc(i);
    memset(s, 0, 4);
    read(fd, s+4, i-4);
 行号
typedef struct {
  union {
    unsigned long l_symndx;  /* function name symbol index */
    unsigned long l_paddr;   /* address of line number     */
  } l_addr;
  unsigned short l_lnno;     /* line number                */
} LINENO;

每个可执行的节有自己的行号表. 节中的每个函数独立编号,函数的首行(有左花括号的行)编号为1. 每个函数在行号表中有一个条目, 其l_lnno为0, l_symndx为符号表中该函数. 其后是该函数每一行的条目, l_lnno甚至为该函数内的行号(1..N), l_paddr设置为该行的第一条汇编代码的地址. 

为得到绝对行号, 需要在符号表中找到该函数的"beginning of function" symbol (类型C_FCN)为该函数的绝对行号,然后加上函数内的相对行号.

参见

 EXE格式
 可執行與可鏈接格式
 Mach-O
 A.out

 可执行文件压缩
 ar (Unix)


外部链接
Microsoft Portable Executable and Common Object File Format Specification (latest edition, OOXML format)
Microsoft Portable Executable and Common Object File Format Specification (1999 edition, .doc format)
The original Portable Executable article  by Matt Pietrek (MSDN Magazine, March 1994)
Part I. An In-Depth Look into the Win32 Portable Executable File Format by Matt Pietrek (MSDN Magazine, February 2002)
Part II. An In-Depth Look into the Win32 Portable Executable File Format by Matt Pietrek (MSDN Magazine, March 2002)
The .NET File Format by Daniel Pistelli
Ero Carrera's blog describing the PE header and how to walk through
PE Internals provides an easy way to learn the Portable Executable File Format 

Category:可执行文件格式
Category:Windows管理