链接程序和库指南

线程局部存储的访问模型

每个 TLS 引用都遵循下列其中一个访问模型。这些模型按照最常见、但最少优化到速度最快、但限制最大的顺序列出。

常规动态 (General Dynamic, GD)-动态 TLS

此模型允许从共享库或动态可执行文件引用所有 TLS 变量。如果第一次从特定线程引用 TLS 块,则此模型还支持延迟分配此块。

局部动态 (Local Dynamic, LD)-局部符号的动态 TLS

此模型是 GD 模型的优化模型。如果编译器确定变量在要生成的动态库中将局部绑定或受到保护,则编译器将指示链接编辑器静态绑定动态 tlsoffset 并使用此模型。与 GD 模型相比,此模型可提供更好的性能。每个函数只需要调用一次 tls_get_addr(),即可确定 dtv0,m 地址。将在链接编辑时绑定的动态 TLS 偏移会与每个引用的 dtv0,m 地址相加。

初始可执行 (Initial Executable, IE)-具有指定偏移的静态 TLS

此模型只能引用初始静态 TLS 模板中包含的 TLS 变量。此模板由进程启动时可用的所有 TLS 块组成。在此模型中,给定变量 x 的相对于线程指针的偏移存储在 xGOT 项中。此模型不能从在初始进程启动后通过延迟装入、过滤器或 dlopen(3C) 装入的共享库引用 TLS 变量。此模型不能访问使用延迟分配的 TLS 块。

局部可执行 (Local Executable, LE)-静态 TLS

此模型只能引用动态可执行文件的 TLS 块中包含的 TLS 变量。链接编辑器静态计算相对于线程指针的偏移,而不需要进行动态重定位或额外引用 GOT。不能使用此模型引用动态可执行文件外部的变量。

链接编辑器可以将代码从更常规的访问模型转换为更优化的模型(如果确定适合进行转换)。使用独特的 TLS 重定位可实现此转换。这些重定位不仅请求执行更新,还标识要使用的 TLS 访问模型。

链接编辑器在了解 TLS 访问模型和要创建的目标文件类型后可以执行转换。例如,如果要将使用 GD 访问模型的可重定位目标文件链接到动态可执行文件,则链接编辑器可以根据需要转换使用 IELE 访问模型的引用。然后执行模型所需的重定位。

下图说明了不同的访问模型,以及一个模型可以转换为另一个模型的时间。

图 8–2 线程局部存储的访问模型和转换

线程局部存储的访问模型和转换

SPARC: 访问线程局部变量

在 SPARC 上,可使用以下代码序列模型来访问线程局部变量。

SPARC: 常规动态 (General Dynamic, GD)

此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。

表 8–2 SPARC: 常规动态线程局部变量的访问代码

代码序列

初始重定位

符号

# %l7 - initialized to GOT pointer



0x00 sethi %hi(@dtlndx(x)), %o0

0x04 add   %o0, %lo(@dtlndx(x)), %o0

0x08 add   %l7, %o0, %o0

0x0c call  x@TLSPLT



# %o0 - contains address of TLS variable
 

 

R_SPARC_TLS_GD_HI22

R_SPARC_TLS_GD_LO10

R_SPARC_TLS_GD_ADD

R_SPARC_TLS_GD_CALL
  

 

x

x

x

x
 

未完成的重定位: 32 位

符号

GOT[n]

GOT[n + 1]
R_SPARC_TLS_DTPMOD32

R_SPARC_TLS_DTPOFF32
x

x
 

未完成的重定位: 64 位

符号

GOT[n]

GOT[n + 1]
R_SPARC_TLS_DTPMOD64

R_SPARC_TLS_DTPOFF64
x

x

sethiadd 指令分别生成 R_SPARC_TLS_GD_HI22R_SPARC_TLS_GD_LO10 重定位。这些重定位指示链接编辑器在 GOT 中分配空间,以存储变量 xTLS_index 结构。链接编辑器通过将相对于 GOT 的偏移替换为新的 GOT 项来处理此重定位。

x 的装入目标文件索引和 TLS 块索引在运行前无法确定。因此,链接编辑器根据 GOT 放置 R_SPARC_TLS_DTPMOD32R_SPARC_TLS_DPTOFF32 重定位,以便运行时链接程序处理。

第二个 add 指令导致生成 R_SPARC_TLS_GD_ADD 重定位。仅当链接编辑器将 GD 代码序列更改为另一个序列时,才使用此重定位。

call 指令使用特殊语法 x@TLSPLT。此调用引用 TLS 变量并生成 R_SPARC_TLS_GD_CALL 重定位。此重定位指示链接编辑器将调用绑定到 __tls_get_addr() 函数,并将 call 指令与 GD 代码序列关联。


注 –

add 指令必须出现在 call 指令前面。不能将 add 指令放在调用的延迟槽中。由于后面进行的代码变换需要已知顺序,所以必须满足此要求。

用作 add 指令(由 R_SPARC_TLS_GD_ADD 重定位标记)的 GOT 指针的寄存器必须是 add 指令中的第一个寄存器。在代码变换期间,此要求允许链接编辑器标识 GOT 指针寄存器。


SPARC: 局部动态 (Local Dynamic, LD)

此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。

表 8–3 SPARC: 局部动态线程局部变量的访问代码

代码序列

初始重定位

符号

# %l7 - initialized to GOT pointer



0x00 sethi %hi(@tmndx(x1)), %o0

0x04 add   %o0, %lo(@tmndx(x1)), %o0

0x08 add   %l7, %o0, %o0

0x0c call  x@TLSPLT



# %o0 - contains address of TLS block of current object



0x10 sethi %hi(@dtpoff(x1)), %l1

0x14 xor   %l1, %lo(@dtpoff(x1)), %l1

0x18 add   %o0, %l1, %l1



# %l1 - contains address of local TLS variable x1



0x20 sethi %hi(@dtpoff(x2)), %l2

0x24 xor   %l2, %lo(@dtpoff(x2)), %l2

0x28 add   %o0, %l2, %l2



# %l2 - contains address of local TLS variable x2
 

 

R_SPARC_TLS_LDM_HI22

R_SPARC_TLS_LDM_LO10

R_SPARC_TLS_LDM_ADD

R_SPARC_TLS_LDM_CALL

 

 

 

R_SPARC_TLS_LDO_HIX22

R_SPARC_TLS_LDO_LOX10

R_SPARC_TLS_LDO_ADD

 

 

 

R_SPARC_TLS_LDO_HIX22

R_SPARC_TLS_LDO_LOX10

R_SPARC_TLS_LDO_ADD
 

 

x1

x1

x1

x1

 

 

 

x1

x1

x1

 

 

 

x2

x2

x2
 

未完成的重定位: 32 位

符号

GOT[n]

GOT[n + 1]
R_SPARC_TLS_DTPMOD32

<none>
x1
 

未完成的重定位: 64 位

符号

GOT[n]

GOT[n + 1]
R_SPARC_TLS_DTPMOD64

<none>
x1

第一个 sethi 指令和 add 指令分别生成 R_SPARC_TLS_LDM_HI22R_SPARC_TLS_LDM_LO10 重定位。这些重定位指示链接编辑器在 GOT 中分配空间,以存储当前目标文件的 TLS_index 结构。链接编辑器通过将相对于 GOT 的偏移替换为新的 GOT 项来处理此重定位。

装入目标文件索引在运行前无法确定。因此,将创建 R_SPARC_TLS_DTPMOD32 重定位,并在 TLS_index 结构的 ti_tlsoffset 字段中填充零。

第二个 addcall 指令分别由 R_SPARC_TLS_LDM_ADDR_SPARC_TLS_LDM_CALL 重定位标记。

后面的 sethi 指令和 xor 指令分别生成 R_SPARC_LDO_HIX22R_SPARC_TLS_LDO_LOX10 重定位。在链接编辑时将确定每个局部符号的 TLS 偏移,因此将直接填充这些值。add 指令使用 R_SPARC_TLS_LDO_ADD 重定位标记。

当一个过程引用多个局部符号时,编译器将生成一次获取 TLS 块的基本地址的代码。然后,使用此基本地址计算每个符号的地址,而不需要单独调用库。


注 –

包含 add 指令(由 R_SPARC_TLS_LDO_ADD 标记)中的 TLS 目标文件地址的寄存器必须是指令序列中的第一个寄存器。在代码变换期间,此要求允许链接编辑器标识寄存器。


32 位 SPARC: 初始可执行 (Initial Executable, IE)

此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。

表 8–4 32 位 SPARC: 初始可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

# %l7 - initialized to GOT pointer, %g7 - thread pointer



0x00 sethi %hi(@tpoff(x)), %o0

0x04 or    %o0, %lo(@tpoff(x)), %o0

0x08 ld    [%l7 + %o0], %o0

0x0c add   %g7, %o0, %o0

 

# %o0 - contains address of TLS variable
 

 

R_SPARC_TLS_IE_HI22

R_SPARC_TLS_IE_LO10

R_SPARC_TLS_IE_LD

R_SPARC_TLS_IE_ADD
 

 

x

x

x

x
 

未完成的重定位

符号

GOT[n]
R_SPARC_TLS_TPOFF32
x

sethi 指令和 or 指令分别生成 R_SPARC_TLS_IE_HI22R_SPARC_TLS_IE_LO10 重定位。这些重定位指示链接编辑器在 GOT 中创建空间,以存储符号 x 的静态 TLS 偏移。针对 GOTR_SPARC_TLS_TPOFF32 重定位未完成,以便运行时链接程序使用符号 x 的负静态 TLS 偏移填充。ldadd 指令分别使用 R_SPARC_TLS_IE_LDR_SPARC_TLS_IE_ADD 重定位标记。


注 –

用作 add 指令(由 R_SPARC_TLS_IE_ADD 重定位标记)的 GOT 指针的寄存器必须是此指令中的第一个寄存器。在代码变换期间,此要求允许链接编辑器标识 GOT 指针寄存器。


64 位 SPARC: 初始可执行 (Initial Executable, IE)

此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。

表 8–5 64 位 SPARC: 初始可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

# %l7 - initialized to GOT pointer, %g7 - thread pointer



0x00 sethi %hi(@tpoff(x)), %o0

0x04 or    %o0, %lo(@tpoff(x)), %o0

0x08 ldx   [%l7 + %o0], %o0

0x0c add   %g7, %o0, %o0

 

# %o0 - contains address of TLS variable
 

 

R_SPARC_TLS_IE_HI22

R_SPARC_TLS_IE_LO10

R_SPARC_TLS_IE_LD

R_SPARC_TLS_IE_ADD
 

 

x

x

x

x
 

未完成的重定位

符号

GOT[n]
R_SPARC_TLS_TPOFF64
x

SPARC: 局部可执行 (Local Executable, LE)

此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。

表 8–6 SPARC: 局部可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

# %g7 - thread pointer



0x00 sethi %hix(@tpoff(x)), %o0

0x04 xor   %o0,%lo(@tpoff(x)),%o0

0x08 add   %g7, %o0, %o0

 

# %o0 - contains address of TLS variable
 

 

R_SPARC_TLS_LE_HIX22

R_SPARC_TLS_LE_LOX10

<none>
 

 

x

x

sethixor 指令分别生成 R_SPARC_TLS_LE_HIX22R_SPARC_TLS_LE_LOX10 重定位。链接编辑器将这些重定位直接绑定到在可执行文件中定义的符号的静态 TLS 偏移。在运行时不需要进行重定位处理。

SPARC: 线程局部存储的重定位类型

下表中列出的 TLS 重定位是针对 SPARC 定义的。此表中的说明使用以下表示法。

@dtlndx(x)

GOT 中分配两个连续项,以存储 TLS_index 结构。此信息将传递给 __tls_get_addr()。引用此项的指令将绑定到两个 GOT 项中的第一项的地址。

@tmndx(x)

GOT 中分配两个连续项,以存储 TLS_index 结构。此信息将传递给 __tls_get_addr()。将此结构的 ti_tlsoffset 字段设置为 0,并且在运行时填充 ti_moduleid。对 __tls_get_addr() 的调用将返回动态 TLS 块的起始偏移。

@dtpoff(x)

计算相对于 TLS 块的 tlsoffset

@tpoff(x)

计算相对于静态 TLS 块的负 tlsoffset。将此值与线程指针相加以计算 TLS 地址。

@dtpmod(x)

计算包含 TLS 符号的目标文件的标识符。

表 8–7 SPARC: 线程局部存储的重定位类型

名称 

值 

字段 

计算 

R_SPARC_TLS_GD_HI22

56

T-simm22

@dtlndx(S + A) >> 10

R_SPARC_TLS_GD_LO10

57

T-simm13

@dtlndx(S + A) & 0x3ff

R_SPARC_TLS_GD_ADD

58

无 

请参阅此表后面的说明。 

R_SPARC_TLS_GD_CALL

59

V-disp30

请参阅此表后面的说明。 

R_SPARC_TLS_LDM_HI22

60

T-simm22

@tmndx(S + A) >> 10

R_SPARC_TLS_LDM_LO10

61

T-simm13

@tmndx(S + A) & 0x3ff

R_SPARC_TLS_LDM_ADD

62

无 

请参阅此表后面的说明。 

R_SPARC_TLS_LDM_CALL

63

V-disp30

请参阅此表后面的说明。 

R_SPARC_TLS_LDO_HIX22

64

T-simm22

@dtpoff(S + A) >> 10

R_SPARC_TLS_LDO_LOX10

65

T-simm13

@dtpoff(S + A) & 0x3ff

R_SPARC_TLS_LDO_ADD

66

无 

请参阅此表后面的说明。 

R_SPARC_TLS_IE_HI22

67

T-simm22

@got(@tpoff(S + A)) >> 10

R_SPARC_TLS_IE_LO10

68

T-simm13

@got(@tpoff(S + A)) & 0x3ff

R_SPARC_TLS_IE_LD

69

无 

请参阅此表后面的说明。 

R_SPARC_TLS_IE_LDX

70

无 

请参阅此表后面的说明。 

R_SPARC_TLS_IE_ADD

71

无 

请参阅此表后面的说明。 

R_SPARC_TLS_LE_HIX22

72

T-imm22

(@tpoff(S + A) ^0xffffffffffffffff) >> 10

R_SPARC_TLS_LE_LOX10

73

T-simm13

(@tpoff(S + A) & 0x3ff) | 0x1c00

R_SPARC_TLS_DTPMOD32

74

V-word32

@dtpmod(S + A)

R_SPARC_TLS_DTPMOD64

75

V-word64

@dtpmod(S + A)

R_SPARC_TLS_DTPOFF32

76

V-word32

@dtpoff(S + A)

R_SPARC_TLS_DTPOFF64

77

V-word64

@dtpoff(S + A)

R_SPARC_TLS_TPOFF32

78

V-word32

@tpoff(S + A)

R_SPARC_TLS_TPOFF64

79

V-word64

@tpoff(S + A)

一些重定位类型的语义不只是简单的计算。

R_SPARC_TLS_GD_ADD

此重定位标记 GD 代码序列的 add 指令。用于 GOT 指针的寄存器是此序列中的第一个寄存器。此重定位标记的指令出现在 R_SPARC_TLS_GD_CALL 重定位标记的 call 指令前面。此重定位用于在链接编辑时在 TLS 模型之间进行转换。

R_SPARC_TLS_GD_CALL

可以按处理引用 __tls_get_addr() 函数的 R_SPARC_WPLT30 重定位的方式处理此重定位。此重定位是 GD 代码序列的一部分。

R_SPARC_LDM_ADD

此重定位标记 LD 代码序列的第一个 add 指令。用于 GOT 指针的寄存器是此序列中的第一个寄存器。此重定位标记的指令出现在 R_SPARC_TLS_GD_CALL 重定位标记的 call 指令前面。此重定位用于在链接编辑时在 TLS 模型之间进行转换。

R_SPARC_LDM_CALL

可以按处理引用 __tls_get_addr() 函数的 R_SPARC_WPLT30 重定位的方式处理此重定位。此重定位是 LD 代码序列的一部分。

R_SPARC_LDO_ADD

此重定位标记 LD 代码序列中的最后一个 add 指令。包含目标文件地址(在代码序列的初始部分中计算得出)的寄存器是此指令中的第一个寄存器。此重定位允许链接编辑器标识此寄存器以进行代码变换。

R_SPARC_TLS_IE_LD

此重定位标记 32 位 IE 代码序列中的 ld 指令。此重定位用于在链接编辑时在 TLS 模型之间进行转换。

R_SPARC_TLS_IE_LDX

此重定位标记 64 位 IE 代码序列中的 ldx 指令。此重定位用于在链接编辑时在 TLS 模型之间进行转换。

R_SPARC_TLS_IE_ADD

此重定位标记 IE 代码序列中的 add 指令。用于 GOT 指针的寄存器是此序列中的第一个寄存器。

32 位 x86: 访问线程局部变量

在 x86 上,可使用以下代码序列模型来访问 TLS。

32 位 x86: 常规动态 (General Dynamic, GD)

此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。

表 8–8 32 位 x86: 常规动态线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 leal  x@tlsgd(,%ebx,1), %eax

0x07 call  x@tlsgdplt



# %eax - contains address of TLS variable
R_386_TLS_GD

R_386_TLS_GD_PLT
x

x
 

未完成的重定位

符号

GOT[n]

GOT[n + 1]
R_386_TLS_DTPMOD32

R_386_TLS_DTPOFF32
x

leal 指令生成 R_386_TLS_GD 重定位,此重定位指示链接编辑器在 GOT 中分配空间,以存储变量 xTLS_index 结构。链接编辑器通过将相对于 GOT 的偏移替换为新的 GOT 项来处理此重定位。

由于在运行前无法确定 x 的装入目标文件索引和 TLS 块索引,因此链接编辑器将根据 GOT 放置 R_386_TLS_DTPMOD32R_386_TLS_DTPOFF32 重定位,以便运行时链接程序处理。生成的 GOT 项的地址将装入寄存器 %eax 中以便调用 ___tls_get_addr()

call 指令导致生成 R_386_TLS_GD_PLT 重定位。此重定位指示链接编辑器将调用绑定到 ___tls_get_addr() 函数,并将 call 指令与 GD 代码序列关联。

call 指令必须紧跟在 leal 指令后面。要允许进行代码变换,必须满足此要求。

x86: 局部动态 (Local Dynamic, LD)

此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。

表 8–9 32 位 x86: 局部动态线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 leal  x1@tlsldm(%ebx), %eax

0x06 call  x1@tlsldmplt



# %eax - contains address of TLS block of current object



0x10 leal  x1@dtpoff(%eax), %edx



# %edx - contains address of local TLS variable x1



0x20 leal  x2@dtpoff(%eax), %edx



# %edx - contains address of local TLS variable x2
R_386_TLS_LDM

R_386_TLS_LDM_PLT

 

 

 

R_386_TLS_LDO_32

 

 

 

R_386_TLS_LDO_32
x1

x1

 

 

 

x1

 

 

 

x2
 

未完成的重定位

符号

GOT[n]

GOT[n + 1]
R_386_TLS_DTPMOD32

<none>
x

第一个 leal 指令生成 R_386_TLS_LDM 重定位。此重定位指示链接编辑器在 GOT 中分配空间,以存储当前目标文件的 TLS_index 结构。链接编辑器通过将相对于 GOT 的偏移替换为新的链接表项来处理此重定位。

装入目标文件索引在运行前无法确定。因此,将创建 R_386_TLS_DTPMOD32 重定位,并在此结构的 ti_tlsoffset 字段中填充零。call 指令使用 R_386_TLS_LDM_PLT 重定位标记。

在链接编辑时将确定每个局部符号的 TLS 偏移,因此链接编辑器将直接填充这些值。

当一个过程引用多个局部符号时,编译器将生成一次获取 TLS 块的基本地址的代码。然后,使用此基本地址计算每个符号的地址,而不需要单独调用库。

32 位 x86: 初始可执行 (Initial Executable, IE)

此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。

IE 模型存在两个代码序列。一个序列用于使用 GOT 指针的位置无关代码。另一个序列用于不使用 GOT 指针的位置相关代码。

表 8–10 32 位 x86: 初始可执行的、位置无关线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl  %gs:0, %eax

0x06 addl  x@gotntpoff(%ebx), %eax



# %eax - contains address of TLS variable
<none>

R_386_TLS_GOTIE
 

x
 

未完成的重定位

符号

GOT[n]
R_386_TLS_TPOFF
x

addl 指令生成 R_386_TLS_GOTIE 重定位。此重定位指示链接编辑器在 GOT 中创建空间,以存储符号 x 的静态 TLS 偏移。针对 GOT 表的 R_386_TLS_TPOFF 重定位未完成,以便运行时链接程序使用符号 x 的静态 TLS 偏移填充。

表 8–11 32 位 x86: 初始可执行的、位置相关线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl  %gs:0, %eax

0x06 addl  x@indntpoff, %eax



# %eax - contains address of TLS variable
<none>

R_386_TLS_IE
 

x
 

未完成的重定位

符号

GOT[n]
R_386_TLS_TPOFF
x

addl 指令生成 R_386_TLS_IE 重定位。此重定位指示链接编辑器在 GOT 中创建空间,以存储符号 x 的静态 TLS 偏移。此序列和位置无关序列之间的主要差别在于,指令直接绑定到所创建的 GOT 项,而不是使用 GOT 指针寄存器的偏移。针对 GOTR_386_TLS_TPOFF 重定位未完成,以便运行时链接程序使用符号 x 的静态 TLS 偏移填充。

将偏移直接嵌入到内存引用中可以装入变量 x 的内容(而不是地址),如下面两个序列中所示。

表 8–12 32 位 x86: 初始可执行的、位置无关动态线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl  x@gotntpoff(%ebx), %eax

0x06 movl  %gs:(%eax), %eax



# %eax - contains address of TLS variable
R_386_TLS_GOTIE

<none>
x
 

未完成的重定位

符号

GOT[n]
R_386_TLS_TPOFF
x

表 8–13 32 位 x86: 初始可执行的、位置无关线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl  x@indntpoff, %ecx

0x06 movl  %gs:(%ecx), %eax



# %eax - contains address of TLS variable
R_386_TLS_IE

<none>
x
 

未完成的重定位

符号

GOT[n]
R_386_TLS_TPOFF
x

在最后一个序列中,如果使用的是 %eax 寄存器而不是 %ecx 寄存器,则第一个指令的长度可为 5 或 6 字节。

32 位 x86: 局部可执行 (Local Executable, LE)

此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。

表 8–14 32 位 x86: 局部可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl  %gs:0, %eax

0x06 leal  x@ntpoff(%eax), %eax



# %eax - contains address of TLS variable
<none>

R_386_TLS_LE
 

x

movl 指令生成 R_386_TLS_LE_32 重定位。链接编辑器将此重定位直接绑定到在可执行文件中定义的符号的静态 TLS 偏移。在运行时不需要进行任何处理。

通过使用以下指令序列,借助相同重定位可以访问变量 x 的内容而不是地址。

表 8–15 32 位 x86: 局部可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl  %gs:0, %eax

0x06 movl  x@ntpoff(%eax), %eax



# %eax - contains address of TLS variable
<none>

R_386_TLS_LE
 

x

可以使用以下序列实现从变量装入或存储到变量,而不必计算变量的地址。请注意,x@ntpoff 表达式不能用作立即值,而应用作绝对地址。

表 8–16 32 位 x86: 局部可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movl %gs:x@ntpoff, %eax



# %eax - contains address of TLS variable
R_386_TLS_LE
x

32 位 x86: 线程局部存储的重定位类型

下表中列出的 TLS 重定位是针对 x86 定义的。此表中的说明使用以下表示法。

@tlsgd(x)

GOT 中分配两个连续项,以存储 TLS_index 结构。此结构将传递给 ___tls_get_addr()。引用此项的指令将绑定到两个 GOT 项中的第一项。

@tlsgdplt(x)

可以按处理引用 ___tls_get_addr() 函数的 R_386_PLT32 重定位的方式处理此重定位。

@tlsldm(x)

GOT 中分配两个连续项,以存储 TLS_index 结构。此结构将传递给 ___tls_get_addr()。将 TLS_indexti_tlsoffset 字段设置为 0,并且在运行时填充 ti_moduleid。对 ___tls_get_addr() 的调用将返回动态 TLS 块的起始偏移。

@gotntpoff(x)

GOT 中分配一项,并使用相对于静态 TLS 块的负 tlsoffset 初始化该项。运行时将使用 R_386_TLS_TPOFF 重定位执行此序列。

@indntpoff(x)

此表达式类似于 @gotntpoff,但它用在位置相关代码中。在 movladdl 指令中,@gotntpoff 将解析为相对于 GOT 起始位置的 GOT 插槽地址。@indntpoff 将解析为绝对 GOT 插槽地址。

@ntpoff(x)

计算相对于静态 TLS 块的负 tlsoffset

@dtpoff(x)

计算相对于 TLS 块的 tlsoffset。此值用作加数的立即值,并且不与特定寄存器关联。

@dtpmod(x)

计算包含 TLS 符号的目标文件的标识符。

表 8–17 32 位 x86: 线程局部存储的重定位类型

名称 

值 

字段 

计算 

R_386_TLS_GD_PLT

12

Word32

@tlsgdplt

R_386_TLS_LDM_PLT

13

Word32

@tlsldmplt

R_386_TLS_TPOFF

14

Word32

@ntpoff(S)

R_386_TLS_IE

15

Word32

@indntpoff(S)

R_386_TLS_GOTIE

16

Word32

@gotntpoff(S)

R_386_TLS_LE

17

Word32

@ntpoff(S)

R_386_TLS_GD

18

Word32

@tlsgd(S)

R_386_TLS_LDM

19

Word32

@tlsldm(S)

R_386_TLS_LDO_32

32

Word32

@dtpoff(S)

R_386_TLS_DTPMOD32

35

Word32

@dtpmod(S)

R_386_TLS_DTPOFF32

36

Word32

@dtpoff(S)

x64: 访问线程局部变量

在 x64 上,可使用以下代码序列模型来访问 TLS。

x64: 常规动态 (General Dynamic, GD)

此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。

表 8–18 x64: 常规动态线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 .byte 0x66

0x01 leaq  x@tlsgd(%rip), %rdi

0x08 .word 0x666

0x0a rex64

0x0b call  __tls_get_addr@plt



# %iax - contains address of TLS variable
<none>

R_AMD64_TLSGD

<none>

<none>

R_AMD64_PLT32
 

x





__tls_get_addr
 

未完成的重定位

符号

GOT[n]

GOT[n + 1]
R_AMD64_DTPMOD64

R_AMD64_DTPOFF64
x

x

__tls_get_addr() 函数采用单个参数,即 tls_index 结构的地址。与 x@tlsgd(%rip) 表达式关联的 R_AMD64_TLSGD 重定位指示链接编辑器在 GOT 中分配 tls_index 结构。tls_index 结构所需的两个元素将保留在连续的 GOT 项(GOT[n]GOT[n+1])中。这些 GOT 项与 R_AMD64_DTPMOD64R_AMD64_DTPOFF64 重定位关联。

地址 0x00 处的指令计算第一个 GOT 项的地址。此计算将 GOT 起始位置的 PC 相对地址(在链接编辑时确定)与当前指令指针相加。使用 %rdi 寄存器将结果传递给 __tls_get_addr() 函数。


注 –

leaq 指令计算第一个 GOT 项的地址。将 GOT 的 PC 相对地址(在链接编辑时确定)与当前指令指针相加来执行此计算。.byte.word.rex64 前缀确保整个指令序列占用 16 字节。由于这些前缀不会对代码造成负面影响,因此可以使用这些前缀。


x64: 局部动态 (Local Dynamic, LD)

此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。

表 8–19 x64: 局部动态线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 leaq  x1@tlsld(%rip), %rdi

0x07 call  __tls_get_addr@plt



# %rax - contains address of TLS block



0x10 leaq  x1@dtpoff(%rax), %rcx



# %rcx - contains address of TLS variable x1



0x20 leaq  x2@dtpoff(%rax), %r9



# %rcx - contains address of TLS variable x2
R_AMD64_TLSLD

R_AMD64_PLT32

 





R_AMD64_DTOFF32

 





R_AMD64_DTOFF32
x1

__tls_get_addr







 

x1





 

x2
 

未完成的重定位

符号

GOT[n]
R_AMD64_DTMOD64
x1

前两个指令与用于常规动态模型的代码序列等效,虽然不进行任何填充。这两个指令必须是连续的。x1@tlsld(%rip) 序列为符号 x1 生成 tls_index 项。此索引是指包含偏移为零的 x1 的当前模块。链接编辑器为目标文件 R_AMD64_DTMOD64 创建一个重定位。

因为各个偏移会单独装入,所以不需要 R_AMD64_DTOFF32 重定位。x1@dtpoff 表达式用于访问符号 x1 的偏移。将指令用作地址 0x10 时,可装入完整的偏移并将该偏移与 %rax 中的 __tls_get_addr() 调用的结果相加,以在 %rcx 中生成结果。x1@dtpoff 表达式创建 R_AMD64_DTPOFF32 重定位。

可以使用以下指令装入变量的值,而不必计算变量的地址。此指令与原始 leaq 指令创建相同的重定位。


movq  x1@dtpoff(%rax), %r11

如果 TLS 块的基本地址保存在一个寄存器中,则装入、存储或计算受保护的线程局部变量的地址需要一个指令。

使用局部动态模型比使用常规动态模型可获得更多好处。访问其他每个线程局部变量只需要三个新指令。此外,不需要其他 GOT 项或运行时重定位。

x64: 初始可执行 (Initial Executable, IE)

此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。

表 8–20 x64: 初始可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movq  %fs:0, %rax

0x09 addq  x@gottpoff(%rip), %rax



# %rax - contains address of TLS variable
<none>

R_AMD64_GOTTPOFF
 

x
 

未完成的重定位

符号

GOT[n]
R_AMD64_TPOFF64
x

符号 xR_AMD64_GOTTPOFF 重定位请求链接编辑器生成 GOT 项和关联的 R_AMD64_TPOFF64 重定位。然后,x@gottpoff(%rip) 指令使用 GOT 项相对于此指令结束位置的偏移。R_AMD64_TPOFF64 重定位使用根据目前所装入模块确定的符号 x 的值。偏移将写入到 GOT 项中,然后由 addq 指令装入。

要装入 x 的内容(而不是 x 的地址),可使用以下序列。

表 8–21 x64: 初始可执行的线程局部变量的访问代码 II

代码序列

初始重定位

符号

0x00 movq  x@gottpoff(%rip), %rax

0x06 movq  %fs:(%rax), %rax



# %rax - contains contents of TLS variable
R_AMD64_GOTTPOFF

<none>
x
 

未完成的重定位

符号

GOT[n]
R_AMD64_TPOFF64
x

x64: 局部可执行 (Local Executable, LE)

此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。

表 8–22 x64: 局部可执行的线程局部变量的访问代码

代码序列

初始重定位

符号

0x00 movq %fs:0, %rax

0x06 leaq  x@tpoff(%rax), %rax



# %rax - contains address of TLS variable
<none>

R_AMD64_TPOFF32
x

要装入 TLS 变量的内容(而不是 TLS 变量的地址),可使用以下序列。

表 8–23 x64: 局部可执行的线程局部变量的访问代码 II

代码序列

初始重定位

符号

0x00 movq %fs:0, %rax

0x06 movq  x@tpoff(%rax), %rax



# %rax - contains contents of TLS variable
<none>

R_AMD64_TPOFF32
x

以下序列更短。

表 8–24 x64: 局部可执行的线程局部变量的访问代码 III

代码序列

初始重定位

符号

0x00 movq  %fs:x@tpoff, %rax



# %rax - contains contents of TLS variable
R_AMD64_TPOFF32
x

x64: 线程局部存储的重定位类型

下表中列出的 TLS 重定位是针对 x64 定义的。此表中的说明使用以下表示法。

@tlsgd(%rip)

GOT 中分配两个连续项,以存储 TLS_index 结构。此结构将传递给 __tls_get_addr()。只能在确切的常规动态代码序列中使用此指令。

@tlsld(%rip)

GOT 中分配两个连续项,以存储 TLS_index 结构。此结构将传递给 __tls_get_addr()。在运行时,将目标文件的 ti_offset 偏移字段设置为零,并初始化 ti_module 偏移。对 __tls_get_addr() 函数的调用将返回动态 TLS 块的起始偏移。只能在确切的代码序列中使用此指令。

@dtpoff

计算变量相对于包含它的 TLS 块起始位置的偏移。计算所得的值将用作加数的立即值,并且不与特定寄存器关联。

@dtpmod(x)

计算包含 TLS 符号的目标文件的标识符。

@gottpoff(%rip)

GOT 中分配一项,以将变量偏移保存在初始 TLS 块中。此偏移相对于 TLS 块的结束位置 %fs:0。运算符只能与 movqaddq 指令一起使用。

@tpoff(x)

计算变量相对于 TLS 块结束位置 %fs:0 的偏移。不创建任何 GOT 项。

表 8–25 x64: 线程局部存储的重定位类型

名称 

值 

字段 

计算 

R_AMD64_DPTMOD64

16

Word64

@dtpmod(s)

R_AMD64_DTPOFF64

17

Word64

@dtpoff(s)

R_AMD64_TPOFF64

18

Word64

@tpoff(s)

R_AMD64_TLSGD

19

Word32

@tlsgd(s)

R_AMD64_TLSLD

20

Word32

@tlsld(s)

R_AMD64_DTPOFF32

21

Word32

@dtpoff(s)

R_AMD64_GOTTPOFF

22

Word32

@gottpoff(s)

R_AMD64_TPOFF32

23

Word32

@gottpoff(s)