SPARC: 常规动态 (General Dynamic, GD)
SPARC: 局部动态 (Local Dynamic, LD)
32 位 SPARC: 初始可执行 (Initial Executable, IE)
64 位 SPARC: 初始可执行 (Initial Executable, IE)
SPARC: 局部可执行 (Local Executable, LE)
32 位 x86: 常规动态 (General Dynamic, GD)
32 位 x86: 初始可执行 (Initial Executable, IE)
32 位 x86: 局部可执行 (Local Executable, LE)
x64: 常规动态 (General Dynamic, GD)
x64: 初始可执行 (Initial Executable, IE)
每个 TLS 引用都遵循下列访问模型之一。这些模型按照最常见、但最少优化到速度最快、但限制最大的顺序列出。
此模型允许从共享目标文件或动态可执行文件中引用所有 TLS 变量。如果是第一次从特定线程引用 TLS 块,此模型还支持延迟分配此块。
此模型是对 GD 模型的优化。编译器可能会确定变量在要生成的目标文件中是局部绑定或受到保护的。在这种情况下,编译器将指示链接编辑器静态绑定动态的 tlsoffset 并使用此模型。与 GD 模型相比,此模型可提供更好的性能。每个函数只需要调用一次 tls_get_addr() 即可确定 dtv0,m 的地址。进行链接编辑时绑定的动态 TLS 偏移会与每个引用的 dtv0,m 地址相加。
此模型只能引用初始静态 TLS 中包含的 TLS 变量。此模板由进程启动时可用的所有 TLS 块和一个小的备份预留空间组成。请参见程序启动。在此模型中,给定变量 x 相对于线程指针的偏移存储在 x 的 GOT 项中。
此模型可以从初始进程启动后通过延迟装入、过滤器或 dlopen(3C) 装入的共享库中引用有限数量的 TLS 变量。该访问可通过固定的备份预留空间来实现。此预留空间只能为未初始化的 TLS 数据项提供存储空间。为实现最大的灵活性,共享目标文件应使用动态的 TLS 模型引用线程局部变量。
注 - 可以使用过滤器动态选择所使用的静态 TLS。共享目标文件可以生成为使用动态 TLS,并在生成使用静态 TLS 的对应共享目标文件时,充当辅助过滤器。如果资源允许装入静态 TLS 目标文件,将使用该库。否则,将回退到动态 TLS 目标文件,以确保共享目标文件提供的功能总是可用。有关过滤器的更多信息,请参见作为过滤器的共享目标文件。
此模型只能引用动态可执行文件的 TLS 块中包含的 TLS 变量。链接编辑器静态地计算相对于线程指针的偏移,而不需要进行动态重定位或额外引用 GOT。此模型不能用于引用动态可执行文件外部的变量。
链接编辑器可以将代码从更常规的访问模型转换为更优化的模型(如果确定适合进行转换)。这种转换可以使用独特的 TLS 重定位来实现。这些重定位不仅请求执行更新,还会标识要使用的 TLS 访问模型。
链接编辑器在了解 TLS 访问模型和要创建的目标文件类型后,便可执行转换。例如,如果一个可重定位目标文件使用 GD 访问模型,被链接到一个动态可执行文件中。在这种情况下,链接编辑器可以适当地使用 IE 或 LE 访问模型转换引用。然后执行模型所需的重定位。
下图说明了不同的访问模型,以及从一个模型到另一个模型的转换。
图 8-2 线程局部存储的访问模型和转换
在 SPARC 上,可使用以下代码序列模型访问线程局部变量。
此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。
表 8-2 SPARC: 常规动态的线程局部变量访问代码
|
sethi 和 add 指令分别生成 R_SPARC_TLS_GD_HI22 和 R_SPARC_TLS_GD_LO10 重定位。这些重定位将指示链接编辑器在 GOT 中分配空间,以存储变量 x 的 TLS_index 结构。链接编辑器通过以相对于 GOT 的偏移替代新的 GOT 项来处理此重定位。
x 的装入目标文件索引和 TLS 块索引在运行前无法确定。因此,链接编辑器将根据 GOT 来放置 R_SPARC_TLS_DTPMOD32 和 R_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 指针寄存器。
此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。
表 8-3 SPARC: 局部动态的线程局部变量访问代码
|
第一个 sethi 指令和 add 指令分别生成 R_SPARC_TLS_LDM_HI22 和 R_SPARC_TLS_LDM_LO10 重定位。这些重定位指示链接编辑器在 GOT 中分配空间,以存储当前目标文件的 TLS_index 结构。链接编辑器通过以相对于 GOT 的偏移替代新的 GOT 项来处理此重定位。
装入目标文件索引在运行前无法确定。因此,将创建 R_SPARC_TLS_DTPMOD32 重定位,并在 TLS_index 结构的 ti_tlsoffset 字段中填充零。
第二个 add 和 call 指令分别使用 R_SPARC_TLS_LDM_ADD 和 R_SPARC_TLS_LDM_CALL 重定位进行标记。
后面的 sethi 指令和 xor 指令分别生成 R_SPARC_LDO_HIX22 和 R_SPARC_TLS_LDO_LOX10 重定位。在链接编辑时已得知了每个局部符号的 TLS 偏移,因此将直接填入这些值。add 指令使用 R_SPARC_TLS_LDO_ADD 重定位进行标记。
当一个过程引用多个局部符号时,编译器将生成代码一次获取 TLS 块的基本地址。然后,使用此基本地址计算每个符号的地址,而不需要单独调用库。
注 - 包含 add 指令(由 R_SPARC_TLS_LDO_ADD 标记)中的 TLS 目标文件地址的寄存器必须是指令序列中的第一个寄存器。在代码变换期间,此要求允许链接编辑器标识寄存器。
此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。
表 8-4 32 位 SPARC: 初始可执行的线程局部变量访问代码
|
sethi 指令和 or 指令分别生成 R_SPARC_TLS_IE_HI22 和 R_SPARC_TLS_IE_LO10 重定位。这些重定位指示链接编辑器在 GOT 中创建空间,以存储符号 x 的静态 TLS 偏移。针对 GOT 的 R_SPARC_TLS_TPOFF32 重定位未完成,以便运行时链接程序使用符号 x 的负静态 TLS 偏移填充。ld 和 add 指令分别使用 R_SPARC_TLS_IE_LD 和 R_SPARC_TLS_IE_ADD 重定位进行标记。
注 - 用作 add 指令(由 R_SPARC_TLS_IE_ADD 重定位标记)的 GOT 指针的寄存器必须是此指令中的第一个寄存器。在代码变换期间,此要求允许链接编辑器标识 GOT 指针寄存器。
此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。
表 8-5 64 位 SPARC: 初始可执行的线程局部变量访问代码
|
此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。
表 8-6 SPARC: 局部可执行的线程局部变量访问代码
|
sethi 和 xor 指令分别生成 R_SPARC_TLS_LE_HIX22 和 R_SPARC_TLS_LE_LOX10 重定位。链接编辑器将这些重定位直接绑定到在可执行文件中定义的符号的静态 TLS 偏移。在运行时不需要进行重定位处理。
下表中列出的 TLS 重定位是针对 SPARC 定义的。表中的说明使用以下表示法。
在 GOT 中分配两个连续项,以存储 TLS_index 结构。此信息将传递给 __tls_get_addr()。此结构的 ti_tlsoffset 字段设置为 0,并且在运行时填充 ti_moduleid。对 __tls_get_addr() 的调用将返回动态 TLS 块的起始偏移。
计算相对于 TLS 块的 tlsoffset。
计算相对于静态 TLS 块的负 tlsoffset。此值与线程指针相加以计算 TLS 地址。
计算包含 TLS 符号的目标文件的标识符。
表 8-7 SPARC: 线程局部存储的重定位类型
|
一些重定位类型的语义不只是简单的计算。
此重定位标记 GD 代码序列的 add 指令。用于 GOT 指针的寄存器是该序列中的第一个寄存器。此重定位所标记的指令出现在 R_SPARC_TLS_GD_CALL 重定位所标记的 call 指令之前。此重定位用于在链接编辑时在 TLS 模型之间进行转换。
此重定位将按引用 __tls_get_addr() 函数的 R_SPARC_WPLT30 重定位的处理方式进行处理。此重定位是 GD 代码序列的一部分。
此重定位标记 LD 代码序列的第一个 add 指令。用于 GOT 指针的寄存器是该序列中的第一个寄存器。此重定位所标记的指令出现在 R_SPARC_TLS_GD_CALL 重定位所标记的 call 指令之前。此重定位用于在链接编辑时在 TLS 模型之间进行转换。
此重定位将按引用 __tls_get_addr() 函数的 R_SPARC_WPLT30 重定位的处理方式进行处理。此重定位是 LD 代码序列的一部分。
此重定位标记 LD 代码序列中的最后一个 add 指令。包含目标文件地址(在代码序列的初始部分中计算得出)的寄存器是此指令中的第一个寄存器。此重定位允许链接编辑器标识此寄存器以进行代码变换。
此重定位标记 32 位 IE 代码序列中的 ld 指令。此重定位用于在链接编辑时在 TLS 模型之间进行转换。
此重定位标记 64 位 IE 代码序列中的 ldx 指令。此重定位用于在链接编辑时在 TLS 模型之间进行转换。
此重定位标记 IE 代码序列中的 add 指令。用于 GOT 指针的寄存器是此序列中的第一个寄存器。
在 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: 初始可执行的、位置无关的线程局部变量访问代码
|
addl 指令生成 R_386_TLS_GOTIE 重定位。此重定位指示链接编辑器在 GOT 中创建空间,以存储符号 x 的静态 TLS 偏移。针对 GOT 表的 R_386_TLS_TPOFF 重定位未完成,以便运行时链接程序使用符号 x 的静态 TLS 偏移填充。
表 8-11 32 位 x86: 初始可执行的、位置相关的线程局部变量访问代码
|
addl 指令生成 R_386_TLS_IE 重定位。此重定位指示链接编辑器在 GOT 中创建空间,以存储符号 x 的静态 TLS 偏移。此序列和位置无关序列之间的主要差别在于,指令直接绑定到所创建的 GOT 项,而不使用 GOT 指针寄存器的偏移。针对 GOT 的 R_386_TLS_TPOFF 重定位未完成,以便运行时链接程序使用符号 x 的静态 TLS 偏移填充。
如下面两个序列中所示,通过将偏移直接嵌入到内存引用中,可以装入变量 x 的内容(而不是地址)。
表 8-12 32 位 x86: 初始可执行的、位置无关的动态线程局部变量访问代码
|
表 8-13 32 位 x86: 初始可执行的、位置无关的线程局部变量访问代码
|
在最后一个序列中,如果使用寄存器 %eax 而非寄存器 %ecx,第一个指令的长度将可以为 5 或 6 个字节。
此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。
表 8-14 32 位 x86: 局部可执行的线程局部变量访问代码
|
movl 指令生成 R_386_TLS_LE_32 重定位。链接编辑器将此重定位直接绑定到在可执行文件中定义的符号的静态 TLS 偏移。在运行时不需要进行任何处理。
通过使用以下指令序列,可以借助同一重定位访问变量 x 的内容(而非地址)。
表 8-15 32 位 x86: 局部可执行的线程局部变量访问代码
|
使用以下序列可以实现从变量装入或存储到变量,而不必计算变量的地址。请注意,x@ntpoff 表达式不能用作立即值,而应用作绝对地址。
表 8-16 32 位 x86: 局部可执行的线程局部变量访问代码
|
下表中列出的 TLS 重定位是针对 x86 定义的。表中的说明使用以下表示法。
此重定位将按引用 ___tls_get_addr() 函数的 R_386_PLT32 重定位的处理方式进行处理。
在 GOT 中分配两个连续项,以存储 TLS_index 结构。此结构将传递给 ___tls_get_addr()。TLS_index 的 ti_tlsoffset 字段将设置为 0,并且在运行时填充 ti_moduleid。对 ___tls_get_addr() 的调用将返回动态 TLS 块的起始偏移。
在 GOT 中分配一项,并使用相对于静态 TLS 块的负 tlsoffset 初始化该项。运行时将使用 R_386_TLS_TPOFF 重定位执行此序列。
此表达式类似于 @gotntpoff,但它用在位置相关代码中。在 movl 或 addl 指令中,@gotntpoff 将解析为相对于 GOT 起始位置的 GOT 插槽地址。@indntpoff 将解析为绝对 GOT 插槽地址。
计算相对于静态 TLS 块的负 tlsoffset。
计算相对于 TLS 块的 tlsoffset。此值用作加数的立即值,并且不与特定寄存器关联。
计算包含 TLS 符号的目标文件的标识符。
表 8-17 32 位 x86: 线程局部存储的重定位类型
|
在 x64 上,可使用以下代码序列模型访问 TLS。
此代码序列实现线程局部存储的访问模型中介绍的 GD 模型。
表 8-18 x64: 常规动态的线程局部变量访问代码
|
__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 个字节。由于前缀不会对代码造成负面影响,因此可以使用前缀。
此代码序列实现线程局部存储的访问模型中介绍的 LD 模型。
表 8-19 x64: 局部动态的线程局部变量访问代码
|
前两个指令与用于常规动态模型的代码序列等效,虽然不进行任何填充。这两个指令必须是连续的。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 项或运行时重定位。
此代码序列实现线程局部存储的访问模型中介绍的 IE 模型。
表 8-20 x64: 初始可执行的线程局部变量访问代码
|
符号 x 的 R_AMD64_GOTTPOFF 重定位请求链接编辑器生成 GOT 项和关联的 R_AMD64_TPOFF64 重定位。然后,x@gottpoff(%rip) 指令使用 GOT 项相对于此指令结束位置的偏移。R_AMD64_TPOFF64 重定位使用根据目前已装入模块确定的符号 x 的值。偏移将写入到 GOT 项中,然后由 addq 指令装入。
要装入 x 的内容(而非 x 的地址),可使用以下序列。
表 8-21 x64: 初始可执行的线程局部变量访问代码 II
|
此代码序列实现线程局部存储的访问模型中介绍的 LE 模型。
表 8-22 x64: 局部可执行的线程局部变量访问代码
|
要装入 TLS 变量的内容(而非 TLS 变量的地址),可使用以下序列。
表 8-23 x64: 局部可执行的线程局部变量的访问代码 II
|
以下序列更为简短。
表 8-24 x64: 局部可执行的线程局部变量访问代码 III
|
下表中列出的 TLS 重定位是针对 x64 定义的。表中的说明使用以下表示法。
在 GOT 中分配两个连续项,以存储 TLS_index 结构。此结构将传递给 __tls_get_addr()。在运行时,目标文件的 ti_offset 偏移字段将设置为零,并且 ti_module 偏移将初始化。对 __tls_get_addr () 函数的调用将返回动态 TLS 块的起始偏移。此指令可以用在确切的代码序列中。
计算变量相对于包含该变量的 TLS 块的起始位置的偏移。计算所得的值将用作加数的立即值,并且不与特定寄存器关联。
计算包含 TLS 符号的目标文件的标识符。
在 GOT 中分配一项,以将变量偏移保存在初始 TLS 块中。此偏移相对于 TLS 块的结束位置 %fs:0。运算符只能与 movq 或 addq 指令一起使用。
计算变量相对于 TLS 块结束位置 %fs:0 的偏移。不会创建任何 GOT 项。
表 8-25 x64: 线程局部存储的重定位类型
|