动态链接
本节介绍用于创建运行程序的目标文件信息和系统操作。此处介绍的大多数信息适用于所有系统。特定于某处理器的信息位于带有相应标记的各节中。
可执行文件和共享目标文件静态表示应用程序。要执行这类程序,系统可使用这些文件创建动态程序表示形式(即进程映像)。进程映像具有包含其文本、数据、栈等内容的段。本节包括以下主要小节。
程序头,其中介绍了直接参与程序执行的目标文件结构。主数据结构是一种程序头表,用于定位文件中的段映像,并包含创建程序内存映像所需的其他信息。
程序装入(特定于处理器),其中介绍了用于将程序装入内存的信息。
运行时链接程序,其中介绍了用于指定和解析进程映像的目标文件之间的符号引用的信息。
程序头
可执行文件或共享目标文件的程序头表是一个结构数组。每种结构都描述了系统准备程序执行所需的段或其他信息。目标文件段包含一个或多个节,如段内容中所述。
程序头仅对可执行文件和共享目标文件有意义。文件使用 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;p_type
此数组元素描述的段类型或解释此数组元素的信息的方式。表 7-25 中指定了类型值及其含义。
p_offset
相对段的第一个字节所在文件的起始位置的偏移。
p_vaddr
段的第一个字节在内存中的虚拟地址。
p_paddr
段在与物理寻址相关的系统中的物理地址。由于此系统忽略了应用程序的物理地址,因此该成员对于可执行文件和共享目标文件具有未指定的内容。
p_filesz
段的文件映像中的字节数,可以为零。
p_memsz
段的内存映像中的字节数,可以为零。
p_flags
与段相关的标志。表 7-26 中指定了类型值及其含义。
p_align
可装入的进程段必须具有 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_SUNW_EH_FRAME
0x6474e550
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
PT_NULL
未使用。没有定义成员值。使用此类型,程序头表可以包含忽略的项。
PT_LOAD
指定可装入段,通过 p_filesz 和 p_memsz 进行描述。文件中的字节会映射到内存段的起始位置。如果段的内存大小 (p_memsz) 大于文件大小 (p_filesz),则将多余字节的值定义为 0这些字节跟在段的已初始化区域后面。文件大小不能大于内存大小。程序头表中的可装入段的各项按升序显示,并基于 p_vaddr 成员进行排列。
PT_DYNAMIC
指定动态链接信息。请参见动态节。
PT_INTERP
指定要作为解释程序调用的以空字符结尾的路径名的位置和大小。对于动态可执行文件,必须设置此类型。此类型可出现在共享目标文件中。此类型不能在一个文件中多次出现。此类型(如果存在)必须位于任何可装入段的各项的前面。有关详细信息,请参见程序的解释程序。
PT_NOTE
指定辅助信息的位置和大小。有关详细信息,请参见注释节。
PT_SHLIB
保留类型,但具有未指定的语义。
PT_PHDR
指定程序头表在文件及程序内存映像中的位置和大小。此段类型不能在一个文件中多次出现。此外,仅当程序头表是程序内存映像的一部分时,才可以出现此段。此类型(如果存在)必须位于任何可装入段的各项的前面。有关详细信息,请参见程序的解释程序。
PT_TLS
指定线程局部存储模板。有关详细信息,请参见线程局部存储节。
PT_LOOS - PT_HIOS
此范围内包含的值保留用于特定于操作系统的语义。
PT_SUNW_UNWIND
此段包含栈扩展表。
PT_SUNW_EH_FRAME
此段包含栈扩展表。PT_SUNW_EH_FRAME 与 PT_SUNW_EH_UNWIND 等效。
PT_LOSUNW - PT_HISUNW
此范围内包含的值(包括这两个值)保留用于特定于 Sun 的语义。
PT_SUNWBSS
与 PT_LOAD 元素相同的属性,用于描述 .SUNW_bss 节。
PT_SUNWSTACK
描述进程栈。只能存在一个 PT_SUNWSTACK 元素。仅访问权限(如 p_flags 字段中所定义)有意义。
PT_SUNWDTRACE
PT_SUNWCAP
指定功能要求。有关详细信息,请参见功能节。
PT_LOPROC - PT_HIPROC
此范围内包含的值(包括这两个值)保留用于特定于处理器的语义。
注 -除非在其他位置具体要求,否则所有程序头的段类型都是可选的。文件的程序头表只能包含与其内容相关的那些元素。
基本地址
可执行文件和共享目标文件都有一个基本地址,该地址是与程序目标文件的内存映像关联的最低虚拟地址。基本地址的其中一种用途是在动态链接过程中重定位程序的内存映像。
可执行文件或共享目标文件的基本地址是在执行过程中通过以下三个值计算得出的:内存装入地址、最大页面大小和程序可装入段的最低虚拟地址。程序头中的虚拟地址可能并不表示程序内存映像的实际虚拟地址。请参见程序装入(特定于处理器)。
要计算基本地址,首先需要确定与 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-8 SPARC: 可执行文件(64 K 对齐)
下表定义了上图中可装入段的各元素。
表 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-9 32 位 x86: 可执行文件(64 K 对齐)
下表定义了上图中可装入段的各元素。
表 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 头、程序头表和其他信息。
最后一个文本页包含数据起始部分的副本。
第一个数据页包含文本结尾的副本。
最后一个数据页可以包含与运行的进程无关的文件信息。从逻辑上而言,系统会强制执行内存权限,如同每个段是完整而独立的一样。为确保地址空间中的每个逻辑页都具有单独一组权限,各段的地址会进行调整。在前面的示例中,包含文本结尾和数据起始部分的文件区域映射了两次:一次映射到文本的虚拟地址,另一次映射到数据的与之不同的虚拟地址。
注 -前面的示例反映了对文本段取整的典型的 Oracle Solaris OS 二进制文件。
数据段结尾要求对未初始化的数据进行特殊处理,系统将其定义为从零值开始。如果文件的最后一个数据页包含不属于逻辑内存页的信息,则必须将无关数据设置为零,而不是设置为可执行文件的未知内容。
其他三个页面中的混合内容在逻辑上不属于进程映像。没有指定系统是否会清除这些混合内容。以下各图中显示了此程序的内存映像,假定页面大小为 4 KB (0x1000)。为简单起见,这些图仅对一种页面大小进行说明。
图 7-10 32 位 SPARC: 进程映像段
图 7-11 x86: 进程映像段
可执行文件和共享目标文件在段装入的某个方面有所不同。可执行文件段通常包含绝对代码。为使进程正确执行,段必须位于用于创建可执行文件的虚拟地址处。系统会使用未更改的 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 段检索路径名,并通过解释程序文件段创建初始进程映像。解释程序负责从系统接收控制并为应用程序提供环境。
在 Oracle Solaris OS 中,解释程序称为运行时链接程序,即 ld.so.1(1)。
运行时链接程序
创建启动动态链接的动态目标文件时,链接编辑器将向可执行文件中添加一个类型为 PT_INTERP 的程序头元素。该元素指示系统将运行时链接程序作为程序的解释程序进行调用。exec(2) 和运行时链接程序进行协作,为程序创建进程映像。
链接编辑器可为可执行文件和共享目标文件构造协助运行时链接程序运行的各种数据。这些数据位于可装入段中,从而使数据在执行过程中可用。这些段包括:
类型为 SHT_DYNAMIC 的 .dynamic 节,其中包含各种数据。位于该节起始位置的结构包含其他动态链接信息的地址。
类型为 SHT_PROGBITS 的 .got 和 .plt 节,其中分别包含以下两个表:全局偏移表和过程链接表。以下各节说明了运行时链接程序如何使用和更改这些表,以便为目标文件创建内存映像。
类型为 SHT_HASH 的 .hash 节,其中包含符号散列表。
共享目标文件可以占用虚拟内存地址,这些虚拟内存地址与文件的程序头表中记录的地址不同。运行时链接程序会重定位内存映像,从而在应用程序获取控制权之前更新绝对地址。
动态节
如果目标文件参与动态链接,则其程序头表将包含一个类型为 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_val
这些目标文件表示具有各种解释的整数值。
d_ptr
这些目标文件表示程序虚拟地址。在执行过程中,文件虚拟地址可能与内存虚拟地址不匹配。对动态结构中包含的地址进行解释时,运行时链接程序会根据原始文件值和内存基本地址来计算实际地址。为确保一致性,文件不应包含用于更正动态结构中的地址的重定位项。
通常,每个动态标记的值决定了 d_un 联合的解释。借助此约定,第三方工具可进行更简单的动态标记解释。值为偶数的标记表示使用 d_ptr 的动态节项。值为奇数的标记表示使用 d_val 的动态节项,或该标记既不使用 d_ptr,也不使用 d_val。值包含在以下特殊兼容性范围中的标记不遵循这些规则。第三方工具必须逐项明确处理这些例外范围。
值小于特定值 DT_ENCODING 的标记。
值位于 DT_LOOS 和 DT_SUNW_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_MAXPOSTAGS
34
未指定
未指定
未指定
DT_LOOS
0x6000000d
未指定
未指定
未指定
DT_SUNW_AUXILIARY
0x6000000d
d_ptr
未指定
可选
DT_SUNW_RTLDINF
0x6000000e
d_ptr
可选
可选
DT_SUNW_FILTER
0x6000000e
d_ptr
未指定
可选
DT_SUNW_CAP
0x60000010
d_ptr
可选
可选
DT_SUNW_SYMTAB
0x60000011
d_ptr
可选
可选
DT_SUNW_SYMSZ
0x60000012
d_val
可选
可选
DT_SUNW_ENCODING
0x60000013
未指定
未指定
未指定
DT_SUNW_SORTENT
0x60000013
d_val
可选
可选
DT_SUNW_SYMSORT
0x60000014
d_ptr
可选
可选
DT_SUNW_SYMSORTSZ
0x60000015
d_val
可选
可选
DT_SUNW_TLSSORT
0x60000016
d_ptr
可选
可选
DT_SUNW_TLSSORTSZ
0x60000017
d_val
可选
可选
DT_SUNW_CAPINFO
0x60000018
d_ptr
可选
可选
DT_SUNW_STRPAD
0x60000019
d_val
可选
可选
DT_SUNW_CAPCHAIN
0x6000001a
d_ptr
可选
可选
DT_SUNW_LDMACH
0x6000001b
d_val
可选
可选
DT_SUNW_CAPCHAINENT
0x6000001d
d_val
可选
可选
DT_SUNW_CAPCHAINSZ
0x6000001f
d_val
可选
可选
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_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
未指定
未指定
未指定
DT_NULL
标记 _DYNAMIC 数组的结尾。
DT_NEEDED
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于提供所需依赖项的名称。动态数组可以包含多个此类型的项。尽管这些项与其他类型的项的关系不重要,但其相对顺序却很重要。请参见共享目标文件依赖项。
DT_PLTRELSZ
与过程链接表关联的重定位项的总大小(以字节为单位)。请参见过程链接表(特定于处理器)。
DT_PLTGOT
DT_HASH
符号散列表的地址。该表引用 DT_SYMTAB 元素指示的符号表。请参见散列表节。
DT_STRTAB
字符串表的地址。运行时链接程序所需的符号名称、依赖项名称和其他字符串位于该表中。请参见字符串表节。
DT_SYMTAB
符号表的地址。请参见符号表节。
DT_RELA
重定位表的地址。请参见重定位节。
目标文件可以有多个重定位节。为可执行文件或共享目标文件创建重定位表时,链接编辑器会连接这些节以形成一个表。尽管这些节在目标文件中可以保持独立,但运行时链接程序将看到一个表。运行时链接程序为可执行文件创建进程映像或将共享目标文件添加到进程映像中时,运行时链接程序将会读取该重定位表并执行关联操作。
此元素要求同时存在 DT_RELASZ 和 DT_RELAENT 元素。如果文件必须重定位,则可以存在 DT_RELA 或 DT_REL。
DT_RELASZ
DT_RELA 重定位表的总大小(以字节为单位)。
DT_RELAENT
DT_RELA 重定位项的大小(以字节为单位)。
DT_STRSZ
DT_STRTAB 字符串表的总大小(以字节为单位)。
DT_SYMENT
DT_SYMTAB 符号项的大小(以字节为单位)。
DT_INIT
初始化函数的地址。请参见初始化节和终止节。
DT_FINI
终止函数的地址。请参见初始化节和终止节。
DT_SONAME
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于标识共享目标文件的名称。请参见记录共享目标文件名称。
DT_RPATH
以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。此元素的用途已被 DT_RUNPATH 取代。请参见运行时链接程序搜索的目录。
DT_SYMBOLIC
表示目标文件包含在其链接编辑过程中应用的符号绑定。此元素的用途已被 DF_SYMBOLIC 标志取代。请参见使用 -B symbolic 选项。
DT_REL
与 DT_RELA 类似,但其表中包含隐式加数。此元素要求同时存在 DT_RELSZ 和 DT_RELENT 元素。
DT_RELSZ
DT_REL 重定位表的总大小(以字节为单位)。
DT_RELENT
DT_REL 重定位项的大小(以字节为单位)。
DT_PLTREL
表示过程链接表指向的重定位项的类型(DT_REL 或 DT_RELA)。过程链接表中的所有重定位都必须使用相同的重定位项。请参见过程链接表(特定于处理器)。此元素要求同时存在 DT_JMPREL 元素。
DT_DEBUG
用于调试。
DT_TEXTREL
表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。此元素的用途已被 DF_TEXTREL 标志取代。请参见与位置无关的代码。
DT_JMPREL
与过程链接表单独关联的重定位项的地址。请参见过程链接表(特定于处理器)。通过分隔这些重定位项,运行时链接程序可在装入启用了延迟绑定的目标文件时忽略这些项。此元素要求同时存在 DT_PLTRELSZ 和 DT_PLTREL 元素。
DT_POSFLAG_1
应用于紧邻的 DT_ 元素的各种状态标志。请参见表 7-35。
DT_BIND_NOW
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。此元素的用途已被 DF_BIND_NOW 标志取代。请参见执行重定位的时间。
DT_INIT_ARRAY
初始化函数的指针数组的地址。此元素要求同时存在 DT_INIT_ARRAYSZ 元素。请参见初始化节和终止节。
DT_FINI_ARRAY
终止函数的指针数组的地址。此元素要求同时存在 DT_FINI_ARRAYSZ 元素。请参见初始化节和终止节。
DT_INIT_ARRAYSZ
DT_INIT_ARRAY 数组的总大小(以字节为单位)。
DT_FINI_ARRAYSZ
DT_FINI_ARRAY 数组的总大小(以字节为单位)。
DT_RUNPATH
以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。请参见运行时链接程序搜索的目录。
DT_FLAGS
特定于此目标文件的标志值。请参见表 7-33。
DT_ENCODING
大于或等于 DT_ENCODING、小于或等于 DT_LOOS 的动态标记值遵循 d_un 联合的解释规则。
DT_PREINIT_ARRAY
预初始化函数的指针数组的地址。此元素要求同时存在 DT_PREINIT_ARRAYSZ 元素。仅在可执行文件中处理该数组。如果该数组包含在共享目标文件中,则会被忽略。请参见初始化节和终止节。
DT_PREINIT_ARRAYSZ
DT_PREINIT_ARRAY 数组的总大小(以字节为单位)。
DT_MAXPOSTAGS
正动态数组标记值的数量。
DT_LOOS - DT_HIOS
此范围内包含的值(包括这两个值)保留用于特定于操作系统的语义。所有这类值都遵循 d_un 联合的解释规则。
DT_SUNW_AUXILIARY
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于逐符号指定一个或多个辅助 filtee。请参见生成辅助过滤器。
DT_SUNW_RTLDINF
保留供运行时链接程序内部使用。
DT_SUNW_FILTER
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于逐符号指定一个或多个标准 filtee。请参见生成标准过滤器。
DT_SUNW_CAP
功能节的地址。请参见功能节。
DT_SUNW_SYMTAB
符号表的地址,其中包含用于扩充 DT_SYMTAB 所提供的符号的局部函数符号。这些符号始终在紧邻 DT_SYMTAB 所提供的符号之前的位置。请参见符号表节。
DT_SUNW_SYMSZ
DT_SUNW_SYMTAB 和 DT_SYMTAB 提供的符号表的组合大小。
DT_SUNW_ENCODING
大于或等于 DT_SUNW_ENCODING、小于或等于 DT_HIOS 的动态标记值遵循 d_un 联合的解释规则。
DT_SUNW_SORTENT
DT_SUNW_SYMSORT 和 DT_SUNW_TLSSORT 符号排序项的大小(以字节为单位)。
DT_SUNW_SYMSORT
符号表索引数组的地址,这些索引提供对 DT_SUNW_SYMTAB 所引用的符号表中函数和变量符号的排序访问。请参见符号排序节。
DT_SUNW_SYMSORTSZ
DT_SUNW_SYMSORT 数组的总大小(以字节为单位)。
DT_SUNW_TLSSORT
符号表索引数组的地址,这些索引提供对 DT_SUNW_SYMTAB 所引用的符号表中线程局部符号的排序访问。请参见符号排序节。
DT_SUNW_TLSSORTSZ
DT_SUNW_TLSSORT 数组的总大小(以字节为单位)。
DT_SUNW_CAPINFO
符号表索引数组的地址,这些索引提供符号与其功能要求之间的关联。请参见功能节。
DT_SUNW_STRPAD
动态字符串表末尾未使用的保留空间的总大小(以字节为单位)。如果目标文件中不存在 DT_SUNW_STRPAD,则没有保留空间可用。
DT_SUNW_CAPCHAIN
功能系列索引数组的地址。每个索引系列都以 0 项结尾。
DT_SUNW_LDMACH
生成目标文件的链接编辑器的计算机体系结构。DT_SUNW_LDMACH 使用与 ELF 头的 e_machine 字段相同的 EM_ 整数值。请参见ELF 头。DT_SUNW_LDMACH 用于标识生成目标文件的链接编辑器的类(32 位或 64 位)和平台。此信息不会用于运行时链接程序,而仅用于说明目的。
DT_SUNW_CAPCHAINENT
DT_SUNW_CAPCHAIN 项的大小(以字节为单位)。
DT_SUNW_CAPCHAINSZ
DT_SUNW_CAPCHAIN 链的总大小(以字节为单位)。
DT_SYMINFO
符号信息表的地址。此元素要求同时存在 DT_SYMINENT 和 DT_SYMINSZ 元素。请参见Syminfo 表节。
DT_SYMINENT
DT_SYMINFO 信息项的大小(以字节为单位)。
DT_SYMINSZ
DT_SYMINFO 表的总大小(以字节为单位)。
DT_VERDEF
版本定义表的地址。该表中的元素包含字符串表 DT_STRTAB 的索引。此元素要求同时存在 DT_VERDEFNUM 元素。请参见版本定义章节。
DT_VERDEFNUM
DT_VERDEF 表中的项数。
DT_VERNEED
版本依赖性表的地址。该表中的元素包含字符串表 DT_STRTAB 的索引。此元素要求同时存在 DT_VERNEEDNUM 元素。请参见版本依赖性节。
DT_VERNEEDNUM
DT_VERNEEDNUM 表中的项数。
DT_RELACOUNT
表示 RELATIVE 重定位计数,该计数是通过串联所有 Elf32_Rela 或 Elf64_Rela 重定位项生成的。请参见组合重定位节。
DT_RELCOUNT
表示 RELATIVE 重定位计数,该计数是通过串联所有 Elf32_Rel 重定位项生成的。请参见组合重定位节。
DT_AUXILIARY
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于指定一个或多个辅助 filtee。请参见生成辅助过滤器。
DT_FILTER
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于指定一个或多个标准 filtee。请参见生成标准过滤器。
DT_CHECKSUM
目标文件中选定的节的简单校验和。请参见 gelf_checksum(3ELF)。
DT_MOVEENT
DT_MOVETAB 移动项的大小(以字节为单位)。
DT_MOVESZ
DT_MOVETAB 表的总大小(以字节为单位)。
DT_MOVETAB
移动表的地址。此元素要求同时存在 DT_MOVEENT 和 DT_MOVESZ 元素。请参见移动节。
DT_CONFIG
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义配置文件。该配置文件仅在可执行文件中有意义,并且通常是特定于此目标文件的。请参见配置缺省搜索路径。
DT_DEPAUDIT
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口。
DT_AUDIT
以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口。
DT_FLAGS_1
特定于此目标文件的标志值。请参见表 7-34。
DT_VALRNGLO-DT_VALRNGHI
此范围内包含的值(包括这两个值)使用动态结构的 d_un.d_val 字段。
DT_ADDRRNGLO - DT_ADDRRNGHI
此范围内包含的值(包括这两个值)使用动态结构的 d_un.d_ptr 字段。如果生成 ELF 目标文件后对其进行了任何调整,则必须相应地更新这些项。
DT_SPARC_REGISTER
DT_SYMTAB 符号表中 STT_SPARC_REGISTER 符号的索引。该符号表中的每个 STT_SPARC_REGISTER 符号都存在一个动态项。请参见寄存器符号。
DT_LOPROC - DT_HIPROC
此范围内包含的值(包括这两个值)保留用于特定于处理器的语义。
除动态数组末尾的 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
目标文件使用静态线程局部存储方案
DF_ORIGIN
表示目标文件要求 $ORIGIN 处理。请参见查找关联的依赖项。
DF_SYMBOLIC
表示目标文件包含在其链接编辑过程中应用的符号绑定。请参见使用 -B symbolic 选项。
DF_TEXTREL
表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。请参见与位置无关的代码。
DF_BIND_NOW
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此项优先于使用延迟绑定的指令。请参见执行重定位的时间。
DF_STATIC_TLS
表示目标文件包含使用静态线程局部存储方案的代码。在通过 dlopen(3C) 或延迟装入而动态装入的目标文件中,不能使用静态线程局部存储。
表 7-34 ELF 动态标志 DT_FLAGS_1
名称
值
含义
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
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_NOHDR
0x100000
内部使用。
DF_1_EDITED
0x200000
目标文件在最初生成后已被修改。
DF_1_NORELOC
0x400000
内部使用。
DF_1_SYMINTPOSE
0x800000
存在各个符号插入项。
DF_1_GLOBAUDIT
0x1000000
建立全局审核。
DF_1_SINGLETON
0x2000000
存在单件符号。
DF_1_NOW
表示在将控制权返回给程序之前,必须处理此目标文件的所有重定位项。通过环境或 dlopen(3C) 指定时,提供的此标志优先于使用延迟绑定的指令。请参见执行重定位的时间。
DF_1_GROUP
表示目标文件是组的成员。此标志通过链接编辑器的 -B group 选项记录在目标文件中。请参见目标文件分层结构。
DF_1_NODELETE
表示不能从进程中删除目标文件。如果使用 dlopen(3C) 通过直接或依赖性方式将目标文件装入进程,则无法使用 dlclose(3C) 卸载该目标文件。此标志通过使用链接编辑器的 -z nodelete 选项记录在目标文件中。
DF_1_LOADFLTR
仅对过滤器有意义。表示立即处理所有关联 filtee。此标志通过使用链接编辑器的 -z loadfltr 选项记录在目标文件中。请参见filtee 处理。
DF_1_INITFIRST
表示在装入其他任何目标文件之前首先运行此目标文件的初始化节。此标志仅适用于专用系统库,并通过使用链接编辑器的 -z initfirst 选项记录在目标文件中。
DF_1_NOOPEN
表示无法使用 dlopen(3C) 将目标文件添加到正在运行的进程。此标志通过使用链接编辑器的 -z nodlopen 选项记录在目标文件中。
DF_1_ORIGIN
表示目标文件要求 $ORIGIN 处理。请参见查找关联的依赖项。
DF_1_DIRECT
表示目标文件应使用直接绑定信息。请参见附录 D。
DF_1_INTERPOSE
表示目标文件符号表将在除主装入目标文件(通常为可执行文件)外的所有符号之前插入。此标志通过使用链接编辑器的 -z interpose 选项进行记录。请参见运行时插入。
DF_1_NODEFLIB
表示此目标文件的依赖性搜索会忽略所有缺省的库搜索路径。此标志通过使用链接编辑器的 -z nodefaultlib 选项记录在目标文件中。请参见运行时链接程序搜索的目录。
DF_1_NODUMP
表示此目标文件不通过 dldump(3C) 进行转储。此选项的替代选项包括没有重定位项的目标文件,这些目标文件可能会包括在使用 crle(1) 生成的替代目标文件中。此标志通过使用链接编辑器的 -z nodump 选项记录在目标文件中。
DF_1_CONFALT
将此目标文件标识为 crle(1) 生成的配置替代目标文件。此标志可触发运行时链接程序来搜索配置文件 $ORIGIN/ld.config.app-name。
DF_1_ENDFILTEE
仅对 filtee 有意义。终止对其他任何 filtee 的过滤器搜索。此标志通过使用链接编辑器的 -z endfiltee 选项记录在目标文件中。请参见减少 filtee 搜索。
DF_1_DISPRELDNE
表示此目标文件应用了位移重定位。由于位移重定位记录在应用重定位后被丢弃,因此该目标文件中将不再存在这些记录。请参见位移重定位。
DF_1_DISPRELPND
表示此目标文件暂挂了位移重定位。由于此目标文件中存在位移重定位,因此可在运行时完成重定位。请参见位移重定位。
DF_1_NODIRECT
DF_1_IGNMULDEF
保留供内核运行时链接程序内部使用。
DF_1_NOKSYMS
保留供内核运行时链接程序内部使用。
DF_1_NOHDR
保留供内核运行时链接程序内部使用。
DF_1_EDITED
表示此目标文件在最初由链接编辑器构造后,已被编辑或被修改。此标志用于警告调试器,某个目标文件在最初生成后进行了更改。
DF_1_NORELOC
保留供内核运行时链接程序内部使用。
DF_1_SYMINTPOSE
表示目标文件包含应在除主装入目标文件(通常为可执行文件)外的所有符号之前插入的各个符号。使用 mapfile 和 INTERPOSE 关键字生成目标文件时记录此标志。请参见SYMBOL_SCOPE / SYMBOL_VERSION 指令。
DF_1_GLOBAUDIT
表示动态可执行文件要求全局审计。请参见记录全局审计程序。
DF_1_SINGLETON
表 7-35 ELF 动态位置标志 DT_POSFLAG_1
名称
值
含义
DF_P1_LAZYLOAD
0x1
标识延迟装入的依赖项。
DF_P1_GROUPPERM
0x2
标识组依赖性。
DF_P1_LAZYLOAD
将以下 DT_NEEDED 项标识为要延迟装入的目标文件。此标志通过使用链接编辑器的 -z lazyload 选项记录在目标文件中。请参见延迟装入动态依赖项。
DF_P1_GROUPPERM
将以下 DT_NEEDED 项标识为要作为组装入的目标文件。此标志通过使用链接编辑器的 -z groupperm 选项记录在目标文件中。请参见隔离组。
全局偏移表(特定于处理器)
通常,与位置无关的代码不能包含绝对虚拟地址。全局偏移表在专用数据中包含绝对地址。因此这些地址可用,并且不会破坏程序文本的位置独立性和共享性。程序使用与位置无关的地址来引用其 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: 过程链接表
对于 32 位 SPARC 动态目标文件,过程链接表位于专用数据中。运行时链接程序可确定目标的绝对地址,并相应地修改过程链接表的内存映像。
过程链接表的前四项是保留项。尽管表 7-36 中显示了过程链接表的示例,但未指定这些项的原始内容。该表中的每一项都占用 3 个字(12 字节),并且表的最后一项后跟 nop 指令。
重定位表与过程链接表关联。_DYNAMIC 数组中的 DT_JMP_REL 项指定了第一个重定位项的位置。对于非保留的过程链接表的每一项,重定位表中都包含相同顺序的对应项。所有这些项的重定位类型均为 R_SPARC_JMP_SLOT。重定位偏移可指定关联的过程链接表项的第一个字节的地址。符号表索引会指向相应的符号。
为说明过程链接表,表 7-36 显示了四项。其中,前两项是初始保留项。第三项是对 name101 的调用。第四项是对 name102 的调用。此示例假定对应 name102 的项是表的最后一项。在该最后一项的后面是 nop 指令。左列显示了进行动态链接之前目标文件中的指令。右列说明了运行时链接程序会用于修复过程链接表各项的可能的指令序列。
表 7-36 32 位 SPARC: 过程链接表示例
目标文件
内存段
.PLT0:
unimp
unimp
unimp
.PLT1:
unimp
unimp
unimp
.PLT0:
save %sp, -64, %sp
call runtime_linker
nop
.PLT1:
.word identification
unimp
unimp
.PLT101:
sethi (.-.PLT0), %g1
ba,a .PLT0
nop
.PLT102:
sethi (.-.PLT0), %g1
ba,a .PLT0
nop
nop
.PLT101:
nop
ba,a name101
nop
.PLT102:
sethi (.-.PLT0), %g1
sethi %hi(name102), %g1
jmpl %g1+%lo(name102), %g0
nop
以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。
初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了可将控制权转移给运行时链接程序自己的其中一个例程。运行时链接程序还会在第二项中存储一个字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用者。
过程链接表的其他所有项最初都会传输给第一项。因此,运行时链接程序会在首次执行表项时获取控制权。例如,该程序会调用 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: 过程链接表
对于 64 位 SPARC 动态目标文件,过程链接表位于专用数据中。运行时链接程序可确定目标的绝对地址,并相应地修改过程链接表的内存映像。
过程链接表的前四项是保留项。尽管表 7-37 中显示了过程链接表的示例,但未指定这些项的原始内容。在该表中,前 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-37 显示了若干项。前三项显示了初始保留项。接下来的三项显示了初始的 32,768 个项的示例以及可能的解析格式,这些格式分别应用于目标地址位于项上下 2 GB 的地址空间内、目标地址位于低位的 4 GB 地址空间内或目标地址位与其他任意位置的情形。最后两项显示了后续各项的示例,这些项由指令和指针对组成。左列显示了进行动态链接之前目标文件中的指令。右列说明了运行时链接程序会用于修复过程链接表各项的可能指令序列。
表 7-37 64 位 SPARC: 过程链接表示例
目标文件
内存段
.PLT0:
unimp
unimp
unimp
unimp
unimp
unimp
unimp
unimp
.PLT1:
unimp
unimp
unimp
unimp
unimp
unimp
unimp
unimp
.PLT2:
unimp
.PLT0:
save %sp, -176, %sp
sethi %hh(runtime_linker_0), %l0
sethi %lm(runtime_linker_0), %l1
or %l0, %hm(runtime_linker_0), %l0
sllx %l0, 32, %l0
or %l0, %l1, %l0
jmpl %l0+%lo(runtime_linker_0), %o1
mov %g1, %o0
.PLT1:
save %sp, -176, %sp
sethi %hh(runtime_linker_1), %l0
sethi %lm(runtime_linker_1), %l1
or %l0, %hm(runtime_linker_1), %l0
sllx %l0, 32, %l0
or %l0, %l1, %l0
jmpl %l0+%lo(runtime_linker_0), %o1
mov %g1, %o0
.PLT2:
.xword identification
.PLT101:
sethi (.-.PLT0), %g1
ba,a %xcc, .PLT1
nop
nop
nop; nop
nop; nop
.PLT102:
sethi (.-.PLT0), %g1
ba,a %xcc, .PLT1
nop
nop
nop; nop
nop; nop
.PLT103:
sethi (.-.PLT0), %g1
ba,a %xcc, .PLT1
nop
nop
nop
nop
nop
nop
.PLT101:
nop
mov %o7, %g1
call name101
mov %g1, %o7
nop; nop
nop; nop
.PLT102:
nop
sethi %hi(name102), %g1
jmpl %g1+%lo(name102), %g0
nop
nop; nop
nop; nop
.PLT103:
nop
sethi %hh(name103), %g1
sethi %lm(name103), %g5
or %hm(name103), %g1
sllx %g1, 32, %g1
or %g1, %g5, %g5
jmpl %g5+%lo(name103), %g0
nop
.PLT32768:
mov %o7, %g5
call .+8
nop
ldx [%o7+.PLTP32768 -
(.PLT32768+4)], %g1
jmpl %o7+%g1, %g1
mov %g5, %o7
...
.PLT32927:
mov %o7, %g5
call .+8
nop
ldx [%o7+.PLTP32927 -
(.PLT32927+4)], %g1
jmpl %o7+%g1, %g1
mov %g5, %o7
.PLT32768:
...
.PLT32927:
.PLTP32768
.xword .PLT0 -
(.PLT32768+4)
...
.PLTP32927
.xword .PLT0 -
(.PLT32927+4)
.PLTP32768
.xword name32768 -
(.PLT32768+4)
...
.PLTP32927
.xword name32927 -
(.PLT32927+4)
以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。
初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了将控制权转移给运行时链接程序自己的例程。运行时链接程序还会在第三项中存储一个扩展字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用者。
过程链接表的其他所有项最初都会传输给第一项或第二项。这些项将建立栈帧并调用运行时链接程序。
通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。
运行时链接程序会计算表插槽对应的重定位项的索引。
使用索引信息,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。
运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。
要使代码可重复执行,可按特定顺序更改过程链接表的指令。如果运行时链接程序在修复函数的过程链接表项时收到信号,则信号处理代码必须能够调用具有可预测的正确结果的原始函数。
运行时链接程序最多可更改八个字来转换一项。对于指令执行,运行时链接程序只能自动更新一个字。因此,通过以下方法可实现重复执行:首先将 nop 指令覆写为其替换指令,如果使用 64 位存储,则之后还要修补 ba,a 和 sethi。如果仅在最后一个修补程序之前调用可重复执行的函数,则运行时链接程序会再次获取控制权。尽管两次调用运行时链接程序修改的过程链接表项都相同,但这些更改不会相互干扰。
如果更改初始 sethi 指令,则只能将该指令替换为 nop。
按照更改第二种格式的项的方式更改指针是通过使用一个原子的 64 位存储器完成的。
注 -为 .PLT101、.PLT102 和 .PLT103 显示的不同指令序列说明了如何优化关联目标的更新。
LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。
32 位 x86: 过程链接表
对于 32 位 x86 动态目标文件,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享目标文件包含不同的过程链接表。
表 7-38 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-39 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: 过程链接表
对于 x64 动态目标文件,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享目标文件包含不同的过程链接表。
表 7-40 x64: 过程链接表示例
.PLT0:
pushq GOT+8(%rip) # GOT[1]
jmp *GOT+16(%rip) # GOT[2]
nop; nop
nop; nop
.PLT1:
jmp *name1@GOTPCREL(%rip) # 16 bytes from .PLT0
pushq $index1
jmp .PLT0
.PLT2:
jmp *name2@GOTPCREL(%rip) # 16 bytes from .PLT1
pushl $index2
jmp .PLT0
以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。
初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。
进程映像中的每个共享目标文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。
例如,该程序会调用 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 重定位项。