PE 格式

本规范描述 Windows 系列操作系统下可执行(映像)文件和目标文件的结构。 这些文件分别称为可移植可执行文件 (PE) 和通用对象文件格式 (COFF) 文件。

注意

提供本文档的目的是帮助开发适用于 Windows 的工具和应用程序,但不能保证是一个覆盖各个方面的完整规范。 Microsoft 保留未经通知便更改此文档的权利。

Microsoft 可移植可执行文件和通用对象文件格式规范的此修订版将取代此规范的所有以前修订版。

一般概念

本文档指定了 Microsoft Windows 系列操作系统下可执行(映像)文件和目标文件的结构。 这些文件分别称为可移植可执行文件 (PE) 和通用对象文件格式 (COFF) 文件。 名称“可移植可执行文件”是指格式并不特定于体系结构这一事实。

下表描述了在整个规范中出现的某些概念:

名称 描述
属性证书
用于将可验证声明与映像关联的证书。 许多不同的可验证声明可以与文件相关联;其中最有用的一个声明是软件制造商的声明,此声明指示映像的消息摘要应该是什么。 消息摘要类似于检验和,只是很难伪造。 因此,很难修改文件以使其具有与原始文件相同的消息摘要。 可以使用公钥或私钥加密方案来验证声明是否是由制造商发出的。 本文档描述了有关属性证书的详细信息,但不允许将其插入到图像文件中。
日期/时间戳
在 PE 或 COFF 文件中的多个位置用于不同目的的戳记。 在大多数情况下,每个标记的格式与 C 运行时库中的时间函数使用的格式相同。 有关异常,请参见调试类型中 IMAGE_DEBUG_TYPE_REPRO 的说明。 如果戳记值为 0 或 0xFFFFFFFF,则它不表示实际或有意义的日期/时间戳。
文件指针
链接器(对于目标文件)或加载器(对于映像文件)处理之前文件本身中项的位置。 换句话说,这是存储在磁盘上的文件内的位置。
链接器
随 Microsoft Visual Studio 一起提供的链接器引用。
对象文件
作为链接器输入提供的文件。 链接器生成一个映像文件,而此映像文件又用作加载器的输入。 术语“目标文件”并不一定意味着与面向对象的编程有任何联系。
已保留,必须为 0
字段的描述,指示该字段的值对于生成器来说必须为零,而使用者必须忽略该字段。
相对虚拟地址 (RVA)
在映像文件中,这是项目加载到内存并从中减去映像文件基地址后的地址。 项目的 RVA 几乎总是与其在磁盘上文件中的位置(文件指针)不同。
在目标文件中,RVA 的意义不大,因为未分配内存位置。 在这种情况下,RVA 将是一个段内的地址(此表后面将进行描述),稍后在链接期间会对此地址应用重定位。 为简单起见,编译器应只将每个部分中的第一个 RVA 设置为零。
section
PE 或 COFF 文件中代码或数据的基本单位。 例如,目标文件中的所有代码都可以组合在单个部分中,或者(取决于编译器行为)每个函数都可以占用自己的部分。 部分越多,文件开销就越大,但链接器能够更有选择性地链接代码。 一个部分类似于 Intel 8086 体系结构中的段。 一个部分中的所有原始数据都必须连续加载。 此外,映像文件可以包含多个具有特殊用途的部分,例如 .tls 或 .reloc 。
虚拟地址 (VA)
与 RVA 相同,只不过不减去映像文件的基址。 该地址称为 VA,因为 Windows 会为每个进程创建一个独立于物理内存的不同 VA 空间。 对于几乎所有目的,VA 应只被视为一个地址。 VA 不如 RVA 那么可预测,因为加载器可能不会在其首选位置加载映像。

概述

以下列表描述了 Microsoft PE 可执行格式,映像标头的基础位于顶部。 从 MS-DOS 2.0 兼容 EXE 头到 PE 头之前未使用的部分是 MS-DOS 2.0 部分,此部分仅用于 MS-DOS 兼容性。

  • MS-DOS 2.0 兼容 EXE 标头

  • unused

  • OEM 标识符

    OEM 信息

    PE 标头偏移量

  • MS-DOS 2.0 存根程序和重定位表

  • unused

  • PE 标头(在 8 字节边界上对齐)

  • 节标头

  • 映像页面:

    导入信息

    导出信息

    基址重定位

    资源信息

以下列表描述了 Microsoft COFF 对象模块格式:

  • Microsoft COFF 标头

  • 节标头

  • 原始数据:

    code

    数据

    调试信息

    重定位

文件标头

PE 文件头由 Microsoft MS-DOS 存根、PE 签名、COFF 文件头和可选头组成。 COFF 对象文件标头由 COFF 文件标头和可选标头组成。 在这两种情况下,节标头都紧跟在文件标头后面。

MS-DOS 存根(仅限映像)

MS-DOS 存根是在 MS-DOS 下运行的有效应用程序。 它放置在 EXE 映像的前面。 链接器在此处放置默认存根,当映像在 MS-DOS 中运行时,此存根会输出消息“此程序不能在 DOS 模式下运行”。 用户可以使用 /STUB 链接器选项指定不同的存根。

在位置 0x3c,存根具有 PE 签名文件偏移量。 此信息使 Windows 能够正确执行映像文件,即使此文件具有 MS-DOS 存根也不例外。 链接期间,此文件偏移量放在位置 0x3c。

签名(仅限映像)

MS-DOS 存根之后,在偏移量 0x3c 处指定的文件偏移处,是一个 4 字节签名,该签名将该文件标识为 PE 格式映像文件。 此签名为“PE\0\0”(字母“P”和“E”后跟两个 null 字节)。

COFF 文件标头(对象和映像)

在对象文件的开头,或紧接在映像文件签名之后,是以下格式的标准 COFF 文件头。 请注意,Windows 加载器将部分数限制为 96。

Offset 大小 字段 说明
0
2
设备
标识目标计算机类型的数字。 有关详细信息,请参阅目标计算机类型
2
2
NumberOfSections
部分数目。 这指示部分表的大小,该表紧跟在标头之后。
4
4
TimeDateStamp
自 1970 年 1 月 1 日 00:00 起的秒数的低 32 位(C 运行时 time_t 值),指示文件的创建时间。
8
4
PointerToSymbolTable
COFF 符号表的文件偏移量;如果没有 COFF 符号表,则为零。 映像的此值应为零,因为 COFF 调试信息已被弃用。
12
4
NumberOfSymbols
符号表中的项数。 此数据可用于查找紧跟在符号表后面的字符串表。 映像的此值应为零,因为 COFF 调试信息已被弃用。
16
2
SizeOfOptionalHeader
可选标头的大小,它是可执行文件所必需的,但对象文件不需要它。 对于对象文件,此值应为零。 有关标头格式的说明,请参阅可选标头(仅限图像)
18
2
特征
指示文件属性的标志。 有关特定标志值,请参阅特征

计算机类型

“计算机”字段具有用于指定 CPU 类型的以下值之一。 映像文件只能在指定计算机或模拟指定计算机的系统上运行。

常量 Value 说明
IMAGE_FILE_MACHINE_UNKNOWN
0x0
假定此字段的内容适用于任何计算机类型
IMAGE_FILE_MACHINE_ALPHA
0x184
Alpha AXP,32 位地址空间
IMAGE_FILE_MACHINE_ALPHA64
0x284
Alpha 64,64 位地址空间
IMAGE_FILE_MACHINE_AM33
0x1d3
Matsushita AM33
IMAGE_FILE_MACHINE_AMD64
0x8664
X64
IMAGE_FILE_MACHINE_ARM
0x1c0
ARM little endian
IMAGE_FILE_MACHINE_ARM64
0xaa64
ARM64 little endian
IMAGE_FILE_MACHINE_ARMNT
0x1c4
ARM Thumb-2 little endian
IMAGE_FILE_MACHINE_AXP64
0x284
AXP 64 (与 Alpha 64 相同)
IMAGE_FILE_MACHINE_EBC
0xebc
EFI 字节代码
IMAGE_FILE_MACHINE_I386
0x14c
Intel 386 或更高版本的处理器和兼容的处理器
IMAGE_FILE_MACHINE_IA64
0x200
Intel Itanium 处理器系列
IMAGE_FILE_MACHINE_LOONGARCH32
0x6232
LoongArch 32 位处理器系列
IMAGE_FILE_MACHINE_LOONGARCH64
0x6264
LoongArch 64 位处理器系列
IMAGE_FILE_MACHINE_M32R
0x9041
Mitsubishi M32R little endian
IMAGE_FILE_MACHINE_MIPS16
0x266
MIPS16
IMAGE_FILE_MACHINE_MIPSFPU
0x366
将 MIPS 与 FPU 结合使用
IMAGE_FILE_MACHINE_MIPSFPU16
0x466
将 MIPS16 与 FPU 结合使用
IMAGE_FILE_MACHINE_POWERPC
0x1f0
Power PC little endian
IMAGE_FILE_MACHINE_POWERPCFP
0x1f1
支持浮点的 Power PC
IMAGE_FILE_MACHINE_R4000
0x166
MIPS little endian
IMAGE_FILE_MACHINE_RISCV32
0x5032
RISC-V 32 位地址空间
IMAGE_FILE_MACHINE_RISCV64
0x5064
RISC-V 64 位地址空间
IMAGE_FILE_MACHINE_RISCV128
0x5128
RISC-V 128 位地址空间
IMAGE_FILE_MACHINE_SH3
0x1a2
Hitachi SH3
IMAGE_FILE_MACHINE_SH3DSP
0x1a3
Hitachi SH3 DSP
IMAGE_FILE_MACHINE_SH4
0x1a6
Hitachi SH4
IMAGE_FILE_MACHINE_SH5
0x1a8
Hitachi SH5
IMAGE_FILE_MACHINE_THUMB
0x1c2
Thumb
IMAGE_FILE_MACHINE_WCEMIPSV2
0x169
MIPS little-endian WCE v2

特征

“特征”字段包含指示对象或映像文件属性的标志。 当前定义了以下标志:

标志 说明
IMAGE_FILE_RELOCS_STRIPPED
0x0001
纯映像、Windows CE 和 Microsoft Windows NT 及更高版本。 这表示该文件不包含基填重定位,因此必须加载到其首选基址。 如果基址不可用,则加载程序将报告错误。 链接器的默认行为是从可执行文件 (EXE) 文件中去除基址重定位。
IMAGE_FILE_EXECUTABLE_IMAGE
0x0002
纯映像。 这表示映像文件有效并且可以运行。 如果未设置此标志,则表示有链接器错误。
IMAGE_FILE_LINE_NUMS_STRIPPED
0x0004
COFF 行号已删除。 此标志已被弃用,应为零。
IMAGE_FILE_LOCAL_SYMS_STRIPPED
0x0008
本地符号的 COFF 符号表条目已删除。 此标志已被弃用,应为零。
IMAGE_FILE_AGGRESSIVE_WS_TRIM
0x0010
已过时。 主动剪裁工作集。 已针对 Windows 2000 及更高版本弃用此标志,此标志必须为零。
IMAGE_FILE_LARGE_ADDRESS_ AWARE
0x0020
应用程序可以处理 > 2 GB 地址。
0x0040
此标志将保留以供将来使用。
IMAGE_FILE_BYTES_REVERSED_LO
0x0080
Little endian:最低有效位 (LSB) 位于内存中最高有效位 (MSB) 之前。 此标志已被弃用,应为零。
IMAGE_FILE_32BIT_MACHINE
0x0100
计算机基于 32 位字体系结构。
IMAGE_FILE_DEBUG_STRIPPED
0x0200
从映像文件中删除了调试信息。
IMAGE_FILE_REMOVABLE_RUN_ FROM_SWAP
0x0400
如果映像位于可移动媒体上,请完全加载该映像并将其复制到交换文件。
IMAGE_FILE_NET_RUN_FROM_SWAP
0x0800
如果映像位于网络媒体上,请完全加载该映像并将其复制到交换文件。
IMAGE_FILE_SYSTEM
0x1000
映像文件是系统文件,而不是用户程序。
IMAGE_FILE_DLL
0x2000
图像文件是动态链接库 (DLL)。 虽然无法直接运行此类文件,但这些文件被视为几乎适用于所有用途的可执行文件。
IMAGE_FILE_UP_SYSTEM_ONLY
0x4000
该文件应仅在单处理器计算机上运行。
IMAGE_FILE_BYTES_REVERSED_HI
0x8000
Big endian:MSB 在内存中的 LSB 之前。 此标志已被弃用,应为零。

可选标头(仅限映像)

每个映像文件都有一个用于向加载程序提供信息的可选标头。 此标头是可选标头,因为某些文件(特别是对象文件)没有此标头。 对于映像文件,此标头是必需的。 对象文件可以具有可选标头,但通常此标头在对象文件中没有函数,只是为了增加其大小。

请注意,可选标头的大小不是固定的。 COFF 标头中的 SizeOfOptionalHeader 字段必须用于验证对特定数据目录的文件的探测是否未超出 SizeOfOptionalHeader。 有关详细信息,请参阅 COFF 文件标头(对象和映像)

还应该使用可选标头的 NumberOfRvaAndSizes 字段来确保对特定数据目录条目的探测不会超出可选标头。 此外,请务必验证可选标头幻数,以确保格式兼容性。

可选标头幻数确定映像是 PE32 还是 PE32+ 可执行文件。

幻数 PE 格式
0x10b
PE32
0x20b
PE32+

PE32+ 映像允许使用 64 位地址空间,同时会将映像大小限制为 2 GB。 其他 PE32+ 修改会在各自的部分中进行介绍。

可选标头本身具有三个主要部分。

偏移量 (PE32/PE32+) 大小 (PE32/PE32+) 标头部分 说明
0
28/24
标准字段
为 COFF 的所有实现(包括 UNIX)定义的字段。
28/24
68/88
特定于 Windows 的字段
用于支持 Windows(例如子系统)的特定功能的其他字段。
96/112
变量
数据目录
在映像文件中找到并由操作系统使用的特殊表(例如,导入表和导出表)的地址/大小对。

可选标头标准字段(仅限映像)

可选标头的前八个字段是为 COFF 的每个实现定义的标准字段。 这些字段包含可用于加载和运行可执行文件的常规信息。 对于 PE32+ 格式,它们保持不变。

Offset 大小 字段 说明
0
2
Magic
标识映像文件状态的无符号整数。 最常见的数字是 0x10B,它将映像文件标识为普通可执行文件。 0x107 将映像文件标识为 ROM 映像,0x20B 将映像文件标识为 PE32+ 可执行文件。
2
1
MajorLinkerVersion
链接器主版本号。
3
1
MinorLinkerVersion
链接器次要版本号。
4
4
SizeOfCode
代码(文本)段的大小,或者如果有多个部分,则是所有代码段的和。
8
4
SizeOfInitializedData
初始化数据部分的大小,或者如果有多个数据部分,则是所有此类部分的和。
12
4
SizeOfUninitializedData
未初始化数据部分 (BSS) 的大小,或者如果有多个 BSS 部分,则是所有此类部分的和。
16
4
AddressOfEntryPoint
可执行文件加载到内存中时相对于映像基址的入口点地址。 对于程序映像,这是起始地址。 对于设备驱动程序,这是初始化函数的地址。 入口点对于 DLL 是可选的。 不存在入口点时,此字段必须为零。
20
4
BaseOfCode
加载到内存中后相对于代码开头部分映像基址的地址。

PE32 包含位于 BaseOfCode 之后的此附加字段,此字段在 PE32+ 中不存在。

Offset 大小 字段 说明
24
4
BaseOfData
加载到内存中后相对于数据开头部分映像基址的地址。

可选标头 Windows 特定字段(仅限映像)

接下来的 21 个字段是 COFF 可选标头格式的扩展。 它们包含 Windows 中的链接器和加载程序所需的其他信息。

偏移量 (PE32/PE32+) 大小(PE32/PE32+) 字段 说明
28/24
4/8
ImageBase
映像加载到内存中后第一个字节的首选地址;必须是 64 K 的倍数。DLL 的默认值为 0x10000000。 Windows CE EXE 的默认值为 0x00010000。 Windows NT、Windows 2000、Windows XP、Windows 95、Windows 98 和 Windows Me 的默认值为 0x00400000。
32/32
4
SectionAlignment
各部分加载到内存中时的对齐值(以字节为单位)。 它必须大于或等于 FileAlignment。 默认值为体系结构的页面大小。
36/36
4
FileAlignment
用于使映像文件中各部分的原始数据一致的对齐系数(以字节为单位)。 该值应为 2 的幂次方,介于 512 和 64K 之间(含)。 默认值为 512。 如果 SectionAlignment 小于体系结构的页面大小,则 FileAlignment 必须与 SectionAlignment 匹配。
40/40
2
MajorOperatingSystemVersion
所需操作系统的主版本号。
42/42
2
MinorOperatingSystemVersion
所需操作系统的次要版本号。
44/44
2
MajorImageVersion
映像的主版本号。
46/46
2
MinorImageVersion
映像的次要版本号。
48/48
2
MajorSubsystemVersion
子系统的主版本号。
50/50
2
MinorSubsystemVersion
子系统的次要版本号。
52/52
4
Win32VersionValue
保留,必须为零。
56/56
4
SizeOfImage
映像加载到内存中时的映像大小(以字节为单位),包括所有标头。 它必须是 SectionAlignment 的倍数。
60/60
4
SizeOfHeaders
MS DOS 存根、PE 标头和节标头的组合大小,其向上舍入到 FileAlignment 的倍数。
64/64
4
CheckSum
映像文件的校验和。 用于计算校验和的算法已合并到 IMAGHELP.DLL 中。 加载时会检查以下内容是否有效:所有驱动程序、启动时加载的任何 DLL 以及加载到关键 Windows 进程中的任何 DLL。
68/68
2
子系统
运行此映像所需的子系统。 有关详细信息,请参阅 Windows 子系统
70/70
2
DllCharacteristics
有关详细信息,请参阅此规范后面的 DLL 特征
72/72
4/8
SizeOfStackReserve
要保留的堆栈的大小。 仅提交 SizeOfStackCommit;其余部分一次提供一页,直到达到保留大小。
76/80
4/8
SizeOfStackCommit
要提交的堆栈的大小。
80/88
4/8
SizeOfHeapReserve
要保留的本地堆空间的大小。 仅提交 SizeOfHeapCommit;其余部分一次提供一页,直到达到保留大小。
84/96
4/8
SizeOfHeapCommit
要提交的本地堆空间的大小。
88/104
4
LoaderFlags
保留,必须为零。
92/108
4
NumberOfRvaAndSizes
可选标头剩余部分中数据目录项的数目。 每项都描述位置和大小。
Windows 子系统

为可选标头的“子系统”字段定义的以下值确定运行映像所需的 Windows 子系统(如果有)。

常量 Value 说明
IMAGE_SUBSYSTEM_UNKNOWN
0
未知子系统
IMAGE_SUBSYSTEM_NATIVE
1
设备驱动程序和本机 Windows 进程
IMAGE_SUBSYSTEM_WINDOWS_GUI
2
Windows 图形用户界面 (GUI) 子系统
IMAGE_SUBSYSTEM_WINDOWS_CUI
3
Windows 字符子系统
IMAGE_SUBSYSTEM_OS2_CUI
5
OS/2 字符子系统
IMAGE_SUBSYSTEM_POSIX_CUI
7
Posix 字符子系统
IMAGE_SUBSYSTEM_NATIVE_WINDOWS
8
本机 Win9x 驱动程序
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI
9
Windows CE
IMAGE_SUBSYSTEM_EFI_APPLICATION
10
可扩展固件接口 (EFI) 应用程序
IMAGE_SUBSYSTEM_EFI_BOOT_ SERVICE_DRIVER
11
具有启动服务的 EFI 驱动程序
IMAGE_SUBSYSTEM_EFI_RUNTIME_ DRIVER
12
具有运行时服务的 EFI 驱动程序
IMAGE_SUBSYSTEM_EFI_ROM
13
EFI ROM 映像
IMAGE_SUBSYSTEM_XBOX
14
XBOX
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION
16
Windows 启动应用程序。
DLL 特征

为可选标头的 DllCharacteristics 字段定义了以下值。

常量 Value 说明
0x0001
保留,必须为零。
0x0002
保留,必须为零。
0x0004
保留,必须为零。
0x0008
保留,必须为零。
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
0x0020
映像可处理高熵 64 位虚拟地址空间。
IMAGE_DLLCHARACTERISTICS_
DYNAMIC_BASE
0x0040
DLL 可以在加载时重定位。
IMAGE_DLLCHARACTERISTICS_
FORCE_INTEGRITY
0x0080
强制实施了代码完整性检查。
IMAGE_DLLCHARACTERISTICS_
NX_COMPAT
0x0100
该映像与 NX 兼容。
IMAGE_DLLCHARACTERISTICS_ NO_ISOLATION
0x0200
隔离感知,但不隔离映像。
IMAGE_DLLCHARACTERISTICS_ NO_SEH
0x0400
不使用结构化异常 (SE) 处理。 该映像中无法调用任何 SE 处理程序。
IMAGE_DLLCHARACTERISTICS_ NO_BIND
0x0800
请勿绑定此映像。
IMAGE_DLLCHARACTERISTICS_APPCONTAINER
0x1000
映像必须在 AppContainer 中执行。
IMAGE_DLLCHARACTERISTICS_ WDM_DRIVER
0x2000
WDM 驱动程序。
IMAGE_DLLCHARACTERISTICS_GUARD_CF
0x4000
映像支持控制流保护。
IMAGE_DLLCHARACTERISTICS_ TERMINAL_SERVER_AWARE
0x8000
终端服务器感知。

可选标头数据目录(仅限映像)

每个数据目录都提供 Windows 使用的表或字符串的地址和大小。 这些数据目录条目已全部加载到内存中,以便系统可以在运行时使用它们。 数据目录是具有以下声明的 8 字节字段:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

第一个字段 (VirtualAddress) 实际上是表的 RVA。 RVA 是加载表后相对于映像基址的表地址。 第二个字段提供以字节为单位的大小。 下表中列出了构成可选标头的最后一部分的数据目录。

请注意,目录数不是固定的。 在查找特定目录之前,请检查可选标头中的 NumberOfRvaAndSizes 字段。

此外,不要假定此表中的 RVA 指向节的开头,或者包含特定表的节具有特定名称。

偏移量 (PE/PE32+) 大小 字段 说明
96/112
8
导出表
导出表地址和大小。 有关详细信息,请参阅 .edata 部分(仅限映像)
104/120
8
导入表
导入表地址和大小。 有关详细信息,请参阅 .idata 部分
112/128
8
资源表
资源表地址和大小。 有关详细信息,请参阅 .rsrc 部分
120/136
8
异常表
异常表地址和大小。 有关详细信息,请参阅 .pdata 部分
128/144
8
证书表
属性证书表地址和大小。 有关详细信息,请参阅属性证书表(仅限映像)
136/152
8
基址重定位表
基址重定位表地址和大小。 有关详细信息,请参阅 .reloc 部分(仅限映像)
144/160
8
调试
调试数据起始地址和大小。 有关详细信息,请参阅 .debug 部分
152/168
8
体系结构
已保留,必须为 0
160/176
8
全局指针
要存储在全局指针寄存器中的值的 RVA。 此结构的大小成员必须设置为零。
168/184
8
TLS 表
线程本地存储 (TLS) 表地址和大小。 有关详细信息,请参阅 .tls 部分
176/192
8
加载配置表
加载配置表地址和大小。 有关详细信息,请参阅加载配置结构(仅限映像)
184/200
8
绑定导入
绑定导入表地址和大小。
192/208
8
IAT
导入地址表地址和大小。 有关详细信息,请参阅导入地址表
200/216
8
延迟导入描述符
延迟导入描述符地址和大小。 有关详细信息,请参阅延迟加载导入表(仅限映像)
208/224
8
CLR 运行时标头
CLR 运行时标头地址和大小。 有关详细信息,请参阅 .cormeta 部分(仅限对象)
216/232
8
保留,必须为零

证书表项指向属性证书表。 这些证书不会作为映像的一部分加载到内存中。 因此,此条目的第一个字段通常是 RVA,并且是一个文件指针。

节表(节标头)

节表的每一行实际上都是节标头。 此表紧跟可选标头(如果有)。 必需进行此定位,因为文件标头不包含指向节表的直接指针。 相反,节表的位置是通过计算标头后第一个字节的位置确定的。 请确保使用文件标头中指定的可选标头的大小。

节表中的条目数由文件标头中的 NumberOfSections 字段提供。 节表中的条目从一 (1) 开始编号。 代码和数据内存节条目按链接器选择的顺序排列。

在映像文件中,节的 VA 必须由链接器分配,以便它们按升序分配且相邻,并且它们必须是可选标头中 SectionAlignment 值的倍数。

每个节标头(节表条目)都具有以下格式,每个条目总共有 40 个字节。

Offset 大小 字段 说明
0
8
名称
8 字节的 Null 填充 UTF-8 编码字符串。 如果字符串长度正好为 8 个字符,则无终止 null 字符。 对于较长的名称,此字段包含一个斜杠 (/),后跟十进制数的 ASCII 表示形式,该十进制数是字符串表中的偏移量。 可执行映像不使用字符串表,也不支持长度超过 8 个字符的节名称。 如果向可执行文件发送对象文件中的长名称,则这些长名称将被截断。
8
4
VirtualSize
加载到内存中时节的总大小。 如果此值大于 SizeOfRawData,则节中会用零填充。 此字段仅对可执行映像有效,应针对对象文件设置为零。
12
4
VirtualAddress
对于可执行映像,是指当节加载到内存中时,该节相对于映像基址的第一个字节的地址。 对于对象文件,此字段是应用重定位前第一个字节的地址;为简单起见,编译器应将此字段设置为零。 否则,它是重定位期间从偏移量中减去的任意值。
16
4
SizeOfRawData
节(对于对象文件)的大小或磁盘上已初始化的数据的大小(对于映像文件)。 对于可执行映像,这必须是可选标头中的 FileAlignment 的倍数。 如果此值小于 VirtualSize,则节的其余部分用零填充。 由于 SizeOfRawData 字段被舍入,但 VirtualSize 字段未被舍入,因此 SizeOfRawData 也可能大于 VirtualSize。 当节仅包含未初始化的数据时,此字段应为零。
20
4
PointerToRawData
指向 COFF 文件中节的第一页的文件指针。 对于可执行映像,这必须是可选标头中的 FileAlignment 的倍数。 对于对象文件,该值应在 4 字节边界上对齐,以获取最佳性能。 当节仅包含未初始化的数据时,此字段应为零。
24
4
PointerToRelocations
指向节的重定位项的开头的文件指针。 对于可执行映像或没有重定位的情况,这项设置为零。
28
4
PointerToLinenumbers
指向节的行号项开头的文件指针。 如果没有 COFF 行号,则此字段设置为零。 映像的此值应为零,因为 COFF 调试信息已被弃用。
32
2
NumberOfRelocations
节的重定位项数。 对于可执行映像,此字段设置为零。
34
2
NumberOfLinenumbers
节的行号项数。 映像的此值应为零,因为 COFF 调试信息已被弃用。
36
4
特征
描述节特性的标志。 有关更多信息,请参见节标记

 

节标志

节标头的“特征”字段中的节标志指示节的特征。

标志 说明
0x00000000
保留供将来使用。
0x00000001
保留供将来使用。
0x00000002
保留供将来使用。
0x00000004
保留供将来使用。
IMAGE_SCN_TYPE_NO_PAD
0x00000008
不应将节填充到下一个边界。 此标志已过时并且已被 IMAGE_SCN_ALIGN_1BYTES 取代。 这仅对对象文件有效。
0x00000010
保留供将来使用。
IMAGE_SCN_CNT_CODE
0x00000020
该节包含可执行代码。
IMAGE_SCN_CNT_INITIALIZED_DATA
0x00000040
该节包含初始化的数据。
IMAGE_SCN_CNT_UNINITIALIZED_ DATA
0x00000080
该节包含未初始化的数据。
IMAGE_SCN_LNK_OTHER
0x00001000
保留供将来使用。
IMAGE_SCN_LNK_INFO
0x00000200
该节包含注释或其他信息。 .drectve 节具有此类型。 这仅对对象文件有效。
0x00040000
保留供将来使用。
IMAGE_SCN_LNK_REMOVE
0x00000800
该节将不会成为映像的一部分。 这仅对对象文件有效。
IMAGE_SCN_LNK_COMDAT
0x00001000
该节包含 COMDAT 数据。 有关详细信息,请参阅 COMDAT 节(仅限对象)。 这仅对对象文件有效。
IMAGE_SCN_GPREL
0x00008000
该节包含通过全局指针 (GP) 引用的数据。
IMAGE_SCN_MEM_PURGEABLE
0x00020000
保留供将来使用。
IMAGE_SCN_MEM_16BIT
0x00020000
保留供将来使用。
IMAGE_SCN_MEM_LOCKED
0x00040000
保留供将来使用。
IMAGE_SCN_MEM_PRELOAD
0x00080000
保留供将来使用。
IMAGE_SCN_ALIGN_1BYTES
0x00100000
在 1 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_2BYTES
0x00200000
在 2 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_4BYTES
0x00300000
在 4 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_8BYTES
0x00400000
在 8 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_16BYTES
0x00500000
在 16 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_32BYTES
0x00600000
在 32 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_64BYTES
0x00700000
在 64 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_128BYTES
0x00800000
在 128 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_256BYTES
0x00900000
在 256 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_512BYTES
0x00A00000
在 512 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_1024BYTES
0x00B00000
在 1024 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_2048BYTES
0x00C00000
在 2048 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_4096BYTES
0x00D00000
在 4096 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_ALIGN_8192BYTES
0x00E00000
在 8192 字节边界上对齐数据。 仅对对象文件有效。
IMAGE_SCN_LNK_NRELOC_OVFL
0x01000000
此节包含扩展的重定位。
IMAGE_SCN_MEM_DISCARDABLE
0x02000000
可以根据需要丢弃此节。
IMAGE_SCN_MEM_NOT_CACHED
0x04000000
无法缓存此节。
IMAGE_SCN_MEM_NOT_PAGED
0x08000000
此节不可分页。
IMAGE_SCN_MEM_SHARED
0x10000000
此节可以在内存中共享。
IMAGE_SCN_MEM_EXECUTE
0x20000000
此节可以作为代码执行。
IMAGE_SCN_MEM_READ
0x40000000
可以读取此节。
IMAGE_SCN_MEM_WRITE
0x80000000
可以写入到此节中。

 

IMAGE_SCN_LNK_NRELOC_OVFL 指示节的重定位计数超过节标头中为其保留的 16 位。 如果设置了位,并且节标头中的 NumberOfRelocations 字段为 0xffff,则实际重定位计数会存储在第一次重定位的 32 位 VirtualAddress 字段中。 如果设置了 IMAGE_SCN_LNK_NRELOC_OVFL,并且节中的重定位少于 0xffff,则会出现错误。

分组的节(仅限对象)

“$”字符(美元符号)在对象文件内的节名称中具有特殊解释。

确定将包含对象节内容的映像节时,链接器将丢弃“$”及其后面的所有字符。 因此,名为 .text$X 的对象节实际上促成了映像中的 .text 节。

但是,“$”后面的字符确定映像节的构成部分的顺序。 映像中连续分配了具有相同对象节名称的所有构成部分,并且按对象节名称以词法顺序对构成部分块进行排序。 因此,对象文件中具有节名称 .text$X 的所有内容最终都一起位于 .text$W 构成部分之后且在 .text$Y 构成部分之前。

映像文件中的节名称永远不会包含“$”字符。

文件的其他内容

到目前为止描述的数据结构(直到并包括可选标头)都位于文件开头的固定偏移量处(如果文件是包含 MS-DOS 存根的映像,则位于 PE 标头的固定偏移量处)。

COFF 对象或映像文件的其余部分包含不一定在任何特定文件偏移量处的数据块。 相反,位置由可选标头或节标头中的指针定义。

SectionAlignment 值小于体系结构页面大小(对于 Intel x86 和 MIPS,此大小为 4 K;对于 Itanium,则为 8 K)的映像是一个例外。 有关 SectionAlignment 的说明,请参阅可选标头(仅限映像)。 在这种情况下,节数据的文件偏移量存在约束,如第 5.1 节“节数据”中所述。另一个例外是,属性证书和调试信息必须放在映像文件的最末端,并且属性证书表紧靠在调试节之前,因为加载程序不会将这些信息映射到内存中。 但是,有关属性证书和调试信息的规则不适用于对象文件。

节数据

节的初始化数据由简单的字节块组成。 但是,对于全部为零的节,不需要包含节数据。

每个节的数据都位于节标头中 PointerToRawData 字段给出的文件偏移量处。 文件中此数据的大小由 SizeOfRawData 字段来指示。 如果 SizeOfRawData 小于 VirtualSize,则剩余部分用零填充。

在映像文件中,节数据必须在可选标头中 FileAlignment 字段指定的边界上对齐。 节数据必须按相应节的 RVA 值的顺序显示(与节表中的各个节标题一样)。

如果可选标头中的 SectionAlignment 值小于体系结构的页面大小,则图像文件还有其他限制。 对于此类文件,文件中节数据的位置必须与加载映像时内存中的节数据位置匹配,以便节数据的物理偏移量与 RVA 相同。

COFF 重定位(仅限对象)

对象文件包含 COFF 重定位,这些重定位指定放在图像文件中并随后加载到内存中时应如何修改节数据。

映像文件不包含 COFF 重定位,因为在平面地址空间中为所有引用的符号都分配了地址。 映像包含 .reloc 部分内基址重定位表中的重定位信息(除非映像具有 IMAGE_FILE_RELOCS_STRIPPED 属性)。 有关详细信息,请参阅 .reloc 部分(仅限映像)

对于对象文件中的每个节,固定长度记录的数组保留该节的 COFF 重定位。 数组的位置和长度在节标头中指定。 数组的每个元素都具有以下格式。

Offset 大小 字段 说明
0
4
VirtualAddress
将重定位应用到的项的地址。 这是节开头的偏移量加上节的 RVA/Offset 字段的值。 请参阅节表(节标头)。 例如,如果节的第一个字节的地址为 0x10,则第三个字节的地址为 0x12。
4
4
SymbolTableIndex
符号表中从零开始的索引。 此符号提供要用于重定位的地址。 如果指定的符号具有节存储类,则符号的地址是具有相同名称的第一个节的地址。
8
2
类型
一个指示应执行的重定位类型的值。 有效的重定位类型取决于计算机类型。 请参阅类型指示器

 

如果 SymbolTableIndex 字段引用的符号具有存储类 IMAGE_SYM_CLASS_SECTION,则符号的地址是节的开头。 该节通常位于同一文件中,但对象文件是存档(库)的一部分时除外。 在这种情况下,可以在存档中具有与当前对象文件相同的存档成员名称的任何其他对象文件中找到该节。 (与存档成员名称的关系用于导入表的链接,即 .idata 节)。

类型指示器

重定位记录的“类型”字段指示应执行哪种类型的重定位。 为每种类型的计算机定义了不同的重定位类型。

x64 处理器

为 x64 处理器和兼容处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_AMD64_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_AMD64_ADDR64
0x0001
重定位目标的 64 位 VA。
IMAGE_REL_AMD64_ADDR32
0x0002
重定位目标的 32 位 VA。
IMAGE_REL_AMD64_ADDR32NB
0x0003
没有映像基址 (RVA) 的 32 位地址。
IMAGE_REL_AMD64_REL32
0x0004
重定位后字节中的 32 位相对地址。
IMAGE_REL_AMD64_REL32_1
0x0005
相对于重定位的字节距离 1 的 32 位地址。
IMAGE_REL_AMD64_REL32_2
0x0006
相对于重定位的字节距离 2 的 32 位地址。
IMAGE_REL_AMD64_REL32_3
0x0007
相对于重定位的字节距离 3 的 32 位地址。
IMAGE_REL_AMD64_REL32_4
0x0008
相对于重定位的字节距离 4 的 32 位地址。
IMAGE_REL_AMD64_REL32_5
0x0009
相对于重定位的字节距离 5 的 32 位地址。
IMAGE_REL_AMD64_SECTION
0x000A
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_AMD64_SECREL
0x000B
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_AMD64_SECREL7
0x000C
相对于包含目标的节基址的 7 位无符号偏移量。
IMAGE_REL_AMD64_TOKEN
0x000D
CLR 令牌。
IMAGE_REL_AMD64_SREL32
0x000E
发送到对象中的 32 位有符号跨度依赖值。
IMAGE_REL_AMD64_PAIR
0x000F
必须紧跟每个跨度依赖值的对。
IMAGE_REL_AMD64_SSPAN32
0x0010
链接时应用的 32 位有符号跨度依赖值。

 

ARM 处理器

为 ARM 处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_ARM_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_ARM_ADDR32
0x0001
目标的 32 位 VA。
IMAGE_REL_ARM_ADDR32NB
0x0002
目标的 32 位 RVA。
IMAGE_REL_ARM_BRANCH24
0x0003
目标的 24 位相对偏移量。
IMAGE_REL_ARM_BRANCH11
0x0004
对子例程调用的引用。 该引用包含两个具有 11 位偏移量的 16 位指令。
IMAGE_REL_ARM_REL32
0x000A
重定位后字节中的 32 位相对地址。
IMAGE_REL_ARM_SECTION
0x000E
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_ARM_SECREL
0x000F
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_ARM_MOV32
0x0010
目标的 32 位 VA。 此重定位使用 MOVW 指令应用于低 16 位,然后使用 MOVT 指令应用于高 16 位。
IMAGE_REL_THUMB_MOV32
0x0011
目标的 32 位 VA。 此重定位使用 MOVW 指令应用于低 16 位,然后使用 MOVT 指令应用于高 16 位。
IMAGE_REL_THUMB_BRANCH20
0x0012
使用 2 字节对齐目标的 21 位相对偏移量修复了该指令。 此偏移量的最低有效位始终为零,并且未存储。 此重定位对应于 Thumb-2 32 位条件 B 指令。
未使用
0x0013
IMAGE_REL_THUMB_BRANCH24
0x0014
使用 2 字节对齐目标的 25 位相对偏移量修复了该指令。 此偏移量的最低有效位为零,并且未存储。此重定位对应于 Thumb-2 B 指令。
IMAGE_REL_THUMB_BLX23
0x0015
使用 4 字节对齐目标的 25 位相对偏移量修复了该指令。 此位移的低 2 位为零,并且未存储。
此重定位对应于 Thumb-2 BLX 指令。
IMAGE_REL_ARM_PAIR
0x0016
仅当重定位紧跟在 ARM_REFHI 或 THUMB_REFHI 之后时,重定位才有效。 其 SymbolTableIndex 包含偏移量,而不是符号表中的索引。

 

ARM64 处理器

为 ARM64 处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_ARM64_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_ARM64_ADDR32
0x0001
目标的 32 位 VA。
IMAGE_REL_ARM64_ADDR32NB
0x0002
目标的 32 位 RVA。
IMAGE_REL_ARM64_BRANCH26
0x0003
B 和 BL 指令的目标 26 位相对偏移量。
IMAGE_REL_ARM64_PAGEBASE_REL21
0x0004
目标的页基址,用于 ADRP 指令。
IMAGE_REL_ARM64_REL21
0x0005
指令 ADR 的目标 12 位相对偏移量
IMAGE_REL_ARM64_PAGEOFFSET_12A
0x0006
目标的 12 位页偏移量,用于带零位偏移的指令 ADD/ADDS(即时)。
IMAGE_REL_ARM64_PAGEOFFSET_12L
0x0007
目标的 12 位页偏移量,用于指令 LDR(已编制索引、未签名即时)。
IMAGE_REL_ARM64_SECREL
0x0008
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_ARM64_SECREL_LOW12A
0x0009
目标节偏移量的位 0:11,用于带零位偏移的指令 ADD/ADDS(即时)。
IMAGE_REL_ARM64_SECREL_HIGH12A
0x000A
目标节偏移量的位 12:23,用于带零位偏移的指令 ADD/ADDS(即时)。
IMAGE_REL_ARM64_SECREL_LOW12L
0x000B
目标节偏移量的位 0:11,用于指令 LDR(已编制索引、未签名即时)。
IMAGE_REL_ARM64_TOKEN
0x000C
CLR 令牌。
IMAGE_REL_ARM64_SECTION
0x000D
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_ARM64_ADDR64
0x000E
重定位目标的 64 位 VA。
IMAGE_REL_ARM64_BRANCH19
0x000F
条件 B 指令的重定位目标的 19 位偏移量。
IMAGE_REL_ARM64_BRANCH14
0x0010
TBZ 和 TBNZ 指令的重定位目标的 14 位偏移量。
IMAGE_REL_ARM64_REL32
0x0011
重定位后字节中的 32 位相对地址。
Hitachi SuperH 处理器

为 SH3 和 SH4 处理器定义了以下重定位类型指示器。 SH5 特定的重定位被注明为 SHM (SH Media)。

常量 Value 说明
IMAGE_REL_SH3_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_SH3_DIRECT16
0x0001
对包含目标符号 VA 的 16 位位置的引用。
IMAGE_REL_SH3_DIRECT32
0x0002
目标符号的 32 位 VA。
IMAGE_REL_SH3_DIRECT8
0x0003
对包含目标符号 VA 的 8 位位置的引用。
IMAGE_REL_SH3_DIRECT8_WORD
0x0004
对包含目标符号的有效 16 位 VA 的 8 位指令的引用。
IMAGE_REL_SH3_DIRECT8_LONG
0x0005
对包含目标符号的有效 32 位 VA 的 8 位指令的引用。
IMAGE_REL_SH3_DIRECT4
0x0006
对其低 4 位包含目标符号 VA 的 8 位位置的引用。
IMAGE_REL_SH3_DIRECT4_WORD
0x0007
对 8 位指令(其低 4 位包含目标符号的有效 16 位 VA)的引用。
IMAGE_REL_SH3_DIRECT4_LONG
0x0008
对 8 位指令(其低 4 位包含目标符号的有效 32 位 VA)的引用。
IMAGE_REL_SH3_PCREL8_WORD
0x0009
对包含目标符号的有效 16 位相对偏移量的 8 位指令的引用。
IMAGE_REL_SH3_PCREL8_LONG
0x000A
对包含目标符号的有效 32 位相对偏移量的 8 位指令的引用。
IMAGE_REL_SH3_PCREL12_WORD
0x000B
对 16 位指令(其低 12 位包含目标符号的有效 16 位相对偏移量)的引用。
IMAGE_REL_SH3_STARTOF_SECTION
0x000C
对 32 位位置的引用,该位置是包含目标符号的节的 VA。
IMAGE_REL_SH3_SIZEOF_SECTION
0x000D
对 32 位位置的引用,该位置是包含目标符号的节的大小。
IMAGE_REL_SH3_SECTION
0x000E
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_SH3_SECREL
0x000F
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_SH3_DIRECT32_NB
0x0010
目标符号的 32 位 RVA。
IMAGE_REL_SH3_GPREL4_LONG
0x0011
相对 GP。
IMAGE_REL_SH3_TOKEN
0x0012
CLR 令牌。
IMAGE_REL_SHM_PCRELPT
0x0013
长字中相对于当前指令的偏移量。 如果未设置 NOMODE 位,请在位 32 处插入低位的反转位以选择 PTA 或 PTB。
IMAGE_REL_SHM_REFLO
0x0014
32 位地址的低 16 位。
IMAGE_REL_SHM_REFHALF
0x0015
32 位地址的高 16 位。
IMAGE_REL_SHM_RELLO
0x0016
相对地址的低 16 位。
IMAGE_REL_SHM_RELHALF
0x0017
相对地址的高 16 位。
IMAGE_REL_SHM_PAIR
0x0018
仅当此重定位紧跟在 REFHALF、RELHALF 或 RELLO 重定位后面时,此重定位才有效。 重定位的 SymbolTableIndex 字段包含位移,而不是符号表中的索引。
IMAGE_REL_SHM_NOMODE
0x8000
重定位将忽略节模式。

 

IBM PowerPC 处理器

为 PowerPC 处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_PPC_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_PPC_ADDR64
0x0001
目标的 64 位 VA。
IMAGE_REL_PPC_ADDR32
0x0002
目标的 32 位 VA。
IMAGE_REL_PPC_ADDR24
0x0003
目标的 VA 的低 24 位。 仅当目标符号是绝对符号并且可以通过符号扩展使其成为其原始值时,这才有效。
IMAGE_REL_PPC_ADDR16
0x0004
目标 VA 的低 16 位。
IMAGE_REL_PPC_ADDR14
0x0005
目标 VA 的低 14 位。 仅当目标符号是绝对符号并且可以通过符号扩展使其成为其原始值时,这才有效。
IMAGE_REL_PPC_REL24
0x0006
符号位置的 24 位 PC 相对偏移量。
IMAGE_REL_PPC_REL14
0x0007
符号位置的 14 位 PC 相对偏移量。
IMAGE_REL_PPC_ADDR32NB
0x000A
目标的 32 位 RVA。
IMAGE_REL_PPC_SECREL
0x000B
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_PPC_SECTION
0x000C
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_PPC_SECREL16
0x000F
目标相对于其节开头的 16 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_PPC_REFHI
0x0010
目标的 32 位 VA 的高 16 位。 这用于加载完整地址的双指令序列中的第一个指令。 此重定位后面必须紧跟一个 PAIR 重定位,该 PAIR 重定位的 SymbolTableIndex 包含一个已签名的 16 位偏移量,此偏移量添加到了从正在重新定位的位置获取的高 16 位。
IMAGE_REL_PPC_REFLO
0x0011
目标 VA 的低 16 位。
IMAGE_REL_PPC_PAIR
0x0012
仅当此重定位紧跟在 REFHI 或 SECRELHI 重定位后面时,此重定位才有效。 其 SymbolTableIndex 包含偏移量,而不是符号表中的索引。
IMAGE_REL_PPC_SECRELLO
0x0013
目标的 32 位偏移量(相对于其节的开头)的低 16 位。
IMAGE_REL_PPC_GPREL
0x0015
相对于 GP 寄存器的目标的 16 位有符号偏移量。
IMAGE_REL_PPC_TOKEN
0x0016
CLR 令牌。

 

Intel 386 处理器

为 Intel 386 处理器和兼容处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_I386_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_I386_DIR16
0x0001
不支持。
IMAGE_REL_I386_REL16
0x0002
不支持。
IMAGE_REL_I386_DIR32
0x0006
目标的 32 位 VA。
IMAGE_REL_I386_DIR32NB
0x0007
目标的 32 位 RVA。
IMAGE_REL_I386_SEG12
0x0009
不支持。
IMAGE_REL_I386_SECTION
0x000A
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_I386_SECREL
0x000B
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_I386_TOKEN
0x000C
CLR 令牌。
IMAGE_REL_I386_SECREL7
0x000D
相对于包含目标的节基址的 7 位偏移量。
IMAGE_REL_I386_REL32
0x0014
目标的 32 位相对偏移量。 这支持 x86 相对分支和调用指令。

 

Intel Itanium 处理器系列 (IPF)

为 Intel Itanium 处理器系列和兼容处理器定义了以下重定位类型指示器。 请注意,指令上的重定位使用绑定的偏移量和重定位偏移量的插槽编号。

常量 Value 说明
IMAGE_REL_IA64_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_IA64_IMM14
0x0001
指令重定位后面可以跟一个 ADDEND 重定位,该 ADDEND 重定位的值将添加到目标地址,然后再插入 IMM14 绑定内的指定插槽中。 重定位目标必须是绝对目标,或者映像必须是固定映像。
IMAGE_REL_IA64_IMM22
0x0002
指令重定位后面可以跟一个 ADDEND 重定位,该 ADDEND 重定位的值将添加到目标地址,然后再插入 IMM22 绑定内的指定插槽中。 重定位目标必须是绝对目标,或者映像必须是固定映像。
IMAGE_REL_IA64_IMM64
0x0003
此重定位的插槽编号必须为 1 (1)。 此重定位后面可以跟一个 ADDEND 重定位,该 ADDEND 重定位的值将添加到目标地址,然后再存储到 IMM14 绑定的所有三个插槽中。
IMAGE_REL_IA64_DIR32
0x0004
目标的 32 位 VA。 仅 /LARGEADDRESSAWARE:NO 映像支持此项。
IMAGE_REL_IA64_DIR64
0x0005
目标的 64 位 VA。
IMAGE_REL_IA64_PCREL21B
0x0006
使用 16 位对齐目标的 25 位相对偏移量修复了该指令。 此位移的低 4 位为零,并且未存储。
IMAGE_REL_IA64_PCREL21M
0x0007
使用 16 位对齐目标的 25 位相对偏移量修复了该指令。 此位移的低 4 位为零,并且未存储。
IMAGE_REL_IA64_PCREL21F
0x0008
此重定位偏移量的 LSB 必须包含插槽编号,而其余部分是绑定地址。 使用 16 位对齐目标的 25 位相对偏移量修复了该绑定。 此位移的低 4 位为零,并且未存储。
IMAGE_REL_IA64_GPREL22
0x0009
指令重定位后面可以依次跟一个 ADDEND 重定位(其值将添加到目标地址),以及计算并应用于GPREL22 绑定的 22 位 GP 相对偏移量。
IMAGE_REL_IA64_LTOFF22
0x000A
使用目标符号文本表条目的 22 位 GP 相对偏移量修复了该指令。 链接器根据此重定位和可能在其后面的 ADDEND 重定位来创建此文本表条目。
IMAGE_REL_IA64_SECTION
0x000B
节的 16 位节索引包含此目标。 这用于支持调试信息。
IMAGE_REL_IA64_SECREL22
0x000C
使用目标相对于其节开头的 22 位偏移量修复了该指令。 此重定位后面可以紧跟 ADDEND 重定位,此 ADDEND 重定位的“值”字段包含目标相对于节开头的 32 位无符号偏移量。
IMAGE_REL_IA64_SECREL64I
0x000D
此重定位的插槽编号必须为 1 (1)。 使用目标相对于其节开头的 64 位偏移量修复了该指令。 此重定位后面可以紧跟 ADDEND 重定位,此 ADDEND 重定位的“值”字段包含目标相对于节开头的 32 位无符号偏移量。
IMAGE_REL_IA64_SECREL32
0x000E
要使用目标相对于其节开头的 32 位偏移量修复的数据的地址。
IMAGE_REL_IA64_DIR32NB
0x0010
目标的 32 位 RVA。
IMAGE_REL_IA64_SREL14
0x0011
这适用于包含两个可重定位目标之间的差异的带符号 14 位即时项。 这是链接器声明性字段,指示编译器已发送此值。
IMAGE_REL_IA64_SREL22
0x0012
这适用于包含两个可重定位目标之间的差异的带符号 22 位即时项。 这是链接器声明性字段,指示编译器已发送此值。
IMAGE_REL_IA64_SREL32
0x0013
这适用于包含两个可重定位值之间的差异的带符号 32 位即时项。 这是链接器声明性字段,指示编译器已发送此值。
IMAGE_REL_IA64_UREL32
0x0014
这适用于包含两个可重定位值之间的差异的无符号 32 位即时项。 这是链接器声明性字段,指示编译器已发送此值。
IMAGE_REL_IA64_PCREL60X
0x0015
一个始终保留为 MLX 绑定的 MLX 指令的 60 位 PC 相关修复。
IMAGE_REL_IA64_PCREL60B
0x0016
60 位 PC 相关修复。 如果目标偏移量适合有符号的 25 位字段,则将整个绑定转换为 MBB 绑定,其中槽 1 中包含 NOP.B,槽 2 中包含 25 位 BR 指令(最低 4 位全部为零并被丢弃)。
IMAGE_REL_IA64_PCREL60F
0x0017
60 位 PC 相关修复。 如果目标偏移量适合有符号的 25 位字段,则将整个绑定转换为 MFB 绑定,其中槽 1 中包含 NOP.F,槽 2 中包含 25 位(最低 4 位全部为零并被丢弃)BR 指令。
IMAGE_REL_IA64_PCREL60I
0x0018
60 位 PC 相关修复。 如果目标偏移量适合有符号的 25 位字段,则将整个绑定转换为 MIB 绑定,其中槽 1 中包含 NOP.I,槽 2 中包含 25 位(最低 4 位全部为零并被丢弃)BR 指令。
IMAGE_REL_IA64_PCREL60M
0x0019
60 位 PC 相关修复。 如果目标偏移量适合有符号的 25 位字段,则将整个绑定转换为 MMB 绑定,其中槽 1 中包含 NOP.M,槽 2 中包含 25 位(最低 4 位全部为零并被丢弃)BR 指令。
IMAGE_REL_IA64_IMMGPREL64
0x001a
64 位 GP 相关修复。
IMAGE_REL_IA64_TOKEN
0x001b
CLR 令牌。
IMAGE_REL_IA64_GPREL32
0x001c
32 位 GP 相关修复。
IMAGE_REL_IA64_ADDEND
0x001F
仅当此重定位紧跟在以下其中一个重定位后面时,此重定位才有效:IMM14、IMM22、IMM64、GPREL22、LTOFF22、LTOFF64、SECREL22、SECREL64I 或 SECREL32。 其值包含要应用于绑定中的指令的加数,不适用于数据。

 

MIPS 处理器

为 MIPS 处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_MIPS_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_MIPS_REFHALF
0x0001
目标的 32 位 VA 的高 16 位。
IMAGE_REL_MIPS_REFWORD
0x0002
目标的 32 位 VA。
IMAGE_REL_MIPS_JMPADDR
0x0003
目标 VA 的低 26 位。 这支持 MIPS J 和 JAL 指令。
IMAGE_REL_MIPS_REFHI
0x0004
目标的 32 位 VA 的高 16 位。 这用于加载完整地址的双指令序列中的第一个指令。 此重定位后面必须紧跟一个 PAIR 重定位,该 PAIR 重定位的 SymbolTableIndex 包含一个已签名的 16 位偏移量,此偏移量添加到了从正在重新定位的位置获取的高 16 位。
IMAGE_REL_MIPS_REFLO
0x0005
目标 VA 的低 16 位。
IMAGE_REL_MIPS_GPREL
0x0006
相对于 GP 寄存器的目标的 16 位有符号偏移量。
IMAGE_REL_MIPS_LITERAL
0x0007
与 IMAGE_REL_MIPS_GPREL 相同。
IMAGE_REL_MIPS_SECTION
0x000A
节的 16 位节索引包含此目标。 这用于支持调试信息。
IMAGE_REL_MIPS_SECREL
0x000B
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_MIPS_SECRELLO
0x000C
目标的 32 位偏移量(相对于其节的开头)的低 16 位。
IMAGE_REL_MIPS_SECRELHI
0x000D
目标的 32 位偏移量(相对于其节的开头)的高 16 位。 IMAGE_REL_MIPS_PAIR 重定位必须紧随其后。 PAIR 重定位的 SymbolTableIndex 包含一个带符号的 16 位偏移量,该偏移量已添加到从正在重新定位的位置获取的高 16 位。
IMAGE_REL_MIPS_JMPADDR16
0x0010
目标 VA 的低 26 位。 这支持 MIPS16 JAL 指令。
IMAGE_REL_MIPS_REFWORDNB
0x0022
目标的 32 位 RVA。
IMAGE_REL_MIPS_PAIR
0x0025
仅当此重定位紧跟在 REFHI 或 SECRELHI 重定位后面时,此重定位才有效。 其 SymbolTableIndex 包含偏移量,而不是符号表中的索引。

 

Mitsubishi M32R

为 Mitsubishi M32R 处理器定义了以下重定位类型指示器。

常量 Value 说明
IMAGE_REL_M32R_ABSOLUTE
0x0000
将忽略重定位。
IMAGE_REL_M32R_ADDR32
0x0001
目标的 32 位 VA。
IMAGE_REL_M32R_ADDR32NB
0x0002
目标的 32 位 RVA。
IMAGE_REL_M32R_ADDR24
0x0003
目标的 24 位 VA。
IMAGE_REL_M32R_GPREL16
0x0004
目标与 GP 寄存器的 16 位偏移量。
IMAGE_REL_M32R_PCREL24
0x0005
目标与程序计数器 (PC) 的 24 位偏移量,左移 2 位并进行了符号扩展
IMAGE_REL_M32R_PCREL16
0x0006
目标与 PC 的 16 位偏移量,左移 2 位并进行了符号扩展
IMAGE_REL_M32R_PCREL8
0x0007
目标与 PC 的 8 位偏移量,左移 2 位并进行了符号扩展
IMAGE_REL_M32R_REFHALF
0x0008
目标 VA 的 16 个 MSB。
IMAGE_REL_M32R_REFHI
0x0009
目标 VA 的 16 个 MSB,针对 LSB 符号扩展进行了调整。 这用于加载完整 32 位地址的双指令序列中的第一个指令。 此重定位后面必须紧跟一个 PAIR 重定位,该 PAIR 重定位的 SymbolTableIndex 包含一个已签名的 16 位偏移量,此偏移量添加到了从正在重新定位的位置获取的高 16 位。
IMAGE_REL_M32R_REFLO
0x000A
目标 VA 的 16 个 LSB。
IMAGE_REL_M32R_PAIR
0x000B
此重定位必须在 REFHI 重定位后面。 其 SymbolTableIndex 包含偏移量,而不是符号表中的索引。
IMAGE_REL_M32R_SECTION
0x000C
包含目标的节的 16 位节索引。 这用于支持调试信息。
IMAGE_REL_M32R_SECREL
0x000D
目标相对于其节开头的 32 位偏移量。 这用于支持调试信息和静态线程本地存储。
IMAGE_REL_M32R_TOKEN
0x000E
CLR 令牌。

 

COFF 行号(已弃用)

COFF 行号不再生成,将来不会使用。

COFF 行号指示源文件中的代码与行号之间的关系。 COFF 行号的 Microsoft 格式类似于标准 COFF,但已扩展,以允许单个节与多个源文件中的行号相关。

COFF 行号由固定长度记录数组组成。 节标头中指定了数组的位置(文件偏移量)和大小。 每条行号记录均采用以下格式。

Offset 大小 字段 说明
0
4
类型​​ (*)
这是 SymbolTableIndex 和 VirtualAddress 这两个字段的并集。 是使用 SymbolTableIndex 还是 RVA 取决于 Linenumber 的值。
4
2
Linenumber
当不是零时,此字段会指定一个从一开始的行号。 当为零时,“类型”字段被解释为函数的符号表索引。

 

“类型”字段是 SymbolTableIndex 和 VirtualAddress 这两个 4 字节字段的并集。

Offset 大小 字段 说明
0
4
SymbolTableIndex
当 Linenumber 为零时使用:函数符号表条目的索引。 此格式用于指示一组行号记录引用的函数。
0
4
VirtualAddress
当 Linenumber 不是零时使用:与所指示源代码行相对应的可执行代码的 RVA。 在对象文件中,这包含节中的 VA。

 

行号记录可以将 Linenumber 字段设置为零,并指向符号表中的函数定义,也可以通过提供正整数(行号)和对象代码中的相应地址来充当标准行号条目。

一组行号条目始终以第一种格式开头:函数符号的索引。 如果这是节中的第一个行号记录,并且设置了该节的 COMDAT 标志,则它也是函数的 COMDAT 符号名称。 请参阅 COMDAT 节(仅限对象)。 符号表中函数的辅助记录具有一个指向 Linenumber 字段的指针,该字段指向此相同行号记录。

标识函数的记录后跟任意数量的提供实际行号信息的行号条目(即,Linenumber 大于零的条目)。 这些条目从一开始,相对于函数的开头,并表示函数中除第一行以外的每个源行。

例如,以下示例的第一个行号记录将指定 ReverseSign 函数(ReverseSign 和 Linenumber 的 SymbolTableIndex 设置为零)。 然后,Linenumber 值为 1、2 和 3 的记录紧随其后,并与源行相对应,如下所示:

// some code precedes ReverseSign function
int ReverseSign(int i)
1: {
2:  return -1 * i;
3: }

COFF 符号表

本节中的符号表继承自传统的 COFF 格式。 它不同于 Microsoft Visual C++ 调试信息。 一个文件可以同时包含 COFF 符号表和 Visual C++ 调试信息,这两种信息是分开的。 某些 Microsoft 工具将符号表用于有限但重要的用途,例如将 COMDAT 信息传达给链接器。 符号表中列出了节名称和文件名以及代码和数据符号。

COFF 标头中指示了符号表的位置。

符号表是一个记录数组,其中每个记录都有 18 个字节长。 每个记录都是标准或辅助符号表记录。 标准记录定义符号或名称,并具有以下格式。

Offset 大小 字段 说明
0
8
名称 (*)
符号的名称,由三个结构的并集表示。 如果名称长度不超过 8 个字节,则使用 8 个字节的数组。 有关详细信息,请参阅符号名称表示形式
8
4

与符号关联的值。 此字段的解释取决于 SectionNumber 和 StorageClass。 典型的含义是可重定位地址。
12
2
SectionNumber
使用节表中从一开始的索引标识节的带符号整数。 某些值具有特殊含义,如第 5.4.2 节“节号值”中所述。
14
2
类型
一个表示类型的数字。 Microsoft 工具会将此字段设置为 0x20(函数)或 0x0(不是函数)。 有关详细信息,请参阅类型表示形式
16
1
StorageClass
一个表示存储类的枚举值。 有关详细信息,请参阅存储类
17
1
NumberOfAuxSymbols
此记录后面的辅助符号表条目的数量。

 

零个或多个辅助符号表记录紧跟在每个标准符号表记录之后。 但是,通常标准符号表记录(具有长文件名的 .file 记录除外)后面的辅助符号表记录不会超过一条。 每个辅助记录的大小虽然与标准符号表记录(18 个字节)的大小相同,但却不定义新符号,辅助记录提供有关定义的最后一个符号的其他信息。 如何选择要使用的多种格式取决于 StorageClass 字段。 第 5.5 节“辅助符号记录”中显示了辅助符号表记录的当前定义格式。

读取 COFF 符号表的工具必须忽略解释未知的辅助符号记录。 这样就可以扩展符号表格式来添加新的辅助记录,而不会中断现有工具。

符号名称表示形式

符号表中的 ShortName 字段由包含名称本身(如果名称长度不超过 8 个字节)的 8 个字节组成,或者 ShortName 字段会提供字符串表的偏移量。 若要确定是提供名称本身还是偏移量,请测试前 4 个字节是否等于零。

按照约定,名称被视为以零结尾的 UTF-8 编码字符串。

Offset 大小 字段 说明
0
8
ShortName
一个 8 字节数组。 如果名称长度小于 8 字节,则将在右侧用 null 填充此数组。
0
4

一个在名称超过 8 个字节时全部设置为零的字段。
4
4
偏移量
字符串表的偏移量。

 

节号值

通常,符号表条目中的“节值”字段是从一开始的节表索引。 但是,此字段是符号的整数,可以采用负值。 以下值(小于 1)具有特殊含义。

常量 Value 说明
IMAGE_SYM_UNDEFINED
0
尚未给符号记录分配节。 零值表示在别处定义了对外部符号的引用。 非零值是一个大小由值指定的公共符号。
IMAGE_SYM_ABSOLUTE
1 -
此符号具有绝对(不可重定位)值,不是地址。
IMAGE_SYM_DEBUG
2 -
此符号提供常规类型或调试信息,但与节不对应。 Microsoft 工具将此设置与 .file 记录(存储类 FILE)一起使用。

 

类型表示形式

符号表条目的“类型”字段包含 2 个字节,其中每个字节都表示类型信息。 LSB 表示简单(基本)数据类型,MSB 表示复杂类型(如果有):

MSB LSB
复杂类型:无、指针、函数、数组。
基本类型:整数、浮点等。

 

系统为基类型定义了以下值,但 Microsoft 工具通常不使用此字段并将 LSB 设置为 0。 相反,会使用 Visual C++ 调试信息来指示类型。 但是,为了保证完整性,此处列出了可能的 COFF 值。

常量 Value 说明
IMAGE_SYM_TYPE_NULL
0
无类型信息或基本类型未知。 Microsoft 工具使用此设置
IMAGE_SYM_TYPE_VOID
1
无有效类型;与 void 指针和函数一起使用
IMAGE_SYM_TYPE_CHAR
2
字符(带符号的字节)
IMAGE_SYM_TYPE_SHORT
3
2 字节有符号整数
IMAGE_SYM_TYPE_INT
4
自然整数类型(通常为 Windows 中的 4 个字节)
IMAGE_SYM_TYPE_LONG
5
4 字节有符号整数
IMAGE_SYM_TYPE_FLOAT
6
4 字节浮点数
IMAGE_SYM_TYPE_DOUBLE
7
8 字节浮点数
IMAGE_SYM_TYPE_STRUCT
8
结构
IMAGE_SYM_TYPE_UNION
9
并集
IMAGE_SYM_TYPE_ENUM
10
枚举类型
IMAGE_SYM_TYPE_MOE
11
枚举的成员(特定值)
IMAGE_SYM_TYPE_BYTE
12
一个字节;1 字节无符号整数
IMAGE_SYM_TYPE_WORD
13
字词;2 字节无符号整数
IMAGE_SYM_TYPE_UINT
14
自然大小的无符号整数(通常为 4 字节)
IMAGE_SYM_TYPE_DWORD
15
4 字节无符号整数

 

最高有效字节指定符号是一个指向 LSB 中指定的基类型的指针、返回此基类型的函数还是此基类型的数组。 Microsoft 工具仅使用此字段来指示符号是否为函数,以便为“类型”字段只生成 0x0 和 0x20 这两个值。 但是,其他工具可以使用此字段来传达更多信息。

正确指定函数属性非常重要。 增量链接正常工作需要此信息。 对于某些体系结构,可能需要这些信息用于其他目的。

常量 Value 说明
IMAGE_SYM_DTYPE_NULL
0
无派生类型;符号是一个简单的标量变量。
IMAGE_SYM_DTYPE_POINTER
1
符号是指向基类型的指针。
IMAGE_SYM_DTYPE_FUNCTION
2
符号是返回基类型的函数。
IMAGE_SYM_DTYPE_ARRAY
3
符号是基类型的数组。

 

存储类

符号表的 StorageClass 字段指示符号表示的定义类型。 下表列出了可能的值。 请注意,StorageClass 字段是一个 1 字节无符号整数。 因此,应采用特殊值 -1 来表示其无符号等效值 0xFF。

尽管传统的 COFF 格式使用许多存储类值,但 Microsoft 工具依赖 Visual C++ 调试格式获取大多数符号信息,并且通常只使用以下四个存储类值:EXTERNAL (2)、STATIC (3)、FUNCTION (101) 和 FILE (103)。 除了在下面的第二列标题中,其他都应采用“值”来表示符号记录的“值”字段(其解释取决于所找到的存储类形式的数字)。

常量 “值”字段的描述/解释
IMAGE_SYM_CLASS_END_OF_FUNCTION
-1 (0xFF)
表示函数末尾的特殊符号,用于调试目的。
IMAGE_SYM_CLASS_NULL
0
没有分配的存储类。
IMAGE_SYM_CLASS_AUTOMATIC
1
自动(堆栈)变量。 “值”字段指定堆栈帧偏移量。
IMAGE_SYM_CLASS_EXTERNAL
2
Microsoft 工具对外部符号使用的值。 “值”字段表示节号为 IMAGE_SYM_UNDEFINED (0) 时的大小。 如果节号不为零,则“值”字段会指定节内的偏移量。
IMAGE_SYM_CLASS_STATIC
3
节内符号的偏移量。 如果“值”字段为零,则符号表示节名称。
IMAGE_SYM_CLASS_REGISTER
4
寄存器变量。 “值”字段指定寄存器编号。
IMAGE_SYM_CLASS_EXTERNAL_DEF
5
外部定义的符号。
IMAGE_SYM_CLASS_LABEL
6
模块中定义的代码标签。 “值”字段指定节中符号的偏移量。
IMAGE_SYM_CLASS_UNDEFINED_LABEL
7
对未定义的代码标签的引用。
IMAGE_SYM_CLASS_MEMBER_OF_STRUCT
8
结构成员。 “值”字段指定第 n 个成员。
IMAGE_SYM_CLASS_ARGUMENT
9
函数的正式变量(参数)。 “值”字段指定第 n 个参数。
IMAGE_SYM_CLASS_STRUCT_TAG
10
结构标记名称条目。
IMAGE_SYM_CLASS_MEMBER_OF_UNION
11
并集成员。 “值”字段指定第 n 个成员。
IMAGE_SYM_CLASS_UNION_TAG
12
并集标记名称条目。
IMAGE_SYM_CLASS_TYPE_DEFINITION
13
Typedef 条目。
IMAGE_SYM_CLASS_UNDEFINED_STATIC
14
静态数据声明。
IMAGE_SYM_CLASS_ENUM_TAG
15
枚举类型标记名称条目。
IMAGE_SYM_CLASS_MEMBER_OF_ENUM
16
枚举的一个成员。 “值”字段指定第 n 个成员。
IMAGE_SYM_CLASS_REGISTER_PARAM
17
寄存器参数。
IMAGE_SYM_CLASS_BIT_FIELD
18
位字段引用。 “值”字段指定位字段中的第 n 位。
IMAGE_SYM_CLASS_BLOCK
100
.bb(块开头)或 .eb(块结尾)记录。 “值”字段是代码位置的可重定位地址。
IMAGE_SYM_CLASS_FUNCTION
101
Microsoft 工具对定义函数(begin 函数 (.bf )、end 函数 (.ef ) 和 lines in 函数 (.lf ))范围的符号记录使用的值。 对于 .lf 记录,“值”字段提供函数中的源行数。 对于 .ef 记录,“值”字段提供函数代码的大小。
IMAGE_SYM_CLASS_END_OF_STRUCT
102
结构末尾条目。
IMAGE_SYM_CLASS_FILE
103
Microsoft 工具以及传统 COFF 格式对源文件符号记录使用的值。 符号后跟命名文件的辅助记录。
IMAGE_SYM_CLASS_SECTION
104
节的定义(Microsoft 工具会改用 STATIC 存储类)。
IMAGE_SYM_CLASS_WEAK_EXTERNAL
105
弱外部。 有关详细信息,请参阅辅助格式 3:弱外部
IMAGE_SYM_CLASS_CLR_TOKEN
107
CLR 令牌符号。 该名称是一个 ASCII 字符串,其中包含令牌的十六进制值。 有关详细信息,请参阅 CLR 令牌定义(仅限对象)

 

辅助符号记录

辅助符号表记录始终在某些标准符号表记录之后,并应用于这些标准符号表记录。 辅助记录可以具有工具可以识别的任何格式,但必须为它们分配 18 个字节,以便将符号表作为常规大小的数组进行维护。 目前,Microsoft 工具可识别以下类型的记录的辅助格式:函数定义、函数开始和结束符号(.bf 和 .ef)、弱外部、文件名和节定义。

传统的 COFF 设计还包括适用于数组和结构的辅助记录格式。 Microsoft 工具不使用这些格式,而是将该符号信息以 Visual C++ 调试格式置于调试节中。

辅助格式 1:函数定义

如果函数定义具有下列所有内容,则符号表记录将标记函数定义的开头:EXTERNAL (2) 的存储类、一个指示它是函数 (0x20) 的“类型”值,以及大于零的节编号。 请注意,节编号为 UNDEFINED (0) 的符号表记录未定义函数,也没有辅助记录。 函数定义符号记录后跟下述格式的辅助记录:

Offset 大小 字段 说明
0
4
TagIndex
相应 .bf(begin 函数)符号记录的符号表索引。
4
4
TotalSize
函数本身的可执行代码的大小。 如果该函数位于其自己的节中,则节标头中的 SizeOfRawData 大于或等于此字段,具体取决于对齐注意事项。
8
4
PointerToLinenumber
函数的第一个 COFF 行号条目的文件偏移量,如果没有,则为零。 有关详细信息,请参阅 COFF 行号(已弃用)
12
4
PointerToNextFunction
下一个函数的记录的符号表索引。 如果该函数是符号表中的最后一个函数,则此字段设置为零。
16
2
未使用

 

辅助格式 2:.bf 和 .ef 符号

对于符号表中的每个函数定义,三个项目将描述开始、结束和行数。 每个符号都有存储类 FUNCTION (101):

名为 .bf 的符号记录(begin 函数)。 “值”字段未使用。

名为 .lf 的符号记录(函数中的行数)。 “值”字段提供函数中的行数。

名为 .ef 的符号记录(函数末尾)。 “值”字段与函数定义符号记录中的“总大小”字段具有相同的数字。

.bf 和 .ef 符号记录(但不是 .lf 记录)后跟以下格式的辅助记录:

Offset 大小 字段 说明
0
4
未使用
4
2
Linenumber
源文件内对应于 .bf 或 .ef 记录的实际行序号(1、2、3 等)。
6
6
未使用
12
4
PointerToNextFunction(仅限 .bf)
下一个 .bf 符号记录的符号表索引。 如果该函数是符号表中的最后一个函数,则此字段设置为零。 它不用于 .ef 记录。
16
2
未使用

 

辅助格式 3:弱外部

“弱外部”是对象文件的一种能够在链接时实现灵活性的机制。 模块可以包含未解析的外部符号 (sym1),但它还可以包含一个辅助记录以指示如果符号 1 在链接时不存在,则改用另一个外部符号 (sym2) 来解析引用。

如果符号 1 的定义已链接,则通常解析对符号的外部引用。 如果 sym1 的定义未链接,则对符号 1 弱外部的所有引用都改为引用 sym2。 外部符号 sym2 必须始终链接;通常,它在包含对 sym1 的弱引用的模块中定义。

弱外部由具有 EXTERNAL 存储类、UNDEF 节号和零值的符号表记录来表示。 弱外部符号记录后跟以下格式的辅助记录:

Offset 大小 字段 说明
0
4
TagIndex
sym2 的符号表索引,如果未找到 sym1,则为要链接的符号。
4
4
特征
IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY 值表示不应执行 sym1 库搜索。
IMAGE_WEAK_EXTERN_SEARCH_LIBRARY 值表示应执行 sym1 库搜索。
IMAGE_WEAK_EXTERN_SEARCH_ALIAS 值指示 sym1 是 sym2 的别名。
8
10
未使用

 

请注意,未在 WINNT.H 中定义“特征”字段。而是使用了“总大小”字段。

辅助格式 4:文件

此格式仿效具有存储类 FILE (103) 的符号表记录。 符号名称本身应为 .file,后面的辅助记录会提供源代码文件的名称。

Offset 大小 字段 说明
0
18
文件名
一个提供源文件的名称的 ANSI 字符串。 如果小于最大长度,则用 null 填充。

 

辅助格式 5:节定义

此格式仿效定义节的符号表记录。 此类记录具有符号名称,该名称是节(如 .text 或 .drectve)的名称,并且具有存储类 STATIC (3)。 辅助记录提供有关它所引用的节的信息。 因此,它会复制节标头中的一些信息。

Offset 大小 字段 说明
0
4
Length
节数据的大小;与节标头中的 SizeOfRawData 相同。
4
2
NumberOfRelocations
节的重定位项数。
6
2
NumberOfLinenumbers
节的行号项数。
8
4
CheckSum
公共数据的校验和。 如果节标头中设置了 IMAGE_SCN_LNK_COMDAT 标志,则此字段适用。 有关详细信息,请参阅 COMDAT 节(仅限对象)
12
2
编号
关联节的节表的从一开始的索引。 当 COMDAT 选择设置为 5 时,会使用此字段。
14
1
选择
COMDAT 选择编号。 如果节是 COMDAT 节,则此字段适用。
15
3
未使用

 

COMDAT 节(仅限对象)

如果节是 COMDAT 节,则节定义辅助格式的“选择”字段适用。 COMDAT 节是由多个对象文件定义的节。 (标志 IMAGE_SCN_LNK_COMDAT 在节标头的“节标志”字段中设置。“选择”字段确定链接器解析 COMDAT 节的多个定义的方式。

具有 COMDAT 节的节值的第一个符号必须是节符号。 此符号具有节的名称、等于零的“值”字段、所述 COMDAT 节的节号、等于 IMAGE_SYM_TYPE_NULL 的“类型”字段、等于 IMAGE_SYM_CLASS_STATIC 的“类”字段和一个辅助记录。 第二个符号称为“COMDAT 符号”,与“选择”字段一起被链接器使用。

“选择”字段的值如下所示。

常量 Value 说明
IMAGE_COMDAT_SELECT_NODUPLICATES
1
如果已定义此符号,则链接器会发出“多重定义符号”错误。
IMAGE_COMDAT_SELECT_ANY
2
定义同一 COMDAT 符号的任何节都可以链接;其余部分会被删除。
IMAGE_COMDAT_SELECT_SAME_SIZE
3
链接器在此符号的定义中选择任意节。 如果所有定义的大小都不同,则会发出“多重定义符号”错误。
IMAGE_COMDAT_SELECT_EXACT_MATCH
4
链接器在此符号的定义中选择任意节。 如果所有定义都不完全匹配,则会发出“多重定义符号”错误。
IMAGE_COMDAT_SELECT_ASSOCIATIVE
5
如果链接了某个其他 COMDAT 节,则会链接此节。 此其他节由节定义的辅助符号记录的“编号”字段指示。 此设置对于组成部分在多个节中(例如,代码在一个节中,数据在另一个节中)但所有节都必须作为一个集进行链接或被弃用的定义非常有用。 与本节关联的其他节必须是 COMDAT 节(可以是另一个关联的 COMDAT 节)。 关联 COMDAT 节的节关联链无法形成循环。 节关联链最终必须到达未设置 IMAGE_COMDAT_SELECT_ASSOCIATIVE 的 COMDAT 节。
IMAGE_COMDAT_SELECT_LARGEST
6
链接器从此符号的所有定义中选择最大的定义。 如果多个定义具有此大小,则它们之间的选择是任意的。

 

CLR 令牌定义(仅限对象)

此辅助符号通常在 IMAGE_SYM_CLASS_CLR_TOKEN 后面。 它用于将令牌与 COFF 符号表的命名空间相关联。

Offset 大小 字段 说明
0
1
bAuxType
必须是 IMAGE_AUX_SYMBOL_TYPE_TOKEN_DEF (1)。
1
1
bReserved
保留,必须为零。
2
4
SymbolTableIndex
此 CLR 令牌定义所引用的 COFF 符号的符号索引。
6
12
保留,必须为零。

 

COFF 字符串表

紧跟在 COFF 符号表后面的是 COFF 字符串表。 通过获取 COFF 标头中的符号表地址并加上符号数乘以符号大小来找到此表的位置。

COFF 字符串表的开头是包含字符串表其余部分的总大小(以字节为单位)的 4 个字节。 此大小包括大小字段本身,因此如果没有字符串,则此位置中的值将为 4。

大小后面是 COFF 符号表中的符号所指向的以 null 结尾的字符串。

属性证书表(仅限映像)

通过添加属性证书表,属性证书可以与映像相关联。 属性证书表由一组连续、四字对齐的属性证书条目组成。 在文件的原始末尾和属性证书表的开头之间插入零填充,以实现这种对齐方式。 每个属性证书条目都包含以下字段。

Offset 大小 字段 说明
0
4
dwLength
指定属性证书条目的长度。
4
2
wRevision
包含证书版本号。 有关详细信息,请参阅以下文本。
6
2
wCertificateType
指定 bCertificate 中的内容类型。 有关详细信息,请参阅以下文本。
8
请参阅以下资源
bCertificate
包含证书,例如验证码签名。 有关详细信息,请参阅以下文本。

 

可选标头数据目录中“证书表”条目中的虚拟地址值是第一个属性证书条目的文件偏移量。 通过从当前属性证书条目开头向前推进该条目的 dwLength 字节(四舍五入为 8 字节倍数)来访问后续条目。 继续此操作,直到四舍五入的 dwLength 值的总和等于可选标头数据目录中证书表条目的大小值。 如果舍入后的 dwLength 值之和不等于大小值,则属性证书表或“大小”字段已损坏。

例如,如果可选标头数据目录的证书表条目包含:

virtual address = 0x5000
size = 0x1000

第一个证书从相对于磁盘文件开头的偏移量 0x5000 开始。 若要推进所有属性证书条目,请执行以下操作:

  1. 将第一个属性证书的 dwLength 值添加到起始偏移量。
  2. 将步骤 1 中的值向上舍入到最接近的 8 字节倍数,以查找第二个属性证书条目的偏移量。
  3. 将步骤 2 的偏移量值加上第二个属性证书条目的 dwLength 值,向上舍入到最接近的 8 字节倍数,以确定第三个属性证书条目的偏移量。
  4. 对每个连续证书重复步骤 3,直到计算的偏移量等于 0x6000(0x5000 开头 + 0x1000 总大小),这表示已遍历整个表。

或者,可以通过在循环中调用 Win32 ImageEnumerateCertificates 函数来枚举证书条目。 有关指向函数引用页面的链接,请参阅引用

属性证书表条目可以包含任何证书类型,前提是条目具有正确的 dwLength 值、唯一 wRevision 值和唯一 wCertificateType 值。 最常见的证书表条目类型是一种 WIN_CERTIFICATE 结构,该结构记录在 Wintrust.h 中,并在本节的其余部分中予以讨论。

WIN_CERTIFICATE wRevision 成员的选项包括(但不限于)以下内容。

名称 说明
0x0100
WIN_CERT_REVISION_1_0
版本 1,Win_Certificate 结构的旧版本。 支持它只是为了验证旧验证码签名
0x0200
WIN_CERT_REVISION_2_0
版本 2 是 Win_Certificate 结构的当前版本。

 

WIN_CERTIFICATE wCertificateType 成员的选项包括(但不限于)下表中的项。 请注意,目前不受支持某些值。

名称 说明
0x0001
WIN_CERT_TYPE_X509
bCertificate 包含 X.509 证书
不支持
0x0002
WIN_CERT_TYPE_PKCS_SIGNED_DATA
bCertificate 包含 PKCS#7 SignedData 结构
0x0003
WIN_CERT_TYPE_RESERVED_1
保留
0x0004
WIN_CERT_TYPE_TS_STACK_SIGNED
终端服务器协议堆栈证书签名
不支持

 

WIN_CERTIFICATE 结构的 bCertificate 成员包含具有 wCertificateType 指定的内容类型的可变长度字节数组。 验证码支持的类型是 WIN_CERT_TYPE_PKCS_SIGNED_DATA,这是一种 PKCS#7 SignedData 结构。 有关验证码数字签名格式的详细信息,请参阅 Windows 验证码可移植可执行签名格式

如果 bCertificate 内容不在四字边界上终止,则在属性证书条目中从 bCertificate 末尾向下一个四字边界填充零。

dwLength 值是最终 WIN_CERTIFICATE 结构的长度,其计算公式为:

dwLength = offsetof(WIN_CERTIFICATE, bCertificate) + (size of the variable-length binary array contained within bCertificate)

此长度应包括用于满足每个 WIN_CERTIFICATE 结构四字对齐这一要求的任何填充大小:

dwLength += (8 - (dwLength & 7)) & 7;

可选标头数据目录(仅限映像)证书表条目中指定的证书表大小包括填充。

有关使用 ImageHlp API 枚举、添加和删除 PE 文件中的证书的详细信息,请参阅 ImageHlp 函数

证书数据

如前一部分所述,属性证书表中的证书可以包含任何证书类型。 确保 PE 文件完整性的证书可能包括 PE 映像哈希。

PE 图像哈希(或文件哈希)类似于文件校验和,该哈希算法会生成与文件完整性相关的消息摘要。 然而,校验和是通过简单的算法生成的,主要用于检测磁盘上的内存块是否已损坏以及存储在那里的值是否已损坏。 文件哈希类似于校验和,因为它也可以检测文件损坏。 然而,与大多数校验和算法不同,在不更改文件哈希值的原始未修改值的情况下修改文件非常困难。 因此,文件哈希可用于检测对文件进行的有意甚至是细微的修改,例如由病毒、黑客或特洛伊木马程序引入的修改。

当包含在证书中时,映像摘要必须排除 PE 映像中的某些字段,例如可选标头数据目录中的校验和和证书表条目。 这是因为添加证书的行为会更改这些字段,并会导致计算不同的哈希值。

Win32 ImageGetDigestStream 函数提供目标 PE 文件中的数据流,用于对函数进行哈希操作。 在 PE 文件中添加或删除证书时,此数据流保持一致。 根据传递给 ImageGetDigestStream 的参数,可以从哈希计算中省略 PE 映像中的其他数据。 有关指向函数引用页面的链接,请参阅引用

延迟加载导入表(仅限映像)

这些表已添加到映像中,以支持应用程序的统一机制,以便在首次调用该 DLL 之前延迟加载 DLL。 表的布局与第 6.4 节(.idata 节)中所述的传统导入表的布局匹配。此处只讨论了一些详细信息。

延迟加载目录表

延迟加载目录表是导入目录表的对应项。 可以通过可选标头数据目录列表中的延迟导入描述符条目(偏移量 200)来检索它。 该表按如下方式排列:

Offset 大小 字段 说明
0
4
属性
必须为零。
4
4
名称
要加载的 DLL 名称的 RVA。 该名称驻留在映像的只读数据部分中。
8
4
模块句柄
要延迟加载的 DLL 的模块句柄(在映像的数据部分中)的 RVA。 提供的例程使用它进行存储以管理延迟加载。
12
4
延迟导入地址表
延迟加载导入地址表的 RVA。 有关详细信息,请参阅延迟导入地址表 (IAT)
16
4
延迟导入名称表
延迟加载名称表的 RVA,其中包含可能需要加载的导入的名称。 这与导入名称表的布局匹配。 有关详细信息,请参阅提示/名称表
20
4
绑定延迟导入表
绑定延迟加载地址表的 RVA(如果存在)。
24
4
卸载延迟导入表
卸载延迟加载地址表的 RVA(如果存在)。 这是延迟导入地址表的确切副本。 如果调用方卸载 DLL,则应将此表复制回延迟导入地址表,以便对 DLL 的后续调用继续正确使用指纹机制。
28
4
时间戳
此映像绑定到的 DLL 的时间戳。

 

此数据结构中引用的表会进行组织并排序,就像进行传统导入时对其对应表进行组织并排序一样。 有关详细信息,请参阅 .idata 节

属性

目前,未定义任何属性标志。 在映像中,链接器会将此字段设置为零。 此字段可用于通过指示新字段的状态来扩展记录,或者可用于指示延迟或卸载帮助程序函数的行为。

名称

要延迟加载的 DLL 的名称驻留在映像的只读数据部分中。 系统通过 szName 字段引用它。

模块句柄

要延迟加载的 DLL 的句柄位于映像的数据部分。 phmod 字段指向句柄。 提供的延迟加载帮助程序使用此位置存储加载的 DLL 的句柄。

延迟导入地址表

延迟导入描述符通过 pIAT 字段引用延迟导入地址表 (IAT)。 延迟加载帮助程序使用实际入口点更新这些指针,以便 thunk 不再处于调用循环中。 函数指针通过表达式 pINT->u1.Function 来访问。

延迟导入名称表

延迟导入名称表 (INT) 包含可能需要加载的导入的名称。 系统以与 IAT 中的函数指针相同的方式对它们进行排序。 它们包含与标准 INT 相同的结构,并通过表达式 pINT->u1.AddressOfData->Name[0] 来访问。

延迟绑定导入地址表和时间戳

延迟绑定导入地址表 (BIAT) 是 IMAGE_THUNK_DATA 项的可选表,在后处理绑定阶段与延迟加载目录表的时间戳字段一起使用。

延迟卸载导入地址表

延迟卸载导入地址表 (UIAT) 是一个可选的 IMAGE_THUNK_DATA 项表,卸载代码使用它来处理显式卸载请求。 它由只读部分中的初始化数据组成,该数据是原始 IAT 的精确副本,此原始 IAT 会将代码提交到延迟加载 thunk。 根据卸载请求,可以释放库,清除 *phmod,并将 UIAT 写入 IAT 以将所有内容还原成其预加载状态。

特殊部分

典型的 COFF 节包含链接器和 Microsoft Win32 加载程序处理的代码或数据,无需专门了解节内容。 这些内容只与正在链接或执行的应用程序相关。

但是,在对象文件或映像文件中找到某些 COFF 节时,这些节具有特殊含义。 工具和加载程序可识别这些节,因为它们在节标头中设置了特殊标志,映像可选标头中的特殊位置指向它们,或者节名称本身指示节的特殊功能。 (即使节名称本身并不表示该节的特殊功能,节名称也是按照惯例规定的,因此本规范的作者在所有情况下都可以引用节名称。)

下表描述了保留的节及其属性,随后详细描述了保留到可执行文件中的节类型以及包含扩展元数据的节类型。

节名称 内容 特征
.bss
未初始化的数据(自由格式)
IMAGE_SCN_CNT_UNINITIALIZED_DATA |IMAGE_SCN_MEM_READ |IMAGE_SCN_MEM_WRITE
.cormeta
指示对象文件包含托管代码的 CLR 元数据
IMAGE_SCN_LNK_INFO
.data
初始化的数据(自由格式)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE
.debug$F
生成的 FPO 调试信息(仅对象、仅 x86 体系结构、现在已过时)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
.debug$P
预编译的调试类型(仅限对象)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
.debug$S
调试符号(仅限对象)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
.debug$T
调试类型(仅限对象)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
.drective
链接器选项
IMAGE_SCN_LNK_INFO
.edata
导出表
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
.idata
导入表
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE
.idlsym
包括注册的 SEH(仅限映像)以支持 IDL 属性。 有关信息,请参阅本主题末尾的参考中的“IDL 属性”。
IMAGE_SCN_LNK_INFO
.pdata
异常信息
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
.rdata
只读初始化数据
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
.reloc
映像重定位
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE
.rsrc
资源目录
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
.sbss
GP 相对未初始化的数据(自由格式)
IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE _SCN_GPREL 应仅针对 IA64 架构设置 IMAGE_SCN_GPREL 标志;该标志对于其他体系结构无效。 IMAGE_SCN_GPREL 标志仅适用于对象文件;当此节类型出现在映像文件中时,不得设置 IMAGE_SCN_GPREL 标志。
.sdata
GP 相对初始化的数据(自由格式)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE _SCN_GPREL 应仅针对 IA64 架构设置 IMAGE_SCN_GPREL 标志;该标志对于其他体系结构无效。 IMAGE_SCN_GPREL 标志仅适用于对象文件;当此节类型出现在映像文件中时,不得设置 IMAGE_SCN_GPREL 标志。
.srdata
GP 相对只读数据(自由格式)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE _SCN_GPREL 应仅针对 IA64 架构设置 IMAGE_SCN_GPREL 标志;该标志对于其他体系结构无效。 IMAGE_SCN_GPREL 标志仅适用于对象文件;当此节类型出现在映像文件中时,不得设置 IMAGE_SCN_GPREL 标志。
.sxdata
注册的异常处理程序数据(仅限自由格式和 x86/对象)
IMAGE_SCN_LNK_INFO 包含该对象文件中代码所引用的每个异常处理程序的符号索引。 该符号可以是 UNDEF 符号或该模块中定义的符号。
.text
可执行代码(自由格式)
IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IIMAGE_SCN_MEM_READ
.tls
线程本地存储(仅限对象)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE
.tls$
线程本地存储(仅限对象)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE
.vsdata
GP 相对初始化的数据(自由格式,并且仅限 ARM、SH4 和 Thumb 体系结构)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE
.xdata
异常信息(自由格式)
IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ

 

此处列出的某些节被标记为“仅限对象”或“仅限映像”,以指示它们的特殊语义分别仅与对象文件或映像文件相关。 标记为“仅限映像”的节可能仍会出现在对象文件中,作为进入映像文件的一种方式,但该节对链接器没有特殊含义,仅对映像文件加载器有特殊含义。

.debug 节

.debug 节在对象文件中用于包含编译器生成的调试信息,在映像文件中用于包含生成的所有调试信息。 本节介绍如何在对象和映像文件中打包调试信息。

下一节介绍可位于映像中任何位置的调试目录的格式。 后续节介绍包含调试信息的对象文件中的“组”。

链接器的默认消息是调试信息未映射到映像的地址空间。 仅当调试信息映射到地址空间时,.debug 节才存在。

调试目录(仅限映像)

映像文件包含一个可选的调试目录,该目录指示存在何种形式的调试信息及其所在位置。 此目录包含调试目录条目数组,图像可选标头中指示了这些条目的位置和大小。

调试目录可以位于可丢弃的 .debug 节(如果存在)中,也可以包含在映像文件的任何其他节中,或者根本不在某个节中。

每个调试目录条目都标识调试信息块的位置和大小。 如果节标头未涵盖调试信息(即它驻留在映像文件中且未映射到运行时地址空间),则指定的 RVA 可以为零。 如果映射了它,则 RVA 是其地址。

调试目录条目采用以下格式:

Offset 大小 字段 说明
0
4
特征
保留,必须为零。
4
4
TimeDateStamp
创建调试数据的时间和日期。
8
2
MajorVersion
调试数据格式的主版本号。
10
2
MinorVersion
调试数据格式的次要版本号。
12
4
类型
调试信息的格式。 此字段允许支持多个调试器。 有关详细信息,请参阅调试类型
16
4
SizeOfData
调试数据的大小(不包括调试目录本身)。
20
4
AddressOfRawData
加载时相对于映像基址的调试数据地址。
24
4
PointerToRawData
指向调试数据的文件指针。

 

调试类型

为调试目录条目的“类型”字段定义了以下值:

常量 Value 说明
IMAGE_DEBUG_TYPE_UNKNOWN
0
所有工具都忽略的未知值。
IMAGE_DEBUG_TYPE_COFF
1
COFF 调试信息(行号、符号表和字符串表)。 此类调试信息也由文件标头中的字段指向。
IMAGE_DEBUG_TYPE_CODEVIEW
2
Visual C++ 调试信息。
IMAGE_DEBUG_TYPE_FPO
3
帧指针省略 (FPO) 信息。 此信息告诉调试器如何解释非标准堆栈帧,这些堆栈帧将 EBP 寄存器用于除用作帧指针之外的其他目的。
IMAGE_DEBUG_TYPE_MISC
4
DBG 文件的位置。
IMAGE_DEBUG_TYPE_EXCEPTION
5
.pdata 节的副本。
IMAGE_DEBUG_TYPE_FIXUP
6
保留。
IMAGE_DEBUG_TYPE_OMAP_TO_SRC
7
从映像中的 RVA 映射到源映像中的 RVA。
IMAGE_DEBUG_TYPE_OMAP_FROM_SRC
8
从源映像中的 RVA 映射到映像中的 RVA。
IMAGE_DEBUG_TYPE_BORLAND
9
留给 Borland 使用。
IMAGE_DEBUG_TYPE_RESERVED10
10
保留。
IMAGE_DEBUG_TYPE_CLSID
11
保留。
IMAGE_DEBUG_TYPE_REPRO
16
PE 确定性或可重现性。
未定义
17
调试信息嵌入在 PE 文件内 PointerToRawData 指定的位置。
未定义
19
存储用于生成 PE/COFF 文件的符号文件内容的加密哈希。
IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS 20 扩展的 DLL 特征位。

 

如果“类型”字段设置为 IMAGE_DEBUG_TYPE_FPO,则调试原始数据是一个数组,其中每个成员都描述函数的堆栈帧。 即使调试类型是 FPO,也不必为映像文件中的每个函数都定义 FPO 信息。 那些没有 FPO 信息的函数被假定具有普通堆栈帧。 FPO 信息的格式如下:

#define FRAME_FPO   0
#define FRAME_TRAP  1
#define FRAME_TSS   2

typedef struct _FPO_DATA {
    DWORD       ulOffStart;            // offset 1st byte of function code
    DWORD       cbProcSize;            // # bytes in function
    DWORD       cdwLocals;             // # bytes in locals/4
    WORD        cdwParams;             // # bytes in params/4
    WORD        cbProlog : 8;          // # bytes in prolog
    WORD        cbRegs   : 3;          // # regs saved
    WORD        fHasSEH  : 1;          // TRUE if SEH in func
    WORD        fUseBP   : 1;          // TRUE if EBP has been allocated
    WORD        reserved : 1;          // reserved for future use
    WORD        cbFrame  : 2;          // frame type
} FPO_DATA;

IMAGE_DEBUG_TYPE_REPRO 类型条目的存在表明 PE 文件是以实现确定性或可再现性的方式构建的。 如果输入未更改,则无论何时何地生成 PE,都可保证输出 PE 文件逐位相同。 PE 文件中的各个日期/时间戳字段都填充了使用 PE 文件内容作为输入的计算哈希值的部分位或所有位,因此不再表示生成 PE 文件或 PE 中相关特定数据的实际日期和时间。 此调试条目的原始数据可能为空,或者可能包含一个计算的哈希值,此哈希值前面是一个表示哈希值长度的四字节值。

如果“类型”字段设置为 IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,则调试原始数据包含扩展的 DLL 特征位,以及可以在映像的可选标头中设置的扩展 DLL 特征位。 请参阅可选标头 Windows 特定字段(仅限映像)部分中的 DLL 特征

扩展的 DLL 特征

为扩展的 DLL 特征位定义了以下值。

常量 Value 说明
IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT 0x0001 映像与控制流强制技术 (CET) 影子堆栈兼容。
IMAGE_DLLCHARACTERISTICS_EX_FORWARD_CFI_COMPAT 0x0040 所有映像代码部分中的所有分支目标都使用前边缘控制流完整性防护指令(如 x86 CET 间接分支跟踪 (IBT) 或 ARM 分支目标标识 (BTI) 指令)进行批注。 Windows 不使用此位。

.debug$F(仅限对象)

本节中的数据已在 Visual C++ 7.0 版及更高版本中被发送到 .debug$S 子节中的更广泛的数据集所取代。

对象文件可以包含内容是一条或多条 FPO_DATA 记录(帧指针省略信息)的 .debug$F 节。 请参阅调试类型中的“IMAGE_DEBUG_TYPE_FPO”。

链接器可识别这些 .debug$F 记录。 如果正在生成调试信息,则链接器会按过程 RVA 对 FPO_DATA 记录进行排序,并为其生成调试目录条目。

编译器不应为过程生成具有标准帧格式的 FPO 记录。

.debug$S(仅限对象)

本部分包含 Visual C++ 调试信息(符号信息)。

.debug$P(仅限对象)

本部分包含 Visual C++ 调试信息(预编译的信息)。 这些是在使用通过该对象生成的预编译标头编译的所有对象之间共享的类型。

.debug$T(仅限对象)

本部分包含 Visual C++ 调试信息(类型信息)。

Microsoft 调试信息的链接器支持

为了支持调试信息,链接器会:

  • .debug$Fdebug$S.debug$P.debug$T 节中收集所有相关调试数据。

  • 将该数据与链接器生成的调试信息一起处理到 PDB 文件中,并创建一个调试目录条目来引用它。

.drectve 节(仅限对象)

如果节标头中设置了 IMAGE_SCN_LNK_INFO 标志,并且节具有 .drectve 节名称,则此节是指令节。 链接器在处理信息后删除 .drectve 节,因此该节不会显示在正在链接的映像文件中。

.drectve 节包含可编码为 ANSI 或 UTF-8 的文本字符串。 如果 UTF-8 字节顺序标记(BOM,即由 0xEF、0xBB 和 0xBF 组成的三字节前缀)不存在,则指令字符串会将被解释为 ANSI。 指令字符串是由空格分隔的一系列链接器选项。 每个选项都包含连字符、选项名称和任何适当的属性。 如果某个选项包含空格,则必须将此选项括在引号中。 .drectve 节不得具有重定位或行号。

.edata 节(仅限映像)

名为 .edata 的导出数据节包含有关其他图像可通过动态链接访问的符号的信息。 导出的符号通常位于 DLL 中,但 DLL 也可以导入符号。

下面概述了导出节的一般结构。 描述的表通常在文件中按显示的顺序连续出现(尽管这不是必需的)。 若要将符号导出为序数,则只需要导出目录表和导出地址表。 (序数是直接通过导出地址表索引访问的导出。)名称指针表、序数表和导出名称表都存在,以支持使用导出名称。

表名称 说明
导出目录表
只有一行的表(与调试目录不同)。 此表指示其他导出表的位置和大小。
导出地址表
导出符号的 RVA 数组。 这些是可执行代码和数据部分中导出的函数和数据的实际地址。 其他映像文件可以通过使用此表(序数)的索引来导入符号,或者可以选择性地使用与序数相对应的公共名称(如果定义了公共名称)来导入符号。
名称指针表
指向公共导出名称并按升序排序的指针的数组。
序数表
对应于名称指针表成员的序号数组。 通信按位置排列;因此,名称指针表和序号表必须具有相同的成员数。 每个序号都是导出地址表的索引。
导出名称表
一系列以 null 结尾的 ASCII 字符串。 名称指针表的成员会指向此区域。 这些名称是导入和导出符号所使用的公共名称;它们不一定与映像文件中使用的专用名称相同。

 

当另一个映像文件按名称导入符号时,Win32 加载程序会在名称指针表中搜索匹配的字符串。 如果找到匹配的字符串,则通过在序号表中查找相应的成员(即,序号表中与名称指针表中找到的字符串指针具有相同索引的成员)来识别关联的序号。 生成的序号是导出地址表的索引,它给出了所需符号的实际位置。 每个导出符号都可以通过序号来访问。

当另一个映像文件按序号导入符号时,无需在名称指针表中搜索匹配的字符串。 因此,直接使用序号更高效。 但是,导出名称更易于记住,并且不要求用户知道符号的表索引。

导出目录表

导出符号信息以导出目录表开头,导出目录表描述了导出符号信息的其余部分。 导出目录表包含用于解析对此映像中入口点的导入的地址信息。

Offset 大小 字段 说明
0
4
导出标志
已保留,必须为 0。
4
4
时间/日期戳
创建导出数据的时间和日期。
8
2
主版本
主版本号。 主要版本号和次要版本号可由用户设置。
10
2
次要版本
次版本号。
12
4
名称 RVA
包含 DLL 名称的 ASCII 字符串的地址。 此地址是相对于映像库的地址。
16
4
序号基数
此映像中导出的起始序号。 此字段指定导出地址表的起始序号。 它通常设置为 1。
20
4
地址表条目
导出地址表中的条目数。
24
4
名称指针数
名称指针表中的条目数。 这也是序号表中的条目数。
28
4
导出地址表 RVA
相对于映像库的导出地址表地址。
32
4
名称指针 RVA
相对于映像库的导出名称指针表地址。 表大小由“名称指针数”字段提供。
36
4
序数表 RVA
相对于映像基址的序号表地址。

 

导出地址表

导出地址表包含导出入口点的地址和导出的数据和绝对值。 序号用作导出地址表的索引。

导出地址表中的每个条目都是使用下表中两种格式之一的字段。 如果指定的地址不在导出节(由可选标头中指示的地址和长度进行定义)中,则字段是导出 RVA,这是代码或数据中的实际地址。 否则,该字段是为另一个 DLL 中的符号命名的转发器 RVA。

Offset 大小 字段 说明
0
4
导出 RVA
加载到内存中后导出符号相对于映像基址的地址。 例如,导出函数的地址。
0
4
转发器 RVA
指向导出节中以 null 结尾的 ASCII 字符串的指针。 此字符串必须在导出表数据目录条目提供的范围内。 请参阅可选标头数据目录(仅限映像)。 此字符串提供 DLL 名称和导出的名称(例如“MYDLL.expfunc”)或 DLL 名称和导出的序号(例如“MYDLL.#27”)。

 

转发器 RVA 从一些其他映像中导出定义,使其看起来好像是由当前映像导出的一样。 因此,系统会同时导入和导出符号。

例如,在 Windows XP 内的 Kernel32.dll 中,名为“HeapAlloc”的导出将转发到字符串“NTDLL.RtlAllocateHeap”。这样,应用程序就可以使用 Windows XP 特定模块 Ntdll.dll,而无需实际包含对它的导入引用。 应用程序的导入表仅引用 Kernel32.dll。 因此,应用程序并不特定于 Windows XP,并且可以在任何 Win32 系统上运行。

导出名称指针表

导出名称指针表是导出名称表中的地址数组 (RVA)。 指针均为 32 位并且是相对于映像基址的指针。 指针按词法排序以允许进行二进制搜索。

仅当导出名称指针表包含指向它的指针时,才会定义导出名称。

导出序号表

导出序号表是导出地址表中 16 位无偏差索引的数组。 序号按导出目录表的“序号基址”字段进行偏置。 换句话说,必须从序号中减去序号基址才能获取导出地址表中的真实索引。

导出名称指针表和导出序号表将形成两个并行数组,这些数组被分隔以允许自然字段对齐。 这两个表实际上充当一个表,其中“导出名称指针”列指向公共(导出)名称,“导出序号”列为该公共名称提供相应的序号。 导出名称指针表的成员和导出序号表的成员在其各自的数组中具有相同的位置(索引),因此它们相关联。

因此,当搜索导出名称指针表并在位置 i 找到匹配字符串时,用于查找符号的 RVA 和偏置序号的算法为:

i = Search_ExportNamePointerTable (name);
ordinal = ExportOrdinalTable [i];

rva = ExportAddressTable [ordinal];
biased_ordinal = ordinal + OrdinalBase;

当按(偏置)序号搜索符号时,用于查找符号 RVA 和名称的算法为:

ordinal = biased_ordinal - OrdinalBase;
i = Search_ExportOrdinalTable (ordinal);

rva = ExportAddressTable [ordinal];
name = ExportNameTable [i];

导出名称表

导出名称表包含导出名称指针表指向的实际字符串数据。 此表中的字符串是其他映像可用于导入符号的公共名称。 这些公共导出名称不一定与符号在其自己的映像文件和源代码中具有的专用符号名称相同,尽管这些名称可能相同。

每个导出的符号都有一个序号值,该值只是导出地址表中的索引。 但是,使用导出名称是可选的。 某些或所有导出的符号可能具有导出名称,或者全都可能没有导出名称。 对于确实具有导出名称的导出符号,导出名称指针表和导出序号表中的相应条目共同将每个名称与序号相关联。

导出名称表的结构是一系列以 null 结尾的长度可变的 ASCII 字符串。

.idata 节

导入符号(包括几乎所有可执行 (EXE) 文件)具有 .idata 节的所有映像文件。 导入信息的典型文件布局如下:

  • 目录表

    Null 目录条目

  • DLL1 导入查找表

    Null

  • DLL2 导入查找表

    Null

  • DLL3 导入查找表

    Null

  • 提示名称表

导入目录表

导入信息以导入目录表开头,此表描述了导入信息的其余部分。 导入目录表包含用于解析对 DLL 映像中入口点的修复引用的地址信息。 导入目录表由导入目录条目数组组成,映像所引用的每个 DLL 都有一个条目。 最后一个目录条目为空(用 null 值填充),这表示目录表的末尾。

每个导入目录条目都采用以下格式:

Offset 大小 字段 说明
0
4
导入查找表 RVA(特征)
导入查找表的 RVA。 此表包含每个导入的名称或序号。 (Winnt.h 中使用了名称“特征”,但该名称不再描述此字段。)
4
4
时间/日期戳
在绑定映像之前设置为零的戳记。 绑定映像后,此字段会设置为 DLL 的时间/数据戳。
8
4
转发器链
第一个转发器引用的索引。
12
4
名称 RVA
包含 DLL 名称的 ASCII 字符串的地址。 此地址是相对于映像库的地址。
16
4
导入地址表 RVA(Thunk 表)
导入地址表的 RVA。 在绑定映像之前,此表的内容与导入查找表的内容相同。

 

导入查找表

导入查找表是 PE32 的 32 位数字数组,或 PE32+ 的 64 位数字数组。 每个条目都使用下表中描述的位域格式。 在此格式中,位 31 是 PE32 的最高有效位,位 63 是 PE32+ 的最高有效位。 这些条目的集合描述了从给定 DLL 进行的所有导入。 最后一个条目设置为零 (NULL) 以指示表的末尾。

大小 位域 说明
31/63
1
序号/名称标志
如果设置了此位,则按序号导入。 否则,按名称导入。 对于 PE32,位屏蔽为 0x80000000,对于 PE32+,位屏蔽为 0X8000000000000000。
15-0
16
序号
16 位序号。 仅当序号/名称标志位字段为 1(按序号导入)时,才使用此字段。 位 30-15 或 62-15 必须是 0。
30-0
31
提示/名称表 RVA
提示/名称表条目的 31 位 RVA。 仅当序号/名称标志位字段为 0(按名称导入)时,才使用此字段。 对于 PE32+,位 62-31 必须为零。

 

提示/名称表

对于整个导入节,一个提示/名称表就够了。 提示/名称表中的每个条目都采用以下格式:

Offset 大小 字段 说明
0
2
提示
导出名称指针表中的索引。 首先尝试用此值进行匹配。 如果失败,则会对 DLL 的导出名称指针表执行二进制搜索。
2
变量
名称
包含要导入的名称的 ASCII 字符串。 这是必须与 DLL 中的公共名称匹配的字符串。 此字符串区分大小写,以 null 字节终止。
*
0 或 1
填充
必要时出现在尾随 null 字节之后的尾随零填充字节,用于对齐偶数边界上的下一个条目。

 

导入地址表

导入地址表的结构和内容与导入查找表的结构和内容相同,直到文件被绑定。 在绑定期间,导入地址表中的条目将被所导入符号的 32 位(对于 PE32)或 64 位(对于 PE32+)地址覆盖。 这些地址是符号的实际内存地址,但从技术上说,它们仍称为“虚拟地址”。加载程序通常会处理绑定。

.pdata 节

.pdata 节包含用于异常处理的函数表条目数组。 映像数据目录中的异常表条目会指向它。 在发送到最终映像之前,必须根据函数地址(每个结构中的第一个字段)对条目进行排序。 目标平台确定使用以下三个函数表条目格式变体中的哪一种。

对于 32 位 MIPS 映像,函数表条目的格式如下:

Offset 大小 字段 说明
0
4
开始地址
相应函数的 VA。
4
4
结束地址
函数末尾的 VA。
8
4
异常处理程序
指向要执行的异常处理程序的指针。
12
4
处理程序数据
指向要传递给处理程序的其他信息的指针。
16
4
Prolog 结束地址
函数的 prolog 末尾的 VA。

 

对于 ARM、PowerPC、SH3 和 SH4 Windows CE 平台,函数表条目采用以下格式:

Offset 大小 字段 说明
0
4
开始地址
相应函数的 VA。
4
8 位
Prolog 长度
函数 prolog 中的指令数。
4
22 位
函数长度
函数中的指令数。
4
1 位
32 位标志
如果设置,则该函数由 32 位指令组成。 如果清除此标志,则该函数由 16 位指令组成。
4
1 位
异常标志
如果设置,则函数存在异常处理程序。 否则,不存在异常处理程序。

 

对于 x64 和 Itanium 平台,函数表条目采用以下格式:

Offset 大小 字段 说明
0
4
开始地址
相应函数的 RVA。
4
4
结束地址
函数末尾的 RVA。
8
4
展开信息
展开信息的 RVA。

 

.reloc 节(仅限映像)

基址重定位表包含映像中所有基址重定位的条目。 可选标头数据目录中的“基址重定位表”字段提供基址重定位表中的字节数。 有关详细信息,请参阅可选标头数据目录(仅限映像)。 基址重定位表被分为若干个块。 每个块都表示 4K 页面的基址重定位。 每个块都必须在 32 位边界上启动。

处理由链接器解析的基址重定位并不需要加载程序,除非加载映像无法加载到 PE 标头中指定的映像基址。

基址重定位块

每个基址重定位块都以以下结构开头:

Offset 大小 字段 说明
0
4
页面 RVA
映像基址以及页面 RVA 会添加到每个偏移量中,以创建必须应用基址重定位的 VA。
4
4
块大小
基址重定位块中的字节总数,包括“页面 RVA”和“块大小”字段以及后面的“类型/偏移量”字段。

 

“块大小”字段后跟任意数量的“类型”或“偏移量”字段条目。 每个条目都是一个 WORD(2 个字节)并且具有以下结构:

Offset 大小 字段 说明
0
4 位
类型
存储在 WORD 的高 4 位中的一个值,用于指示要应用的基址重定位类型。 有关详细信息,请参阅基址重定位类型
0
12 位
偏移量
存储在 WORD 剩余 12 位中相对于块的“页面 RVA”字段中所指定起始地址的偏移量。 此偏移量指定要应用基址重定位的位置。

 

若要应用基址重定位,会计算首选基址与在其中实际加载映像的基址之间的差异。 如果在首选基址处加载映像,则差异为零,因此无需应用基址重定位。

基址重定位类型

常量 Value 说明
IMAGE_REL_BASED_ABSOLUTE
0
跳过基址重定位。 此类型可用于填充块。
IMAGE_REL_BASED_HIGH
1
基址重定位在偏移量处将差值的高 16 位添加到 16 位字段。 16 位字段表示 32 位单词的高值。
IMAGE_REL_BASED_LOW
2
基址重定位会在偏移量处将差值的低 16 位添加到 16 位字段。 16 位字段表示 32 位单词的下半部分。
IMAGE_REL_BASED_HIGHLOW
3
基址重定位会在偏移量处将差值的所有 32 位应用于 32 位字段。
IMAGE_REL_BASED_HIGHADJ
4
基址重定位在偏移量处将差值的高 16 位添加到 16 位字段。 16 位字段表示 32 位单词的高值。 32 位值的低 16 位存储在此基址重定位后面的 16 位单词中。 这意味着此基址重定位占用两个槽。
IMAGE_REL_BASED_MIPS_JMPADDR
5
重定位的解释取决于计算机类型。
当计算机类型为 MIPS 时,基址重定位适用于 MIPS 跳转指令。
IMAGE_REL_BASED_ARM_MOV32
5
仅当计算机类型为 ARM 或 Thumb 时,此重定位才有意义。 基址重定位会跨连续 MOVW/MOVT 指令对应用符号的 32 位地址。
IMAGE_REL_BASED_RISCV_HIGH20
5
仅当计算机类型为 RISC-V 时,此重定位才有意义。 基址重定位适用于 32 位绝对地址的高 20 位。
6
保留,必须为零。
IMAGE_REL_BASED_THUMB_MOV32
7
仅当计算机类型为 Thumb 时,此重定位才有意义。 基址重定位会将符号的 32 位地址应用于连续的 MOVW/MOVT 指令对。
IMAGE_REL_BASED_RISCV_LOW12I
7
仅当计算机类型为 RISC-V 时,此重定位才有意义。 基址重定位适用于采用 RISC-V I 类型指令格式的 32 位绝对地址的低 12 位。
IMAGE_REL_BASED_RISCV_LOW12S
8
仅当计算机类型为 RISC-V 时,此重定位才有意义。 基址重定位适用于采用 RISC-V S 类型指令格式的 32 位绝对地址的低 12 位。
IMAGE_REL_BASED_LOONGARCH32_MARK_LA
8
仅当计算机类型为 LoongArch 32 位时,此重定位才有意义。 基址重定位适用于由两个连续指令构成的 32 位绝对地址。
IMAGE_REL_BASED_LOONGARCH64_MARK_LA
8
仅当计算机类型为 LoongArch 64 位时,此重定位才有意义。 基址重定位适用于由四个连续指令构成的 64 位绝对地址。
IMAGE_REL_BASED_MIPS_JMPADDR16
9
仅当计算机类型为 MIPS 时,此重定位才有意义。 基址重定位适用于 MIPS16 跳转指令。
IMAGE_REL_BASED_DIR64
10
基址重定位会在偏移量处将差值应用于 64 位字段。

 

.tls 节

.tls 节为静态线程本地存储 (TLS) 提供直接 PE 和 COFF 支持。 TLS 是 Windows 支持的一种特殊存储类,其中数据对象不是自动(堆栈)变量,而是运行代码的每个单独线程的本地变量。 因此,每个线程都可以为使用 TLS 声明的变量维护不同的值。

请注意,可以使用 API 调用 TlsAlloc、TlsFree、TlsSetValue 和 TlsGetValue 来支持任意数量的 TLS 数据。 PE 或 COFF 实现是使用 API 的替代方法,从高级语言程序员的角度来看,此实现具有更简单的优势。 通过此实现,可以定义和初始化 TLS 数据,这与程序中的普通静态变量类似。 例如,在 Visual C++ 中,可以如下定义静态 TLS 变量,而无需使用 Windows API:

__declspec (thread) int tlsFlag = 1;

为了支持此编程构造,PE 和 COFF .tls 节会指定以下信息:初始化数据、每个线程初始化和终止的回调例程以及以下讨论中介绍的 TLS 索引。

注意

在 Windows Vista 之前,静态声明的 TLS 数据对象只能在静态加载的图像文件中使用。 这种事实使得在 DLL 中使用静态 TLS 数据很不可靠,除非你知道永远不会使用 LoadLibrary API 函数动态加载 DLL 或与其静态链接的任何内容。 但是,从 Windows Vista 开始,对 Windows 加载程序进行了改进,以便更好地支持使用静态 TLS 的动态加载 DLL。 此更改意味着,即使使用 LoadLibrary 动态加载具有静态声明的 TLS 数据对象的 DLL,现在也能更可靠地使用。 加载程序能够在加载时为此类 DLL 分配 TLS 槽,从而减少早期版本的 Windows 中存在的限制。

 

注意

对 32 位偏移量和索引乘数 4 的引用适用于采用 32 位体系结构的系统。 在基于 64 位体系结构的系统中,根据需要对其进行调整。

可执行代码通过以下步骤访问静态 TLS 数据对象:

  1. 在链接时,链接器设置 TLS 目录的“索引地址”字段。 此字段指向程序需要接收 TLS 索引的位置。

    Microsoft 运行时库通过定义 TLS 目录的内存映像并为其提供特殊名称“__tls_used”(Intel x86 平台)或“_tls_used”(其他平台)来促进此过程。 链接器查找此内存映像,并使用该处的数据创建 TLS 目录。 支持 TLS 并使用 Microsoft 链接器的其他编译器必须使用相同的技术。

  2. 在创建线程时,加载程序通过在 FS 寄存器中放置线程环境块 (TEB) 的地址来传达线程的 TLS 数组的地址。 指向 TLS 数组的指针位于 TEB 开头 0x2C 偏移量处。 此行为特定于 Intel x86。

  3. 加载程序将 TLS 索引的值分配给“索引地址”字段指示的位置。

  4. 可执行代码检索 TLS 索引和 TLS 数组的位置。

  5. 该代码使用 TLS 索引和 TLS 数组位置(将索引乘以 4,并将其用作数组的偏移量),以获取给定程序和模块的 TLS 数据区域的地址。 每个线程都有自己的 TLS 数据区域,但这对程序是透明的,程序不需要知道如何为单个线程分配数据。

  6. 访问作为 TLS 数据区域中的一些固定偏移量的单个 TLS 数据对象。

TLS 数组是系统为每个线程维护的地址数组。 此数组中的每个地址都为程序中给定模块(EXE 或 DLL)提供 TLS 数据的位置。 TLS 索引指示要使用的数组成员。 索引是用于标识模块的数字(仅对系统有意义)。

TLS 目录

TLS 目录采用以下格式:

偏移量 (PE32/PE32+) 大小(PE32/PE32+) 字段 说明
0
4/8
原始数据启动 VA
TLS 模板的起始地址。 该模板是用于初始化 TLS 数据的数据块。 每次创建线程时,系统都会复制所有这些数据,因此不得损坏这些数据。 请注意,此地址不是 RVA;它是 .reloc 节中应存在基址重定位的地址。
4/8
4/8
原始数据结束 VA
TLS 最后一个字节的地址,零填充除外。 与原始数据开始 VA 字段一样,这是一个 VA,而不是 RVA。
8/16
4/8
索引地址
用于接收加载程序分配的 TLS 索引的位置。 此位置在普通数据部分中,因此可以为其提供一个程序可访问的符号名称。
12/24
4/8
回调地址
指向 TLS 回调函数数组的指针。 此数组以 null 结尾,因此,如果不支持回调函数,则此字段会指向设置为零的 4 个字节。 有关这些函数的原型的信息,请参阅 TLS 回调函数
16/32
4
零填充的大小
模板的大小(以字节为单位),此大小超出了“原始数据开始 VA”和“原始数据结束 VA”字段分隔的初始化数据。 模板总大小应与映像文件中 TLS 数据的总大小相同。 零填充是初始化的非零数据之后的数据量。
20/36
4
特征
四位 [23:20] 描述对齐信息。 可能的值是定义为 IMAGE_SCN_ALIGN_* 的值,这些值还用于描述对象文件中节的对齐方式。 其他 28 位保留供将来使用。

 

TLS 回调函数

程序可以提供一个或多个 TLS 回调函数,以支持 TLS 数据对象的其他初始化和终止。 此类回调函数的典型用途是调用对象的构造函数和析构函数。

尽管通常没有多个回调函数,但回调会作为数组实现,以便可以在需要时添加其他回调函数。 如果有多个回调函数,则按数组中显示其地址的顺序调用每个函数。 null 指针会终止数组。 拥有一个空列表(不支持回调)是完全有效的,在这种情况下,回调数组只有一个成员 - 一个 null 指针。

PIMAGE_TLS_CALLBACK 类型的指针指向的回调函数原型具有与 DLL 入口点函数相同的参数:

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved
    );

“保留”参数应设置为零。 “原因”参数的取值可为:

设置 说明
DLL_PROCESS_ATTACH
1
已启动一个新进程,包括第一个线程。
DLL_THREAD_ATTACH
2
已创建新线程。 此通知已发送到除第一个线程以外的所有线程。
DLL_THREAD_DETACH
3
线程即将终止。 此通知已发送到除第一个线程以外的所有线程。
DLL_PROCESS_DETACH
0
进程即将终止,包括原始线程。

 

加载配置结构(仅限映像)

加载配置结构 (IMAGE_LOAD_CONFIG_DIRECTORY) 以前在 Windows NT 操作系统本身中仅在非常有限的情况下使用,用于描述难以或太大而无法在映像的文件标头或可选标头中描述的各种功能。 当前版本的 Microsoft 链接器和 Windows XP 及更高版本的 Windows 对包含所保留 SEH 技术的 32 位基于 x86 的系统使用此结构的新版本。 这将提供操作系统在异常调度期间使用的安全结构化异常处理程序的列表。 如果处理程序地址驻留在映像的 VA 范围内,并且标记为可感知保留的 SEH(即,在可选标头的 DllCharacteristics 字段中清除了 IMAGE_DLLCHARACTERISTICS_NO_SEH,如前所述),则该处理程序必须位于该映像的已知安全处理程序列表中。 否则,操作系统将终止应用程序。 这有助于防止过去用来控制操作系统的“x86 异常处理程序劫持”漏洞。

Microsoft 链接器会自动提供默认加载配置结构,以包含保留的 SEH 数据。 如果用户代码已提供负载配置结构,则必须包含新的保留 SEH 字段。 否则,链接器不能包含保留的 SEH 数据,并且图像未标记为包含保留的 SEH。

加载配置目录

预保留 SEH 加载配置结构的数据目录条目必须指定加载配置结构的特定大小,因为操作系统加载程序始终要求它是一个特定值。 在这方面,大小实际上只是一项版本检查。 为了与 Windows XP 和早期版本的 Windows 兼容,对于 x86 映像,大小必须为 64。

加载配置布局

对于 32 位和 64 位 PE 文件,加载配置结构具有以下布局:

Offset 大小 字段 说明
0
4
特征
指示文件属性的标志,当前未使用。
4
4
TimeDateStamp
日期和时间戳值。 该值以系统时钟自 1970 年 1 月 1 日协调世界时午夜 (00:00:00) 以来经过的秒数来表示。 可以使用 C 运行时 (CRT) 时间函数打印时间戳。
8
2
MajorVersion
主版本号。
10
2
MinorVersion
次版本号。
12
4
GlobalFlagsClear
当加载程序启动进程时要为此进程清除的全局加载程序标志。
16
4
GlobalFlagsSet
当加载程序启动进程时要为此进程设置的全局加载程序标志。
20
4
CriticalSectionDefaultTimeout
要用于此进程的所放弃关键部分的默认超时值。
24
4/8
DeCommitFreeBlockThreshold
返还给系统之前必须释放的内存(以字节为单位)。
28/32
4/8
DeCommitTotalFreeThreshold
可用内存总量(以字节为单位)。
32/40
4/8
LockPrefixTable
[仅 x86] 使用 LOCK 前缀以便可在单个处理器计算机上替换为 NOP 的地址列表的 VA。
36/48
4/8
MaximumAllocationSize
最大分配大小(以字节为单位)。
40/56
4/8
VirtualMemoryThreshold
最大虚拟内存大小(以字节为单位)。
44/64
4/8
ProcessAffinityMask
将此字段设置为非零值等效于在进程启动期间使用此值调用 SetProcessAffinityMask(仅限 .exe)
48/72
4
ProcessHeapFlags
与 HeapCreate 函数的第一个参数相对应的进程堆标志。 这些标志适用于进程启动期间创建的进程堆。
52/76
2
CSDVersion
Service Pack 版本标识符。
54/78
2
保留
必须为零。
56/80
4/8
EditList
保留供系统使用。
60/88
4/8
SecurityCookie
一个指向 Visual C++ 或 GS 实现使用的 Cookie 的指针。
64/96
4/8
SEHandlerTable
[仅限 x86] 映像中每个有效且唯一的 SE 处理程序的 RVA 排序表的 VA。
68/104
4/8
SEHandlerCount
[仅限 x86] 表中的唯一处理程序的计数。
72/112
4/8
GuardCFCheckFunctionPointer
存储控制流保护检查函数指针的 VA。
76/120
4/8
GuardCFDispatchFunctionPointer
存储控制流保护调度函数指针的 VA。
80/128
4/8
GuardCFFunctionTable
映像中每个控制流保护函数的 RVA 排序表的 VA。
84/136
4/8
GuardCFFunctionCount
上表中唯一的 RVA 计数。
88/144
4
GuardFlags
控制流保护相关标志。
92/148
12
代码完整性
代码完整性信息。
104/160
4/8
GuardAddressTakenIatEntryTable
在其中存储控制流保护地址获取 IAT 表的 VA。
108/168
4/8
GuardAddressTakenIatEntryCount
上表中唯一的 RVA 计数。
112/176
4/8
GuardLongJumpTargetTable
在其中存储控制流保护长跳目标表的 VA。
116/184
4/8
GuardLongJumpTargetCount
上表中唯一的 RVA 计数。

 

GuardFlags 字段包含一个或多个以下标志和子字段的组合:

  • 模块使用系统提供的支持执行控制流完整性检查。

    #define IMAGE_GUARD_CF_INSTRUMENTED 0x00000100

  • 模块执行控制流和写入完整性检查。

    #define IMAGE_GUARD_CFW_INSTRUMENTED 0x00000200

  • 模块包含有效的控制流目标元数据。

    #define IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT 0x00000400

  • 模块不使用 /GS 安全 Cookie。

    #define IMAGE_GUARD_SECURITY_COOKIE_UNUSED 0x00000800

  • 模块支持只读延迟加载 IAT。

    #define IMAGE_GUARD_PROTECT_DELAYLOAD_IAT 0x00001000

  • 延迟加载导入表在其自己的可以自由重新保护的 .didat 节(没有任何其他内容)中。

    #define IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION 0x00002000

  • 模块包含隐藏的导出信息。 这还可以推断出地址获取 IAT 表也存在于加载配置中。

    #define IMAGE_GUARD_CF_EXPORT_SUPPRESSION_INFO_PRESENT 0x00004000

  • 模块支持禁止导出。

    #define IMAGE_GUARD_CF_ENABLE_EXPORT_SUPPRESSION 0x00008000

  • 模块包含 longjmp 目标信息。

    #define IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT 0x00010000

  • 包含控制流保护函数表条目(即每个表条目的额外字节计数)步幅的子字段的掩码。

    #define IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK 0xF0000000

此外,Windows SDK winnt.h 标头定义了此宏,以将 GuardFlags 值右移一定量的位数,从而右对齐控制流保护函数表步幅:

#define IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT 28

.rsrc 节

资源由多级二进制排序树结构编制索引。 常规设计可以包含 2**31 个级别。 但是,按照惯例,Windows 使用三个级别:

类型名称语言

一系列资源目录表按以下方式关联所有级别:每个目录表后跟一系列目录条目,这些条目为该级别(类型、名称或语言级别)提供名称或标识符 (ID),以及数据说明或其他目录表的地址。 如果地址指向数据说明,则数据是树中的叶。 如果地址指向另一个目录表,则该表会在下一级别列出目录条目。

叶的类型、名称和语言 ID 由通过目录表到达叶的路径确定。 第一个表确定类型 ID、(第一个表中的目录条目所指向的)第二个表确定名称 ID,第三个表确定语言 ID。

.rsrc 节的一般结构为:

数据 说明
资源目录表(和资源目录条目)
一系列表(树中每个节点组都有一个表)。 所有顶级(类型)节点都列在第一个表中。 此表中的条目指向第二级表。 每个第二级树都具有相同的类型 ID,但名称 ID 不同。 第三级树具有相同的类型和名称 ID,但语言 ID 不同。
每个单独的表后面紧跟目录条目,其中每个条目都有一个名称或数字标识符,以及一个指向下一个较低级别的数据说明或表的指针。
资源目录字符串
双字节对齐的 Unicode 字符串,用作目录条目所指向的字符串数据。
资源数据说明
由表所指向的记录数组,用于描述资源数据的实际大小和位置。 这些记录是资源描述树中的叶。
资源数据
资源部分的原始数据。 “资源数据说明”字段中的大小和位置信息将分隔资源数据的各个区域。

 

资源目录表

每个资源目录表都采用以下格式。 此数据结构应被视为表的标题,因为该表实际上包含目录条目(如第 6.9.2 节“资源目录条目”中所述)和此结构:

Offset 大小 字段 说明
0
4
特征
资源标志。 保留此字段供将来使用。 它当前设置为零。
4
4
时间/日期戳
资源编译器创建资源数据的时间。
8
2
主版本
用户设置的主要版本号。
10
2
次要版本
用户设置的次要版本号。
12
2
名称条目数
紧跟在表后面的目录条目数,这些条目使用字符串标识类型、名称或语言条目(具体取决于表的级别)。
14
2
ID 条目数
紧跟在“名称”条目后面的目录条目数,这些条目使用类型、名称或语言条目的数字 ID。

 

资源目录条目

目录条目构成表的行。 每个资源目录条目都采用以下格式。 资源目录表指示条目是名称条目还是 ID 条目,该表指示后面的名称和 ID 条目数(请记住,对于表,所有名称条目都在所有 ID 条目之前)。 表的所有条目都按升序排序:命名条目按区分大小写的字符串排序,ID 条目按数字值排序。 偏移量相对于 IMAGE_DIRECTORY_ENTRY_RESOURCE DataDirectory 中的地址。 有关详细信息,请参阅 PE 内部对等互连:Win32 可移植可执行文件格式教程

Offset 大小 字段 说明
0
4
名称偏移量
提供类型、名称或语言 ID 条目的字符串的偏移量,具体取决于表级别。
0
4
整数 ID
一个标识类型、名称或语言 ID 条目的 32 位整数。
4
4
数据条目偏移量
高位 0。 资源数据条目(叶)的地址。
4
4
子目录偏移量
高位 1。 较低的 31 位是另一个资源目录表(下一级别)的地址。

 

资源目录字符串

资源目录字符串区域由 Unicode 字符串组成,这些字符串是单词对齐的字符串。 这些字符串一起存储在最后一个资源目录条目之后和第一个资源数据条目之前。 这样可以最大程度地减少这些可变长度字符串对固定大小目录条目的对齐方式的影响。 每个资源目录字符串都采用以下格式:

Offset 大小 字段 说明
0
2
Length
不包括长度字段本身在内的字符串大小。
2
变量
Unicode 字符串
单词对齐的可变长度 Unicode 字符串数据。

 

资源数据输入

每个资源数据条目都将描述资源数据区域中原始数据的实际单位。 资源数据条目采用以下格式:

Offset 大小 字段 说明
0
4
数据 RVA
资源数据区域中资源数据单位的地址。
4
4
大小
“数据 RVA”字段指向的资源数据的大小(以字节为单位)。
8
4
代码页
用于解码资源数据中的代码点值的代码页。 通常,代码页将是 Unicode 代码页。
12
4
已保留,必须为 0。

 

.cormeta 节(仅限对象)

CLR 元数据存储在本节中。 它用于指示对象文件包含托管代码。 元数据的格式未记录,但可以交给 CLR 接口来处理元数据。

.sxdata 节

该对象的 .sxdata 节中列出了对象的有效异常处理程序。 该节带有 IMAGE_SCN_LNK_INFO 标记。 它包含每个有效处理程序的 COFF 符号索引,每个索引使用 4 个字节。

此外,编译器通过发出绝对符号“@feat.00”并将值字段的 LSB 设置为 1,从而将 COFF 对象标记为已注册的 SEH。 没有注册的 SEH 处理程序的 COFF 对象将具有“@feat.00”符号,但没有 .sxdata 节。

存档(库)文件格式

COFF 存档格式提供用于存储对象文件集合的标准机制。 在编程文档中,这些集合通常称为库。

存档的前 8 个字节由文件签名组成。 存档的其余部分由一系列存档成员组成,如下所示:

  • 第一个和第二个成员是“链接器成员”。每个成员都有自己的格式,如导入名称类型部分中所述。 通常,链接器会将信息放入这些存档成员中。 链接器成员包含存档的目录。

  • 第三个成员是“长名称”成员。 此可选成员由一系列以 null 结尾的 ASCII 字符串组成,其中每个字符串都是另一个存档成员的名称。

  • 存档的其余部分由标准(对象文件)成员组成。 其中每个成员都包含一个对象文件的全部内容。

存档成员标头位于每个成员之前。 以下列表显示了存档的一般结构:

签名 :"!<arch>\n"
标头
第一个链接器成员
标头
第二个链接器成员
标头
长名称成员
标头
OBJ 文件 1 的内容
(COFF 格式)
标头
OBJ 文件 2 的内容
(COFF 格式)

...

标头
OBJ 文件 N 的内容
(COFF 格式)

存档文件签名

存档文件签名标识文件类型。 任何将存档文件作为输入的实用工具(例如链接器)都可以通过读取此签名来检查文件类型。 签名由以下 ASCII 字符组成,其中以下每个字符均按字面意思表示,换行符 (\n) 除外:

!<arch>\n

Windows SDK winnt.h 标头定义以下宏:

#define IMAGE_ARCHIVE_START_SIZE             8
#define IMAGE_ARCHIVE_START                  "!<arch>\n"
#define IMAGE_ARCHIVE_END                    "`\n"
#define IMAGE_ARCHIVE_PAD                    "\n"
#define IMAGE_ARCHIVE_LINKER_MEMBER          "/               "
#define IMAGE_ARCHIVE_LONGNAMES_MEMBER       "//              "
#define IMAGE_ARCHIVE_HYBRIDMAP_MEMBER       "/<HYBRIDMAP>/   "

存档成员标头

每个成员(链接器、长名称或对象文件成员)前面都有一个标头。 存档成员标头采用以下格式,其中每个字段都是一个左对齐并将空格填充到字段末尾的 ASCII 文本字符串。 这些字段中没有任何用于结尾的 null 字符。

每个成员标头都在上一个存档成员末尾之后的第一个偶数地址开始,一个字节“\n”(IMAGE_ARCHIVE_PAD) 可能会插入到存档成员之后,以使以下成员在偶数地址开始。

Offset 大小 字段 说明
0
16
名称
通过追加斜杠 (/) 来终止的存档成员名称。 如果第一个字符是斜杠,则名称具有特殊解释,如下表所述。
16
12
日期
创建存档成员的日期和时间:这是自 1/1/1970 UCT 以来的秒数的 ASCII 十进制表示形式。
28
6
用户 ID
用户 ID 的 ASCII 十进制表示形式。 此字段在 Windows 平台上未包含有意义的值,因为 Microsoft 工具会发出所有空白。
34
6
组 ID
组 ID 的 ASCII 十进制表示形式。 此字段在 Windows 平台上未包含有意义的值,因为 Microsoft 工具会发出所有空白。
40
8
模式
成员的文件模式的 ASCII 八进制表示形式。 这是 C 运行时函数_wstat 中的 ST_MODE 值。
48
10
大小
存档成员的总大小(不包括标头的大小)的 ASCII 十进制表示形式。
58
2
标头结尾
C 字符串“`\n”(IMAGE_ARCHIVE_END) 中的两个字节 (0x60 0x0A)。

“名称”字段具有下表中所显示的格式之一。 如前所述,这些字符串中的每一个都保持左对齐,并在 16 字节字段内用尾随空格填充:

“名称”字段的内容 说明
名称/
存档成员的名称。
/
存档成员是两个链接器成员之一。 这两个链接器成员都有此名称。
//
存档成员是由一系列以 null 结尾的 ASCII 字符串组成的长名称成员。 长名称成员是第三个存档成员,并且是可选的。
/n
存档成员的名称位于长名称成员中的偏移量 n 处。 数字 n 是偏移量的十进制表示形式。 例如:“/26”表示存档成员的名称位于长名称成员内容开头之后 26 个字节处。

 

第一个链接器成员

第一个链接器成员的名称为 "/" (IMAGE_ARCHIVE_LINKER_MEMBER)。 包含了第一个链接器成员以实现向后兼容性。 当前链接器不使用它,但其格式必须正确。 此链接器成员像第二个链接器成员一样提供符号名称的目录。 对于每个符号,此信息指示在何处查找包含符号的存档成员。

第一个链接器成员具有以下格式。 此信息出现在标头后面:

Offset 大小 字段 说明
0
4
符号数
包含索引符号数的无符号长整型数值。 此数字以 big-endian 格式存储。 每个对象文件成员通常会定义一个或多个外部符号。
4
4 * n
偏移量
存档成员标头的文件偏移量数组,其中 n 等于“符号数”字段值。 数组中的每个数字都是以 big-endian 格式存储的无符号长整型数值。 对于字符串表中命名的每个符号,偏移量数组中的相应元素会提供包含此符号的存档成员的位置。
*
*
字符串表
一系列以 null 结尾的字符串,用于命名目录中的所有符号。 每个字符串都在上一个字符串中的 null 字符之后立即开始。 字符串数必须等于“符号数”字段的值。

 

偏移量数组中的元素必须按升序排列。 此事实意味着必须按照存档成员顺序排列字符串表中的符号。 例如,第一个对象文件成员中的所有符号都必须在第二个对象文件中的符号之前列出。

第二个链接器成员

与第一个链接器成员一样,第二个链接器成员的名称也为 "/" (IMAGE_ARCHIVE_LINKER_MEMBER)。 尽管这两个链接器成员都提供符号目录和包含符号的存档成员,但所有当前链接器都会优先使用第二个链接器成员。 第二个链接器成员会按词法顺序包含符号名称,这样就可以按名称更快地进行搜索。

第二个成员采用以下格式。 此信息出现在标头后面:

Offset 大小 字段 说明
0
4
成员数
包含存档成员数的无符号长整型数值。
4
4 * m
偏移量
按升序排列的存档成员标头文件偏移量数组。 每个偏移量都是一个无符号长整型数值。 数字 m 等于“成员数”字段的值。
*
4
符号数
包含索引符号数的无符号长整型数值。 每个对象文件成员通常会定义一个或多个外部符号。
*
2 * n
索引
一个将符号名称映射到存档成员偏移量的从 1 开始的索引(无符号短整型数值)数组。 数字 n 等于“符号数”字段的值。 对于字符串表中命名的每个符号,索引数组中的相应元素会提供偏移量数组的索引。 偏移量数组反过来会提供包含符号的存档成员的位置。
*
*
字符串表
一系列以 null 结尾的字符串,用于命名目录中的所有符号。 每个字符串都在上一个字符串中的 null 字节之后立即开始。 字符串数必须等于“符号数”字段的值。 此表按词法升序列出所有符号名称。

 

长名称成员

长名称成员的名称是 "//" (IMAGE_ARCHIVE_LONGNAMES_MEMBER)。 长名称成员是一系列存档成员名称的字符串。 仅当“名称”字段(16 字节)中没有足够空间时,才会在此处显示一个名称。 长名称成员是可选成员。 它可以只有一个标头而其他全为空,也可以完全不存在,甚至没有标头。

字符串以 null 结尾。 每个字符串都在上一个字符串中的 null 字节之后立即开始。

导入库格式

传统的导入库(即描述从一个映像导出供另一个映像使用的内容的库)通常采用第 7 节存档(库)文件格式中描述的布局。 主要区别在于,导入库成员包含伪对象文件而不是实际文件,其中每个成员都包含生成第 6.4 节(.idata 节)中所述的导入表所需的节组成部分。链接器在生成导出应用程序时会生成此存档。

可以从一小组信息推断导入的节组成部分。 链接器可以在创建库时为每个成员在导入库中生成完整、详细的信息,也可以只将规范信息写入到库中,让稍后使用此库的应用程序动态生成必要的数据。

在采用长格式的导入库中,单个成员包含以下信息:

  • 存档成员标头
  • 文件标头
  • 节标头
  • 对应于每个节标头的数据
  • COFF 符号表
  • 字符串

相比之下,短导入库的编写方式如下:

  • 存档成员标头
  • 导入标头
  • 以 Null 结尾的导入名称字符串
  • 以 null 结尾的 DLL 名称字符串

此信息足以在使用时准确重新构造成员的全部内容。

导入标头

导入标头包含以下字段和偏移量:

Offset 大小 字段 说明
0
2
Sig1
必须是 IMAGE_FILE_MACHINE_UNKNOWN。 有关详细信息,请参阅目标计算机类型
2
2
Sig2
必须是 0xFFFF。
4
2
版本
结构版本。
6
2
设备
标识目标计算机类型的数字。 有关详细信息,请参阅目标计算机类型
8
4
日期/时间戳
创建文件的时间和日期。
12
4
数据的大小
标头后面的字符串的大小。
16
2
序号/提示
由“名称类型”字段中的值所确定的导入的序号或提示。
18
2 位
类型
导入类型。 有关特定值和说明,请参阅导入类型
3 位
名称类型
导入名称类型。 有关详细信息,请参阅导入名称类型
11 位
保留
已保留,必须为 0。

 

此结构后跟两个以 null 结尾的字符串,这些字符串描述导入的符号的名称及其来自的 DLL。

导入类型

为导入标头中的“类型”字段定义了以下值:

常量 Value 说明
IMPORT_OBJECT_CODE
0
可执行代码。
IMPORT_OBJECT_DATA
1
数据。
IMPORT_OBJECT_CONST
2
在 .def 文件中指定为 CONST。

这些值用于确定哪些节组成部分必须由使用库的工具生成(如果它必须访问该数据)。

导入名称类型

以 null 结尾的导入符号名称紧跟在其关联的导入标头后面。 为导入标头中的“名称类型”字段定义了以下值。 它们指示名称如何用于生成表示导入的正确符号:

常量 Value 说明
IMPORT_OBJECT_ORDINAL 0 导入按序号进行。 这表示导入标头的“序号/提示”字段中的值是导入的序号。 如果未指定此常量,则“序号/提示”字段应始终解释为导入的提示。
IMPORT_OBJECT_NAME 1 导入名称与公共符号名称相同。
IMPORT_OBJECT_NAME_NOPREFIX 2 导入名称是公共符号名称,但跳过了前导 ? 或 @,或者可选择性跳过 _。
IMPORT_OBJECT_NAME_UNDECORATE 3 导入名称是公共符号名称,但跳过了前导 ? 或 @,或者可选择性跳过 _,并且会在第一个 @ 处截断。

附录 A:计算验证码 PE 映像哈希

应使用多个属性证书来验证映像的完整性。 但是,最常见的是验证码签名。 验证码签名可用于验证 PE 映像文件的相关部分在文件原始格式方面是否未进行过任何更改。 为了完成此任务,验证码签名会包含名为 PE 映像哈希的内容

什么是验证码 PE 映像哈希?

验证码 PE 映像哈希(或简称为文件哈希)类似于文件校验和,因为它会生成与文件完整性相关的小值。 校验和由简单的算法生成,主要用于检测内存故障。 也就是说,它用于检测磁盘上的内存块是否已损坏,并且其中存储的值是否已损坏。 文件哈希类似于校验和,因为它也可以检测文件损坏。 但是,与大多数校验和算法不同,很难修改文件以使其具有与其原始(未修改)形式相同的文件哈希。 也就是说,校验和旨在检测导致损坏的简单内存故障,但文件哈希可用于检测对文件的有意甚至细微的修改,例如由病毒、黑客或特洛伊木马程序引入的修改。

在验证码签名中,文件哈希是使用只有文件签名者知道的私钥进行数字签名的。 软件使用者可以通过计算文件的哈希值并将其与验证码数字签名中包含的已签名哈希值进行比较来验证文件的完整性。 如果文件哈希不匹配,则 PE 映像哈希涵盖的一部分文件已被修改。

验证码 PE 映像哈希中涵盖的内容是什么?

在 PE 映像哈希的计算中不能或不需要包括所有映像文件数据。 有时它只是呈现出不需要的特征(例如,无法从公开发布的文件中删除调试信息);有时这根本不可能。 例如,无法将映像文件中的所有信息都包含在验证码签名中,然后将包含该 PE 映像哈希的验证码签名插入到 PE 映像中,以便将来能够通过在计算中再次包含所有映像文件数据来生成相同的 PE 映像哈希,因为该文件现在包含原来不在那里的验证码签名。

生成验证码 PE 映像哈希的过程

本节介绍如何计算 PE 映像哈希,以及可以修改 PE 映像的哪些部分而不会使验证码签名失效。

注意

特定文件的 PE 映像哈希可以包含在单独的目录文件中,而无需在哈希文件中包含属性证书。 这是相关的,因为通过修改实际上不包含验证码签名的 PE 映像,可以使验证码签名的目录文件中的 PE 映像哈希无效。

节表中指定的 PE 映像各节中的所有数据都经过了完整哈希处理,以下排除范围除外:

  • 可选标头的 Windows 特定字段的文件 CheckSum 字段。 此校验和包括整个文件(包括文件中的任何属性证书)。 插入验证码签名后,校验和很可能与原始值不同。

  • 与属性证书相关的信息。 PE 映像中与验证码签名相关的区域未包含在 PE 映像哈希计算中,因为可以在映像中添加或删除验证码签名,而不会影响映像的整体完整性。 这不是问题,因为存在依赖于重新签名 PE 映像或添加时间戳的用户方案。 验证码会从哈希计算中排除以下信息:

    • 可选标头数据目录的“证书表”字段。

    • 证书表和正上方列出的“证书表”字段指向的相应证书。

    为了计算 PE 图像哈希,验证码会按地址范围对节表中指定的节进行排序,然后对生成的字节序列进行哈希处理,并忽略排除范围。

  • 最后一节末尾之后的信息。 最后一节之后的区域(由最高偏移量定义)未经过哈希处理。 此区域通常包含调试信息。 调试信息通常可以被认为是对调试器的建议;它不会影响可执行程序的实际完整性。 在产品交付后,完全可以从映像中删除调试信息,并且不会影响程序的功能。 事实上,有时这样做是作为一种节省磁盘的措施。 值得注意的是,如果不使验证码签名无效,则无法删除 PE 映像指定部分中包含的调试信息。

您可以使用 Windows 平台 SDK 中提供的 makecert 和 signtool 工具来尝试创建和验证验证码签名。 有关详细信息,请参阅以下“参考”。

参考

适用于 Windows 的下载和工具(包括 Windows SDK)

创建、查看和管理证书

内核模式代码签名演练 (.doc)

SignTool

Windows 验证码可移植可执行签名格式 (.docx)

ImageHlp 函数