本章介绍由汇编程序和链接编辑器生成的目标文件的可执行链接格式 (excutable and linking format, ELF)。其中存在三种重要类型的目标文件。
可重定位目标文件包含代码部分和数据部分。此文件适合与其他可重定位目标文件链接,从而创建动态可执行文件、共享库文件或其他可重定位目标文件。
动态可执行文件包含可随时执行的程序。此文件指定了 exec(2) 创建程序的进程映像的方式。此文件通常在运行时绑定到共享库文件以创建进程映像。
共享库文件包含适用于进行其他链接的代码和数据。链接编辑器可将此文件与其他可重定位目标文件和共享库文件一起处理,以创建其他目标文件。运行时链接程序会将此文件与动态可执行文件和其他共享库文件合并,以创建进程映像。
本章的第一节,文件格式,重点介绍目标文件的格式以及格式如何与创建程序相关。第二节,动态链接,重点介绍格式如何与装入程序相关。
程序可以使用 ELF 访问库 libelf 提供的函数来处理目标文件。有关 libelf 内容的说明,请参阅 elf(3ELF)。/usr/demo/ELF 目录下的 SUNWosdem 软件包中提供了使用 libelf 的源代码样例。
目标文件既可用于程序链接,也可用于程序执行。为了方便和提高效率,目标文件格式提供了文件内容的平行视图,以便反映这些活动的不同需要。下图显示了目标文件的结构。
ELF 头位于目标文件的起始位置,其中包含用于说明文件结构的指南。
仅有 ELF 头在文件中具有固定位置。由于ELF 格式具有灵活性,因此不要求头表、节或段具有指定的顺序。但是,此图是 Solaris 中使用的典型布局。
节表示 ELF 文件中可以处理的最小不可分割单位。段是节的集合。段表示可由 exec(2) 或运行时链接程序映射到内存映像的最小独立单位。
节包含链接视图的批量目标文件信息。此数据包括指令、数据、符号表和重定位信息。本章的第一部分提供了各节的说明。本章的第二部分讨论了各段以及文件的程序执行视图。
程序头表(如果存在)指示系统如何创建进程映像。用于生成进程映像、可执行文件和共享库的文件必须具有程序头表。可重定位目标文件无需程序头表。
节头表包含说明文件各节的信息。每节在表中有一个与之对应的项。每一项都指定了节名和节大小之类的信息。链接编辑过程中使用的文件必须具有节头表。
目标文件格式支持 8 位字节、32 位体系结构和 64 位体系结构的各种处理器。不过,数据表示形式最好可扩展为更大或更小的体系结构。表 7–1 和表 7–2 列出了 32 位数据类型和 64 位数据类型。
目标文件表示格式与计算机无关的一些控制数据。此格式可提供目标文件的通用标识和解释。目标文件中的其余数据使用目标处理器的编码,无论在什么计算机上创建该文件都是如此。
表 7–1 ELF 32 位数据类型
名称 |
大小 |
对齐 |
用途 |
---|---|---|---|
Elf32_Addr |
4 |
4 |
无符号程序地址 |
Elf32_Half |
2 |
2 |
无符号中整数 |
Elf32_Off |
4 |
4 |
无符号文件偏移 |
Elf32_Sword |
4 |
4 |
带符号整数 |
Elf32_Word |
4 |
4 |
无符号整数 |
unsigned char |
1 |
1 |
无符号小整数 |
表 7–2 ELF 64 位数据类型
名称 |
大小 |
对齐 |
目的 |
---|---|---|---|
Elf64_Addr |
8 |
8 |
无符号程序地址 |
Elf64_Half |
2 |
2 |
无符号中整数 |
Elf64_Off |
8 |
8 |
无符号文件偏移 |
Elf64_Sword |
4 |
4 |
带符号整数 |
Elf64_Word |
4 |
4 |
无符号整数 |
Elf64_Xword |
8 |
8 |
无符号长整数 |
Elf64_Sxword |
8 |
8 |
带符号长整数 |
unsigned char |
1 |
1 |
无符号小整数 |
目标文件格式定义的所有数据结构都遵循相关类别的自然大小和对齐规则。数据结构可以包含显式填充,以确保 4 字节目标文件的 4 字节对齐,从而强制结构大小为 4 的倍数,依此类推。数据在文件的开头也会适当对齐。例如,包含 Elf32_Addr 成员的结构在文件中与 4 字节边界对齐。同样,包含 Elf64_Addr 成员的结构与 8 字节边界对齐。
为便于移植,ELF 不使用位字段。
目标文件中的一些控制结构可以增大,因为 ELF 头包含这些控制结构的实际大小。如果目标文件格式发生变化,则程序可能会遇到大于或小于所需大小的控制结构。因此,程序可能会忽略额外信息。这些忽略的信息的处理方式取决于上下文,如果定义了扩展内容,则会指定处理方式。
ELF 头具有以下结构。 请参见 sys/elf.h。
#define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; } Elf32_Ehdr; typedef struct { unsigned char e_ident[EI_NIDENT]; Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; Elf64_Off e_phoff; Elf64_Off e_shoff; Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx; } Elf64_Ehdr;
将文件标记为目标文件的初始字节。这些字节可提供与计算机无关的数据,用于解码和解释文件的内容。ELF 标识中提供了完整说明。
标识目标文件类型,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
ET_NONE |
0 |
无文件类型 |
ET_REL |
1 |
可重定位文件 |
ET_EXEC |
2 |
可执行文件 |
ET_DYN |
3 |
共享库文件 |
ET_CORE |
4 |
核心转储文件 |
ET_LOPROC |
0xff00 |
特定于处理器 |
ET_HIPROC |
0xffff |
特定于处理器 |
虽然未指定核心转储文件内容,但类型 ET_CORE 保留用于标记文件。从 ET_LOPROC 到 ET_HIPROC 之间的值(包括这两个值)保留用于特定于处理器的语义。其他值保留供将来使用。
指定独立文件所需的体系结构。下表中列出了相关体系结构。
名称 |
值 |
含义 |
---|---|---|
EM_NONE |
0 |
无计算机 |
EM_SPARC |
2 |
SPARC |
EM_386 |
3 |
Intel 80386 |
EM_SPARC32PLUS |
18 |
Sun SPARC 32+ |
EM_SPARCV9 |
43 |
SPARC V9 |
EM_AMD64 |
62 |
AMD 64 |
其他值保留供将来使用。特定于处理器的 ELF 名称通过使用计算机名来进行区分。例如,为 e_flags 定义的标志会使用前缀 EF_。EM_XYZ 计算机的名为 WIDGET 的标志可称为 EF_XYZ_WIDGET。
标识目标文件版本,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
EV_NONE |
0 |
无效版本 |
EV_CURRENT |
>=1 |
当前版本 |
值 1 表示原始文件格式。EV_CURRENT 的值可根据需要进行更改,以反映当前版本号。
系统首先将控制权转移到的虚拟地址,从而启动进程。如果文件没有关联的入口点,则此成员值为零。
程序头表的文件偏移(以字节为单位)。如果文件没有程序头表,则此成员值为零。
节头表的文件偏移(以字节为单位)。如果文件没有节头表,则此成员值为零。
与文件关联的特定于处理器的标志。标志名称采用 EF_machine_flag 形式。对于 x86,此成员目前为零。下表中列出了 SPARC 标志。
名称 |
值 |
含义 |
---|---|---|
EF_SPARC_EXT_MASK |
0xffff00 |
供应商扩展掩码 |
EF_SPARC_32PLUS |
0x000100 |
通用 V8+ 功能 |
EF_SPARC_SUN_US1 |
0x000200 |
Sun UltraSPARC™ 1 扩展 |
EF_SPARC_HAL_R1 |
0x000400 |
HAL R1 扩展 |
EF_SPARC_SUN_US3 |
0x000800 |
Sun UltraSPARC 3 扩展 |
EF_SPARCV9_MM |
0x3 |
内存型号掩码 |
EF_SPARCV9_TSO |
0x0 |
总体存储排序 |
EF_SPARCV9_PSO |
0x1 |
部分存储排序 |
EF_SPARCV9_RMO |
0x2 |
非严格存储排序 |
ELF 头的大小(以字节为单位)。
文件的程序头表中某一项的大小(以字节为单位)。所有项的大小都相同。
程序头表中的项数。生成的 e_phentsize 和 e_phnum 指定了表的大小(以字节为单位)。如果文件没有程序头表,则 e_phnum 值为零。
节头的大小(以字节为单位)。节头是节头表中的一项。所有项的大小都相同。
节头表中的项数。生成的 e_shentsize 和 e_shnum 指定了节头表的大小(以字节为单位)。如果文件没有节头表,则 e_shnum 值为零。
如果节数大于或等于 SHN_LORESERVE (0xff00),则 e_shnum 值为零。节头表的实际项数包含在节头表中索引为 0 的 sh_size 字段中。否则,初始节头项的 sh_size 成员值为零。请参见表 7–6 和表 7–7。
与节名字符串表关联的项的节头表索引。如果文件没有节名字符串表,则此成员值为 SHN_UNDEF。
如果节名字符串表的节索引大于或等于 SHN_LORESERVE (0xff00),则此成员值为 SHN_XINDEX (0xffff),节名字符串表的实际节索引包含在节头中索引为 0 的 sh_link 字段中。否则,初始节头项的 sh_link 成员值为零。请参见表 7–6 和表 7–7。
ELF 提供了一个目标文件框架,用于支持多个处理器、多种数据编码和多类计算机。要支持此目标文件系列,文件的初始字节应指定解释文件的方式。这些字节与发出查询的处理器以及文件的其余内容无关。
ELF 头和目标文件的初始字节对应于 e_ident 成员。
表 7–3 ELF 标识索引
名称 |
值 |
目的 |
---|---|---|
EI_MAG0 |
0 |
文件标识 |
EI_MAG1 |
1 |
文件标识 |
EI_MAG2 |
2 |
文件标识 |
EI_MAG3 |
3 |
文件标识 |
EI_CLASS |
4 |
文件类 |
EI_DATA |
5 |
数据编码 |
EI_VERSION |
6 |
文件版本 |
EI_OSABI |
7 |
操作系统/ABI 标识 |
EI_ABIVERSION |
8 |
ABI 版本 |
EI_PAD |
9 |
填充字节的开头 |
EI_NIDENT |
16 |
e_ident[] 的大小 |
这些索引可访问值为以下各项的字节。
4 字节魔数,用于将文件标识为 ELF 目标文件,如下表中所列。
名称 |
值 |
位置 |
---|---|---|
ELFMAG0 |
0x7f |
e_ident[EI_MAG0] |
ELFMAG1 |
'E' |
e_ident[EI_MAG1] |
ELFMAG2 |
'L' |
e_ident[EI_MAG2] |
ELFMAG3 |
'F' |
e_ident[EI_MAG3] |
字节 e_ident[EI_CLASS] 用于标识文件的类或容量,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
ELFCLASSNONE |
0 |
无效类 |
ELFCLASS32 |
1 |
32 位目标文件 |
ELFCLASS64 |
2 |
64 位目标文件 |
文件格式设计用于在各种大小的计算机之间进行移植,而不会将最大计算机的大小强加给最小的计算机。文件类可定义目标文件容器的数据结构所使用的基本类型。包含在目标文件各节中的数据可以遵循其他编程模型。
类 ELFCLASS32 支持文件和虚拟地址空间最高为 4 GB 的计算机。该类使用表 7–1 中定义的基本类型。
类 ELFCLASS64 保留用于 64 位体系结构,如 64 位 SPARC 和 x64。该类使用表 7–2 中定义的基本类型。
字节 e_ident[EI_DATA] 用于指定目标文件中特定于处理器的数据的数据编码,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
ELFDATANONE |
0 |
无效数据编码 |
ELFDATA2LSB |
1 |
请参见图 7–2。 |
ELFDATA2MSB |
2 |
请参见图 7–3。 |
数据编码一节中提供了有关这些编码的更多信息。其他值保留供将来使用。
字节 e_ident[EI_VERSION] 用于指定 ELF 头版本号。当前,该值必须为 EV_CURRENT。
字节 e_ident[EI_OSABI] 用于标识操作系统以及目标文件所面向的 ABI。其他 ELF 结构中的一些字段包含的标志和值具有特定于操作系统或 ABI 的含义。这些字段的解释由此字节的值确定。
字节 e_ident[EI_ABIVERSION] 用于标识目标文件所面向的 ABI 的版本。此字段用于区分 ABI 的各个不兼容版本。此版本号的解释依赖于 EI_OSABI 字段标识的 ABI。如果没有为对应于处理器的 EI_OSABI 字段指定值,或者没有为 EI_OSABI 字节的特定值所确定的 ABI 指定版本值,则会使用值零来表示未指定的值。
该值用于标记 e_ident 中未使用字节的起始位置。这些字节会保留并设置为零。读取目标文件的程序应忽略这些值。
文件的数据编码指定解释文件中的基本目标文件的方式。类 ELFCLASS32 文件使用将占用 1、2 和 4 个字节的目标文件。类 ELFCLASS64 文件使用将占用 1、2、4 和 8 个字节的目标文件。按照定义的编码,目标文件使用如下描述的数字表示。字节编号显示在左上角。
ELFDATA2LSB 编码用于指定 2 的补码值,其中最低有效字节占用最低地址。
ELFDATA2MSB 编码用于指定 2 的补码值,其中最高有效字节占用最低地址。
使用目标文件的节头表,可以定位文件的所有节。节头表是 Elf32_Shdr 或 Elf64_Shdr 结构的数组。节头表索引是此数组的下标。ELF 头的 e_shoff 成员表示从文件的起始位置到节头表的字节偏移。e_shnum 成员表示节头表包含的项数。e_shentsize 成员表示每一项的大小(以字节为单位)。
如果节数大于或等于 SHN_LORESERVE (0xff00),则 e_shnum 值为 SHN_UNDEF (0)。节头表的实际项数包含在节头表中索引为 0 的 sh_size 字段中。否则,初始项的 sh_size 成员值为零。
如果上下文中限制了索引大小,则会保留部分节头表索引。例如,符号表项的 st_shndx 成员和 ELF 头的 e_shnum 和 e_shstrndx 成员。在这类上下文中,保留的值不表示目标文件中的实际各节。同样在这类上下文中,转义值表示会在其他位置(较大字段中)找到实际节索引。
表 7–4 ELF 特殊节索引
名称 |
值 |
---|---|
SHN_UNDEF |
0 |
SHN_LORESERVE |
0xff00 |
SHN_LOPROC |
0xff00 |
SHN_BEFORE |
0xff00 |
SHN_AFTER |
0xff01 |
SHN_AMD64_LCOMMON |
0xff02 |
SHN_HIPROC |
0xff1f |
SHN_LOOS |
0xff20 |
SHN_LOSUNW |
0xff3f |
SHN_SUNW_IGNORE |
0xff3f |
SHN_HISUNW |
0xff3f |
SHN_HIOS |
0xff3f |
SHN_ABS |
0xfff1 |
SHN_COMMON |
0xfff2 |
SHN_XINDEX |
0xffff |
SHN_HIRESERVE |
0xffff |
虽然索引 0 保留作为未定义的值,但节头表包含对应于索引 0 的项。即,如果 ELF 头的 e_shnum 成员表示文件在节头表中具有 6 项,则这些节的索引为 0 到 5。初始项的内容会在本节的后面指定。
此节索引用于在可重定位目标文件中提供临时符号定义。保留供 dtrace(1M) 内部使用。
与 SHF_LINK_ORDER 和 SHF_ORDERED 节标志一起用于初始和最终节排序。 请参见表 7–8。
已定义的与此节相关的符号为通用符号,如 FORTRAN COMMON 或未分配的 C 外部变量。这些符号有时称为暂定符号。
所保留索引的范围的上边界。系统保留了 SHN_LORESERVE 和 SHN_HIRESERVE 之间的索引(包括这两个值)。这些值不会引用节头表。节头表不包含对应于所保留索引的项。
节包含目标文件中的所有信息,但 ELF 头、程序头表和节头表除外。此外,目标文件中的各节还满足多个条件:
目标文件中的每一节仅有一个说明该节的节头。可能会有节头存在但节不存在的情况。
每一节在文件中占用可能为空的相邻的一系列字节。
文件中的各节不能重叠。文件中的字节不能位于多个节中。
目标文件可以包含非活动空间。各种头和节可能不会包括目标文件中的每个字节。非活动数据的内容未指定。
节头具有以下结构。 请参见 sys/elf.h。
typedef struct { elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr; typedef struct { Elf64_Word sh_name; Elf64_Word sh_type; Elf64_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf64_Xword sh_size; Elf64_Word sh_link; Elf64_Word sh_info; Elf64_Xword sh_addralign; Elf64_Xword sh_entsize; } Elf64_Shdr;
节的名称。此成员值是节头字符串表的节索引,用于指定以空字符结尾的字符串的位置。表 7–10 中列出了节名及其说明。
用于将节的内容和语义分类。表 7–5 中列出了节类型及其说明。
节可支持用于说明杂项属性的 1 位标志。表 7–8 中列出了标志定义。
如果节显示在进程的内存映像中,则此成员会指定节的第一个字节所在的地址。否则,此成员值为零。
从文件的起始位置到节中第一个字节的字节偏移。对于 SHT_NOBITS 节,此成员表示文件中的概念性偏移,因为该节在文件中不占用任何空间。
节的大小(以字节为单位)。除非节类型为 SHT_NOBITS,否则该节将在文件中占用 sh_size 个字节。SHT_NOBITS 类型的节大小可以不为零,但该节在文件中不占用任何空间。
节头表索引链接,其解释依赖于节类型。表 7–9 说明了相应的值。
额外信息,其解释依赖于节类型。表 7–9 说明了相应的值。
一些节具有地址对齐约束。例如,如果某节包含双字,则系统必须确保整个节双字对齐。在此情况下,sh_addr 的值在以 sh_addralign 的值为模数进行取模时,同余数必须等于 0。当前,仅允许使用 0 和 2 的正整数幂。值 0 和 1 表示节没有对齐约束。
一些节包含固定大小的项的表,如符号表。对于这样的节,此成员会指定每一项的大小(以字节为单位)。如果节不包含固定大小的项的表,则此成员值为零。
节头的 sh_type 成员用于指定节的语义,如下表中所示。
表 7–5 ELF 节类型 sh_type
名称 |
值 |
---|---|
SHT_NULL |
0 |
SHT_PROGBITS |
1 |
SHT_SYMTAB |
2 |
SHT_STRTAB |
3 |
SHT_RELA |
4 |
SHT_HASH |
5 |
SHT_DYNAMIC |
6 |
SHT_NOTE |
7 |
SHT_NOBITS |
8 |
SHT_REL |
9 |
SHT_SHLIB |
10 |
SHT_DYNSYM |
11 |
SHT_INIT_ARRAY |
14 |
SHT_FINI_ARRAY |
15 |
SHT_PREINIT_ARRAY |
16 |
SHT_GROUP |
17 |
SHT_SYMTAB_SHNDX |
18 |
SHT_LOOS |
0x60000000 |
SHT_LOSUNW |
0x6ffffff4 |
SHT_SUNW_dof |
0x6ffffff4 |
SHT_SUNW_cap |
0x6ffffff5 |
SHT_SUNW_SIGNATURE |
0x6ffffff6 |
SHT_SUNW_ANNOTATE |
0x6ffffff7 |
SHT_SUNW_DEBUGSTR |
0x6ffffff8 |
SHT_SUNW_DEBUG |
0x6ffffff9 |
SHT_SUNW_move |
0x6ffffffa |
SHT_SUNW_COMDAT |
0x6ffffffb |
SHT_SUNW_syminfo |
0x6ffffffc |
SHT_SUNW_verdef |
0x6ffffffd |
SHT_SUNW_verneed |
0x6ffffffe |
SHT_SUNW_versym |
0x6fffffff |
SHT_HISUNW |
0x6fffffff |
SHT_HIOS |
0x6fffffff |
SHT_LOPROC |
0x70000000 |
SHT_SPARC_GOTDATA |
0x70000000 |
SHT_AMD64_UNWIND |
0x70000001 |
SHT_HIPROC |
0x7fffffff |
SHT_LOUSER |
0x80000000 |
SHT_HIUSER |
0xffffffff |
标识符号表。通常,SHT_SYMTAB 节会提供用于链接编辑的符号。作为完整的符号表,该表可以包含许多对于动态链接不需要的符号。因此,目标文件还可以包含 SHT_DYNSYM 节,其中包含一组尽可能少的动态链接符号,从而可节省空间。 有关详细信息,请参见符号表节。
标识字符串表。目标文件可以有多个字符串表节。 有关详细信息,请参见字符串表节。
标识包含显式加数的重定位项,如 32 位类的目标文件的 Elf32_Rela 类型。目标文件可以有多个重定位节。 有关详细信息,请参见重定位节。
标识符号散列表。动态链接的目标文件必须包含符号散列表。当前,目标文件只能有一个散列表,但此限制在将来可能会放宽。 有关详细信息,请参见散列表节。
标识动态链接的信息。当前,目标文件只能有一个动态节。 有关详细信息,请参见动态节。
标识以某种方法标记文件的信息。 有关详细信息,请参见注释节。
标识在文件中不占用任何空间,但在其他方面与 SHT_PROGBITS 类似的节。虽然此节不包含任何字节,但 sh_offset 成员包含概念性文件偏移。
标识不包含显式加数的重定位项,如 32 位类的目标文件的 Elf32_Rel 类型。目标文件可以有多个重定位节。 有关详细信息,请参见重定位节。
标识包含指针数组的节,这些指针指向初始化函数。数组中的每个指针都视为不返回任何值的无参数过程。 有关详细信息,请参见初始化和终止节。
标识包含指针数组的节,这些指针指向终止函数。数组中的每个指针都视为不返回任何值的无参数过程。 有关详细信息,请参见初始化和终止节。
标识包含指针数组的节,这些指针指向在其他所有初始化函数之前调用的函数。数组中的每个指针都视为不返回任何值的无参数过程。 有关详细信息,请参见初始化和终止节。
标识节组。节组可标识一组相关的节,这些节必须作为一个单位由链接编辑器进行处理。SHT_GROUP 类型的节只能出现在可重定位目标文件中。 有关详细信息,请参见组节。
标识包含扩展节索引的节,扩展节索引与符号表关联。如果符号表引用的任何节头索引包含转义值 SHN_XINDEX,则需要关联的 SHT_SYMTAB_SHNDX。
SHT_SYMTAB_SHNDX 节是 Elf32_Word 值的数组。此数组包含一项,可与关联的符号表项中的每一项对应。这些值表示针对其定义符号表各项的节头索引。仅当对应符号表项的 st_shndx 字段包含转义值 SHN_XINDEX 时,匹配的 Elf32_Word 才会包含实际节头索引。否则,该项必须为 SHN_UNDEF (0)。
指定硬件和软件的功能要求。 有关详细信息,请参见硬件和软件功能节。
注释节的处理遵循用于处理节的所有缺省规则。仅当注释节位于不可分配的内存中时,才会发生异常。如果未设置节头标志 SHF_ALLOC,则链接编辑器将忽略针对此节的所有不满足要求的重定位而无任何提示。
标识调试信息。使用链接编辑器的 -s 选项,或者在链接编辑之后使用 strip(1),可以将此类型的节从目标文件中删除。
标识用于处理部分初始化的符号的数据。 有关详细信息,请参见移动节。
标识允许将相同数据的多个副本减少为单个副本的节。 有关详细信息,请参见COMDAT 节。
标识其他符号信息。 有关详细信息,请参见Syminfo 表节。
标识此文件定义的细分版本。 有关详细信息,请参见版本定义节。
标识此文件所需的细分依赖性。 有关详细信息,请参见版本依赖性节。
标识用于说明符号与文件提供的版本定义之间关系的表。 有关详细信息,请参见版本符号节。
标识特定于 SPARC 的数据,使用相对于 GOT 的寻址引用这些数据。即,相对于指定给符号 _GLOBAL_OFFSET_TABLE_ 的地址的偏移。对于 64 位 SPARC,此节中的数据必须在链接编辑时绑定到 {+-} 2^32 字节的 GOT 地址中的位置。
指定保留用于应用程序的索引范围的上边界。应用程序可以使用 SHT_LOUSER 和 SHT_HIUSER 之间的节类型,而不会与当前或将来系统定义的节类型产生冲突。
其他节类型的值会保留。如前所述,即使索引 0 (SHN_UNDEF) 标记了未定义的节引用,仍会存在对应于该索引的节头。下表显示了这些值。
表 7–6 ELF 节头表项:索引 0
名称 |
值 |
说明 |
---|---|---|
sh_name |
0 |
无名称 |
sh_type |
SHT_NULL |
非活动 |
sh_flags |
0 |
无标志 |
sh_addr |
0 |
无地址 |
sh_offset |
0 |
无文件偏移 |
sh_size |
0 |
无大小 |
sh_link |
SHN_UNDEF |
无链接信息 |
sh_info |
0 |
无辅助信息 |
sh_addralign |
0 |
无对齐 |
sh_entsize |
0 |
无项 |
如果节或程序头的数目超过 ELF 头数据大小,则节头 0 的各元素可用于定义扩展的 ELF 头属性。下表显示了这些值。
表 7–7 ELF 扩展的节头表项:索引 0
名称 |
值 |
说明 |
---|---|---|
sh_name |
0 |
无名称 |
sh_type |
SHT_NULL |
非活动 |
sh_flags |
0 |
无标志 |
sh_addr |
0 |
无地址 |
sh_offset |
0 |
无文件偏移 |
sh_size |
e_shnum |
节头表中的项数 |
sh_link |
e_shstrndx |
与节名字符串表关联的项的节头索引 |
sh_info |
0 |
无辅助信息 |
sh_addralign |
0 |
无对齐 |
sh_entsize |
0 |
无项 |
节头的 sh_flags 成员包含用于说明节属性的 1 位标志:
表 7–8 ELF 节属性标志
名称 |
值 |
---|---|
SHF_WRITE |
0x1 |
SHF_ALLOC |
0x2 |
SHF_EXECINSTR |
0x4 |
SHF_MERGE |
0x10 |
SHF_STRINGS |
0x20 |
SHF_INFO_LINK |
0x40 |
SHF_LINK_ORDER |
0x80 |
SHF_OS_NONCONFORMING |
0x100 |
SHF_GROUP |
0x200 |
SHF_TLS |
0x400 |
SHF_MASKOS |
0x0ff00000 |
SHF_AMD64_LARGE |
0x10000000 |
SHF_ORDERED |
0x40000000 |
SHF_EXCLUDE |
0x80000000 |
SHF_MASKPROC |
0xf0000000 |
如果在 sh_flags 中设置了标志位,则该节的此属性处于启用状态。否则,此属性处于禁用状态,或者不适用。未定义的属性会保留并设置为零。
标识可以将其中包含的数据合并以消除重复的节。除非还设置了 SHF_STRINGS 标志,否则该节中的数据元素大小一致。每个元素的大小在节头的 sh_entsize 字段中指定。如果还设置了 SHF_STRINGS 标志,则数据元素会包含以空字符结尾的字符串。每个字符的大小在节头的 sh_entsize 字段中指定。
此节可向链接编辑器中添加特殊排序要求。如果此节头的 sh_link 字段引用其他节(链接到的节),则会应用这些要求。如果将此节与输出文件中的其他节合并,则此节将按照与这些其他节相同的相对顺序显示。同样,链接到的节将按照与其合并的节相同的相对顺序显示。
特殊的 sh_link 值 SHN_BEFORE 和 SHN_AFTER(请参见表 7–4)表示,已排序的节将分别位于要排序的集合中其他所有各节之前或之后。如果已排序集合中的多个节包含这些特殊值之一,则会保持输入文件链接行的顺序。
此标志的一个典型用法是生成按地址顺序引用文本或数据节的表。
如果缺少 sh_link 排序信息,则合并到输出文件一个节内的单个输入文件中的各节是相邻的。这些节会与输入文件中的节一样进行相对排序。构成多个输入文件的节按照链接行顺序显示。
此节除了要求标准链接规则之外,还要求特定于操作系统的特殊处理,以避免不正确的行为。如果此节具有 sh_type 值,或者对于这些字段包含特定于操作系统范围的 sh_flags 位,并且链接编辑器无法识别这些值,则包含此节的目标文件会由于出错而被拒绝。
此节是节组的一个成员,可能是唯一的成员。此节必须由 SHT_GROUP 类型的节引用。只能对可重定位目标文件中包含的节设置 SHF_GROUP 标志。 有关详细信息,请参见组节。
此节包含线程局部存储。进程中的每个线程都包含此数据的一个不同实例。 有关详细信息,请参见第 8 章,线程局部存储。
x64 的缺省编译模型仅用于 32 位位移。此位移限制了节的大小,并最终限制段为 2 GB。特定于处理器的 SHF_AMD64_LARGE 属性标志用于标识可包含超过 2 GB 的节。此标志允许链接使用不同代码模型的目标文件。
不包含 SHF_AMD64_LARGE 属性标志的 x64 目标文件节可以由使用小代码模型的目标文件任意引用。包含此标志的节只能由使用较大代码模型的目标文件引用。例如,x64 中间代码模型目标文件可以引用包含属性标志的节和不包含属性标志的节中的数据。但是,x64 小代码模型目标文件只能引用不包含此标志的节中的数据。
此节要求相对于相同类型的其他节进行排序。已排序的节会合并到由 sh_link 项指向的节中。已排序节的 sh_link 项可以指向其本身。
如果已排序节的 sh_info 项在相同输入文件中是有效节,则将基于由 sh_info 项所指向的节的输出文件内的相对排序,对已排序的节进行排序。
特殊的 sh_info 值 SHN_BEFORE 和 SHN_AFTER(请参见表 7–4)表示,已排序的节将分别位于要排序的集合中其他所有各节之前或之后。如果已排序集合中的多个节包含这些特殊值之一,则会保持输入文件链接行的顺序。
如果缺少 sh_info 排序信息,则合并到输出文件一个节内的单个输入文件中的节是相邻的。这些节会与输入文件中的节一样进行相对排序。构成多个输入文件的节按照链接行顺序显示。
此节不包括在可执行文件或共享库的链接编辑的输入中。如果还设置了 SHF_ALLOC 标志,或者存在针对此节的重定位,则会忽略此标志。
根据节类型,节头中的两个成员 sh_link 和 sh_info 会包含特殊信息。
表 7–9 ELF sh_link 和 sh_info 解释
sh_type |
sh_link |
sh_info |
---|---|---|
SHT_DYNAMIC |
关联的字符串表的节头索引。 |
0 |
SHT_HASH |
关联的符号表的节头索引。 |
0 |
SHT_REL SHT_RELA |
关联的符号表的节头索引。 | |
SHT_SYMTAB SHT_DYNSYM |
关联的字符串表的节头索引。 |
比上一个局部符号 STB_LOCAL 的符号表索引大一。 |
SHT_GROUP |
关联的符号表的节头索引。 |
关联的符号表中项的符号表索引。指定的符号表项的名称用于提供节组的签名。 |
SHT_SYMTAB_SHNDX |
关联的符号表的节头索引。 |
0 |
SHT_SUNW_move |
关联的符号表的节头索引。 |
0 |
SHT_SUNW_COMDAT |
0 |
0 |
SHT_SUNW_syminfo |
关联的符号表的节头索引。 |
关联的 .dynamic 节的节头索引。 |
SHT_SUNW_verdef |
关联的字符串表的节头索引。 |
节中版本定义的编号。 |
SHT_SUNW_verneed |
关联的字符串表的节头索引。 |
节中版本依赖性的编号。 |
SHT_SUNW_versym |
关联的符号表的节头索引。 |
0 |
包含程序和控制信息的各种节。下表中的各节由系统使用,并且具有指明的类型和属性。
表 7–10 ELF 特殊节
名称 |
类型 |
属性 |
---|---|---|
.bss |
SHT_NOBITS |
SHF_ALLOC + SHF_WRITE |
.comment |
SHT_PROGBITS |
无 |
.data、.data1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE |
.dynamic |
SHT_DYNAMIC |
SHF_ALLOC + SHF_WRITE |
.dynstr |
SHT_STRTAB |
SHF_ALLOC |
.dynsym |
SHT_DYNSYM |
SHF_ALLOC |
.eh_frame_hdr |
SHT_AMD64_UNWIND |
SHF_ALLOC |
.eh_frame |
SHT_AMD64_UNWIND |
SHF_ALLOC + SHF_WRITE |
.fini |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
.fini_array |
SHT_FINI_ARRAY |
SHF_ALLOC + SHF_WRITE |
.got |
SHT_PROGBITS | |
.hash |
SHT_HASH |
SHF_ALLOC |
.init |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
.init_array |
SHT_INIT_ARRAY |
SHF_ALLOC + SHF_WRITE |
.interp |
SHT_PROGBITS |
请参见程序的解释程序 |
.note |
SHT_NOTE |
无 |
.lbss |
SHT_NOBITS |
SHF_ALLOC + SHF_WRITE + SHF_AMD64_LARGE |
.ldata、.ldata1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE + SHF_AMD64_LARGE |
.lrodata、.lrodata1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_AMD64_LARGE |
.plt |
SHT_PROGBITS | |
.preinit_array |
SHT_PREINIT_ARRAY |
SHF_ALLOC + SHF_WRITE |
.rela |
SHT_RELA |
无 |
.relname |
SHT_REL |
请参见重定位节 |
.relaname |
SHT_RELA |
请参见重定位节 |
.rodata、.rodata1 |
SHT_PROGBITS |
SHF_ALLOC |
.shstrtab |
SHT_STRTAB |
无 |
.strtab |
SHT_STRTAB |
请参阅此表后面的说明。 |
.symtab |
SHT_SYMTAB |
请参见符号表节 |
.symtab_shndx |
SHT_SYMTAB_SHNDX |
请参见符号表节 |
.tbss |
SHT_NOBITS |
SHF_ALLOC + SHF_WRITE + SHF_TLS |
.tdata、.tdata1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE + SHF_TLS |
.text |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
.SUNW_bss |
SHT_NOBITS |
SHF_ALLOC + SHF_WRITE |
.SUNW_heap |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE |
.SUNW_cap |
SHT_SUNW_cap |
SHF_ALLOC |
.SUNW_move |
SHT_SUNW_move |
SHF_ALLOC |
.SUNW_reloc |
SHT_REL SHT_RELA |
SHF_ALLOC |
.SUNW_syminfo |
SHT_SUNW_syminfo |
SHF_ALLOC |
.SUNW_version |
SHT_SUNW_verdef SHT_SUNW_verneed SHT_SUNW_versym |
SHF_ALLOC |
构成程序的内存映像的未初始化数据。根据定义,系统在程序开始运行时会将数据初始化为零。如节类型 SHT_NOBITS 所指明的那样,此节不会占用任何文件空间。
注释信息,通常由编译系统的组件提供。此节可以由 mcs(1) 进行处理。
构成程序的内存映像的已初始化数据。
动态链接信息。 有关详细信息,请参见动态节。
进行动态链接所需的字符串,通常是表示与符号表各项关联的名称的字符串。
动态链接符号表。 有关详细信息,请参见符号表节。
用于展开栈的调用帧信息。
可执行指令,用于构成包含此节的可执行文件或共享库的单个终止函数。 有关详细信息,请参见初始化和终止例程。
函数指针数组,用于构成包含此节的可执行文件或共享库的单个终止数组。 有关详细信息,请参见初始化和终止例程。
全局偏移表。 有关详细信息,请参见全局偏移表(特定于处理器)。
符号散列表。 有关详细信息,请参见散列表节。
可执行指令,用于构成包含此节的可执行文件或共享库的单个初始化函数。 有关详细信息,请参见初始化和终止例程。
函数指针数组,用于构成包含此节的可执行文件或共享库的单个初始化数组。 有关详细信息,请参见初始化和终止例程。
程序的解释程序的路径名。 有关详细信息,请参见程序的解释程序。
特定于 x64 的已初始化数据。此数据与 .data 类似,但用于大小超过 2 GB 的节。
特定于 x64 的只读数据。此数据与 .rodata 类似,但用于大小超过 2 GB 的节。
注释节中说明了该格式的信息。
过程链接表。 有关详细信息,请参见过程链接表(特定于处理器)。
函数指针数组,用于构成包含此节的可执行文件或共享库的单个 预初始化 数组。 有关详细信息,请参见初始化和终止例程。
不适用于特定节的重定位。此节的用途之一是用于寄存器重定位。 有关详细信息,请参见寄存器符号。
重定位信息,如重定位节中所述。如果文件具有包括重定位的可装入段,则此节的属性将包括 SHF_ALLOC 位。否则,该位会处于禁用状态。通常,name 由应用重定位的节提供。因此,.text 的重定位节的名称通常为 .rel.text 或 .rela.text。
通常构成进程映像中的非可写段的只读数据。 有关详细信息,请参见程序头。
节名。
字符串,通常是表示与符号表各项关联的名称的字符串。如果文件具有包括符号字符串表的可装入段,则此节的属性将包括 SHF_ALLOC 位。否则,该位会处于禁用状态。
符号表,如符号表节中所述。如果文件具有包括符号表的可装入段,则此节的属性将包括 SHF_ALLOC 位。否则,该位会处于禁用状态。
此节包含特殊符号表的节索引数组,如 .symtab 所述。如果关联的符号表节包括 SHF_ALLOC 位,则此节的属性也将包括该位。否则,该位会处于禁用状态。
此节包含未初始化的线程局部数据,这些数据构成程序的内存映像。根据定义,针对每个新执行流对数据进行实例化时,系统会将数据初始化为零。如节类型 SHT_NOBITS 所指明的那样,此节不会占用任何文件空间。 有关详细信息,请参见第 8 章,线程局部存储。
这些节包含已初始化的线程局部数据,这些数据构成程序的内存映像。对于每个新执行流,系统会对数据内容的副本进行实例化。 有关详细信息,请参见第 8 章,线程局部存储。
程序的文本或可执行指令。
共享库的部分初始化数据,这些数据构成程序的内存映像。数据会在运行时进行初始化。如节类型 SHT_NOBITS 所指明的那样,此节不会占用任何文件空间。
从 dldump(3C) 中创建的动态可执行文件的堆。
硬件和软件的功能要求。 有关详细信息,请参见硬件和软件功能节。
部分初始化数据的附加信息。 有关详细信息,请参见移动节。
重定位信息,如重定位节中所述。此节是多个重定位节的串联,用于为引用各个重定位记录提供更好的临近性。由于仅有重定位记录的偏移有意义,因此节的 sh_info 值为零。
其他符号表信息。 有关详细信息,请参见Syminfo 表节。
版本控制信息。 有关详细信息,请参见版本控制节。
具有点 (.) 前缀的节名为系统而保留,但如果这些节的现有含义符合要求,则应用程序也可以使用这些节。应用程序可以使用不带前缀的名称,以避免与系统节产生冲突。使用目标文件格式,可以定义非保留的节。一个目标文件可以包含多个同名的节。
保留用于处理器体系结构的节名通过在节名前加上体系结构名称的缩写而构成。该名称应来自用于 e_machine 的体系结构名称。例如,.Foo.psect 是根据 FOO 体系结构定义的 psect 节。
现有扩展使用其历史名称。
COMDAT 节由其节名 (sh_name) 唯一标识。如果链接编辑器遇到节名相同的 SHT_SUNW_COMDAT 类型的多个节,则将保留第一个节,并废弃其余的节。任何应用于已废弃的 SHT_SUNW_COMDAT 节的重定位都会被忽略。在已废弃的节中定义的任何符号都会被删除。
此外,使用 -xF 选项调用编译器时,链接编辑器还支持用于对节重新排序的节命名约定。如果将函数放入名为 .sectname%funcname 的 SHT_SUNW_COMDAT 节中,则最后保留的几个 SHT_SUNW_COMDAT 节都将并入名为 .sectname 的节中。此方法可用于将 SHT_SUNW_COMDAT 节放入 .text、.data 或其他任何节等最终目标位置。
一些节会出现在相关的组中。例如,内置函数的外部定义除了要求包含可执行指令的节外,还会要求其他信息。此附加信息可以是包含引用的字面值的只读数据节、一个或多个调试信息节或其他信息节。
组节之间可以存在内部引用。但是,如果删除了其中某节,或者将其中某节替换为另一个目标文件的副本,则这些引用将没有意义。因此,应将这些组作为一个单位包括在链接目标文件中或从中忽略。
SHT_GROUP 类型的节可定义这样分组的一组节:其中一个所包含目标文件的符号表中的符号名称将为节组提供签名。SHT_GROUP 节的节头会指定标识符号项。sh_link 成员包含符号表节的节头索引,其中会包含该项。sh_info 成员包含标识项的符号表索引。节头的 sh_flags 成员值为零。节名 (sh_name) 未指定。
SHT_GROUP 节的节数据是 Elf32_Word 项的数组。第一项是一个标志字。其余项是一系列节头索引。
表 7–11 ELF 组节标志
名称 |
值 |
---|---|
GRP_COMDAT |
0x1 |
GRP_COMDAT 是一个 COMDAT 组。该组可以与另一个目标文件中的 COMDAT 组重复,其中,重复的定义是具有相同的组签名。在这类情况下,链接编辑器将仅保留其中一个重复组。其余组的成员会被废弃。
SHT_GROUP 节中的节头索引可标识构成该组的节。这些节必须在其 sh_flags 节头成员中设置 SHF_GROUP 标志。如果链接编辑器决定删除节组,则它将删除组的所有成员。
为了便于删除组,并且不保留未使用的引用,同时仅对符号表进行最少的处理,请遵循以下规则:
必须使用符号表各项中包含的 STB_GLOBAL 或 STB_WEAK 绑定和节索引 SHN_UNDEF,才能从组外的节中引用包含该组的节。包含引用的目标文件中定义的相同符号必须具有与独立于该引用的符号表项。组外的各节不能引用对组节中包含的地址具有 STB_LOCAL 绑定的符号,包括 STT_SECTION 类型的符号。
不允许从组外对包含该组的节进行非符号引用。例如,不能在 sh_link 或 sh_info 成员中使用组成员的节头索引。
如果废弃了某个组的成员,则可以删除所定义的与该组某一节相关的符号表项。如果此符号表项包含在不属于该组的符号表节中,则会进行此删除。
SHT_SUNW_cap 节标识目标文件的硬件和软件功能。此节包含以下结构的数组。 请参见 sys/link.h。
typedef struct { Elf32_Word c_tag; union { Elf32_Word c_val; Elf32_Addr c_ptr; } c_un; } Elf32_Cap; typedef struct { Elf64_Xword c_tag; union { Elf64_Xword c_val; Elf64_Addr c_ptr; } c_un; } Elf64_Cap;
对于此类型的每一个目标文件,c_tag 会控制 c_un 的解释。
这些目标文件表示具有各种解释的整数值。
这些目标文件表示程序虚拟地址。
存在以下功能标记。
表 7–12 ELF 功能数组标记
名称 |
值 |
c_un |
---|---|---|
CA_SUNW_NULL |
0 |
忽略 |
CA_SUNW_HW_1 |
1 |
c_val |
CA_SUNW_SF_1 |
2 |
c_val |
标记功能数组的结尾。
表示硬件功能值。c_val 元素包含用于表示关联硬件功能的值。在 SPARC 平台上,硬件功能在 sys/auxv_SPARC.h 中定义。在 x86 平台上,硬件功能在 sys/auxv_386.h 中定义。
表示软件功能值。c_val 元素包含用于表示 sys/elf.h 中定义的关联软件功能的值。
可重定位目标文件可以包含功能节。链接编辑器会将多个可重定位输入目标文件中的所有功能节合并到一个单独的功能节中。使用链接编辑器,还可在生成目标文件时定义功能。 请参见标识硬件和软件功能。
包含功能节(其中包含硬件功能信息)的动态库具有与该节关联的 PT_SUNWCAP 程序头。使用此程序头,运行时链接程序可以针对可供进程使用的硬件功能来验证目标文件。
利用不同硬件功能的动态库可以提供使用过滤器的灵活运行时环境。 请参见特定于硬件功能的共享库。
散列表由用于符号表访问的 Elf32_Word 或 Elf64_Word 目标文件组成。SHT_HASH 节提供了此散列表。与散列关联的符号表在散列表节头的 sh_link 项中指定。下图中使用了标签来帮助说明散列表的结构,但这些标签不属于规范的一部分。
bucket 数组包含 nbucket 项,chain 数组包含 nchain 项。索引从 0 开始。bucket 和 chain 都包含符号表索引。链表的各项与符号表对应。符号表的项数应等于 nchain,因此符号表索引也可选择链表的各项。
接受符号名称的散列函数会返回一个值,用于计算 bucket 索引。因此,如果散列函数为某个名称返回值 x,则 bucket [x% nbucket] 将会计算出索引 y。此索引为符号表和链表的索引。如果符号表项不是需要的名称,则 chain[y] 将使用相同的散列值计算出符号表的下一项。
在所选符号表项具有需要的名称或者 chain 项包含值 STN_UNDEF 之前,可以遵循 chain 链接。
散列函数如下所示:
unsigned long elf_Hash(const unsigned char *name) { unsigned long h = 0, g; while (*name) { h = (h << 4) + *name++; if (g = h & 0xf0000000) h ^= g >> 24; h &= ~g; } return h; }
通常在 ELF 文件中,已初始化的数据变量会保留在目标文件中。如果数据变量很大,并且仅包含少量的已初始化(非零)元素,则整个变量仍会保留在目标文件中。
包含大的部分初始化数据变量的目标文件(如 FORTRAN COMMON 块)可能会产生较大的磁盘空间开销。SHT_SUNW_move 节提供了一种压缩这些数据变量的机制。此压缩机制可减小关联目标文件的磁盘空间大小。
SHT_SUNW_move 节包含多个类型为 ELF32_Move 或 Elf64_Move 的项。使用这些项,可以将数据变量定义为暂定项目 (.bss)。这些项目在目标文件中不占用任何空间,但在运行时会构成目标文件的内存映像。移动记录可确定如何初始化内存映像的数据,从而构造完整的数据变量。
ELF32_Move 和 Elf64_Move 项的定义如下:
typedef struct { Elf32_Lword m_value; Elf32_Word m_info; Elf32_Word m_poffset; Elf32_Half m_repeat; Elf32_Half m_stride; } Elf32_Move; #define ELF32_M_SYM(info) ((info)>>8) #define ELF32_M_SIZE(info) ((unsigned char)(info)) #define ELF32_M_INFO(sym, size) (((sym)<<8)+(unsigned char)(size)) typedef struct { Elf64_Lword m_value; Elf64_Xword m_info; Elf64_Xword m_poffset; Elf64_Half m_repeat; Elf64_Half m_stride; } Elf64_Move; #define ELF64_M_SYM(info) ((info)>>8) #define ELF64_M_SIZE(info) ((unsigned char)(info)) #define ELF64_M_INFO(sym, size) (((sym)<<8)+(unsigned char)(size))
这些结构的元素如下:
初始化值,即移到内存映像中的值。
符号表索引(与应用初始化相关)以及初始化的偏移的大小(以字节为单位)。成员的低 8 位定义大小,该大小可以是 1、2、4 或 8。高位字节定义符号索引。
与应用初始化的关联符号相关的偏移。
重复计数。
幅度计数。该值表示在执行重复初始化时应跳过的单位数。单位是由 m_info 定义的初始化目标文件的大小。m_stride 值为零表示连续对单位执行初始化。
以下数据定义以前在目标文件中会占用 0x8000 个字节:
typedef struct { int one; char two; } Data Data move[0x1000] = { {0, 0}, {1, '1'}, {0, 0}, {0xf, 'F'}, {0xf, 'F'}, {0, 0}, {0xe, 'E'}, {0, 0}, {0xe, 'E'} };
SHT_SUNW_move 节可用于说明此数据。数据项可以在 .bss 节中定义并初始化为相应的移动项。
$ elfdump -s data | fgrep move [17] 0x00020868 0x00008000 OBJT GLOB 0 .bss move $ elfdump -m data Move Section: .SUNW_move offset ndx size repeat stride value with respect to 0x8 0x17 4 1 0 0x1 move 0xc 0x17 1 1 0 0x31 move 0x18 0x17 4 2 2 0xf move 0x1c 0x17 1 2 8 0x46 move 0x28 0x17 4 2 4 0xe move 0x2c 0x17 1 2 16 0x45 move |
可重定位目标文件提供的移动节可串联并在链接编辑器所创建的目标文件中输出。但是,在以下条件下链接编辑器将处理移动项,并将其内容扩展到旧的数据项中:
供应商或系统工程师可能需要使用特殊信息标记目标文件,以便其他程序可根据此信息检查一致性或兼容性。为此,可使用 SHT_NOTE 类型的节和 PT_NOTE 类型的程序头元素。
节和程序头元素中的注释信息包含任意数量的项,如下图所示。对于 64 位目标文件和 32 位目标文件,每一项都是一个目标处理器格式的 4 字节字的数组。图 7–6 中所示的标签用于帮助说明注释信息的结构,但不属于规范的一部分。
名称中的前 namesz 个字节,表示项的属主或创建者的字符(以空字符结尾)。不存在用于避免名称冲突的正式机制。根据约定,供应商使用其各自的名称(如 "XYZ Computer Company")作为标识符。如果不存在 name,则 namesz 值为零。如有必要,可使用填充确保描述符 4 字节对齐。namesz 中不包括这种填充方式。
desc 中的前 descsz 个字节包含注释描述符。如果不存在描述符,则 descsz 值为零。如有必要,可使用填充确保下一个注释项 4 字节对齐。descsz 中不包括这种填充方式。
提供对描述符的解释。每个创建者可控制其各自的类型。单个 type 值可以存在多种解释。程序必须同时识别名称和 type 才能理解描述符。类型当前必须为非负数。
下图中所示的注释段包含两项。
系统会保留没有名称 (namesz == 0) 以及名称长度为零 (name[0] == '\0') 的注释信息,但当前不定义任何类型。其他所有名称必须至少有一个非空字符。
重定位是连接符号引用与符号定义的过程。例如,程序调用函数时,关联的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须包含说明如何修改其节内容的信息。通过此信息,可执行文件和共享库文件可包含进程的程序映像的正确信息。可重定位项即是这些数据。
可重定位项可具有以下结构。 请参见sys/elf.h 。
typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; Elf32_Sword r_addend; } Elf32_Rela; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; } Elf64_Rel; typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rela;
此成员指定应用可重定位操作的位置。不同的目标文件对于此成员的解释会稍有不同。
对于可重定位文件,该值表示节偏移。重定位节可说明如何修改文件中的其他节。重定位偏移会在第二节中指定一个存储单元。
对于可执行文件或共享库,该值表示受重定位影响的存储单元的虚拟地址。此信息使重定位项对于运行时链接程序更为有用。
虽然为了使相关程序可以更有效地访问,不同目标文件的成员的解释会发生变化,但重定位类型的含义保持相同。
此成员指定必须对其进行重定位的符号表索引以及要应用的重定位类型。例如,调用指令的重定位项包含所调用的函数的符号表索引。如果索引是未定义的符号索引 STN_UNDEF,则重定位将使用零作为符号值。
重定位类型特定于处理器。重定位项的重定位类型或符号表索引是将 ELF32_R_TYPE 或 ELF32_R_SYM 分别应用于项的 r_info 成员所得的结果:
#define ELF32_R_SYM(info) ((info)>>8) #define ELF32_R_TYPE(info) ((unsigned char)(info)) #define ELF32_R_INFO(sym, type) (((sym)<<8)+(unsigned char)(type)) #define ELF64_R_SYM(info) ((info)>>32) #define ELF64_R_TYPE(info) ((Elf64_Word)(info)) #define ELF64_R_INFO(sym, type) (((Elf64_Xword)(sym)<<32)+ \ (Elf64_Xword)(type))
对于 Elf64_Rel 和 Elf64_Rela 结构,r_info 字段可进一步细分为 8 位类型标识符和 24位类型相关数据字段:
#define ELF64_R_TYPE_DATA(info) (((Elf64_Xword)(info)<<32)>>40) #define ELF64_R_TYPE_ID(info) (((Elf64_Xword)(info)<<56)>>56) #define ELF64_R_TYPE_INFO(data, type) (((Elf64_Xword)(data)<<8)+ \ (Elf64_Xword)(type))
此成员指定常量加数,用于计算将存储在可重定位字段中的值。
Rela 项包含显式加数。Rel 类型的项会在要修改的位置中存储一个隐式加数。32 位 SPARC 仅使用 Elf32_Rela 重定位项。 64 位 SPARC 和 64 位 x86 仅使用 Elf64_Rela 重定位项。因此,r_addend 成员可用作重定位加数。x86 仅使用 Elf32_Rel 重定位项。要重定位的字段包含该加数。在所有情况下,加数和计算所得的结果使用相同的字节顺序。
重定位节可以引用其他两个节:符号表(由 sh_link 节头项标识)和要修改的节(由 sh_info 节头项标识)。节中指定了这些关系。如果可重定位目标文件中存在重定位节,则需要 sh_info 项,但对于可执行文件和共享库,该项是可选的。重定位偏移满足执行重定位的要求。
重定位项说明如何修改下图中的指令和数据字段。位数显示在框的下角。
在 SPARC 平台上,重定位项应用于字节 (byte8)、半字 (half16) 或字。
在 64 位 SPARC 和 x64 上,重定位项还会应用于扩展字 (xword64):
在 x86 上,重定位项应用于字 (word32):
word32 可指定一个占用 4 个字节的 32 位字段,此字段以任意字节对齐。这些值使用与 x86 体系结构中的其他字值相同的字节顺序:
在所有情况下,r_offset 值都会指定受影响存储单元的第一个字节的偏移或虚拟地址。重定位类型可指定要更改的位以及计算这些位的值的方法。
针对以下重定位类型进行的计算假定,操作会将可重定位文件转换为可执行文件或共享库文件。在概念上,链接编辑器会将一个或多个可重定位文件合并以形成输出。链接编辑器首先确定如何合并和定位输入文件。然后,链接编辑器会更新符号值并执行重定位。应用于可执行文件或共享库文件的重定位类似,并会取得相同的结果。本节的表中的说明使用以下表示法:
用于计算可重定位字段的值的加数。
执行过程中将共享库装入内存的基本地址。通常,生成的共享库文件的基本虚拟地址为 0。但是,共享库的执行地址不相同。 请参见程序头。
执行过程中,重定位项的符号地址所在的全局偏移表中的偏移。 请参见全局偏移表(特定于处理器)。
全局偏移表的地址。 请参见全局偏移表(特定于处理器)。
符号的过程链接表项的节偏移或地址。 请参见过程链接表(特定于处理器)。
使用 r_offset 计算出的重定位的存储单元的节偏移或地址。
索引位于重定位项中的符号的值。
下表中的字段名称可确定重定位类型是否会检查 overflow。计算出的重定位值可以大于预期的字段,并且重定位类型可以验证 (V) 值是否适合结果或将结果截断 (T)。例如,V-simm13 表示计算出的值不能包含 simm13 字段外有意义的非零位。
表 7–13 SPARC: ELF 重定位类型
名称 |
值 |
字段 |
计算 |
---|---|---|---|
R_SPARC_NONE |
0 |
无 |
无 |
R_SPARC_8 |
1 |
V-byte8 |
S + A |
R_SPARC_16 |
2 |
V-half16 |
S + A |
R_SPARC_32 |
3 |
V-word32 |
S + A |
R_SPARC_DISP8 |
4 |
V-byte8 |
S + A - P |
R_SPARC_DISP16 |
5 |
V-half16 |
S + A - P |
R_SPARC_DISP32 |
6 |
V-disp32 |
S + A - P |
R_SPARC_WDISP30 |
7 |
V-disp30 |
(S + A - P) >> 2 |
R_SPARC_WDISP22 |
8 |
V-disp22 |
(S + A - P) >> 2 |
R_SPARC_HI22 |
9 |
T-imm22 |
(S + A) >> 10 |
R_SPARC_22 |
10 |
V-imm22 |
S + A |
R_SPARC_13 |
11 |
V-simm13 |
S + A |
R_SPARC_LO10 |
12 |
T-simm13 |
(S + A) & 0x3ff |
R_SPARC_GOT10 |
13 |
T-simm13 |
G & 0x3ff |
R_SPARC_GOT13 |
14 |
V-simm13 |
G |
R_SPARC_GOT22 |
15 |
T-simm22 |
G >> 10 |
R_SPARC_PC10 |
16 |
T-simm13 |
(S + A - P) & 0x3ff |
R_SPARC_PC22 |
17 |
V-disp22 |
(S + A - P) >> 10 |
R_SPARC_WPLT30 |
18 |
V-disp30 |
(L + A - P) >> 2 |
R_SPARC_COPY |
19 |
无 |
请参阅此表后面的说明。 |
R_SPARC_GLOB_DAT |
20 |
V-word32 |
S + A |
R_SPARC_JMP_SLOT |
21 |
无 |
请参阅此表后面的说明。 |
R_SPARC_RELATIVE |
22 |
V-word32 |
B + A |
R_SPARC_UA32 |
23 |
V-word32 |
S + A |
R_SPARC_PLT32 |
24 |
V-word32 |
L + A |
R_SPARC_HIPLT22 |
25 |
T-imm22 |
(L + A) >> 10 |
R_SPARC_LOPLT10 |
26 |
T-simm13 |
(L + A) & 0x3ff |
R_SPARC_PCPLT32 |
27 |
V-word32 |
L + A - P |
R_SPARC_PCPLT22 |
28 |
V-disp22 |
(L + A - P) >> 10 |
R_SPARC_PCPLT10 |
29 |
V-simm13 |
(L + A - P) & 0x3ff |
R_SPARC_10 |
30 |
V-simm10 |
S + A |
R_SPARC_11 |
31 |
V-simm11 |
S + A |
R_SPARC_HH22 |
34 |
V-imm22 |
(S + A) >> 42 |
R_SPARC_HM10 |
35 |
T-simm13 |
((S + A) >> 32) & 0x3ff |
R_SPARC_LM22 |
36 |
T-imm22 |
(S + A) >> 10 |
R_SPARC_PC_HH22 |
37 |
V-imm22 |
(S + A - P) >> 42 |
R_SPARC_PC_HM10 |
38 |
T-simm13 |
((S + A - P) >> 32) & 0x3ff |
R_SPARC_PC_LM22 |
39 |
T-imm22 |
(S + A - P) >> 10 |
R_SPARC_WDISP16 |
40 |
V-d2/disp14 |
(S + A - P) >> 2 |
R_SPARC_WDISP19 |
41 |
V-disp19 |
(S + A - P) >> 2 |
R_SPARC_7 |
43 |
V-imm7 |
S + A |
R_SPARC_5 |
44 |
V-imm5 |
S + A |
R_SPARC_6 |
45 |
V-imm6 |
S + A |
R_SPARC_HIX22 |
48 |
V-imm22 |
((S + A) ^ 0xffffffffffffffff) >> 10 |
R_SPARC_LOX10 |
49 |
T-simm13 |
((S + A) & 0x3ff) | 0x1c00 |
R_SPARC_H44 |
50 |
V-imm22 |
(S + A) >> 22 |
R_SPARC_M44 |
51 |
T-imm10 |
((S + A) >> 12) & 0x3ff |
R_SPARC_L44 |
52 |
T-imm13 |
(S + A) & 0xfff |
R_SPARC_REGISTER |
53 |
V-word32 |
S + A |
R_SPARC_UA16 |
55 |
V-half16 |
S + A |
R_SPARC_GOTDATA_HIX22 |
80 |
T-imm22 |
((S + A - GOT) >> 10) ^ ((S + A - GOT) >> 31) |
R_SPARC_GOTDATA_LOX22 |
81 |
T-imm13 |
((S + A - GOT) & 0x3ff) | (((S + A - GOT) >> 31) & 0x1c00) |
R_SPARC_GOTDATA_OP_HIX22 |
82 |
T-imm22 |
(G >> 10) ^ (G >> 31) |
R_SPARC_GOTDATA_OP_LOX22 |
83 |
T-imm13 |
(G & 0x3ff) | ((G >> 31) & 0x1c00) |
R_SPARC_GOTDATA_OP |
84 |
Word32 |
请参阅此表后面的说明。 |
其他重定位类型可用于线程局部存储引用。这些重定位类型将在第 8 章,线程局部存储中介绍。
一些重定位类型的语义不只是简单的计算:
与 R_SPARC_LO10 类似,不同的是此重定位指向符号的 GOT 项的地址。此外,R_SPARC_GOT10 还指示链接编辑器创建全局偏移表。
与 R_SPARC_13 类似,不同的是此重定位指向符号的 GOT 项的地址。此外,R_SPARC_GOT13 还指示链接编辑器创建全局偏移表。
与 R_SPARC_22 类似,不同的是此重定位指向符号的 GOT 项的地址。此外,R_SPARC_GOT22 还指示链接编辑器创建全局偏移表。
与 R_SPARC_WDISP30 类似,不同的是此重定位指向符号的过程链接表项的地址。此外,R_SPARC_WPLT30 还指示链接编辑器创建过程链接表。
由链接编辑器为动态可执行文件创建,用于保留只读文本段。此重定位偏移成员指向可写段中的位置。符号表索引指定应在当前目标文件和共享库中同时存在的符号。执行过程中,运行时链接程序将与共享库的符号关联的数据复制到偏移所指定的位置。 请参见复制重定位。
与 R_SPARC_32 类似,不同的是此重定位会将 GOT 项设置为所指定符号的地址。使用特殊重定位类型,可以确定符号和 GOT 项之间的对应关系。
由链接编辑器为动态库创建,用于提供延迟绑定。此重定位偏移成员可指定过程链接表项的位置。运行时链接程序会修改过程链接表项,以将控制权转移到指定的符号地址。
由链接编辑器为动态库创建。此重定位偏移成员可指定共享库中包含表示相对地址的值的位置。运行时链接程序通过将装入共享库的虚拟地址与相对地址相加,计算对应的虚拟地址。此类型的重定位项必须为符号表索引指定零值。
与 R_SPARC_32 类似,不同的是此重定位指向未对齐的字。必须将要重定位的字作为任意对齐的四个独立字节进行处理,而不是作为根据体系结构要求对齐的字进行处理。
与 R_SPARC_HI22 类似,不同的是此重定位会进行截断而不是验证。
与 R_SPARC_PC22 类似,不同的是此重定位会进行截断而不是验证。
与 R_SPARC_LOX10 一起用于可执行文件,这些可执行文件在 64 位地址空间中的上限为 4 GB。与 R_SPARC_HI22 类似,但会提供链接值的补码。
与 R_SPARC_HIX22 一起使用。与 R_SPARC_LO10 类似,但始终设置链接值的位 10 到 12。
与 R_SPARC_H44 和 R_SPARC_M44 重定位类型一起使用,以生成 44 位的绝对寻址模型。
用于初始化寄存器符号。此重定位偏移成员包含要初始化的寄存器编号。对于此寄存器必须存在对应的寄存器符号。该符号必须为 SHN_ABS 类型。
这些重定位类型用于代码转换。
重定位计算中使用的以下表示法是特定于 64 位 SPARC 的。
用于计算重定位字段的值的辅助加数。此加数通过应用 ELF64_R_TYPE_DATA 宏从 r_info 字段中提取。
下表中列出的重定位类型是扩展或修改针对 32 位 SPARC 定义的重定位类型所得的。 请参见SPARC: 重定位类型。
表 7–14 64 位 SPARC: ELF 重定位类型
名称 |
值 |
字段 |
计算 |
---|---|---|---|
R_SPARC_HI22 |
9 |
V-imm22 |
(S + A) >> 10 |
R_SPARC_GLOB_DAT |
20 |
V-xword64 |
S + A |
R_SPARC_RELATIVE |
22 |
V-xword64 |
B + A |
R_SPARC_64 |
32 |
V-xword64 |
S + A |
R_SPARC_OLO10 |
33 |
V-simm13 |
((S + A) & 0x3ff) + O |
R_SPARC_DISP64 |
46 |
V-xword64 |
S + A - P |
R_SPARC_PLT64 |
47 |
V-xword64 |
L + A |
R_SPARC_REGISTER |
53 |
V-xword64 |
S + A |
R_SPARC_UA64 |
54 |
V-xword64 |
S + A |
R_SPARC_H34 |
85 |
V-imm22 |
(S + A) >> 12 |
以下重定位类型的语义不只是简单的计算:
与 R_SPARC_LO10 类似,不同的是会添加额外的偏移,以充分利用 13 位带符号的直接字段。
下表中列出的重定位类型是针对 32 位 x86 定义的。
表 7–15 32 位 x86: ELF 重定位类型
名称 |
值 |
字段 |
计算 |
---|---|---|---|
R_386_NONE |
0 |
无 |
无 |
R_386_32 |
1 |
word32 |
S + A |
R_386_PC32 |
2 |
word32 |
S + A - P |
R_386_GOT32 |
3 |
word32 |
G + A |
R_386_PLT32 |
4 |
word32 |
L + A - P |
R_386_COPY |
5 |
无 |
请参阅此表后面的说明。 |
R_386_GLOB_DAT |
6 |
word32 |
S |
R_386_JMP_SLOT |
7 |
word32 |
S |
R_386_RELATIVE |
8 |
word32 |
B + A |
R_386_GOTOFF |
9 |
word32 |
S + A - GOT |
R_386_GOTPC |
10 |
word32 |
GOT + A - P |
R_386_32PLT |
11 |
word32 |
L + A |
R_386_16 |
20 |
word16 |
L + A |
R_386_PC16 |
21 |
word16 |
L + A - P |
R_386_8 |
22 |
word8 |
L + A |
R_386_PC8 |
23 |
word8 |
L + A - P |
其他重定位类型可用于线程局部存储引用。这些重定位类型将在第 8 章,线程局部存储中介绍。
一些重定位类型的语义不只是简单的计算:
计算 GOT 的基本地址与符号的 GOT 项之间的距离。此重定位还指示链接编辑器创建全局偏移表。
计算符号的过程链接表项的地址,并指示链接编辑器创建一个过程链接表。
由链接编辑器为动态可执行文件创建,用于保留只读文本段。此重定位偏移成员指向可写段中的位置。符号表索引指定应在当前目标文件和共享库中同时存在的符号。执行过程中,运行时链接程序将与共享库的符号关联的数据复制到偏移所指定的位置。 请参见复制重定位。
用于将 GOT 项设置为所指定符号的地址。使用特殊重定位类型,可以确定符号和 GOT 项之间的对应关系。
由链接编辑器为动态库创建,用于提供延迟绑定。此重定位偏移成员可指定过程链接表项的位置。运行时链接程序会修改过程链接表项,以将控制权转移到指定的符号地址。
由链接编辑器为动态库创建。此重定位偏移成员可指定共享库中包含表示相对地址的值的位置。运行时链接程序通过将装入共享库的虚拟地址与相对地址相加,计算对应的虚拟地址。此类型的重定位项必须为符号表索引指定值零。
计算符号的值与 GOT 的地址之间的差值。此重定位还指示链接编辑器创建全局偏移表。
与 R_386_PC32 类似,不同的是它在其计算中会使用 GOT 的地址。此重定位中引用的符号通常是 _GLOBAL_OFFSET_TABLE_,该符号还指示链接编辑器创建全局偏移表。
下表中列出的重定位是针对 x64 定义的。
表 7–16 x64: ELF 重定位类型
名称 |
值 |
字段 |
计算 |
---|---|---|---|
R_AMD64_NONE |
0 |
无 |
无 |
R_AMD64_64 |
1 |
word64 |
S + A |
R_AMD64_PC32 |
2 |
word32 |
S + A - P |
R_AMD64_GOT32 |
3 |
word32 |
G + A |
R_AMD64_PLT32 |
4 |
word32 |
L + A - P |
R_AMD64_COPY |
5 |
无 |
请参阅此表后面的说明。 |
R_AMD64_GLOB_DAT |
6 |
word64 |
S |
R_AMD64_JUMP_SLOT |
7 |
word64 |
S |
R_AMD64_RELATIVE |
8 |
word64 |
B + A |
R_AMD64_GOTPCREL |
9 |
word32 |
G + GOT + A - P |
R_AMD64_32 |
10 |
word32 |
S + A |
R_AMD64_32S |
11 |
word32 |
S + A |
R_AMD64_16 |
12 |
word16 |
S + A |
R_AMD64_PC16 |
13 |
word16 |
S + A - P |
R_AMD64_8 |
14 |
word8 |
S + A |
R_AMD64_PC8 |
15 |
word8 |
S + A - P |
R_AMD64_PC64 |
24 |
word64 |
S + A - P |
R_AMD64_GOTOFF64 |
25 |
word64 |
S + A - GOT |
R_AMD64_GOTPC32 |
26 |
work32 |
GOT + A + P |
其他重定位类型可用于线程局部存储引用。这些重定位类型将在第 8 章,线程局部存储中介绍。
大多数重定位类型的特殊语义与用于 x86 的语义相同。一些重定位类型的语义不只是简单的计算:
此重定位类型具有与 R_AMD64_GOT32 或等效 R_386_GOTPC 重定位类型不同的语义。x64 体系结构提供了相对于指令指针的寻址模式。因此,可以使用单个指令从 GOT 装入地址。
针对 R_AMD64_GOTPCREL 重定位类型进行的计算提供了 GOT 中指定了符号地址的位置与应用重定位的位置之间的差值。
计算出的值会截断为 32 位。链接编辑器可验证为重定位生成的值是否会使用零扩展为初始的 64 位值。
计算出的值会截断为 32 位。链接编辑器可验证为重定位生成的值是否会使用符号扩展为初始的 64 位值。
这些重定位类型不适用于 x64 ABI,在此列出是为了说明。R_AMD64_8 重定位类型会将计算出的值截断为 8 位。R_AMD64_16 重定位类型会将所计算的值截断为 16 位。
字符串表节包含以空字符结尾的字符序列,通常称为字符串。目标文件使用这些字符串表示符号和节的名称。可以将字符串作为字符串表节的索引进行引用。
第一个字节(索引零)包含空字符。同样,字符串表的最后一个字节也包含空字符,从而确保所有字符串都以空字符结尾。根据上下文,索引为零的字符串不会指定任何名称或指定空名称。
允许使用空字符串表节。节头的 sh_size 成员值为零。对于空字符串表,非零索引无效。
节头的 sh_name 成员包含节头字符串表的节索引。节头字符串表由 ELF 头的 e_shstrndx 成员指定。下图显示了具有 25 个字节的字符串表,并且其字符串与各种索引关联。
下表显示了上图所示的字符串表中的字符串。
表 7–17 ELF 字符串表索引
索引 |
字符串 |
---|---|
0 |
无 |
1 |
name |
7 |
Variable |
11 |
able |
16 |
able |
24 |
空字符串 |
如示例所示,字符串表索引可以指向节中的任何字节。一个字符串可以出现多次。可以存在对子字符串的引用。一个字符串可以多次引用。另外,还允许使用未引用的字符串。
目标文件的符号表包含定位和重定位程序的符号定义和符号引用所需的信息。符号表索引是此数组的下标。索引 0 指定表中的第一项并用作未定义的符号索引。 请参见表 7–21。
符号表项具有以下格式。 请参见 sys/elf.h。
typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym; typedef struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Half st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym;
目标文件的符号字符串表的索引,其中包含符号名称的字符表示形式。如果该值为非零,则表示指定符号名称的字符串表索引。否则,符号表项没有名称。
关联符号的值。根据上下文,该值可以是绝对值或地址。 请参见符号值。
许多符号具有关联大小。例如,数据目标文件的大小是目标文件中包含的字节数。如果符号没有大小或大小未知,则此成员值为零。
符号的类型和绑定属性。表 7–18 中显示了值和含义的列表。以下代码说明了如何处理这些值。 请参见 sys/elf.h。
#define ELF32_ST_BIND(info) ((info) >> 4) #define ELF32_ST_TYPE(info) ((info) & 0xf) #define ELF32_ST_INFO(bind, type) (((bind)<<4)+((type)&0xf)) #define ELF64_ST_BIND(info) ((info) >> 4) #define ELF64_ST_TYPE(info) ((info) & 0xf) #define ELF64_ST_INFO(bind, type) (((bind)<<4)+((type)&0xf))
符号的可见性。表 7–20 中显示了值和含义的列表。以下代码说明了如何处理 32 位目标文件和 64 位目标文件的值。其他位设置为零,并且未定义任何含义。
#define ELF32_ST_VISIBILITY(o) ((o)&0x3) #define ELF64_ST_VISIBILITY(o) ((o)&0x3)
所定义的每一个符号表项都与某节有关。此成员包含相关节头表索引。部分节索引会表示特殊含义。 请参见表 7–4。
如果此成员包含 SHN_XINDEX,则实际节头索引会过大而无法放入此字段中。实际值包含在 SHT_SYMTAB_SHNDX 类型的关联节中。
根据符号的 st_info 字段确定的符号绑定可确定链接可见性和行为。
表 7–18 ELF 符号绑定:ELF32_ST_BIND 和 ELF64_ST_BIND
名称 |
值 |
---|---|
STB_LOCAL |
0 |
STB_GLOBAL |
1 |
STB_WEAK |
2 |
STB_LOOS |
10 |
STB_HIOS |
12 |
STB_LOPROC |
13 |
STB_HIPROC |
15 |
此范围内包含的值保留用于特定于操作系统的语义。
此范围内包含的值保留用于特定于处理器的语义。
链接编辑器合并多个可重定位目标文件时,不允许多次定义相同名称的 STB_GLOBAL 符号。但是,如果存在已定义的全局符号,则出现相同名称的弱符号不会导致错误。链接编辑器会接受全局定义,并忽略弱定义。
同样,如果存在通用符号,则出现相同名称的弱符号也不会导致错误。链接编辑器将使用通用定义,并忽略弱定义。通用符号具有包含 SHN_COMMON 的 st_shndx 字段。 请参见符号解析。
链接编辑器搜索归档库时,将会提取包含未定义全局符号或暂定全局符号的定义的归档成员。此成员的定义可以是全局符号或弱符号。
缺省情况下,链接编辑器不会提取归档成员来解析未定义的弱符号。未解析的弱符号的值为零。使用 -z weakextract 可覆盖此缺省行为。使用此选项,弱引用可提取归档成员。
弱符号主要适用于系统软件。建议不要在应用程序中使用弱符号。
在每个符号表中,具有 STB_LOCAL 绑定的所有符号都优先于弱符号和全局符号。如节中所述,符号表节的 sh_info 节头成员包含第一个非局部符号的符号表索引。
根据符号的 st_info 字段确定的符号类型用于对关联实体进行一般分类。
表 7–19 ELF 符号类型:ELF32_ST_TYPE 和 ELF64_ST_TYPE
名称 |
值 |
---|---|
STT_NOTYPE |
0 |
STT_OBJECT |
1 |
STT_FUNC |
2 |
STT_SECTION |
3 |
STT_FILE |
4 |
STT_COMMON |
5 |
STT_TLS |
6 |
STT_LOOS |
10 |
STT_HIOS |
12 |
STT_LOPROC |
13 |
STT_SPARC_REGISTER |
13 |
STT_HIPROC |
15 |
未指定符号类型。
此符号与变量、数组等数据目标文件关联。
此符号与函数或其他可执行代码关联。
此符号与节关联。此类型的符号表各项主要用于重定位,并且通常具有 STB_LOCAL 绑定。
通常,符号的名称会指定与目标文件关联的源文件的名称。文件符号具有 STB_LOCAL 绑定和节索引 SHN_ABS。此符号(如果存在)位于文件的其他 STB_LOCAL 符号前面。
符号索引为 1 的 SHT_SYMTAB 是表示目标文件的 STT_FILE 符号。通常,此符号后跟文件的 STT_SECTION 符号。这些节符号又后跟已降为局部符号的任何全局符号。
此符号标记未初始化的通用块。此符号的处理与对 STT_OBJECT 的处理完全相同。
此符号指定线程局部存储实体。定义后,此符号可为符号指明指定的偏移,而不是实际地址。
线程局部存储重定位只能引用 STT_TLS 类型的符号。从可分配节中引用 STT_TLS 类型的符号只能通过使用特殊线程局部存储重定位来实现。 有关详细信息,请参见第 8 章,线程局部存储。从非可分配节中引用 STT_TLS 类型的符号没有此限制。
此范围内包含的值保留用于特定于操作系统的语义。
此范围内包含的值保留用于特定于处理器的语义。
符号的可见性根据其 st_other 字段确定。此可见性可以在可重定位目标文件中指定。此可见性定义了在符号成为可执行文件或共享库的一部分后访问该符号的方式。
表 7–20 ELF 符号可见性
名称 |
值 |
---|---|
STV_DEFAULT |
0 |
STV_INTERNAL |
1 |
STV_HIDDEN |
2 |
STV_PROTECTED |
3 |
具有 STV_DEFAULT 属性的符号的可见性与符号的绑定类型指定的可见性相同。全局符号和弱符号在其定义组件(可执行文件或共享库)外部可见。局部符号处于隐藏状态。另外,还可以替换全局符号和弱符号。可以在另一个组件中通过定义相同的名称插入这些符号。
如果当前组件中定义的符号在其他组件中可见,但不能被替换,则该符号会处于受保护状态。定义组件中对这类符号的任何引用都必须解析为该组件中的定义。即使在另一个组件中存在按缺省规则插入的符号定义,也必须进行此解析。具有 STB_LOCAL 绑定的符号将没有 STV_PROTECTED 可见性。
如果当前组件中定义的符号的名称对于其他组件不可见,则该符号处于隐藏状态。必须对这类符号进行保护。此属性用于控制组件的外部接口。由这样的符号命名的目标文件仍可以在另一个组件中引用(如果将目标文件的地址传到外部)。
如果可执行文件或共享库中包括可重定位目标文件,则该目标文件中包含的隐藏符号将会删除或转换为使用 STB_LOCAL 绑定。
此可见性属性当前被保留。
在链接编辑过程中,可见性属性不会影响可执行文件或共享库中符号的解析。这样的解析由绑定类型控制。一旦链接编辑器选定了其解析,这些属性即会强加两种要求。两种要求都基于以下事实,即所链接的代码中的引用可能已优化,从而可利用这些属性。
所有非缺省可见性属性在应用于符号引用时都表示,在链接的目标文件中必须提供满足该引用的定义。如果在链接的目标文件中没有定义此类型的符号引用,则该引用必须具有 STB_WEAK 绑定。在此情况下,引用将解析为零。
如果任何名称的引用或定义是具有非缺省可见性属性的符号,则该可见性属性将传播给链接的目标文件中的解析符号。如果针对符号的不同实例指定不同的可见性属性,则最具约束的可见性属性将传播给链接的目标文件中的解析符号。这些属性按最低到最高约束进行排序,依次为 STV_PROTECTED、STV_HIDDEN 和 STV_INTERNAL。
如果符号的值指向节中的特定位置,则符号的节索引成员 st_shndx 会包含节头表的索引。节在重定位过程中移动时,符号的值也会更改。符号的引用仍然指向程序中的相同位置。一些特殊节索引值会具有其他语义:
此符号标记尚未分配的通用块。与节的 sh_addralign 成员类似,符号的值也会指定对齐约束。链接编辑器在值为 st_value 的倍数的地址位置为符号分配存储空间。符号的大小会指明所需的字节数。
此节表索引表示未定义符号。链接编辑器将此目标文件与用于定义所表示的符号的另一目标文件合并时,此文件中对该符号的引用将与该定义绑定。
如之前所述,索引 0 (STN_UNDEF) 的符号表项会保留。此项具有下表中列出的值。
表 7–21 ELF 符号表项:索引 0
名称 |
值 |
说明 |
---|---|---|
st_name |
0 |
无名称 |
st_value |
0 |
零值 |
st_size |
0 |
无大小 |
st_info |
0 |
无类型,本地绑定 |
st_other |
0 |
|
st_shndx |
SHN_UNDEF |
无节 |
不同目标文件类型的符号表的各项对于 st_value 成员的解释稍有不同。
在可重定位文件中,st_value 包含所定义符号的节偏移。st_value 表示从 st_shndx 所标识的节的起始位置的偏移。
在可执行文件和共享库文件中,st_value 包含虚拟地址。为使这些文件的符号更适用于运行时链接程序,节偏移(文件解释)会替换为与节数无关的虚拟地址(内存解释)。
尽管符号表值对于不同的目标文件具有类似含义,但通过适当的程序可以有效地访问数据。
SPARC 体系结构支持用于初始化全局寄存器的寄存器符号。下表中列出了寄存器符号的符号表项包含的各项。
表 7–22 SPARC: ELF 符号表项:寄存器符号
字段 |
含义 |
---|---|
st_name |
符号名称的字符串表的索引;若其值为 0 则代表临时寄存器。 |
st_value |
寄存器编号。有关整数寄存器赋值的信息,请参见 ABI 手册。 |
st_size |
未使用 (0)。 |
st_info |
绑定通常为 STB_GLOBAL,类型必须是 STT_SPARC_REGISTER。 |
st_other |
未使用 (0)。 |
st_shndx |
如果该目标文件初始化此寄存器符号,则为 SHN_ABS,否则为 SHN_UNDEF。 |
下表中列出了为 SPARC 定义的寄存器值。
表 7–23 SPARC: ELF 寄存器编号
名称 |
值 |
含义 |
---|---|---|
STO_SPARC_REGISTER_G2 |
0x2 |
%g2 |
STO_SPARC_REGISTER_G3 |
0x3 |
%g3 |
如果缺少特定全局寄存器的项,则意味着目标文件根本没有使用特定全局寄存器。
syminfo 节包含多个类型为 Elf32_Syminfo 或 Elf64_Syminfo 的项。.SUNW_syminfo 节中包含与关联符号表 (sh_link) 中的每一项对应的项。
如果目标文件中存在此节,则可通过采用关联符号表的符号索引,并使用该索引在此节中查找对应的 Elf32_Syminfo 项或 Elf64_Syminfo 项,从而找到其他符号信息。关联的符号表和 Syminfo 表的项数将始终相同。
索引 0 用于存储 Syminfo 表的当前版本,即 SYMINFO_CURRENT。由于符号表项 0 始终保留用于 UNDEF 符号表项,因此该用法不会造成任何冲突。
Syminfo 项具有以下格式。 请参见 sys/link.h。
typedef struct { Elf32_Half si_boundto; Elf32_Half si_flags; } Elf32_Syminfo; typedef struct { Elf64_Half si_boundto; Elf64_Half si_flags; } Elf64_Syminfo;
.dynamic 节中某项的索引,由 sh_info 字段标识,该字段用于扩充 Syminfo 标志。例如,DT_NEEDED 项标识与 Syminfo 项关联的动态库。以下各项是 si_boundto 的保留值。
名称 |
值 |
含义 |
---|---|---|
SYMINFO_BT_SELF |
0xffff |
符号与自身绑定。 |
SYMINFO_BT_PARENT |
0xfffe |
符号与父级绑定。父级是指导致此动态库被装入的第一个目标文件。 |
SYMINFO_BT_NONE |
0xfffd |
符号没有任何特殊的符号绑定。 |
此位字段可以设置标志,如下表所示。
名称 |
值 |
含义 |
---|---|---|
SYMINFO_FLG_DIRECT |
0x01 |
符号引用与包含定义的目标文件直接关联。 |
SYMINFO_FLG_COPY |
0x04 |
符号定义通过副本重定位生成。 |
SYMINFO_FLG_LAZYLOAD |
0x08 |
符号引用应延迟装入的目标文件。 |
SYMINFO_FLG_DIRECTBIND |
0x10 |
符号引用应与定义直接绑定。 |
SYMINFO_FLG_NOEXTDIRECT |
0x20 |
不允许将外部引用与此符号定义直接绑定。 |
链接编辑器创建的目标文件可以包含以下两种类型的版本控制信息:
sys/link.h 中定义了这些节的组成结构。包含版本控制信息的节名为 .SUNW_version。
此节由 SHT_SUNW_verdef 类型定义。如果此节存在,则必须同时存在 SHT_SUNW_versym 节。这两种结构在文件中提供符号与版本定义之间的关联。 请参见创建版本定义。此节中的元素具有以下结构:
typedef struct { Elf32_Half vd_version; Elf32_Half vd_flags; Elf32_Half vd_ndx; Elf32_Half vd_cnt; Elf32_Word vd_hash; Elf32_Word vd_aux; Elf32_Word vd_next; } Elf32_Verdef; typedef struct { Elf32_Word vda_name; Elf32_Word vda_next; } Elf32_Verdaux; typedef struct { Elf64_Half vd_version; Elf64_Half vd_flags; Elf64_Half vd_ndx; Elf64_Half vd_cnt; Elf64_Word vd_hash; Elf64_Word vd_aux; Elf64_Word vd_next; } Elf64_Verdef; typedef struct { Elf64_Word vda_name; Elf64_Word vda_next; } Elf64_Verdaux;
此成员标识该结构的版本,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
VER_DEF_NONE |
0 |
无效版本。 |
VER_DEF_CURRENT |
>=1 |
当前版本。 |
值 1 表示原始节格式。扩展要求使用更大数字的新版本。VER_DEF_CURRENT 的值可根据需要进行更改,以反映当前版本号。
此成员包含特定于版本定义的信息,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
VER_FLG_BASE |
0x1 |
文件的版本定义。 |
VER_FLG_WEAK |
0x2 |
弱版本标识符。 |
对文件应用版本定义或符号自动缩减后,基版本定义将始终存在。基版本可为文件保留的符号提供缺省版本。弱版本定义 (weak version definition) 没有与版本关联的符号。 请参见创建弱版本定义 (weak version definition)。
Elf32_Verdaux 数组中的元素数目。
版本定义名称的散列值。该值是通过使用散列表节中介绍的同一散列函数生成的。
从此 Elf32_Verdef 项的开头到版本定义名称的 Elf32_Verdaux 数组的字节偏移。该数组中的第一个元素必须存在。此元素指向该结构定义的版本定义字符串。也可以存在其他元素。元素数目由 vd_cnt 值表示。这些元素表示此版本定义的依赖项。每种依赖项都会具有各自的版本定义结构。
从此 Elf32_Verdef 结构的开头到下一个 Elf32_Verdef 项的字节偏移。
以空字符结尾的字符串的字符串表偏移,用于提供版本定义的名称。
从此 Elf32_Verdaux 项的开头到下一个 Elf32_Verdaux 项的字节偏移。
版本符号节由类型 SHT_SUNW_versym 定义。此节包含具有以下结构的元素的数组。
typedef Elf32_Half Elf32_Versym; typedef Elf64_Half Elf64_Versym;
该数组的元素数目必须等于关联符号表中包含的符号表项的数目。此数目根据该节的 sh_link 值确定。该数组的每一个元素都包含一个索引,这些索引可以具有下表中所示的值。
表 7–24 ELF 版本依赖性索引
名称 |
值 |
含义 |
---|---|---|
VER_NDX_LOCAL |
0 |
符号具有局部范围的索引。 |
VER_NDX_GLOBAL |
1 |
符号具有全局范围的索引,并且会指定给基版本定义。 |
|
>1 |
符号具有全局范围的索引,并且会指定给用户定义的版本定义。 |
任何大于 VER_NDX_GLOBAL 的索引值都必须与 SHT_SUNW_verdef 节中的项的 vd_ndx 值对应。如果不存在大于 VER_NDX_GLOBAL 的索引值,则无需存在 SHT_SUNW_verdef 节。
版本依赖性节由 SHT_SUNW_verneed 类型定义。此节通过指明动态依赖项所需的版本定义,对文件的动态依赖性要求进行补充。仅当依赖项包含版本定义时,才会在此节中进行记录。此节中的元素具有以下结构:
typedef struct { Elf32_Half vn_version; Elf32_Half vn_cnt; Elf32_Word vn_file; Elf32_Word vn_aux; Elf32_Word vn_next; } Elf32_Verneed; typedef struct { Elf32_Word vna_hash; Elf32_Half vna_flags; Elf32_Half vna_other; Elf32_Word vna_name; Elf32_Word vna_next; } Elf32_Vernaux; typedef struct { Elf64_Half vn_version; Elf64_Half vn_cnt; Elf64_Word vn_file; Elf64_Word vn_aux; Elf64_Word vn_next; } Elf64_Verneed; typedef struct { Elf64_Word vna_hash; Elf64_Half vna_flags; Elf64_Half vna_other; Elf64_Word vna_name; Elf64_Word vna_next; } Elf64_Vernaux;
此成员标识该结构的版本,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
VER_NEED_NONE |
0 |
无效版本。 |
VER_NEED_CURRENT |
>=1 |
当前版本。 |
值 1 表示原始节格式。扩展要求使用更大数字的新版本。VER_NEED_CURRENT 的值可根据需要进行更改,以反映当前版本号。
Elf32_Vernaux 数组中的元素数目。
以空字符结尾的字符串的字符串表偏移,用于提供版本依赖性的文件名。此名称与文件中找到的 .dynamic 依赖项之一匹配。 请参见动态节。
字节偏移,范围从此 Elf32_Verneed 项的开头到关联文件依赖项所需的版本定义的 Elf32_Vernaux 数组。必须存在至少一种版本依赖性。也可以存在其他版本依赖性,具体数目由 vn_cnt 值表示。
从此 Elf32_Verneed 项的开头到下一个 Elf32_Verneed 项的字节偏移。
版本依赖性名称的散列值。该值是通过使用散列表节中介绍的同一散列函数生成的。
版本依赖性特定信息,如下表中所列。
名称 |
值 |
含义 |
---|---|---|
VER_FLG_WEAK |
0x2 |
弱版本标识符。 |
弱版本依赖性表示与弱版本定义 (weak version definition) 的原始绑定。
目前未使用。
以空字符结尾的字符串的字符串表偏移,用于提供版本依赖性的名称。
从此 Elf32_Vernaux 项的开头到下一个 Elf32_Vernaux 项的字节偏移。
本节介绍用于创建运行程序的目标文件信息和系统操作。此处介绍的大多数信息适用于所有系统。特定于某处理器的信息位于带有相应标记的各节中。
可执行文件和共享库文件静态表示应用程序。要执行这类程序,系统可使用这些文件创建动态程序表示形式(即进程映像)。进程映像具有包含其文本、数据、栈等内容的段。本节包括以下主要小节。
程序头,其中介绍了参与程序执行的目标文件结构。主数据结构是一种程序头表,用于定位文件中的段映像,并包含创建程序内存映像所需的其他信息。
程序装入(特定于处理器),其中介绍了用于将程序装入内存的信息。
运行时链接程序,其中介绍了用于指定和解析进程映像的目标文件之间的符号引用的信息。
可执行文件或共享库文件的程序头表是一个结构数组。每种结构都描述了系统准备程序执行所需的段或其他信息。目标文件段包含一个或多个节,如段内容中所述。
程序头仅对可执行文件和共享库文件有意义。文件使用 ELF 头的 e_phentsize 和 e_phnum 成员来指定各自的程序头大小。
程序头具有以下结构。 请参见 sys/elf.h。
typedef struct { Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr; typedef struct { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64_Xword p_align; } Elf64_Phdr;
此数组元素描述的段类型或解释此数组元素的信息的方式。表 7–25 中指定了类型值及其含义。
相对段的第一个字节所在文件的起始位置的偏移。
段在与物理寻址相关的系统中的物理地址。由于此系统忽略了应用程序的物理地址,因此该成员对于可执行文件和共享库具有未指定的内容。
段的文件映像中的字节数,可以为零。
段的内存映像中的字节数,可以为零。
与段相关的标志。表 7–26 中指定了类型值及其含义。
可装入的进程段必须具有 p_vaddr 和 p_offset 的同余值(以页面大小为模数)。此成员可提供一个值,用于在内存和文件中根据该值对齐各段。值 0 和 1 表示无需对齐。另外,p_align 应为 2 的正整数幂,并且 p_vaddr 应等于 p_offset(以 p_align 为模数)。 请参见程序装入(特定于处理器)。
某些项用于描述进程段。其他项则提供补充信息,并且不会构成进程映像。除非明确指定了顺序,否则段的各项可以任何顺序显示。下表中列出了定义的类型值。
表 7–25 ELF 段类型
名称 |
值 |
---|---|
PT_NULL |
0 |
PT_LOAD |
1 |
PT_DYNAMIC |
2 |
PT_INTERP |
3 |
PT_NOTE |
4 |
PT_SHLIB |
5 |
PT_PHDR |
6 |
PT_TLS |
7 |
PT_LOOS |
0x60000000 |
PT_SUNW_UNWIND |
0x6464e550 |
PT_LOSUNW |
0x6ffffffa |
PT_SUNWBSS |
0x6ffffffa |
PT_SUNWSTACK |
0x6ffffffb |
PT_SUNWDTRACE |
0x6ffffffc |
PT_SUNWCAP |
0x6ffffffd |
PT_HISUNW |
0x6fffffff |
PT_HIOS |
0x6fffffff |
PT_LOPROC |
0x70000000 |
PT_HIPROC |
0x7fffffff |
未使用。没有定义成员值。使用此类型,程序头表可以包含忽略的项。
指定可装入段,通过 p_filesz 和 p_memsz 进行描述。文件中的字节会映射到内存段的起始位置。如果段的内存大小 (p_memsz) 大于文件大小 (p_filesz),则将多余字节的值定义为 0。这些字节跟在段的已初始化区域后面。文件大小不能大于内存大小。程序头表中的可装入段的各项按升序显示,并基于 p_vaddr 成员进行排列。
指定动态链接信息。 请参见动态节。
指定要作为解释程序调用的以空字符结尾的路径名的位置和大小。对于动态可执行文件,必须设置此类型。此类型可出现在共享库中。此类型不能在一个文件中多次出现。此类型(如果存在)必须位于任何可装入段的各项的前面。 有关详细信息,请参见程序的解释程序。
指定辅助信息的位置和大小。 有关详细信息,请参见注释节。
保留类型,但具有未指定的语义。
指定程序头表在文件及程序内存映像中的位置和大小。此段类型不能在一个文件中多次出现。此外,仅当程序头表是程序内存映像的一部分时,才可以出现此段。此类型(如果存在)必须位于任何可装入段的各项的前面。 有关详细信息,请参见程序的解释程序。
指定线程局部存储模板。 有关详细信息,请参见线程局部存储节。
此段包含栈扩展表。
与 PT_LOAD 元素相同的属性,用于描述 .SUNW_bss 节。
描述进程栈。只能存在一个 PT_SUNWSTACK 元素。仅访问权限(如 p_flags 字段中所定义)有意义。
保留供 dtrace(1M) 内部使用。
指定硬件功能要求。 有关详细信息,请参见硬件和软件功能节。
除非在其他位置具体要求,否则所有程序头的段类型都是可选的。文件的程序头表只能包含与其内容相关的那些元素。
可执行文件和共享库文件都有一个基本地址,该地址是与程序目标文件的内存映像关联的最低虚拟地址。基本地址的其中一种用途是在动态链接过程中重定位程序的内存映像。
可执行文件或共享库文件的基本地址是在执行过程中通过以下三个值计算得出的:内存装入地址、最大页面大小和程序可装入段的最低虚拟地址。程序头中的虚拟地址可能并不表示程序内存映像的实际虚拟地址。 请参见程序装入(特定于处理器)。
要计算基本地址,首先需要确定与 PT_LOAD 段的最低 p_vaddr 值关联的内存地址。然后,将内存地址截断为最大页面大小的最接近倍数,从而获取基本地址。根据装入内存的文件的类型,内存地址可能与 p_vaddr 值不匹配。
系统要装入的程序必须至少包含一个可装入段,即使文件格式并不要求此限制也是如此。系统创建可装入段的内存映像时,将会授予如 p_flags 成员中所指定的访问权限。PF_MASKPROC 掩码中包括的所有位都保留用于特定于处理器的语义。
表 7–26 ELF 段标志
名称 |
值 |
含义 |
---|---|---|
PF_X |
0x1 |
执行 |
PF_W |
0x2 |
写 |
PF_R |
0x4 |
读 |
PF_MASKPROC |
0xf0000000 |
未指定 |
如果权限位是 0,则会拒绝该位的访问类型。实际内存权限取决于内存管理单元,该单元可随系统的不同而变化。尽管所有标志组合均有效,但系统仍可授予比请求更多的访问权限。不过,如果不显式指定写权限,则段在任何情况下都不会具有该权限。下表列出了确切的标志解释及允许的标志解释。
表 7–27 ELF 段权限
标志 |
值 |
确切解释 |
允许解释 |
---|---|---|---|
无 |
0 |
拒绝所有访问 |
拒绝所有访问 |
PF_X |
1 |
仅执行 |
读、执行 |
PF_W |
2 |
只写 |
读、写、执行 |
PF_W + PF_X |
3 |
写、执行 |
读、写、执行 |
PF_R |
4 |
只读 |
读、执行 |
PF_R + PF_X |
5 |
读、执行 |
读、执行 |
PF_R + PF_W |
6 |
读、写 |
读、写、执行 |
PF_R + PF_W + PF_X |
7 |
读、写、执行 |
读、写、执行 |
例如,典型的文本段具有读和执行权限,但没有写权限。数据段通常具有读、写和执行权限。
目标文件段由一节或多节组成,但此事实对程序头是透明的。另外,无论文件段包含一节还是包含多节,对程序装入都没有实际意义。但是,必须存在各种数据以便执行程序、进行动态链接等操作。下图使用一般术语说明了段内容。段中各节的顺序和成员关系可能会有所变化。
文本段包含只读指令和数据。数据段包含可写数据和指令。有关所有特殊节的列表,请参见表 7–10。
PT_DYNAMIC 程序头元素指向 .dynamic 节。.got 和 .plt 节还包含与位置无关的代码和动态链接的相关信息。
.plt 可以位于文本或数据段中,具体取决于处理器。 有关详细消息,请参见全局偏移表(特定于处理器)和过程链接表(特定于处理器)。
类型为 SHT_NOBITS 的节不会占用文件空间,但却可构成段的内存映像。通常,这些未初始化的数据驻留在段尾,从而使 p_memsz 大于关联程序头元素中的 p_filesz。
系统创建或扩充进程映像时,系统会以逻辑方式将文件的段复制到虚拟内存段。系统以物理方式读取文件的时间和可能性取决于程序的执行行为、系统负载等。
除非进程在执行过程中引用了逻辑页,否则进程不需要物理页。进程通常会保留许多页面不对其进行引用。因此,延迟物理读取可以提高系统性能。要实际达到这种效率,可执行文件和共享库文件必须具有文件偏移和虚拟地址同余(以页面大小为模数)的段映像。
32 位段的虚拟地址和文件偏移对模数 64 K (0x10000) 同余。64 位段的虚拟地址和文件偏移对模数 1 MB (0x100000) 同余。通过将各段与最大页面大小对齐,无论物理页大小如何,文件都适合进行换页。
缺省情况下,64 位 SPARC 程序与 0x100000000 的起始地址链接。整个程序位于 4 GB 上的地址空间内,包括其文本、数据、堆、栈和共享库依赖性。这有助于确保 64 位程序正确,因为如果程序截断其任何指针,则程序在其最低有效的 4 GB 地址空间中将出现错误。尽管 64 位程序在 4 GB 上的地址空间内进行链接,但仍可以使用 mapfile 和链接编辑器的 -M 选项,链接 4 GB 以下的地址空间内的程序。 请参见 /usr/lib/ld/sparcv9/map.below4G。
下图显示了 SPARC 版本的可执行文件。
下表定义了上图中可装入段的各元素。
表 7–28 SPARC: ELF 程序头段(64 K 对齐)
成员 |
文本 |
数据 |
---|---|---|
p_type |
PT_LOAD |
PT_LOAD |
p_offset |
0x0 |
0x4000 |
p_vaddr |
0x10000 |
0x24000 |
p_paddr |
未指定 |
未指定 |
p_filesize |
0x3a82 |
0x4f5 |
p_memsz |
0x3a82 |
0x10a4 |
p_flags |
PF_R + PF_X |
PF_R + PF_W + PF_X |
p_align |
0x10000 |
0x10000 |
下图显示了 x86 版本的可执行文件。
下表定义了上图中可装入段的各元素。
表 7–29 32 位 x86: ELF 程序头段(64 K 对齐)
成员 |
文本 |
数据 |
---|---|---|
p_type |
PT_LOAD |
PT_LOAD |
p_offset |
0x0 |
0x4000 |
p_vaddr |
0x8050000 |
0x8064000 |
p_paddr |
未指定 |
未指定 |
p_filesize |
0x32fd |
0x3a0 |
p_memsz |
0x32fd |
0xdc4 |
p_flags |
PF_R + PF_X |
PF_R + PF_W + PF_X |
p_align |
0x10000 |
0x10000 |
此示例的文件偏移和虚拟地址以文本和数据的最大页面大小为模数同余。根据页面大小和文件系统块大小,最多可有四个文件页包含混合文本或数据。
第一个文本页包含 ELF 头、程序头表和其他信息。
最后一个文本页包含数据起始部分的副本。
第一个数据页包含文本结尾的副本。
最后一个数据页可以包含与运行的进程无关的文件信息。从逻辑上而言,系统会强制执行内存权限,如同每个段是完整而独立的一样。为确保地址空间中的每个逻辑页都具有单独一组权限,各段的地址会进行调整。在前面的示例中,包含文本结尾和数据起始部分的文件区域映射了两次:一次映射到文本的虚拟地址,另一次映射到数据的与之不同的虚拟地址。
前面的示例反映了对其文本段取整的典型的 Solaris 系统二进制文件。
数据段结尾要求对未初始化的数据进行特殊处理,系统将其定义为从零值开始。如果文件的最后一个数据页包含不属于逻辑内存页的信息,则必须将无关数据设置为零,而不是设置为可执行文件的未知内容。
其他三个页面中的混合内容在逻辑上不属于进程映像。没有指定系统是否会清除这些混合内容。以下各图中显示了此程序的内存映像,假定页面大小为 4 KB (0x1000)。为简单起见,这些图仅对一种页面大小进行说明。
可执行文件和共享库在段装入的某个方面有所不同。可执行文件段通常包含绝对代码。为使进程正确执行,段必须位于用于创建可执行文件的虚拟地址处。系统会使用未更改的 p_vaddr 值作为虚拟地址。
另一方面,共享库段通常包含与位置无关的代码。使用此代码,段的虚拟地址在不同进程之间会进行更改,而不会使执行行为无效。
尽管系统会为各个进程选择虚拟地址,但仍会保持各段之间的相对位置。由于与位置无关的代码在各段之间使用相对地址,因此内存中虚拟地址之间的差值必须与文件中虚拟地址之间的差值匹配。
以下各表显示针对多个进程可能指定的共享库虚拟地址,从而说明了固定的相对位置。此外,这些表中还包括基本地址计算。
表 7–30 32 位 SPARC: ELF 共享库段地址示例
源 |
文本 |
数据 |
基本地址 |
---|---|---|---|
文件 |
0x0 |
0x4000 |
0x0 |
进程 1 |
0xc0000000 |
0xc0024000 |
0xc0000000 |
进程 2 |
0xc0010000 |
0xc0034000 |
0xc0010000 |
进程 3 |
0xd0020000 |
0xd0024000 |
0xd0020000 |
进程 4 |
0xd0030000 |
0xd0034000 |
0xd0030000 |
表 7–31 32 位 x86: ELF 共享库段地址示例
源 |
文本 |
数据 |
基本地址 |
---|---|---|---|
文件 |
0x0 |
0x4000 |
0x0 |
进程 1 |
0x8000000 |
0x8004000 |
0x80000000 |
进程 2 |
0x80081000 |
0x80085000 |
0x80081000 |
进程 3 |
0x900c0000 |
0x900c4000 |
0x900c0000 |
进程 4 |
0x900c6000 |
0x900ca000 |
0x900c6000 |
启动动态链接的动态可执行文件或共享库可以包含一个 PT_INTERP 程序头元素。在 exec(2) 过程中,系统将从 PT_INTERP 段检索路径名,并通过解释程序文件段创建初始进程映像。解释程序负责从系统接收控制并为应用程序提供环境。
在 Solaris OS 中,解释程序称为运行时链接程序,即 ld.so.1(1)。
创建启动动态链接的动态库时,链接编辑器将向可执行文件中添加一个类型为 PT_INTERP 的程序头元素。该元素指示系统将运行时链接程序作为程序的解释程序进行调用。exec(2) 和运行时链接程序可进行协作,以便为程序创建进程映像。
链接编辑器可为可执行文件和共享库文件构造协助链接程序运行的各种数据。这些数据位于可装入段中,从而使数据在执行过程中可用。这些段包括:
类型为 SHT_DYNAMIC 的 .dynamic 节,其中包含各种数据。位于该节起始位置的结构包含其他动态链接信息的地址。
类型为 SHT_PROGBITS 的 .got 和 .plt 节,其中分别包含以下两个表:全局偏移表和过程链接表。以下各节说明了运行时链接程序如何使用和更改这些表,以便为目标文件创建内存映像。
共享库可以占用虚拟内存地址,这些虚拟内存地址与文件的程序头表中记录的地址不同。运行时链接程序会重定位内存映像,从而在应用程序获取控制权之前更新绝对地址。
如果目标文件参与动态链接,则其程序头表将包含一个类型为 PT_DYNAMIC 的元素。此段包含 .dynamic 节。特殊符号 _DYNAMIC 用于标记包含以下结构的数组的节。 请参见sys/link.h。
typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; Elf32_Off d_off; } d_un; } Elf32_Dyn; typedef struct { Elf64_Xword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn;
对于此类型的每个目标文件,d_tag 将控制 d_un 的解释。
这些目标文件表示具有各种解释的整数值。
这些目标文件表示程序虚拟地址。在执行过程中,文件虚拟地址可能与内存虚拟地址不匹配。对动态结构中包含的地址进行解释时,运行时链接程序会根据原始文件值和内存基本地址来计算实际地址。为确保一致性,文件不应包含用于更正动态结构中的地址的重定位项。
除两个特殊兼容性范围中的那些标记外,每个动态标记的值都可确定 d_un 联合的解释。借助此约定,外部工具可进行更简单的动态标记解释。值为偶数的标记表示使用 d_ptr 的动态节项。值为奇数的标记表示使用 d_val 的动态节项,也即是该标记既不使用 d_ptr,也不使用 d_val。值小于特定值 DT_ENCODING 的标记以及值位于 DT_HIOS 和 DT_LOPROC 之间的标记不遵循这些规则。
下表概述了可执行文件和共享库文件的标记要求。如果某标记带有强制标志,则动态链接数组必须包含此类型的项。同样,可选表示该标记的项可以出现但不是必需的。
表 7–32 ELF 动态数组标记
名称 |
值 |
d_un |
可执行文件 |
共享库文件 |
---|---|---|---|---|
DT_NULL |
0 |
忽略 |
强制 |
强制 |
DT_NEEDED |
1 |
d_val |
可选 |
可选 |
DT_PLTRELSZ |
2 |
d_val |
可选 |
可选 |
DT_PLTGOT |
3 |
d_ptr |
可选 |
可选 |
DT_HASH |
4 |
d_ptr |
强制 |
强制 |
DT_STRTAB |
5 |
d_ptr |
强制 |
强制 |
DT_SYMTAB |
6 |
d_ptr |
强制 |
强制 |
DT_RELA |
7 |
d_ptr |
强制 |
可选 |
DT_RELASZ |
8 |
d_val |
强制 |
可选 |
DT_RELAENT |
9 |
d_val |
强制 |
可选 |
DT_STRSZ |
10 |
d_val |
强制 |
强制 |
DT_SYMENT |
11 |
d_val |
强制 |
强制 |
DT_INIT |
12 |
d_ptr |
可选 |
可选 |
DT_FINI |
13 |
d_ptr |
可选 |
可选 |
DT_SONAME |
14 |
d_val |
忽略 |
可选 |
DT_RPATH |
15 |
d_val |
可选 |
可选 |
DT_SYMBOLIC |
16 |
忽略 |
忽略 |
可选 |
DT_REL |
17 |
d_ptr |
强制 |
可选 |
DT_RELSZ |
18 |
d_val |
强制 |
可选 |
DT_RELENT |
19 |
d_val |
强制 |
可选 |
DT_PLTREL |
20 |
d_val |
可选 |
可选 |
DT_DEBUG |
21 |
d_ptr |
可选 |
忽略 |
DT_TEXTREL |
22 |
忽略 |
可选 |
可选 |
DT_JMPREL |
23 |
d_ptr |
可选 |
可选 |
DT_BIND_NOW |
24 |
忽略 |
可选 |
可选 |
DT_INIT_ARRAY |
25 |
d_ptr |
可选 |
可选 |
DT_FINI_ARRAY |
26 |
d_ptr |
可选 |
可选 |
DT_INIT_ARRAYSZ |
27 |
d_val |
可选 |
可选 |
DT_FINI_ARRAYSZ |
28 |
d_val |
可选 |
可选 |
DT_RUNPATH |
29 |
d_val |
可选 |
可选 |
DT_FLAGS |
30 |
d_val |
可选 |
可选 |
DT_ENCODING |
32 |
未指定 |
未指定 |
未指定 |
DT_PREINIT_ARRAY |
32 |
d_ptr |
可选 |
忽略 |
DT_PREINIT_ARRAYSZ |
33 |
d_val |
可选 |
忽略 |
DT_LOOS |
0x6000000d |
未指定 |
未指定 |
未指定 |
DT_SUNW_RTLDINF |
0x6000000e |
d_ptr |
可选 |
可选 |
DT_HIOS |
0x6ffff000 |
未指定 |
未指定 |
未指定 |
DT_VALRNGLO |
0x6ffffd00 |
未指定 |
未指定 |
未指定 |
DT_CHECKSUM |
0x6ffffdf8 |
d_val |
可选 |
可选 |
DT_PLTPADSZ |
0x6ffffdf9 |
d_val |
可选 |
可选 |
DT_MOVEENT |
0x6ffffdfa |
d_val |
可选 |
可选 |
DT_MOVESZ |
0x6ffffdfb |
d_val |
可选 |
可选 |
DT_FEATURE_1 |
0x6ffffdfc |
d_val |
可选 |
可选 |
DT_POSFLAG_1 |
0x6ffffdfd |
d_val |
可选 |
可选 |
DT_SYMINSZ |
0x6ffffdfe |
d_val |
可选 |
可选 |
DT_SYMINENT |
0x6ffffdff |
d_val |
可选 |
可选 |
DT_VALRNGHI |
0x6ffffdff |
未指定 |
未指定 |
未指定 |
DT_ADDRRNGLO |
0x6ffffe00 |
未指定 |
未指定 |
未指定 |
DT_CONFIG |
0x6ffffefa |
d_ptr |
可选 |
可选 |
DT_DEPAUDIT |
0x6ffffefb |
d_ptr |
可选 |
可选 |
DT_AUDIT |
0x6ffffefc |
d_ptr |
可选 |
可选 |
DT_PLTPAD |
0x6ffffefd |
d_ptr |
可选 |
可选 |
DT_MOVETAB |
0x6ffffefe |
d_ptr |
可选 |
可选 |
DT_SYMINFO |
0x6ffffeff |
d_ptr |
可选 |
可选 |
DT_ADDRRNGHI |
0x6ffffeff |
未指定 |
未指定 |
未指定 |
DT_RELACOUNT |
0x6ffffff9 |
d_val |
可选 |
可选 |
DT_RELCOUNT |
0x6ffffffa |
d_val |
可选 |
可选 |
DT_FLAGS_1 |
0x6ffffffb |
d_val |
可选 |
可选 |
DT_VERDEF |
0x6ffffffc |
d_ptr |
可选 |
可选 |
DT_VERDEFNUM |
0x6ffffffd |
d_val |
可选 |
可选 |
DT_VERNEED |
0x6ffffffe |
d_ptr |
可选 |
可选 |
DT_VERNEEDNUM |
0x6fffffff |
d_val |
可选 |
可选 |
DT_LOPROC |
0x70000000 |
未指定 |
未指定 |
未指定 |
DT_SPARC_REGISTER |
0x70000001 |
d_val |
可选 |
可选 |
DT_AUXILIARY |
0x7ffffffd |
d_val |
未指定 |
可选 |
DT_USED |
0x7ffffffe |
d_val |
可选 |
可选 |
DT_FILTER |
0x7fffffff |
d_val |
未指定 |
可选 |
DT_HIPROC |
0x7fffffff |
未指定 |
未指定 |
未指定 |
标记 _DYNAMIC 数组的结尾。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于提供所需依赖项的名称。动态数组可以包含多个此类型的项。尽管这些项与其他类型的项的关系不重要,但其相对顺序却很重要。 请参见共享库依赖项。
与过程链接表关联的重定位项的总大小(以字节为单位)。 请参见过程链接表(特定于处理器)。
与过程链接表或全局偏移表关联的地址。 请参见过程链接表(特定于处理器)和全局偏移表(特定于处理器)。
符号散列表的地址。该表引用 DT_SYMTAB 元素指示的符号表。 请参见散列表节。
字符串表的地址。运行时链接程序所需的符号名称、依赖项名称和其他字符串位于该表中。 请参见字符串表节。
符号表的地址。 请参见符号表节。
重定位表的地址。 请参见重定位节。
目标文件可以有多个重定位节。为可执行文件或共享库文件创建重定位表时,链接编辑器会连接这些节以形成一个表。尽管这些节在目标文件中可以保持独立,但运行时链接程序将看到一个表。运行时链接程序为可执行文件创建进程映像或将共享库添加到进程映像中时,运行时链接程序将会读取该重定位表并执行关联操作。
此元素要求同时存在 DT_RELASZ 和 DT_RELAENT 元素。如果文件必须重定位,则可以存在 DT_RELA 或 DT_REL。
DT_RELA 重定位表的总大小(以字节为单位)。
DT_RELA 重定位项的大小(以字节为单位)。
DT_STRTAB 字符串表的总大小(以字节为单位)。
DT_SYMTAB 符号项的大小(以字节为单位)。
初始化函数的地址。 请参见初始化和终止节。
终止函数的地址。 请参见初始化和终止节。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于标识共享库的名称。 请参见记录共享库名称。
以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。此元素的用途已被 DT_RUNPATH 取代。 请参见运行时链接程序搜索的目录。
表示目标文件包含在其链接编辑过程中应用的符号绑定。此元素的用途已被 DF_SYMBOLIC 标志取代。 请参见使用 -B symbolic。
与 DT_RELA 类似,但其表中包含隐式加数。此元素要求同时存在 DT_RELSZ 和 DT_RELENT 元素。
DT_REL 重定位表的总大小(以字节为单位)。
DT_REL 重定位项的大小(以字节为单位)。
表示过程链接表指向的重定位项的类型(DT_REL 或 DT_RELA)。过程链接表中的所有重定位都必须使用相同的重定位项。 请参见过程链接表(特定于处理器)。此元素要求同时存在 DT_JMPREL 元素。
用于调试。
表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。此元素的用途已被 DF_TEXTREL 标志取代。 请参见与位置无关的代码。
与过程链接表单独关联的重定位项的地址。 请参见过程链接表(特定于处理器)。通过分隔这些重定位项,运行时链接程序可在装入启用了延迟绑定的目标文件时忽略这些项。此元素要求同时存在 DT_PLTRELSZ 和 DT_PLTREL 元素。
应用于紧邻的 DT_ 元素的各种状态标志。 请参见表 7–35。
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。此元素的用途已被 DF_BIND_NOW 标志取代。 请参见执行重定位的时间。
初始化函数的指针数组的地址。此元素要求同时存在 DT_INIT_ARRAYSZ 元素。 请参见初始化和终止节。
终止函数的指针数组的地址。此元素要求同时存在 DT_FINI_ARRAYSZ 元素。 请参见初始化和终止节。
DT_INIT_ARRAY 数组的总大小(以字节为单位)。
DT_FINI_ARRAY 数组的总大小(以字节为单位)。
以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。 请参见运行时链接程序搜索的目录。
特定于此目标文件的标志值。 请参见表 7–33。
大于或等于 DT_ENCODING 且小于或等于 DT_HIOS 的动态标记值,遵循 d_un 联合的解释规则。
预初始化函数的指针数组的 地址 。此元素要求同时存在 DT_PREINIT_ARRAYSZ 元素。仅在可执行文件中处理该数组。如果该数组包含在共享库中,则会被忽略。 请参见初始化和终止节。
DT_PREINIT_ARRAY 数组的总大小(以字节为单位)。
此范围内包含的值保留用于特定于操作系统的语义。所有这类值都遵循 d_un 联合的解释规则。
保留供运行时链接程序内部使用。
符号信息表的地址。此元素要求同时存在 DT_SYMINENT 和 DT_SYMINSZ 元素。 请参见Syminfo 表节。
DT_SYMINFO 信息项的大小(以字节为单位)。
DT_SYMINFO 表的总大小(以字节为单位)。
版本定义表的地址。该表中的元素包含字符串表 DT_STRTAB 的索引。此元素要求同时存在 DT_VERDEFNUM 元素。 请参见版本定义节。
DT_VERDEF 表中的项数。
版本依赖性表的地址。该表中的元素包含字符串表 DT_STRTAB 的索引。此元素要求同时存在 DT_VERNEEDNUM 元素。 请参见版本依赖性节。
DT_VERNEEDNUM 表中的项数。
表示 RELATIVE 重定位计数,该计数是通过串联所有 Elf32_Rela 或 Elf64_Rela 重定位项生成的。 请参见组合重定位节。
表示 RELATIVE 重定位计数,该计数是通过串联所有 Elf32_Rel 重定位项生成的。 请参见组合重定位节。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于指定一个或多个辅助 filtee。 请参见生成辅助过滤器。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于指定一个或多个标准 filtee。 请参见生成标准过滤器。
目标文件中选定的节的简单校验和。 请参见 gelf_checksum(3ELF)。
DT_MOVETAB 移动项的大小(以字节为单位)。
DT_MOVETAB 表的总大小(以字节为单位)。
移动表的地址。此元素要求同时存在 DT_MOVEENT 和 DT_MOVESZ 元素。请参见移动节。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义配置文件。该配置文件仅在可执行文件中有意义,并且通常是特定于此目标文件的。请参见配置缺省搜索路径。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口。
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口。
特定于此目标文件的标志值。请参见表 7–34。
特定于此目标文件的功能值。请参见表 7–36。
此范围内包含的值使用动态结构的 d_un.d_val 字段。
此范围内包含的值使用动态结构的 d_un.d_ptr 字段。如果生成 ELF 目标文件后对其进行了任何调整,则必须相应地更新这些项。
DT_SYMTAB 符号表中 STT_SPARC_REGISTER 符号的索引。该符号表中的每个 STT_SPARC_REGISTER 符号都存在一个动态项。请参见寄存器符号。
此范围内包含的值保留用于特定于处理器的语义。
除动态数组结尾的 DT_NULL 元素以及 DT_NEEDED 和 DT_POSFLAG_1 元素的相对顺序以外,各项可以采用任何顺序显示。未显示在该表中的标记值为保留值。
表 7–33 ELF 动态标志 DT_FLAGS
名称 |
值 |
含义 |
---|---|---|
DF_ORIGIN |
0x1 |
要求 $ORIGIN 处理 |
DF_SYMBOLIC |
0x2 |
要求符号解析 |
DF_TEXTREL |
0x4 |
存在文本重定位项 |
DF_BIND_NOW |
0x8 |
要求非延迟绑定 |
DF_STATIC_TLS |
0x10 |
目标文件使用静态线程局部存储方案 |
表示目标文件要求 $ORIGIN 处理。请参见查找关联的依赖项。
表示目标文件包含在其链接编辑过程中应用的符号绑定。请参见使用 -B symbolic。
表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。请参见与位置无关的代码。
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。请参见执行重定位的时间。
表示目标文件包含使用静态线程局部存储方案的代码。在通过 dlopen(3C) 或延迟装入动态装入的目标文件中,不能使用静态线程局部存储。由于此限制,因此链接编辑器不支持创建要求静态线程局部存储的共享库。
名称 |
值 |
含义 |
---|---|---|
DF_1_NOW |
0x1 |
执行完整的重定位处理。 |
DF_1_GLOBAL |
0x2 |
未使用。 |
DF_1_GROUP |
0x4 |
表示目标文件是组的成员。 |
DF_1_NODELETE |
0x8 |
不能从进程中删除目标文件。 |
DF_1_LOADFLTR |
0x10 |
确保立即装入 filtee。 |
DF_1_INITFIRST |
0x20 |
首先进行目标文件初始化。 |
DF_1_NOOPEN |
0x40 |
目标文件不能用于 dlopen(3C)。 |
DF_1_ORIGIN |
0x80 |
要求 $ORIGIN 处理。 |
DF_1_DIRECT |
0x100 |
已启用直接绑定。 |
DF_1_INTERPOSE |
0x400 |
目标文件是插入项。 |
DF_1_NODEFLIB |
0x800 |
忽略缺省的库搜索路径。 |
DF_1_NODUMP |
0x1000 |
不能使用 dldump(3C) 转储目标文件。 |
DF_1_CONFALT |
0x2000 |
目标文件是配置替代项。 |
DF_1_ENDFILTEE |
0x4000 |
filtee 终止过滤器搜索。 |
DF_1_DISPRELDNE |
0x8000 |
已执行位移重定位。 |
DF_1_DISPRELPND |
0x10000 |
位移重定位暂挂。 |
DF_1_NODIRECT |
0x20000 |
目标文件包含非直接绑定。 |
DF_1_IGNMULDEF |
0x40000 |
内部使用。 |
DF_1_NOKSYMS |
0x80000 |
内部使用。 |
DF_1_NORELOC |
0x400000 |
内部使用。 |
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此标志优先于使用延迟绑定的指令。请参见执行重定位的时间。
表示目标文件是组的成员。此标志通过链接编辑器的 -B group 选项记录在目标文件中。请参见目标文件分层结构。
表示不能从进程中删除目标文件。如果使用 dlopen(3C) 通过直接或依赖性方式将目标文件装入进程,则无法使用 dlclose(3C) 卸载该目标文件。此标志通过使用链接编辑器的 -z nodelete 选项记录在目标文件中。
仅对过滤器有意义。表示立即处理所有关联 filtee 。此标志通过使用链接编辑器的 -z loadfltr 选项记录在目标文件中。请参见filtee 处理。
表示在装入其他任何目标文件之前首先运行此目标文件的初始化节。此标志仅适用于专用系统库,并通过使用链接编辑器的 -z initfirst 选项记录在目标文件中。
表示无法使用 dlopen(3C) 将目标文件添加到运行的进程。此标志通过使用链接编辑器的 -z nodlopen 选项记录在目标文件中。
表示目标文件要求 $ORIGIN 处理。请参见查找关联的依赖项。
表示目标文件应使用直接绑定信息。请参见直接绑定。
表示目标文件符号表将在除主装入目标文件(通常为可执行文件)外的所有符号之前插入。此标志通过使用链接编辑器的 -z interpose 选项进行记录。请参见直接绑定。
表示此目标文件的依赖性搜索会忽略所有缺省的库搜索路径。此标志通过使用链接编辑器的 -z nodefaultlib 选项记录在目标文件中。请参见运行时链接程序搜索的目录。
表示此目标文件不通过 dldump(3C) 进行转储。此选项的替代选项包括没有重定位项的目标文件,这些目标文件可能会包括在使用 crle(1) 生成的替代目标文件中。此标志通过使用链接编辑器的 -z nodump 选项记录在目标文件中。
将此目标文件标识为 crle(1) 生成的配置替代目标文件。此标志可触发运行时链接程序来搜索配置文件 $ORIGIN/ld.config.app-name。
仅对 filtee 有意义。终止对其他任何 filtee 的过滤器搜索。此标志通过使用链接编辑器的 -z endfiltee 选项记录在目标文件中。请参见减少 filtee 搜索。
表示此目标文件应用了位移重定位。由于位移重定位记录在应用重定位后已被废弃,因此此目标文件中将不再存在这些记录。请参见位移重定位。
表示此目标文件暂挂了位移重定位。由于此目标文件中存在位移重定位,因此可在运行时完成重定位。请参见位移重定位。
表示此目标文件包含无法直接绑定的符号。请参见定义其他符号。
保留供内核运行时链接程序内部使用。
保留供内核运行时链接程序内部使用。
名称 |
值 |
含义 |
---|---|---|
DF_P1_LAZYLOAD |
0x1 |
标识延迟装入的依赖项。 |
DF_P1_GROUPPERM |
0x2 |
标识组依赖性。 |
将以下 DT_NEEDED 项标识为要延迟装入的目标文件。此标志通过使用链接编辑器的 -z lazyload 选项记录在目标文件中。请参见延迟装入动态依赖项。
将以下 DT_NEEDED 项标识为要作为组装入的目标文件。此标志通过使用链接编辑器的 -z groupperm 选项记录在目标文件中。请参见隔离组。
名称 |
值 |
含义 |
---|---|---|
DTF_1_PARINIT |
0x1 |
需要部分初始化。 |
DTF_1_CONFEXP |
0x2 |
需要配置文件。 |
表示目标文件需要部分初始化。请参见移动节。
将此目标文件标识为 crle(1) 生成的配置替代目标文件。此标志可触发运行时链接程序来搜索配置文件 $ORIGIN/ld.config.app-name。此标志的效果与 DF_1_CONFALT 相同。
通常,与位置无关的代码不能包含绝对虚拟地址。全局偏移表在专用数据中包含绝对地址。因此这些地址可用,并且不会破坏程序文本的位置独立性和共享性。程序使用与位置无关的地址来引用其 GOT 并提取绝对值。此方法可将与位置无关的引用重定向到绝对位置。
最初,GOT 包含其重定位项所需的信息。系统为可装入目标文件创建内存段后,运行时链接程序将会处理这些重定位项。某些重定位项的类型可以为 R_xxxx_GLOB_DAT,用于引用 GOT。
运行时链接程序可确定关联符号值,计算其绝对地址以及将相应的内存表各项设置为正确的值。尽管链接编辑器创建目标文件时绝对地址未知,但运行时链接程序知道所有内存段的地址,因此可以计算其中包含的符号的绝对地址。
如果程序要求直接访问某符号的绝对地址,则该符号将具有一个 GOT 项。由于可执行文件和共享库具有不同的 GOT,因此一个符号的地址可以出现在多个表中。运行时链接程序在向进程映像中的任何代码授予控制权之前,将首先处理所有的 GOT 重定位项。此处理操作可确保绝对地址在执行过程中可用。
表项零保留用于存储动态结构(使用符号 _DYNAMIC 引用)的地址。使用此符号,运行时链接程序等程序可在尚未处理其重定位项的情况下查找各自的动态结构。此方法对于运行时链接程序尤其重要,因为它必须对自身进行初始化,而不依赖于其他程序来重定位其内存映像。
系统可为不同程序中的同一共享库选择不同的内存段地址。系统甚至可以为同一程序的不同执行方式选择不同的库地址。但是,一旦建立进程映像,内存段即不会更改各地址。只要存在进程,其内存段就会位于固定的虚拟地址。
GOT 的格式和解释是特定于处理器的。符号 _GLOBAL_OFFSET_TABLE_ 可用于访问该表。此符号可以位于 .got 节的中间,以提供地址数组的负下标和非负下标。对于 32 位代码,符号类型是 Elf32_Addr 数组;对于 64 位代码,符号类型是 Elf64_Addr 数组:
extern Elf32_Addr _GLOBAL_OFFSET_TABLE_[]; extern Elf64_Addr _GLOBAL_OFFSET_TABLE_[];
全局偏移表可将与位置无关的地址计算结果转换为绝对位置。同样,过程链接表也可将与位置无关的函数调用转换为绝对位置。链接编辑器无法解析不同动态库之间的执行传输(如函数调用)。因此,链接编辑器会安排程序将控制权转移给过程链接表中的各项。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享库文件包含不同的过程链接表。
对于 32 位 SPARC 动态库,过程链接表位于专用数据中。运行时链接程序可确定目标的绝对地址,并相应地修改过程链接表的内存映像。
过程链接表的前四项是保留项。尽管表 7–37 中显示了过程链接表的示例,但未指定这些项的原始内容。该表中的每一项都占用 3 个字(12 字节),并且表的最后一项后跟 nop 指令。
重定位表与过程链接表关联。_DYNAMIC 数组中的 DT_JMP_REL 项指定了第一个重定位项的位置。对于非保留的过程链接表的每一项,重定位表都包含相同顺序的对应项。所有这些项的重定位类型均为 R_SPARC_JMP_SLOT。重定位偏移可指定关联的过程链接表项的第一个字节的地址。符号表索引会指向相应的符号。
为说明过程链接表,表 7–37 显示了四项。其中,前两项是初始保留项。第三项是对 name101 的调用。第四项是对 name102 的调用。此示例假定对应 name102 的项是表的最后一项。在该最后一项的后面是 nop 指令。左列显示了进行动态链接之前目标文件中的指令。右列说明了运行时链接程序会用于修复过程链接表各项的可能的指令序列。
表 7–37 32 位 SPARC: 过程链接表示例
以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。
初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了可将控制权转移给运行时链接程序自己的其中一个例程。运行时链接程序还会在第二项中存储一个字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用方。
过程链接表的其他所有项最初都会传输给第一项。因此,运行时链接程序会在首次执行表项时获取控制权。例如,该程序会调用 name101,以将控制权转移给标签 .PLT101。
sethi 指令可分别计算当前过程链接表各项和初始过程链接表各项(.PLT101 和 .PLT0)之间的距离。该值会占用 %g1 寄存器最高有效的 22 位。
接下来,ba,a 指令会跳至 .PLT0 以建立栈帧,然后调用运行时链接程序。
通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。
通过将 %g1 值移位并除以过程链接表各项的大小,运行时链接程序可计算对应 name101 的重定位项的索引。重定位项 101 的类型为 R_SPARC_JMP_SLOT。此重定位偏移可指定 .PLT101 的地址,并且其符号表索引会指向 name101。因此,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。
运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。
要使代码可重复执行,可按特定顺序更改过程链接表的指令。如果运行时链接程序在修复函数的过程链接表项时收到信号,则信号处理代码必须能够调用具有可预测的正确结果的原始函数。
运行时链接程序更改三个字才能转换一项。对于指令执行,运行时链接程序只能自动更新一个字。因此,通过以相反顺序更新每个字可实现重复执行。如果仅在最后一个修补程序之前调用可重复执行的函数,则运行时链接程序会再次获取控制权。尽管两次调用运行时链接程序修改的过程链接表项都相同,但这些更改不会相互干扰。
过程链接表项的第一条 sethi 指令可以填充 jmp1 指令的延迟插槽。尽管 sethi 会更改 %g1 寄存器的值,但可以安全废弃以前的内容。
转换之后,过程链接表的最后一项 .PLT102 需要一条延迟指令用于其 jmp1。所需的结尾 nop 将填充此延迟插槽。
为 .PLT101 和 .PLT102 显示的不同指令序列说明了如何优化关联目标的更新。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。
对于 64 位 SPARC 动态库,过程链接表位于专用数据中。运行时链接程序可确定目标的绝对地址,并相应地修改过程链接表的内存映像。
过程链接表的前四项是保留项。尽管表 7–38 中显示了过程链接表的示例,但未指定这些项的原始内容。在该表中,前 32,768 项的每一项都占用 8 个字(32 字节),并且必须与 32 字节边界对齐。整个表必须与 256 字节边界对齐。如果所需项数大于 32,768,则其余各项由 6 个字(24 字节)和 1 个指针(8 字节)组成。指令以 160 项并后跟 160 个指针的块方式收集到一起。最后一组项和指针可以包含的项数少于 160。不需要进行填充。
数字 32,768 和 160 分别基于分支和装入目标文件位移的限制,并且位移会向下舍入以使代码和数据之间的分区落到 256 字节边界上,从而提高高速缓存的性能。
重定位表与过程链接表关联。_DYNAMIC 数组中的 DT_JMP_REL 项指定了第一个重定位项的位置。对于非保留的过程链接表的每一项,重定位表中都包含相同顺序的对应项。所有这些项的重定位类型均为 R_SPARC_JMP_SLOT。对于前 32,767 个插槽,重定位偏移将指定关联过程链接表项的第一个字节的地址,并且加数字段为零。符号表索引会指向相应的符号。对于插槽 32,768 及其之后的插槽,重定位偏移将指定关联指针的第一个字节的地址。加数字段是未重定位的值 -(.PLTN + 4)。符号表索引会指向相应的符号。
为说明过程链接表,表 7–38 显示了若干项。前三项显示了初始保留项。接下来的三项显示了初始的 32,768 个项的示例以及可能的解析格式,这些格式分别应用于目标地址位于项上下 2 GB 的地址空间内、目标地址位于低位的 4 GB 地址空间内或目标地址位与其他任意位置的情形。最后两项显示了后续各项的示例,这些项由指令和指针对组成。左列显示了进行动态链接之前目标文件中的指令。右列说明了运行时链接程序会用于修复过程链接表各项的可能指令序列。
表 7–38 64 位 SPARC: 过程链接表示例
以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。
初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了将控制权转移给运行时链接程序自己的例程。运行时链接程序还会在第三项中存储一个扩展字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用方。
过程链接表的其他所有项最初都会传输给第一项或第二项。这些项将建立栈帧并调用运行时链接程序。
通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。
运行时链接程序会计算表插槽对应的重定位项的索引。
使用索引信息,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。
运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。
要使代码可重复执行,可按特定顺序更改过程链接表的指令。如果运行时链接程序在修复函数的过程链接表项时收到信号,则信号处理代码必须能够调用具有可预测的正确结果的原始函数。
运行时链接程序最多可更改八个字来转换一项。对于指令执行,运行时链接程序只能自动更新一个字。因此,通过以下方法可实现重复执行:首先将 nop 指令覆写为其替换指令,如果使用 64 位存储,则之后还要修补 ba,a 和 sethi。如果仅在最后一个修补程序之前调用可重复执行的函数,则运行时链接程序会再次获取控制权。尽管两次调用运行时链接程序修改的过程链接表项都相同,但这些更改不会相互干扰。
如果更改初始 sethi 指令,则只能将该指令替换为 nop。
按照更改第二种格式的项的方式更改指针是通过使用一个原子的 64 位存储器完成的。
为 .PLT101、.PLT102 和 .PLT103 显示的不同指令序列说明了如何优化关联目标的更新。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。
对于 32 位 x86 动态库,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享库文件包含不同的过程链接表。
表 7–39 32 位 x86: 绝对过程链接表示例
.PLT0: pushl got_plus_4 jmp *got_plus_8 nop; nop nop; nop .PLT1: jmp *name1_in_GOT pushl $offset jmp .PLT0@PC .PLT2: jmp *name2_in_GOT pushl $offset jmp .PLT0@PC |
表 7–40 32 位 x86: 与位置无关的过程链接表示例
.PLT0: pushl 4(%ebx) jmp *8(%ebx) nop; nop nop; nop .PLT1: jmp *name1@GOT(%ebx) pushl $offset jmp .PLT0@PC .PLT2: jmp *name2@GOT(%ebx) pushl $offset jmp .PLT0@PC |
如前面的示例所示,对于绝对代码和与位置无关的代码,过程链接表指令会使用不同的操作数寻址模式。但是,它们的运行时链接程序接口却相同。
以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。
初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。
如果过程链接表与位置无关,则全局偏移表的地址必须位于 %ebx 中。进程映像中的每个共享库文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。因此,调用函数在调用过程链接表项之前,必须首先设置全局偏移表基本寄存器。
例如,该程序会调用 name1,以将控制权转移给标签 .PLT1。
第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表包含以下 pushl 指令的地址,而不是 name1 的实际地址。
该程序将在栈中推送一个重定位偏移 (offset)。该重定位偏移是重定位表中一个 32 位的非负字节偏移。指定的重定位项的类型为 R_386_JMP_SLOT,其偏移指定了前面的 jmp 指令中使用的全局偏移表项。该重定位项还包含符号表索引,以供运行时链接程序用于获取引用的符号 name1。
推送该重定位偏移后,程序将跳至过程链接表中的第一项 .PLT0。pushl 指令会在栈中推送全局偏移表的第二项(got_plus_4 或 4(%ebx))的值,从而为运行时链接程序提供一个字的标识信息。然后,程序将跳至全局偏移表的第三项(got_plus_8 或 8(%ebx))中的地址,以继续跳至运行时链接程序。
运行时链接程序将展开栈、检查指定的重定位项、获取符号的值、在全局偏移项表中存储 name1 的实际地址并跳至目标。
过程链接表项的后续执行结果会直接传输给 name1,而不会再次调用运行时链接程序。位于 .PLT1 的 jmp 指令将跳至 name1,而不是对 pushl 指令失败。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_386_JMP_SLOT 重定位项。
对于 x64 动态库,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享库文件包含不同的过程链接表。
表 7–41 x64: 过程链接表示例
以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。
初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。
进程映像中的每个共享库文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。
例如,该程序会调用 name1,以将控制权转移给标签 .PLT1。
第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表包含以下 pushq 指令的地址,而不是 name1 的实际地址。
该程序将在栈中推送一个重定位索引 (index1)。该重定位索引是重定位表中一个 32 位的非负索引。重定位表由 DT_JUMPREL 动态节项标识。指定的重定位项的类型为 R_AMD64_JMP_SLOT,其偏移指定了前面的 jmp 指令中使用的全局偏移表项。该重定位项还包含符号表索引,以供运行时链接程序用于获取引用的符号 name1。
推送该重定位索引后,程序将跳至过程链接表中的第一项 .PLT0。pushq 指令会在栈中推送全局偏移表的第二项 (GOT+8) 的值,从而为运行时链接程序提供一个字的标识信息。然后,程序将跳至第三个全局偏移表项 (GOT+16) 中的地址,以继续跳至运行时链接程序。
运行时链接程序将展开栈、检查指定的重定位项、获取符号的值、在全局偏移项表中存储 name1 的实际地址并跳至目标。
过程链接表项的后续执行结果会直接传输给 name1,而不会再次调用运行时链接程序。位于 .PLT1 的 jmp 指令将跳至 name1,而不是对 pushq 指令失败。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_AMD64_JMP_SLOT 重定位项。