JavaScript is required to for searching.
跳过导航链接
退出打印视图
链接程序和库指南     Oracle Solaris 10 8/11 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

1.  Oracle Solaris 链接编辑器介绍

2.  链接编辑器

3.  运行时链接程序

4.  共享目标文件

5.  应用程序二进制接口与版本控制

6.  支持接口

7.  目标文件格式

8.  线程局部存储

C/C++ 编程接口

线程局部存储节

线程局部存储的运行时分配

程序启动

创建线程

启动后动态装入

延迟分配线程局部存储块

线程局部存储的访问模型

SPARC: 线程局部变量访问

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

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

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

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

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

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

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

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

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

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

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

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

x64: 线程局部变量访问

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

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

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

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

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

9.  Mapfile

A.  链接编辑器快速参考

B.  版本控制快速参考

C.  使用动态字符串标记建立依赖性

D.  直接绑定

E.  System V 发行版 4(版本 1)Mapfile

F.  链接程序和库的更新及新增功能

索引

线程局部存储的访问模型

每个 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 数据项提供存储空间。为实现最大的灵活性,共享目标文件应使用动态的 TLS 模型引用线程局部变量。


注 - 可以使用过滤器动态选择所使用的静态 TLS。共享目标文件可以生成为使用动态 TLS,并在生成使用静态 TLS 的对应共享目标文件时,充当辅助过滤器。如果资源允许装入静态 TLS 目标文件,将使用该库。否则,将回退到动态 TLS 目标文件,以确保共享目标文件提供的功能总是可用。有关过滤器的更多信息,请参见作为过滤器的共享目标文件


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

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

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

链接编辑器在了解 TLS 访问模型和要创建的目标文件类型后,便可执行转换。例如,如果一个可重定位目标文件使用 GD 访问模型,被链接到一个动态可执行文件中。在这种情况下,链接编辑器可以适当地使用 IELE 访问模型转换引用。然后执行模型所需的重定位。

下图说明了不同的访问模型,以及从一个模型到另一个模型的转换。

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

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

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
0x07 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
0x09 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
0x09 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)