ELF对线程局部储存的处理(5)

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

 

未解决的重定位

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值