链接程序和库指南

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