链接程序和库指南

动态链接

本节介绍用于创建运行程序的目标文件信息和系统操作。此处介绍的大多数信息适用于所有系统。特定于某处理器的信息位于带有相应标记的各节中。

可执行文件和共享库文件静态表示应用程序。要执行这类程序,系统可使用这些文件创建动态程序表示形式(即进程映像)。进程映像具有包含其文本、数据、栈等内容的段。本节包括以下主要小节。

程序头

可执行文件或共享库文件的程序头表是一个结构数组。每种结构都描述了系统准备程序执行所需的段或其他信息。目标文件段包含一个或多个节,如段内容中所述。

程序头仅对可执行文件和共享库文件有意义。文件使用 ELF 头的 e_phentsizee_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_vaddrp_offset 的同余值(以页面大小为模数)。此成员可提供一个值,用于在内存和文件中根据该值对齐各段。值 01 表示无需对齐。另外,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

PT_NULL

未使用。没有定义成员值。使用此类型,程序头表可以包含忽略的项。

PT_LOAD

指定可装入段,通过 p_fileszp_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_LOSUNW - PT_HISUNW

此范围内包含的值保留用于特定于 Sun 的语义。

PT_SUNWBSS

PT_LOAD 元素相同的属性,用于描述 .SUNW_bss 节。

PT_SUNWSTACK

描述进程栈。只能存在一个 PT_SUNWSTACK 元素。仅访问权限(如 p_flags 字段中所定义)有意义。

PT_SUNWDTRACE

保留供 dtrace(1M) 内部使用。

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 对齐)

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–9 32 位 x86: 可执行文件(64 K 对齐)

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

此示例的文件偏移和虚拟地址以文本和数据的最大页面大小为模数同余。根据页面大小和文件系统块大小,最多可有四个文件页包含混合文本或数据。


注 –

前面的示例反映了对其文本段取整的典型的 Solaris 系统二进制文件。


数据段结尾要求对未初始化的数据进行特殊处理,系统将其定义为从零值开始。如果文件的最后一个数据页包含不属于逻辑内存页的信息,则必须将无关数据设置为零,而不是设置为可执行文件的未知内容。

其他三个页面中的混合内容在逻辑上不属于进程映像。没有指定系统是否会清除这些混合内容。以下各图中显示了此程序的内存映像,假定页面大小为 4 KB (0x1000)。为简单起见,这些图仅对一种页面大小进行说明。

图 7–10 32 位 SPARC: 进程映像段

SPARC 进程映像段示例。

图 7–11 x86: 进程映像段

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 段检索路径名,并通过解释程序文件段创建初始进程映像。解释程序负责从系统接收控制并为应用程序提供环境。

在 Solaris OS 中,解释程序称为运行时链接程序,即 ld.so.1(1)

运行时链接程序

创建启动动态链接的动态库时,链接编辑器将向可执行文件中添加一个类型为 PT_INTERP 的程序头元素。该元素指示系统将运行时链接程序作为程序的解释程序进行调用。exec(2) 和运行时链接程序可进行协作,以便为程序创建进程映像。

链接编辑器可为可执行文件和共享库文件构造协助链接程序运行的各种数据。这些数据位于可装入段中,从而使数据在执行过程中可用。这些段包括:

共享库可以占用虚拟内存地址,这些虚拟内存地址与文件的程序头表中记录的地址不同。运行时链接程序会重定位内存映像,从而在应用程序获取控制权之前更新绝对地址。

动态节

如果目标文件参与动态链接,则其程序头表将包含一个类型为 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_HIOSDT_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

未指定 

未指定 

未指定 

DT_NULL

标记 _DYNAMIC 数组的结尾。

DT_NEEDED

以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于提供所需依赖项的名称。动态数组可以包含多个此类型的项。尽管这些项与其他类型的项的关系不重要,但其相对顺序却很重要。 请参见共享库依赖项

DT_PLTRELSZ

与过程链接表关联的重定位项的总大小(以字节为单位)。 请参见过程链接表(特定于处理器)

DT_PLTGOT

与过程链接表或全局偏移表关联的地址。 请参见过程链接表(特定于处理器)全局偏移表(特定于处理器)

DT_HASH

符号散列表的地址。该表引用 DT_SYMTAB 元素指示的符号表。 请参见散列表节

DT_STRTAB

字符串表的地址。运行时链接程序所需的符号名称、依赖项名称和其他字符串位于该表中。 请参见字符串表节

DT_SYMTAB

符号表的地址。 请参见符号表节

DT_RELA

重定位表的地址。 请参见重定位节

目标文件可以有多个重定位节。为可执行文件或共享库文件创建重定位表时,链接编辑器会连接这些节以形成一个表。尽管这些节在目标文件中可以保持独立,但运行时链接程序将看到一个表。运行时链接程序为可执行文件创建进程映像或将共享库添加到进程映像中时,运行时链接程序将会读取该重定位表并执行关联操作。

此元素要求同时存在 DT_RELASZDT_RELAENT 元素。如果文件必须重定位,则可以存在 DT_RELADT_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_RELSZDT_RELENT 元素。

DT_RELSZ

DT_REL 重定位表的总大小(以字节为单位)。

DT_RELENT

DT_REL 重定位项的大小(以字节为单位)。

DT_PLTREL

表示过程链接表指向的重定位项的类型(DT_RELDT_RELA)。过程链接表中的所有重定位都必须使用相同的重定位项。 请参见过程链接表(特定于处理器)。此元素要求同时存在 DT_JMPREL 元素。

DT_DEBUG

用于调试。

DT_TEXTREL

表示一个或多个重定位项可能会要求修改非可写段,并且运行时链接程序可以相应地进行准备。此元素的用途已被 DF_TEXTREL 标志取代。 请参见与位置无关的代码

DT_JMPREL

与过程链接表单独关联的重定位项的地址。 请参见过程链接表(特定于处理器)。通过分隔这些重定位项,运行时链接程序可在装入启用了延迟绑定的目标文件时忽略这些项。此元素要求同时存在 DT_PLTRELSZDT_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_HIOS 的动态标记值,遵循 d_un 联合的解释规则。

DT_PREINIT_ARRAY

预初始化函数的指针数组的 地址 。此元素要求同时存在 DT_PREINIT_ARRAYSZ 元素。仅在可执行文件中处理该数组。如果该数组包含在共享库中,则会被忽略。 请参见初始化和终止节

DT_PREINIT_ARRAYSZ

DT_PREINIT_ARRAY 数组的总大小(以字节为单位)。

DT_LOOS - DT_HIOS

此范围内包含的值保留用于特定于操作系统的语义。所有这类值都遵循 d_un 联合的解释规则。

DT_SUNW_RTLDINF

保留供运行时链接程序内部使用。

DT_SYMINFO

符号信息表的地址。此元素要求同时存在 DT_SYMINENTDT_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_RelaElf64_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_MOVEENTDT_MOVESZ 元素。请参见移动节

DT_CONFIG

以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义配置文件。该配置文件仅在可执行文件中有意义,并且通常是特定于此目标文件的。请参见配置缺省搜索路径

DT_DEPAUDIT

以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口

DT_AUDIT

以空字符结尾的字符串的 DT_STRTAB 字符串表偏移,用于定义一个或多个审计库。请参见运行时链接程序审计接口

DT_FLAGS_1

特定于此目标文件的标志值。请参见表 7–34

DT_FEATURE_1

特定于此目标文件的功能值。请参见表 7–36

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_NEEDEDDT_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

目标文件不能用于 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

内部使用。 

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

表示目标文件应使用直接绑定信息。请参见直接绑定

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

保留供内核运行时链接程序内部使用。

表 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 选项记录在目标文件中。请参见隔离组

表 7–36 ELF 动态功能标志 DT_FEATURE_1

名称 

值 

含义 

DTF_1_PARINIT

0x1

需要部分初始化。 

DTF_1_CONFEXP

0x2

需要配置文件。 

DTF_1_PARINIT

表示目标文件需要部分初始化。请参见移动节

DTF_1_CONFEXP

将此目标文件标识为 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: 过程链接表

对于 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: 过程链接表示例

目标文件

内存段

.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

以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。

  1. 初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了可将控制权转移给运行时链接程序自己的其中一个例程。运行时链接程序还会在第二项中存储一个字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用方。

  2. 过程链接表的其他所有项最初都会传输给第一项。因此,运行时链接程序会在首次执行表项时获取控制权。例如,该程序会调用 name101,以将控制权转移给标签 .PLT101

  3. sethi 指令可分别计算当前过程链接表各项和初始过程链接表各项(.PLT101.PLT0)之间的距离。该值会占用 %g1 寄存器最高有效的 22 位。

  4. 接下来,ba,a 指令会跳至 .PLT0 以建立栈帧,然后调用运行时链接程序。

  5. 通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。

  6. 通过将 %g1 值移位并除以过程链接表各项的大小,运行时链接程序可计算对应 name101 的重定位项的索引。重定位项 101 的类型为 R_SPARC_JMP_SLOT。此重定位偏移可指定 .PLT101 的地址,并且其符号表索引会指向 name101。因此,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。

运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。


注 –

.PLT101.PLT102 显示的不同指令序列说明了如何优化关联目标的更新。


LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。

64 位 SPARC: 过程链接表

对于 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: 过程链接表示例

目标文件

内存段

.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:

    <unchanged>

    <unchanged>

    <unchanged>

    <unchanged>



    <unchanged>

    <unchanged>



    ...



.PLT32927:

    <unchanged>

    <unchanged>

    <unchanged>

    <unchanged>



    <unchanged>

    <unchanged>
.PLTP32768

    .xword  .PLT0 -

              (.PLT32768+4)

    ...



.PLTP32927

    .xword  .PLT0 -

              (.PLT32927+4)
.PLTP32768

    .xword  name32768 -

              (.PLT32768+4)

    ...

    

.PLTP32927

    .xword  name32927 -

	      (.PLT32927+4)

以下步骤介绍了运行时链接程序和程序如何通过过程链接表来共同解析符号引用。所介绍的这些步骤仅用于说明。没有指定运行时链接程序的准确执行时行为。

  1. 初始创建程序的内存映像时,运行时链接程序会更改初始过程链接表的各项。修改这些项是为了将控制权转移给运行时链接程序自己的例程。运行时链接程序还会在第三项中存储一个扩展字的标识信息。运行时链接程序获取控制权后,会检查该字以标识调用方。

  2. 过程链接表的其他所有项最初都会传输给第一项或第二项。这些项将建立栈帧并调用运行时链接程序。

  3. 通过标识值,运行时链接程序可获取其用于目标文件的数据结构,包括重定位表。

  4. 运行时链接程序会计算表插槽对应的重定位项的索引。

  5. 使用索引信息,运行时链接程序可获取符号的实际值、展开栈、修改过程链接表项并将控制权转移给所需目标。

运行时链接程序不必在内存段列下创建指令序列。如果运行时链接程序创建了指令序列,则某些点需要更多说明。

按照更改第二种格式的项的方式更改指针是通过使用一个原子的 64 位存储器完成的。


注 –

.PLT101.PLT102.PLT103 显示的不同指令序列说明了如何优化关联目标的更新。


LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_SPARC_JMP_SLOT 重定位项。

32 位 x86: 过程链接表

对于 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


注 –

如前面的示例所示,对于绝对代码和与位置无关的代码,过程链接表指令会使用不同的操作数寻址模式。但是,它们的运行时链接程序接口却相同。


以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。

  1. 初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。

  2. 如果过程链接表与位置无关,则全局偏移表的地址必须位于 %ebx 中。进程映像中的每个共享库文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。因此,调用函数在调用过程链接表项之前,必须首先设置全局偏移表基本寄存器。

  3. 例如,该程序会调用 name1,以将控制权转移给标签 .PLT1

  4. 第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表包含以下 pushl 指令的地址,而不是 name1 的实际地址。

  5. 该程序将在栈中推送一个重定位偏移 (offset)。该重定位偏移是重定位表中一个 32 位的非负字节偏移。指定的重定位项的类型为 R_386_JMP_SLOT,其偏移指定了前面的 jmp 指令中使用的全局偏移表项。该重定位项还包含符号表索引,以供运行时链接程序用于获取引用的符号 name1

  6. 推送该重定位偏移后,程序将跳至过程链接表中的第一项 .PLT0pushl 指令会在栈中推送全局偏移表的第二项(got_plus_44(%ebx))的值,从而为运行时链接程序提供一个字的标识信息。然后,程序将跳至全局偏移表的第三项(got_plus_88(%ebx))中的地址,以继续跳至运行时链接程序。

  7. 运行时链接程序将展开栈、检查指定的重定位项、获取符号的值、在全局偏移项表中存储 name1 的实际地址并跳至目标。

  8. 过程链接表项的后续执行结果会直接传输给 name1,而不会再次调用运行时链接程序。位于 .PLT1jmp 指令将跳至 name1,而不是对 pushl 指令失败。

LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_386_JMP_SLOT 重定位项。

x64: 过程链接表

对于 x64 动态库,过程链接表位于共享文本中,但使用专用全局偏移表中的地址。运行时链接程序可确定目标的绝对地址,并相应地修改全局偏移表的内存映像。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享库文件包含不同的过程链接表。

表 7–41 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

以下步骤介绍了运行时链接程序和程序如何通过过程链接表和全局偏移表来协作解析符号引用。

  1. 初始创建程序的内存映像时,运行时链接程序会将全局偏移表中的第二项和第三项设置为特殊值。以下步骤说明了这些值。

  2. 进程映像中的每个共享库文件都有各自的过程链接表,并且控制权仅转移给位于同一目标文件内的过程链接表项。

  3. 例如,该程序会调用 name1,以将控制权转移给标签 .PLT1

  4. 第一条指令会跳至全局偏移表项中对应于 name1 的地址。最初,全局偏移表包含以下 pushq 指令的地址,而不是 name1 的实际地址。

  5. 该程序将在栈中推送一个重定位索引 (index1)。该重定位索引是重定位表中一个 32 位的非负索引。重定位表由 DT_JUMPREL 动态节项标识。指定的重定位项的类型为 R_AMD64_JMP_SLOT,其偏移指定了前面的 jmp 指令中使用的全局偏移表项。该重定位项还包含符号表索引,以供运行时链接程序用于获取引用的符号 name1

  6. 推送该重定位索引后,程序将跳至过程链接表中的第一项 .PLT0pushq 指令会在栈中推送全局偏移表的第二项 (GOT+8) 的值,从而为运行时链接程序提供一个字的标识信息。然后,程序将跳至第三个全局偏移表项 (GOT+16) 中的地址,以继续跳至运行时链接程序。

  7. 运行时链接程序将展开栈、检查指定的重定位项、获取符号的值、在全局偏移项表中存储 name1 的实际地址并跳至目标。

  8. 过程链接表项的后续执行结果会直接传输给 name1,而不会再次调用运行时链接程序。位于 .PLT1jmp 指令将跳至 name1,而不是对 pushq 指令失败。

LD_BIND_NOW 环境变量可更改动态链接行为。如果其值不为空,则运行时链接程序会在将控制权转移给程序之前处理 R_AMD64_JMP_SLOT 重定位项。