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

来源:互联网 发布:mac popo登录408 编辑:程序博客网 时间:2024/06/06 05:19

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)。对于处理少于2138K)字节或多于221字节数据,优化是可能的,这时使用的重定位分别是R_IA_64_TPREL14R_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_HIX22R_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

 

未解决的重定位

 

代码分别在r0r1寄存器载入了地址的两个部分,线程指针的相对偏远(链接时刻已知)及线程指针。因为就这个代码序列而言,没有进一步优化的可能,带有标签.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-32GNU版本类似。它仅是把可以作为立即数得到的偏移,加上线程指针。线程指针从%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

 

未解决的重定位

 

原创粉丝点击