可移植性可执行文件 扩展名
.acm , .ax , .cpl , .dll , .drv , .efi , .exe , .mui , .ocx , .scr , .sys , .tsp
互联网 媒体类型
application/vnd.microsoft.portable-executable
开发者 Microsoft 格式类型 二进制 可执行文件 、目标代码 、函式庫 扩展自 DOS MZ可执行文件 COFF
可移植性可执行文件 (英语 :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的开始位置。
PE签名是一个4字节的项:字符P和E,随后是2个空字节。
在Winnt.h中定义的标准COFF头的结构_IMAGE_FILE_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字节无符号整型。这个字段指定了映像文件的特征,并保存了这些二进制标志的位或运算值。
常见的节:[1] (页面存档备份 ,存于互联网档案馆 )
.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)为该函数的绝对行号,然后加上函数内的相对行号.
参见
外部链接