PE/COFF 可执行格式
先来看一下 COFF
COFF
COFF 几乎和 ELF 一样,也是由文件头以及后面的若干个段组成,再加上符号表和调试信息构成 COFF 的基本结构。COFF 文件头包含了两个部分,一个是描述文件总体结构的映像头,另一个是描述该文件各段属性的段表。后面跟着代码段、数据段等,最后还有符号表。几乎都与 ELF 对于。
冷知识:PE 文件装载时直接映射到进程的虚拟空间中运行,作为进程虚拟空间的映像,所以 PE 很多时候又称为映像文件。
COFF 映像头
它是一个 "IMAGE_FILE_HEADER" 的结构与 ELF 中的 "Elf32_Ehdr" 结构的作用相同。
包含内容:
- 目标机器类型
- PE 包含的段的数量
- PE 文件创建时间
- PE 中符号表的位置
- Optional Header 的大小,这个只存在于 PE 不存在于COFF
COFF 段表
COFF 的段表是一个 "IMAGE_SECTION_HEADER" 结构的数组,数组里每一个元素代表一个段,元素个数就是映像头中包含的段数。
每个段的属性包括:
- 段名
- 物理地址
- 虚拟地址
- 原始数据大小
- 段在文件中的位置
- 段的重定位表在文件中的位置
- 段的行号表在文件中的位置
- 标志位
链接指示信息
COFF 中存在两个 ELF 中不存在的段 ".drectve",".debug$S" 段
.drectve
".drectve"段的内容是编译器传递给链接器的指令,编译器通过这个段标志位的组合告诉链接器如何链接这个目标文件。
.debug
COFF 中所有以 ".debug" 开始的段都是包含的调试信息。
- .debug$S 符号相关调试信息
- .debug$P 预编译头文件相关调试信息
- .debug$T 类型相关调试信息
".debug" 段的格式被定义在 PE 格式文件标准中。
PE
PE 是 Win32 平台的标准可执行文件格式,但是在 Windwos 平台,Visual C++ 编译器生产的目标文件仍然是 COFF 。PE 与 ELF 同根同源,均由 COFF 文件格式发展而来。
所以 PE 与 ELF 基本相同,都采用了基于段的格式。在 PE/COFF 文件中,至少包含一个代码段,通常称为 ".code" ,数据段称为 ".data",而不同的编译器产生的目标文件段名也不同,但都大同小异,而且段名只有提示作用,除了利用链接脚本控制链接时有一定作用以为,并没有太大的实际意义。
PE 与 COFF 的区别
PE 是基于 COFF 的扩展,在 COFF 的基础上多了几个结构,最主要的变化有两个:
- 一、文件开始部分不是 COFF 的映射头,而是 DOS MZ 可执行文件格式的文件头和代码桩。
- 二、原 COFF 中的 "IMAGE_FILE_HEADER" 部分扩展成了 PE 文件头结构 "IMAGE_NT_HEADERS",这个结构包括了原来的映射头,另外增加了 PE 扩展头部结构 "IMAGE_OPTION_HEADER"
PE 数据目录
Windows 系统装载 PE 可执行文件时,经常需要很快找到导入表、导出表、重定位表等所需的数据结构。而这类数据的位置属性等都保存在一个被称为数据目录的结构里。就是前面的 "IMAGE_OPTIONAL_HEADER" 结构中的 "IMAGE_DATA_DIRECTORY" 数组。这个数组每一个元素都对应一个包含一定含义的表,例如导入表、资源表、异常表、重定位表、调试信息表等。这些表多数都与装载和 DLL 动态链接相关,跟静态链接没什么关系。