XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog
 

PE 格式

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

 备注

本文档旨在帮助开发适用于 Windows 的工具和应用程序,但不保证在所有方面都是完整的规范。 Microsoft 保留更改此文档的权利,无需通知。

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

一般概念

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

下表描述了整个规范中显示的某些概念:

名称说明
属性证书 用于将可验证语句与映像关联的证书。 可以将多个不同的可验证语句与文件关联;其中一个最有用的是软件制造商的语句,该语句指示图像的消息摘要预期是什么。 消息摘要类似于校验和,只是它极难伪造。 因此,很难将文件修改为具有与原始文件相同的消息摘要。 制造商可以使用公钥或私钥加密方案来验证该语句。 本文档介绍属性证书的详细信息,但不允许将其插入图像文件。
日期/时间戳 在 PE 或 COFF 文件中的多个位置用于不同用途的标记。 在大多数情况下,每个标记的格式与 C 运行时库中的时间函数使用的格式相同。 有关异常,请参阅 调试类型中IMAGE_DEBUG_TYPE_REPRO的描述符。 如果标记值为 0 或 0xFFFFFFFF,则它不表示实际或有意义的日期/时间戳。
文件指针 在对象文件) 的情况下,链接器 (处理文件本身中的项的位置;对于图像文件) ,则加载程序 (处理。 换句话说,这是存储在磁盘上的文件中的一个位置。
链接器 对 Microsoft Visual Studio 中提供的链接的引用。
对象文件 作为链接器输入的文件。 链接器生成一个图像文件,而图像文件又由加载程序用作输入。 术语“对象文件”不一定意味着与面向对象的编程有任何连接。
reserved, 必须为 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”,后跟两个空字节) 。

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 类型。 映像文件只能在指定的计算机或模拟指定计算机的系统上运行。

常数“值”说明
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 松下 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 三菱 M32R 小 endian
IMAGE_FILE_MACHINE_MIPS16 0x266 MIPS16
IMAGE_FILE_MACHINE_MIPSFPU 0x366 使用 FPU 的 MIPS
IMAGE_FILE_MACHINE_MIPSFPU16 0x466 使用 FPU 的 MIPS16
IMAGE_FILE_MACHINE_POWERPC 0x1f0 Power PC little endian
IMAGE_FILE_MACHINE_POWERPCFP 0x1f1 支持浮点的 Power PC
IMAGE_FILE_MACHINE_R4000 0x166 MIPS 小 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 日立 SH3
IMAGE_FILE_MACHINE_SH3DSP 0x1a3 Hitachi SH3 DSP
IMAGE_FILE_MACHINE_SH4 0x1a6 日立 SH4
IMAGE_FILE_MACHINE_SH5 0x1a8 日立 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 大 endian:MSB 位于内存中的 LSB 之前。 此标志已弃用,应为零。

可选标头 (仅限图像)

每个图像文件都有一个向加载程序提供信息的可选标头。 从某种意义上说,此标头是可选的,即某些文件具体 (,) 对象文件没有它。 对于图像文件,此标头是必需的。 对象文件可以有一个可选的标头,但通常此标头在对象文件中没有函数,除了增加其大小。

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

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

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

Magic 数字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 包含此附加字段,在 PE32+ 中不存在,在 BaseOfCode 之后。

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

可选标头Windows-Specific字段 (仅图像)

接下来的 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 用于使映像文件中各部分的原始数据一致的对齐系数(以字节为单位)。 该值应为介于 512 和 64 K 之间的 2 的幂(包括 512 到 64 K)。 默认值为 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 校验 图像文件校验和。 用于计算校验和的算法已合并到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 子系统。

常数“值”说明
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 字段定义的。

常数“值”说明
  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 字节字段:

C++
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 section (Only Image) 
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 Global Ptr 要存储在全局指针寄存器中的值的 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 (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_数据 0x00000080 节包含未初始化的数据。
IMAGE_SCN_LNK_OTHER 0x00000100 保留供将来使用。
IMAGE_SCN_LNK_INFO 0x00000200 该部分包含注释或其他信息。 .drectve 部分具有此类型。 这仅适用于对象文件。
  0x00000400 保留供将来使用。
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 贡献之前结束。

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

文件的其他内容

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

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

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

分区数据

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

每个节的数据位于节标头中的 PointerToRawData 字段提供的文件偏移量处。 文件中此数据的大小由 SizeOfRawData 字段指示。 如果 SizeOfRawData 小于 VirtualSize,则余数填充为零。

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

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

COFF 仅 (对象重定位)

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

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

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

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

 

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

类型指示器

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

x64 处理器

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

常数“值”说明
IMAGE_REL_AMD64_ABSOLUTE 0x0000 将忽略重定位。
IMAGE_REL_AMD64_ADDR64 0x0001 重定位目标的 64 位 VA。
IMAGE_REL_AMD64_ADDR32 0x0002 重定位目标的 32 位 VA。
IMAGE_REL_AMD64_ADDR32NB 0x0003 没有映像基础的 32 位地址 (RVA) 。
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 处理器定义了以下重定位类型指示器。

常数“值”说明
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。 对于低 16 位,使用 MOVW 指令,然后针对高 16 位使用 MOVT 应用此重定位。
IMAGE_REL_THUMB_MOV32 0x0011 目标的 32 位 VA。 对于低 16 位,使用 MOVW 指令,然后针对高 16 位使用 MOVT 应用此重定位。
IMAGE_REL_THUMB_BRANCH20 0x0012 该指令使用 21 位相对位移固定到 2 字节对齐目标。 位移的最小有效位始终为零且不存储。 此重定位对应于 Thumb-2 32 位条件 B 指令。
未使用 0x0013  
IMAGE_REL_THUMB_BRANCH24 0x0014 指令通过 25 位相对位移固定到 2 字节对齐目标。 位移的最小有效位为零,不存储。此重定位对应于 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 处理器定义了以下重定位类型指示器。

常数“值”说明
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 12 位相对于目标位移,用于指令 ADR
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 重定位目标的 19 位偏移量,用于条件 B 指令。
IMAGE_REL_ARM64_BRANCH14 0x0010 重定位目标的 14 位偏移量,有关 TBZ 和 TBNZ 的说明。
IMAGE_REL_ARM64_REL32 0x0011 重定位后字节的 32 位相对地址。
日立超级H 处理器

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

常数“值”说明
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 对 8 位位置的引用,该位置的低 4 位包含目标符号的 VA。
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 处理器定义了以下重定位类型指示器。

常数“值”说明
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 重定位,其 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 和兼容的处理器定义的。

常数“值”说明
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 处理器系列和兼容处理器定义了以下重定位类型指示器。 请注意,指令上的重定位使用捆绑包的偏移量和槽号进行重定位偏移。

常数“值”说明
IMAGE_REL_IA64_ABSOLUTE 0x0000 重定位将被忽略。
IMAGE_REL_IA64_IMM14 0x0001 指令重定位可以后跟一个 ADDEND 重定位,其值在插入到 IMM14 捆绑包中的指定槽之前将它添加到目标地址。 重定位目标必须是绝对的,或者图像必须是固定的。
IMAGE_REL_IA64_IMM22 0x0002 指令重定位可以后跟一个 ADDEND 重定位,其值在插入到 IMM22 捆绑包中的指定槽之前将它添加到目标地址。 重定位目标必须是绝对的,或者图像必须是固定的。
IMAGE_REL_IA64_IMM64 0x0003 此重定位的槽号必须为 1 (1) 。 重定位后可以有一个 ADDEND 重定位,其值在存储在 IMM64 捆绑包的所有三个槽之前会添加到目标地址。
IMAGE_REL_IA64_DIR32 0x0004 目标的 32 位 VA。 仅 /LARGEADDRESSAWARE:NO 映像支持此操作。
IMAGE_REL_IA64_DIR64 0x0005 目标的 64 位 VA。
IMAGE_REL_IA64_PCREL21B 0x0006 指令通过 25 位相对位移固定到 16 位对齐目标。 位移的低 4 位为零,不存储。
IMAGE_REL_IA64_PCREL21M 0x0007 指令通过 25 位相对位移固定到 16 位对齐目标。 不存储位移的低 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 重定位,其“值”字段包含从部分开头开始的目标的 32 位无符号偏移量。
IMAGE_REL_IA64_SECREL64I 0x000D 此重定位的槽号必须为 1 (1) 。 指令使用目标从其部分开头开始的 64 位偏移量进行修复。 此重定位后,可以立即执行 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 一个 60 位 PC 相对修复,始终作为 MLX 捆绑包的一个转网指令。
IMAGE_REL_IA64_PCREL60B 0x0016 60 位 PC 相对修复。 如果目标位移适合有符号 25 位字段,请使用 NOP 将整个捆绑转换为 MBB 捆绑。槽 1 中的 B 和 25 位 BR 指令 (,4 个最低位全部为零,并在槽 2 中) 下降。
IMAGE_REL_IA64_PCREL60F 0x0017 60 位 PC 相对修复。 如果目标位移适合有符号 25 位字段,请将整个捆绑转换为具有 NOP 的 MFB 捆绑。槽 1 中的 F 和 25 位 (4 个最低位全部为零,并在槽 2 中) BR 指令下降。
IMAGE_REL_IA64_PCREL60I 0x0018 60 位 PC 相对修复。 如果目标位移适合有符号 25 位字段,请使用 NOP 将整个捆绑转换为 MIB 捆绑包。我在槽 1 和一个 25 位 (4 个最低位全部为零,并丢弃了槽 2 中的 BR 指令) 。
IMAGE_REL_IA64_PCREL60M 0x0019 60 位 PC 相对修复。 如果目标位移适合有符号 25 位字段,请将整个捆绑包转换为具有 NOP 的 MMB 捆绑包。槽 1 中的 M 和 25 位 (4 个最低位全部为零,并在槽 2 中) 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、SECREL22、SECREL64I 或 SECREL32。 其值包含要应用于捆绑包中的指令的附录,而不是用于数据。

 

MIPS 处理器

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

常数“值”说明
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 重定位,其 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 包含符号表中的位移,而不是索引。

 

三菱 M32R

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

常数“值”说明
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 目标从程序计数器的 24 位偏移量 (电脑) ,向左移动 2 位并扩展了符号
IMAGE_REL_M32R_PCREL16 0x0006 目标与电脑的 16 位偏移量,向左移动 2 位,并扩展了符号
IMAGE_REL_M32R_PCREL8 0x0007 目标与电脑的 8 位偏移量,向左移动 2 位,并扩展了符号
IMAGE_REL_M32R_REFHALF 0x0008 目标 VA 的 16 MSB。
IMAGE_REL_M32R_REFHI 0x0009 目标 VA 的 16 个 MSB,根据 LSB 符号扩展进行调整。 这用于加载完整 32 位地址的双指令序列中的第一个指令。 此重定位后必须紧接一个 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 亚麻布 如果不是零,则此字段指定一个从 1 开始的行号。 如果为零,Type 字段将解释为函数的符号表索引。

 

Type 字段是两个 4 字节字段的并集:SymbolTableIndex 和 VirtualAddress。

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

 

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

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

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

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

C++
// 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 标识节的带符号整数,使用节表中的从 1 开始的索引。 某些值具有特殊含义,如第 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 Offset 字符串表的偏移量。

 

节号值

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

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

 

类型表示形式

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

MSBLsb
复杂类型:none、指针、函数、数组。 基类型:整数、浮点等。

 

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

常数“值”说明
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生成的两个值。 但是,其他工具可以使用此字段来传达更多信息。

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

常数“值”说明
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 Union 标记名称条目。
IMAGE_SYM_CLASS_TYPE_DEFINITION 13 Typedef 条目。
IMAGE_SYM_CLASS_UNDEFINED_STATIC 14 静态数据声明。
IMAGE_SYM_CLASS_ENUM_TAG 15 枚举类型 tagname 条目。
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 ) ,以及函数 ( .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) 的 Type 值以及大于零的节号。 请注意,节号为 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 的符号记录 (开始函数) 。 “值”字段未使用。

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

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

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

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

 

辅助格式 3:弱外部

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

如果链接了 sym1 的定义,则正常解析对符号的外部引用。 如果未链接 sym1 的定义,则对 sym1 的弱外部的所有引用都改为引用 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 校验 公共数据的校验和。 如果在节标题中设置了IMAGE_SCN_LNK_COMDAT标志,则适用。 有关详细信息,请参阅 COMDAT 节 (仅对象) 
12 2 Number 关联节的节表中的从 1 开始的索引。 当 COMDAT 选择设置为 5 时,使用此选项。
14 1 选择 COMDAT 选择编号。 如果节是 COMDAT 节,则这适用。
15 3 未使用  

 

COMDAT 节 (对象仅)

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

具有 COMDAT 节的节值的第一个符号必须是节符号。 此符号具有节的名称,Value 字段等于零,相关 COMDAT 节的节号,类型字段等于 IMAGE_SYM_TYPE_NULL,类字段等于 IMAGE_SYM_CLASS_STATIC,以及一个辅助记录。 第二个符号称为“COMDAT 符号”,由链接器与“选择”字段结合使用。

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

常数“值”说明
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 值之和等于可选标头数据目录中“证书表”条目的 Size 值。 如果舍入 dwLength 值的总和不等于 Size 值,则属性证书表或 Size 字段已损坏。

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

C++
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 指定的内容类型的可变长度字节数组。 Authenticode 支持的类型是 WIN_CERT_TYPE_PKCS_SIGNED_DATA,PKCS#7 SignedData 结构。 有关 Authenticode 数字签名格式的详细信息,请参阅 Windows Authenticode 可移植可执行签名格式

如果 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 映像中的其他数据。 有关函数引用页的链接,请参阅 引用

Delay-Load仅) (映像导入表

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

Delay-Load目录表

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

Offset大小字段说明
0 4 属性 必须为零。
4 4 名称 要加载的 DLL 的名称的 RVA。 该名称位于映像的只读数据部分中。
8 4 模块句柄 模块的 RVA 处理 (要延迟加载的 DLL 的图像) 的数据部分。 它由提供的用于管理延迟加载的例程进行存储。
12 4 延迟导入地址表 延迟加载导入地址表的 RVA。 有关详细信息,请参阅 延迟导入地址表 (IAT) 
16 4 延迟导入名称表 延迟加载名称表的 RVA,其中包含可能需要加载的导入的名称。 这与导入名称表的布局匹配。 有关详细信息,请参阅 提示/名称表
20 4 绑定延迟导入表 绑定延迟加载地址表的 RVA(如果存在)。
24 4 卸载延迟导入表 卸载延迟加载地址表的 RVA(如果存在)。 这是延迟导入地址表的确切副本。 如果调用方卸载 DLL,则应将此表复制回延迟导入地址表,以便对 DLL 的后续调用继续正确使用 thunking 机制。
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 将代码引用到延迟加载 thunks。 在卸载请求中,可以释放库,清除 *phmod,并通过 IAT 写入的 UIAT,以将一切还原到其预加载状态。

特殊部分

典型的 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/object) 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 指向调试数据的文件指针。

 

调试类型

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

常数“值”说明
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 保留给博兰。
IMAGE_DEBUG_TYPE_RESERVED10 10 保留。
IMAGE_DEBUG_TYPE_CLSID 11 保留。
IMAGE_DEBUG_TYPE_REPRO 16 PE 确定性或可重现性。
IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS 20 扩展 DLL 特征位。

 

如果 Type 字段设置为 IMAGE_DEBUG_TYPE_FPO,则调试原始数据是一个数组,其中每个成员描述函数的堆栈帧。 映像文件中并非每个函数都必须为其定义 FPO 信息,即使调试类型为 FPO。 假定那些没有 FPO 信息的函数具有正常的堆栈帧。 FPO 信息的格式如下所示:

C++
#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 特征位,以及可在 image 的可选标头中设置的那些特征位。 请参阅可选标头Windows-Specific字段部分中的 DLL 特征, (仅限图像) 

扩展 DLL 特征

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

常数“值”说明
IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT 0x0001 映像与 CET 兼容。

仅限 .debug$F (对象)

在 Visual C++ 版本 7.0 及更高版本中,本部分中的数据已被发出到 .debug$S 子节中的一组更广泛的数据取代。

对象文件可以包含 .debug$F 部分,其内容是一个或多个FPO_DATA记录 (帧指针遗漏信息) 。 请参阅 调试类型中的“IMAGE_DEBUG_TYPE_FPO”。

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

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

仅) .debug$S (对象

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

仅限 .debug$P (对象)

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

仅限 .debug$T (对象)

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

Microsoft 调试信息的链接器支持

若要支持调试信息,链接器:

  • 从 .debug$F、debug$S、.debug$P 和 .debug$T 部分收集所有相关调试数据。

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

.drectve Section 仅 (对象)

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

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

.edata 部分 (仅映像)

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

下面概述了导出部分的一般结构。 描述的表通常按 (显示的顺序在文件中连续,但) 这不是必需的。 只有导出目录表和导出地址表才能将符号导出为序号。 (序号是由其导出地址表 index 直接访问的导出。) 名称指针表、序号表和导出名称表都存在,以支持使用导出名称。

表名称说明
导出目录表 与调试目录) 不同,只有一行 (表。 此表指示其他导出表的位置和大小。
导出地址表 已导出符号的 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,它是代码或数据中的实际地址。 否则,字段是一个转发器 RVA,用于命名另一个 DLL 中的符号。

Offset大小字段说明
0 4 导出 RVA 加载到内存中的已导出符号的地址,相对于映像库。 例如,导出函数的地址。
0 4 转发器 RVA 指向 export 节中以 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 和有偏差序号的算法为:

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

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

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

C++
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

  • Hint-Name表

导入目录表

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

每个导入目录条目具有以下格式:

Offset大小字段说明
0 4 导入查找表 RVA (特征) 导入查阅表格的 RVA。 此表包含每个导入的名称或序号。 (名称“Characteristics”在 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,0x8000000000000000 PE32+。
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 Pad 尾随零填充字节(如有必要)出现在尾随 null 字节之后,以在偶数边界上对齐下一个条目。

 

导入地址表

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

.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 位 Offset 存储在 WORD 的剩余 12 位中,与块的“页面 RVA”字段中指定的起始地址的偏移量。 此偏移量指定要应用基本重定位的位置。

 

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

基本重定位类型

常数“值”说明
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-type 指令格式形成的 32 位绝对地址的低 12 位。
IMAGE_REL_BASED_RISCV_LOW12S 8 仅当计算机类型为 RISC-V 时,此重定位才有意义。 基重定位适用于以 RISC-V S-type 指令格式形成的 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 部分为静态线程本地存储提供直接 PE 和 COFF 支持, (TLS) 。 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 索引,将在以下讨论中介绍。

 备注

静态声明的 TLS 数据对象只能在静态加载的图像文件中使用。 这一事实使得在 DLL 中使用静态 TLS 数据不可靠,除非你知道 DLL 或与 DLL 静态链接的任何内容永远不会使用 LoadLibrary API 函数动态加载。

 

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

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

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

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

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

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

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

  6. 单个 TLS 数据对象作为 TLS 数据区域的某个固定偏移量进行访问。

TLS 数组是系统为每个线程维护的地址数组。 此数组中的每个地址为给定模块提供 TLS 数据的位置, (EXE 或 DLL) 程序中。 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 指针终止数组。 ) 不支持任何回调 (空列表是完全有效的,在这种情况下,回调数组只具有一个成员-空指针。

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

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

保留参数应设置为零。 Reason 参数可以采用以下值:

设置说明
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 感知 (即,IMAGE_DLLCHARACTERISTICS_NO_SEH在可选标头的 DllCharacteristics 字段中清除,如前面) 所述,则处理程序必须位于该映像的已知安全处理程序列表中。 否则,操作系统将终止应用程序。 这有助于防止过去用于控制操作系统的“x86 异常处理程序劫持”攻击。

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

加载配置目录

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

加载配置布局

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

Offset大小字段说明
0 4 特征 指示文件的属性的标志,当前未使用。
4 4 TimeDateStamp 日期和时间戳值。 该值表示为自 1970 年 1 月 1 日世界协调时间 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 前缀的地址列表的 VA,以便可以在单处理器计算机上用 NOP 替换它们。
36/48 4/8 MaximumAllocationSize 最大分配大小(以字节为单位)。
40/56 4/8 VirtualMemoryThreshold 最大虚拟内存大小(以字节为单位)。
44/64 4/8 ProcessAffinityMask 将此字段设置为非零值等效于在进程启动 (.exe 期间使用此值调用 SetProcessAffinityMask,仅)
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 处理程序的已排序表的 VA。
68/104 4/8 SEHandlerCount [仅限 x86]表中唯一处理程序的计数。
72/112 4/8 GuardCFCheckFunctionPointer 存储控制流防护检查函数指针的 VA。
76/120 4/8 GuardCFDispatchFunctionPointer 存储 Control Flow Guard 调度函数指针的 VA。
80/128 4/8 GuardCFFunctionTable 图像中每个控制流防护函数的已排序表的 VVA。
84/136 4/8 GuardCFFunctionCount 上表中唯一的 RVA 计数。
88/144 4 GuardFlags Control Flow Guard 相关标志。
92/148 12 CodeIntegrity 代码完整性信息。
104/160 4/8 GuardAddressTakenIatEntryTable 存储控制流防护地址采用 IAT 表的 VA。
108/168 4/8 GuardAddressTakenIatEntryCount 上表中唯一的 RVA 计数。
112/176 4/8 GuardLongJumpTargetTable 存储 Control Flow Guard 长跳转目标表的 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 条目 (记住,所有 Name 条目都位于表的所有 ID 条目之前) 。 表的所有条目都按升序排序:按区分大小写的字符串和按数值的 ID 条目进行排序。 偏移量相对于 IMAGE_DIRECTORY_ENTRY_RESOURCE DataDirectory 中的地址。 有关详细信息 ,请参阅 PE 内部对等互连:Win32 可移植可执行文件格式教程 。

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

 

资源目录字符串

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

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

 

资源数据输入

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

Offset大小字段说明
0 4 数据 RVA 资源数据区域中资源数据单元的地址。
4 4 大小 Data RVA 字段指向的资源数据的大小(以字节为单位)。
8 4 codepage 用于解码资源数据中的码位值的代码页。 通常,代码页将是 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 个字节由文件签名组成。 存档的其余部分由一系列存档成员组成,如下所示:

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

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

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

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

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

...

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

存档文件签名

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

!<arch>\n

存档成员标头

每个成员 (链接器、longname 或对象-文件成员) 前面都有一个标头。 存档成员标头具有以下格式,其中每个字段都是一个 ASCII 文本字符串,该字符串保持对齐,并在字段末尾填充空格。 这些字段中没有终止 null 字符。

每个成员标头从上一个存档成员末尾之后的第一个偶数地址开始。

Offset大小字段说明
0 16 名称 存档成员的名称,并附加斜杠 (/) 以终止该名称。 如果第一个字符是斜杠,则名称具有特殊解释,如下表所述。
16 12 Date 创建存档成员的日期和时间:这是自 1970/1/1 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”中的两个字节 (0x60 0x0A) 。

 

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

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

 

第一个链接器成员

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

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

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

 

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

第二个链接器成员

第二个链接器成员的名称为“/”,就像第一个链接器成员一样。 尽管这两个链接器成员都提供符号目录和包含它们的存档成员,但第二个链接器成员优先用于第一个链接器成员,而不是所有当前链接器。 第二个链接器成员包括按词法顺序排列的符号名称,因此可以更快地按名称进行搜索。

第二个成员具有以下格式。 此信息显示在 标头后面:

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

 

Longnames 成员

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

字符串以 null 结尾。 每个字符串紧跟在上一个字符串中的 null 字节之后。

导入库格式

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

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

在具有 long 格式的导入库中,单个成员包含以下信息:

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

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

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

此信息足以在成员使用时准确重建其整个内容。

导入标头

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

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

 

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

导入类型

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

常数“值”说明
IMPORT_CODE 0 可执行代码。
IMPORT_DATA 1 数据。
IMPORT_CONST 2 在 .def 文件中指定为 CONST。

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

导入名称类型

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

常数“值”说明
IMPORT_ORDINAL 0 导入按序号进行。 这表示导入标头的序号/提示字段中的值是导入的序号。 如果未指定此常量,则应始终将“序号/提示”字段解释为导入的提示。
IMPORT_NAME 1 导入名称与公共符号名称相同。
IMPORT_NAME_NOPREFIX 2 导入名称是公共符号名称,但跳过前导 ?、@或可选 _。
IMPORT_NAME_UNDECORATE 3 导入名称是公共符号名称,但跳过前导 ?、@或可选 _,并在第一个 @处截断。

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

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

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

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

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

验证码 PE 图像哈希中涵盖哪些内容?

在 PE 图像哈希的计算中包括所有图像文件数据是不可能的,也不可取。 有时,它只是提供不需要的特征 (例如,无法从公开发布的文件中删除调试信息) ;有时根本不可能。 例如,无法在 Authenticode 签名中包含图像文件中的所有信息,然后将包含该 PE 图像哈希的 Authenticode 签名插入到 PE 图像中,然后能够通过在计算中再次包含所有图像文件数据来生成相同的 PE 图像哈希,因为该文件现在包含最初不存在的 Authenticode 签名。

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

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

 备注

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

除以下排除范围外,在分区表中指定的 PE 映像部分中的所有数据均全部进行哈希处理:

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

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

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

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

    若要计算 PE 图像哈希,Authenticode 按地址范围对节表中指定的节进行排序,然后对生成的字节序列进行哈希处理,并传递排除范围。

  • 最后一节末尾过去的信息。 最后一节 (由最高偏移量) 定义的区域不进行哈希处理。 此区域通常包含调试信息。 调试信息通常可视为向调试器提供咨询;它不会影响可执行程序的实际完整性。 交付产品后,完全可以从映像中删除调试信息,而不会影响程序的功能。 事实上,有时这是一种节省磁盘的措施。 值得注意的是,如果不使 Authenticode 签名无效,则无法删除 PE 映像的指定部分中包含的调试信息。

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

reference

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

创建、查看和管理证书

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

SignTool

Windows Authenticode 可移植可执行签名格式 (.docx)

ImageHlp 函数

 
 

 

PE文件详解(六)

这篇文章转载自小甲鱼的PE文件详解系列原文传送门 之前简单提了一下节表和数据目录表,那么他们有什么区别? 其实这些东西都是人为规定的,一个数据在文件中或者在内存中的位置基本是固定的,通过数据目录表进行索引和通过节表进行索引都是可以找到的,也可以这么说,同一个数据在节表和数据目录表中都有一份索引值,那么这两个表有什么区别?一般将具有相同属性的值放到同一个节区中,这也就是说同一个节区的值只是保护属性相同,但是他们的用途不一定是一样的,但是在同一数据目录表中的数据的作用是相同的,比如输入函数表中只会保存输入函数的相关信息,输出函数表中只会保存输出函数的信息,而输入输出函数在PE文件中可能都位于.text这个节中。

输入函数表

输入函数:一般将那些在本程序中调用,但是它的代码不在本程序中的函数称为输入函数,输入函数一般都在另外一个独立的dll中。 在之前谈到PE头的时候说到,在PE头中有一个结构是数据目录表,它的结构如下:

IMAGE_DATA_DIRECTORY STRUCT
      VirtualAddress    DWORD       ?   ; 数据的起始RVA
      isize             DWORD       ?   ; 数据块的长度
IMAGE_DATA_DIRECTORY ENDS

这个结构大小为8,相对于PE文件头的偏移为0x78。 在PE文件中,通过一个数组来保存多个数据目录表的信息,而输入函数表则是这个数组的第二个元素。 而输入表是以一个 IMAGE_IMPORT_DESCRIPTOR(简称IID) 的数组开始。 每个被 PE文件链接进来的 DLL文件都分别对应一个 IID数组结构。 在这个 IID数组中,并没有指出有多少个项(就是没有明确指明有多少个链接文件),但它最后是以一个全为NULL(0) 的 IID 作为结束的标志。

IMAGE_IMPORT_DESCRIPTOR

IMAGE_IMPORT_DESCRIPTOR STRUCT 
    union 
        Characteristics           DWORD   ? 
        OriginalFirstThunk        DWORD   ? 
    ends 
    TimeDateStamp                 DWORD   ? 
    ForwarderChain                DWORD   ? 
    Name                          DWORD   ? 
    FirstThunk                    DWORD   ?
IMAGE_IMPORT_DESCRIPTOR ENDS

OriginalFirstThunk

它指向first thunk,IMAGE_THUNK_DATA,该 thunk 拥有 Hint 和 Function name 的地址。

TimeDateStamp

该字段可以忽略。如果那里有绑定的话它包含时间/数据戳(time/data stamp)。如果它是0,就没有绑定在被导入的DLL中发生。 在最近,它被设置为0xFFFFFFFF以表示绑定发生。

ForwarderChain

一般情况下我们也可以忽略该字段。在老版的绑定中,它引用API的第一个forwarder chain(传递器链表)。 它可被设置为0xFFFFFFFF以代表没有forwarder。

Name

它表示DLL 名称的相对虚地址(译注:相对一个用null作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称。 如:KERNEL32.DLL)。

FirstThunk

它包含由IMAGE_THUNK_DATA定义的 first thunk数组的虚地址,通过loader用函数虚地址初始化thunk。 在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。 这个OriginalFirstThunk 和 FirstThunk明显是亲家,两家伙首先名字就差不多哈。那他们有什么不可告人的秘密呢?

IMAGE_THUNK_DATA STRUC
    union u1
        ForwarderString      DWORD  ?        ; 指向一个转向者字符串的RVA
        Function             DWORD  ?        ; 被输入的函数的内存地址
        Ordinal              DWORD  ?        ; 被输入的API 的序数值
        AddressOfData        DWORD  ?        ; 指向 IMAGE_IMPORT_BY_NAME
    ends
IMAGE_THUNK_DATA ENDS

我们可以看出由于是union结构,所以IMAGE_THUNK_DATA 事实上是4个字节大小。 这个共用体是怎么使用的呢: 当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。 当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。 接下来说明IMAGE_IMPORT_BY_NAME 结构:

IMAGE_IMPORT_BY_NAME STRUCT
    Hint      WORD      ? 
    Name      BYTE      ?
IMAGE_IMPORT_BY_NAME ENDS

结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0。 Name 字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。

输入函数表的加载

从上面的图上来看,OriginalFirstThunk与FirstThunk指向的是同一个数据结构,在PE文件中既可以通过OriginalFirstThunk来找到函数名,也可以通过FirstThunk来找到函数名,为什么会出现两个指针指向同一个数据结构的现象呢,其实这个与PE文件的加载有关 第一个数组(由 OriginalFirstThunk 所指向)是单独的一项,而且不能被改写,我们前边称为 INT。 第二个数组(由 FirstThunk 所指向)事实上是由 PE 装载器重写的。 PE 装载器首先搜索 OriginalFirstThunk ,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME 结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,也就是说此时的FirstThunk 不在指向这个INAGE_IMPORT_BY_NAME结构,而是真实的函数的RVA。因此我们称为输入地址表(IAT)。 所以,当我们的 PE 文件装载内存后准备执行时,刚刚的图就会转化为下图:

实验操作

我们来编译一个具体的程序,源代码如下:

#include <windows.h>

int WINAPI WinMain( HINSTANCE hInstance, 
                   HINSTANCE hPrevInstance, 
                   PSTR szCmdLine, 
                   int iCmdShow
                   )
{
      MessageBox( NULL, TEXT("Hello, welcome to Fishc.com!"), 
                  TEXT("Welcome!"), MB_OKCANCEL | MB_OK
                  );

      return 0;
}

这个程序就是弹出一个MessageBox,通过W32Dasm静态反汇编发现MessageBox函数所在地址应该在0x0042A2AC

在数据目录表中根据OriginalFirstThunk 项获取函数名称

用UE打开这个PE文件,发现输入函数表的RVA = 0x0002A000 在节表中查询发现它是在.idata这个节中 通过之前说的公式,可以得到,这个RVA在文件中的偏移地址为0x0002A000 - 0x0002A000 + 0x00028000 = 0x00028000读取在这个位置的信息发现,OriginalFirstThunk = 0x0002A15C,这个偏移,发现它仍然在这个节中,通过上述公式计算得出,他在文件中的偏移地址为:0x0002815C

从这个位置得到的值来看,它的最高值为0,也就是IMAGE_THUNK_DATA保存的是函数名的字符串,字符串的RVA为0x0002A2DC,通过计算得到它在文件中的偏移为:0x000282DC. 从图上可以看出这个地址所对应的值正好是函数的名称MessgeBoxA

通过FirstThunk成员找到函数名称

首先根据PE文件的内容,可以知道,输入函数表在PE文件的偏移为0x00028000,而根据这个结构来看,FirstThunk在x00028000 + 16 = 0x00028010的位置,在这位置,我们发现它里面的值为0x0002A2AC

计算得到在磁盘中的偏移为0x000282AC,在PE文件中这个值为0x0002A2DC,它的最高位仍然为0,也就是说这个地址保存的内容为函数名称。 另外我们发现这个值与之前用OriginalFirstThunk 寻址到的函数名称所在RVA一样,也就是说到此成功找到函数名称

查找函数在内存的偏移地址

根据上面所说的内容,只有当这个PE文件被加载到内存中,PE加载器才会将IMAGE_IMPORT_BY_NAME 结构中的值替换为对应函数的地址,所以要查找函数的地址就需要先将PE文件加载到内存,然后再将内存中的数据抓取下来,最后再来分析得出这个函数的偏移地址。 其实这个工作可以由lordPE工具来帮忙完成。首先是启动程序,然后打开lordPE,找到程序的进程,然后选择dump full抓取全部即可 这样会生成一个dump文件,分析这个文件,就可以得出相应的内容: 由于这个是内存镜像的拷贝,所以在这在内存中的RVA就是在文件中的偏移。 首先得到导入表的偏移为0x0002a000,这个值里面存储的值为0x0002A15C,这个值是OriginalFirstThunk的值,通过这个值找到对应的IMAGE_THUNK_DATA地址:0x0002A2DC 我们发现这个值得高地址为是0,那么它所指向的应该就是函数名称,我们寻址到这个地址,发现它正好是函数名称 接下来,再来解析函数地址,在0x0002a010中找到对应的FirstThunk值,这个值为0x0002A2AC,它是指向一个IMAGE_THUNK_DATA结构,在这个地址处,发现它的值为0x77D507EA,这个值的最高位为1,所以它对应的是一个函数地址,它的低32位是一个函数编号,此时0x0002A2AC指向的不在是一个IMAGE_IMPORT_BY_NAME结构,而是函数地址的偏移,而这个程序是由VC6.0编译而成,VC6默认的加载地址为0x00400000,所以基址 + 偏移地址就是函数的正确地址,也就是0x0042A2AC,与之前用静态反汇编得到的值相同

posted on 2023-01-18 16:39  不及格的程序员-八神  阅读(71)  评论(0编辑  收藏  举报