4.4. 局部可执行TLS 模式
类似局部动态模式相对于常规动态模式加入的优化,优化局部动态模式得到局部可执行模式。它使用的限制性比局部动态模式还要大。它仅能用于执行映像自身的代码,并访问该执行映像本身的变量。
把使用限制在执行映像意味着,仅对于局部可执行模式而言, TLS 块可以相对于线程指针取址。限定变量是那些定义在执行映像里的变量,这意味着总是使用第一个 TLS 块,这个块用于执行映像,因而对于地址计算来说,其它块的大小都不再重要。它亦意味着,当构建最终映像时,链接器知道到 TCB 的偏移是多少。计算实际偏移的公式依赖于架构,它包括涉及线程指针,第一个 TLS 块的偏移 tlsoffset1 ,及变量在这个 TLS 块中的偏移 offsetx 的一个加法或减法。其结果在链接时刻已知,并作为一个立即数在代码中可用。
在后面章节中,架构描述里的代码实现了以下代码行,这些代码必须在可执行映像中:
static __thread int x;
&x
4.4.1. IA-64 局部可执行 TLS 模式
用于这个模式的代码序列非常简单。如果线程寄存器的值,适当地保存在一个可用在 add 指令的寄存器中,代码序列仅为每个新增的变量增加一条指令。
局部可执行模式代码序列 | 初始重定位 符号 |
0x00 ld8 t2=tp ;; 0x10 addl loc0=@tprel (x), r2 |
R_IA_64_TPREL22 x |
| 未解决的重定位 |
除了把线程指针的值移入 r2 寄存器,为 add 指令做准备以外,代码所做的就是把偏移常量加上线程指针( add 指令不能直接使用 tp 寄存器)。变量赋予重定位 R_IA_64_TPREL22 ,链接器执行确定 tlsoffset1 + offsetx 。即,除了变量在 TLS 块的偏移外,只有 TLS 块的对齐会影响结果。
如同初始可执行模式,这里给出的代码序列是三个可能中的之一。允许处理的线程局部数据可以多达 221 字节( 2M )。对于处理少于 213 ( 8K )字节或多于 221 字节数据,优化是可能的,这时使用的重定位分别是 R_IA_64_TPREL14 及 R_IA_64_TPREL64I ,指令是一个短 add 或长 move 。
4.4.2. IA-32 局部可执行 TLS 模式
IA-32 代码序列基本上只是把,可作为立即数得到的偏移,加上线程指针的值。线程指针的确定方式可能不一样;在 Sun 的模式中,它可以通过从 %gs 段的 0 偏移处载入来确定。
局部可执行模式代码序列 | 初始重定位 符号 |
0x00 movl $x@tpoff, %edx 0x05 movl %gs:0, %eax 0x0b subl %edx, %eax | R_386_TLS_LE_32 x |
| 未解决的重定位 |
在这里使用的表达式 x@tpoff 不是相对于 GOT 的偏移,而是一个立即数。对此,链接器产生了一个可以被链接器解析的 R_386_TLS_LE_32 重定位。这样确定的值是变量在 TLS 块中的正偏移。把它从线程指针值减去,就在 %eax 寄存器中得到 x 的最终地址。 GNU 版本再一次具有更短小的优势。
局部可执行模式代码序列, II | 初始重定位 符号 |
0x00 movl %gs:0, %eax 0x06 leal x@ntpoff (%eax), %eax |
R_386_TLS_LE x |
| 未解决的重定位 |
这里 GNU 版本使用了一个计算变量在 TLS 块中负偏移,而不是正偏移的重定位。其显著的好处在于,偏移可以被直接嵌入到一个内存地址(参见下面)。
那么在 Sun 的模式下,载入 x 的内容(而不是其地址),使用以下代码序列:
局部可执行模式代码序列, III | 初始重定位 符号 |
0x00 movl $x@tpoff, %edx 0x05 movl %gs:0, %eax 0x0b subl %edx, %eax 0x0d movl (%eax), %eax | R_386_TLS_LE_32 x |
| 未解决的重定位 |
在末尾有一个额外载入外,这与前面的序列相同。相比而言, GNU 序列还是要短些:
局部可执行模式代码序列, IV | 初始重定位 符号 |
0x00 movl %gs:0, %eax 0x06 movl x@ntpoff (%eax), %eax |
R_386_TLS_LE x |
| 未解决的重定位 |
如果不是要计算变量的地址,而是载入或保存它,可以使用下面的序列。注意到在这个情形下,表达式 x@ntpoff 不作为一个立即数,而是作为一个绝对地址来使用。
局部可执行模式代码序列, V | 初始重定位 符号 |
0x00 movl %gs:x@ntpoff, %eax | R_386_TLS_LE x |
| 未解决的重定位 |
载入或保存操作比计算地址更简单,这个事实一开始肯定令人吃惊。不过段寄存器的处理是怪异的。你可以把段寄存器 %gs 视作,把虚拟地址空间的 0 地址移到另一个位置的,一个手段。这个新位置,一旦计算出来,对 CPU 内部而言是可以直接访问的。这就是为什么在用户层面计算它的地址时,额外要求转移后( shifted )的地址空间的第一个字包含移位值( shift value )或地址。
4.4.3. SPARC 局部可执行 TLS 模式
SPARC 的局部可执行模式是尽可能精简的。它只是把可作为一个立即数得到的偏移,加上线程指针值。
局部可执行模式代码序列, V | 初始重定位 符号 |
0x00 sethi %hix (@tpoff (x)), %o0 0x04 xor %o0, %lox (@tpoff (x)), %o0 0x08 add %g7, %o0, %o0 | R_SPARC_TLS_LE_HIX22 x R_SPARC_TLS_LE_LOX22 x |
| 未解决的重定位 |
表达式 %hix *tpoff (x)) 及 %lox (tpoff (x)) 使得链接器发布 R_SPARC_TLS_LE_HIX22 及 R_SPARC_TLS_LE_LOX22 重定位,这些重定位要求,链接器在这些指令中,把偏移值作为立即数填入。这会把偏移载入 %o0 寄存器。接下来的 add 指令要求这里的偏移是负数。为了计算最终的地址,偏移被加上线程寄存器 %g7 的值。这个 add 指令没有被重定位标记。其原因是,链接器不需要出于放宽( relaxation )来识别这个指令,它不可能再简化了。
4.4.4. SH 局部可执行 TLS 模式
正如其它架构那样,局部可执行模式的代码序列实在简单。主要的差别是对于所有的 SH 代码,需要一个数据重定位。
局部可执行模式代码序列, V | 初始重定位 符号 |
0x00 mov.l .Ln, r0 0x02 stc gbr, r1 0x04 add r1, r0 ... .Ln: .long x@tpoff |
R_SH_TLS_LE_32 x |
| 未解决的重定位 |
代码分别在 r0 及 r1 寄存器载入了地址的两个部分,线程指针的相对偏远(链接时刻已知)及线程指针。因为就这个代码序列而言,没有进一步优化的可能,带有标签 .Ln 的字的实际位置不重要。
4.4.5. Alpha 局部可执行 TLS 模式
Alpha 的局部可执行模式序列小而精。依照应用所期望的 TLS 的大小,可以有三个序列可选择。在下面的序列中,预期会调用 PAL_rduniq ,并且线程指针被拷贝入 $tp 。
局部可执行模式代码序列, V | 初始重定位 符号 |
0x00 lda $l, x1($tp) !tprel … 0x10 ldah $1, x2 ($tp) !tprelhi 0x14 lda $1, x2 ($1) !tprello ... 0x20 ldq $1, x3 ($gp) !gottprel 0x24 addl $1, $tp, $1 | R_ALPHA_TPREL16 x1
R_ALPHA_TPRELHI x2 R_ALPHA_TPRELLO x2
R_ALPHA_GOTTPREL x3 |
| 未解决的重定位 |
第一个序列可用于 32K ,第二个则用于 2G ,而第三个可用于 64 位的完全位移( displacement )。
4.4.6. x86-64 局部可执行 TLS 模式
x86-64 代码序列与 IA-32 的 GNU 版本类似。它仅是把可以作为立即数得到的偏移,加上线程指针。线程指针从 %fs 段的 0 偏移处载入。
局部可执行模式代码序列 | 初始重定位 符号 |
0x00 movq %fs:0, %rax 0x09 leaq x@tpoff (%rax), %rax |
R_x86_64_TPOFF32 x |
| 未解决的重定位 |
为了载入一个 TLS 变量,而不是计算其地址,可以使用以下序列:
局部可执行模式代码序列 | 初始重定位 符号 |
0x00 movq %fs:0, %rax 0x09 movq x@tpoff (%rax), %rax |
R_x86_64_TPOFF32 x |
| 未解决的重定位 |
或更简短:
局部可执行模式代码序列 | 初始重定位 符号 |
0x00 movq %fs: x@tpoff, %rax | R_x86_64_TPOFF32 x |
| 未解决的重定位 |
4.4.7. s390 局部可执行 TLS 模式
s390 的局部可执行模式仅是把可作为立即数得到的偏移加上线程指针。通常,这个偏移可以有 32 位,这要求一个字常数库的项。
局部可执行模式代码序列 | 初始重定位 符号 |
ear %r7, %a0 |
R_390_TLS_LE32 x |
l %r8, .L1-.L0 (%r13) | |
la %r9, 0 (%r8, %r7) # %r9 = &x ... .L0 : # literal pool, address in %r13 .L1: .long x@ntpoff | |
| 未解决的重定位 |
链接器把重定位 R_390_TLS_LE32 解析为到线程指针的负偏移。
4.4.8. s390x 局部可执行 TLS 模式
s390x 的局部可执行模式与 s390 的区别仅在于线程指针的提取,及偏移的大小。
局部可执行模式代码序列 | 初始重定位 符号 |
ear %r7, %a0 sllg %r7, %r7, 32 ear %r7, %a1 |
R_390_TLS_LE64 x |
lg %r8, .L1-.L0 (%r13) | |
la %r9, 0 (%r8, %r7) # %r9 = &x ... .L0: # literal pool, address in %r13 .L1: .quad x@ntpoff | |
| 未解决的重定位 |