链接程序和库指南

过程链接表(特定于处理器)

全局偏移表可将与位置无关的地址计算结果转换为绝对位置。同样,过程链接表也可将与位置无关的函数调用转换为绝对位置。链接编辑器无法解析不同动态库之间的执行传输(如函数调用)。因此,链接编辑器会安排程序将控制权转移给过程链接表中的各项。这样,运行时链接程序就会重定向各项,而不会破坏程序文本的位置独立性和共享性。可执行文件和共享库文件包含不同的过程链接表。

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 重定位项。