32-bit x86: 线程局部变量访问
在 x86 上,可使用以下代码序列模型访问 TLS。
32-bit x86: 常规动态 (General Dynamic, GD)
此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。
表 14-8 32-bit 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 中分配空间,以存储变量 x 的 TLS_index 结构。链接编辑器通过以相对于 GOT 的偏移替代新的 GOT 项来处理此重定位。
由于在运行前无法确定 x 的装入目标文件索引和 TLS 块索引,因此链接编辑器将根据 GOT 来放置 R_386_TLS_DTPMOD32 和 R_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 模型。
表 14-9 32-bit 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-bit x86: 初始可执行 (Initial Executable, IE)
此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。
IE 模型存在两个代码序列。一个序列针对使用 GOT 指针的位置无关代码。另一个序列针对不使用 GOT 指针的位置相关代码。
表 14-10 32-bit 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 偏移填充。
表 14-11 32-bit 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 指针寄存器的偏移。针对 GOT 的 R_386_TLS_TPOFF 重定位未完成,以便运行时链接程序使用符号 x 的静态 TLS 偏移填充。
如下面两个序列中所示,通过将偏移直接嵌入到内存引用中,可以装入变量 x 的内容(而不是地址)。
表 14-12 32-bit 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
|
|
表 14-13 32-bit 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-bit x86: 局部可执行 (Local Executable, LE)
此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。
表 14-14 32-bit 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 的内容(而非地址)。
表 14-15 32-bit 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 表达式不能用作立即值,而应用作绝对地址。
表 14-16 32-bit x86: 局部可执行的线程局部变量访问代码代码序列
| 初始重定位
| 符号
|
0x00 movl %gs:x@ntpoff, %eax
# %eax - contains address of TLS variable
| R_386_TLS_LE
| x
|
|