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