重定位是连接符号引用与符号定义的过程。例如,程序调用函数时,关联的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须包含说明如何修改其节内容的信息。通过此信息,可执行文件和共享库文件可包含进程的程序映像的正确信息。可重定位项即是这些数据。
可重定位项可具有以下结构。 请参见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 位。