x64: 线程局部变量访问
在 x64 上,可使用以下代码序列模型访问 TLS。
x64: 常规动态 (General Dynamic, GD)
此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。
表 14-18 x64: 常规动态的线程局部变量访问代码代码序列
| 初始重定位
| 符号
|
0x00 .byte 0x66
0x01 leaq x@tlsgd(%rip), %rdi
0x08 .word 0x6666
0x0a rex64
0x0b call __tls_get_addr@plt
# %rax - 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_DTPMOD64 和 R_AMD64_DTPOFF64 重定位相关联。
地址 0x00 处的指令计算第一个 GOT 项的地址。通过将 GOT 起始位置的 PC 相对地址(在链接编辑时得知)与当前指令指针相加进行此计算。结果将通过 %rdi 寄存器传递给 __tls_get_addr() 函数。
注 - leaq 指令计算第一个 GOT 项的地址。通过将 GOT 的 PC 相对地址(在链接编辑时得知)与当前指令指针相加进行此计算。.byte、.word、和 .rex64 前缀可确保整个指令序列占用 16 个字节。由于前缀不会对代码造成负面影响,因此可以使用前缀。
x64: 局部动态 (Local Dynamic, LD)
此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。
表 14-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
# %r9 - 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 模型。
表 14-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
|
|
符号 x 的 R_AMD64_GOTTPOFF 重定位请求链接编辑器生成 GOT 项和关联的 R_AMD64_TPOFF64 重定位。然后,x@gottpoff(%rip) 指令使用 GOT 项相对于此指令结束位置的偏移。R_AMD64_TPOFF64 重定位使用由目前已装入模块确定的符号 x 值。偏移将写入到 GOT 项中,然后由 addq 指令装入。
要装入 x 的内容(而非 x 的地址),可使用以下序列。
表 14-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 模型。
表 14-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 变量的地址),可使用以下序列。
表 14-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
|
|
以下序列更为简短。
表 14-24 x64: 局部可执行的线程局部变量访问代码 III代码序列
| 初始重定位
| 符号
|
0x00 movq %fs:x@tpoff, %rax
# %rax - contains contents of TLS variable
| R_AMD64_TPOFF32
| x
|
|