在 x86 上,可使用以下代码序列模型来访问 TLS。
此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。
表 8–8 32 位 x86: 常规动态线程局部变量的访问代码
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 指令后面。要允许进行代码变换,必须满足此要求。
此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。
表 8–9 32 位 x86: 局部动态线程局部变量的访问代码
第一个 leal 指令生成 R_386_TLS_LDM 重定位。此重定位指示链接编辑器在 GOT 中分配空间,以存储当前目标文件的 TLS_index 结构。链接编辑器通过将相对于 GOT 的偏移替换为新的链接表项来处理此重定位。
装入目标文件索引在运行前无法确定。因此,将创建 R_386_TLS_DTPMOD32 重定位,并在此结构的 ti_tlsoffset 字段中填充零。call 指令使用 R_386_TLS_LDM_PLT 重定位标记。
在链接编辑时将确定每个局部符号的 TLS 偏移,因此链接编辑器将直接填充这些值。
当一个过程引用多个局部符号时,编译器将生成一次获取 TLS 块的基本地址的代码。然后,使用此基本地址计算每个符号的地址,而不需要单独调用库。
此代码序列实现线程局部存储的访问模型中介绍的 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 指针寄存器的偏移。针对 GOT 的 R_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 字节。
此代码序列实现线程局部存储的访问模型中介绍的 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 |