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

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

4.3.初始可执行TLS模式

如果已知被访问的变量出现在程序启动时的一个模块中,并且如果程序选择使用静态访问模式,可以使用一个限制更多的优化。后一个条件意味着,所产生的代码不会使用函数__tls_get_addr,这又意味着,推迟为以这个方式访问的TLS块分配内存,是不可能的。但对于动态加载的模块,推迟分配仍然是可能的。

这个优化背后的想法是,在动态链接器载入所有被执行映像(及其它一些,像由LD_PRELOAD命名的,映像)直接或间接引用的模块后,每个在这些模块的TLS块中的变量都有到TCB的固定偏移,因为要求连续分配用于初始化载入模块的内存。通过在3.4节描述的特定于架构的tlsoffsetm的公式(其中m是在其中找到这个变量的模块的ID),然后加上这个变量在TLS块中的偏移,来计算这些偏移。

这个优化的后果是,对于每个变量,在其GOT项中有一个运行时重定位,它指示动态链接器计算到TCB的偏移。这里不需要计算模块ID。因此,从常规动态模式算起,运行时重定位的数目少了一半。

在下面讨论中的代码序列实现了对一个变量x的简单访问:

extern __thread int x;

&x;

4.3.1.IA-64初始可执行TLS模式

初始可执行模式要求代码序列,在GOT的某个位置,获取由动态链接器放入的,到TCB的相对偏移。并把这个值加上线程指针。它非常短小精悍。

 

初始可执行模式代码序列

初始重定位                    符号

0x00  addl  t1=@ltoff (@tprel (x)), gp

      ;;

0x10  ld8   t2=[t1]

0x20  add   loc0=t2, tp

R_IA_64_LTOFF_TPRELD22      x

R_IA_64_DTPREL22              x

 

GOT [n]

未解决的重定位

R_IA_64_TPREL64LSB        x

 

表达式@ltoff (@tprel (x))指示链接器创建一个R_IA_64_LTOFF_TPREL22重定位,这进而要求链接器构建一个带有一个R_IA_64_TPREL64LSB重定位的GOT项。这个重定位在程序启动时由动态链接器处理,产生该变量相对于TCB块(由tp寄存器指向)的偏移。这个偏移值仅需要由0x10处的指令载入ld8,然后加上tp寄存器的值,在loc0寄存器中得到最终的地址。

为了改进行为,这些指令可以和其它指令任意混合(theinstructions can be freely mixed with other to enhance policy)。尤其是tp寄存器的处理可以被优化。

4.3.2.IA-32初始可执行TLS模式

IA-32用于初始可执行模式的代码是简单而快的。唯一的问题是定位TCB块。到目前为止所支持的,平台所使用的机制,是使用%gs段寄存器。通过这个段寄存器,访问偏移为0的内存,能够载入TCB的地址。同平时一样,我们先处理Sun的版本。

 

初始可执行模式代码序列

初始重定位                    符号

0x00 movl  x@tpoff (%ebx), %edx

0x06 movl  %gs:0, %eax

0x0c subl  %edx, %eax

R_386_TLS_IE_32               x

 

GOT [n]

未解决的重定位

R_386_TLS_TPOFF32            x

 

汇编器为x@tpoff (%ebx)表达式产生一个R_386_TLS_IE_32重定位,它要求链接器产生一个带有R_386_TLS_TPOFF32重定位的GOT项。然后这个GOT项的偏移被用在这个指令中。重定位R_386_TLS_TPOFF32,在程序启动期间,由动态链接器在这个期间载入的模块中查找符号x,得到处理。得到的偏移被写入该GOT项,随后被0x00处的指令载入%edx寄存器。

0x06处的movl指令把当前线程的线程指针载入%eax寄存器。这一步最终要被调整为平台访问线程指针所采用的方法。

最后,subl指令计算出最终的地址。注意到必须从线程指针减去这个偏移。在IA-32所采用的版本II的线程局部储存数据结构中,TLS块位于TCB之前。

这个代码序列仅要求三条指令,并像常规动态模式那样占据14个字节。这可以做得更好,正如GNU版本所显示的那样。这里有两个不同的GNU版本,一个用于位置无关代码,它使用GOT指针;一个不使用GOT指针。位置无关版本:

 

初始可执行模式代码序列,II

初始重定位                    符号

0x00 movl  %gs:0, %eax

0x06 addl  x@gotntpoff (%ebx), %eax

 

R_386_TLS_GOTIE               x

 

GOT [n]

未解决的重定位

R_386_TLS_TPOFF               x

 

这个代码序列的行为基本相同,除了加上而不是减去GOT的值,它把从GOT载入与算术运算合并到一条指令中。没有GOT指针的版本是:

 

初始可执行模式代码序列,III

初始重定位                    符号

0x00 movl  %gs:0, %eax

0x06 addl  x@indntpoff (%ebx), %eax

 

R_386_TLS_IE                   x

 

GOT [n]

未解决的重定位

R_386_TLS_TPOFF               x

 

这个代码序列形成相同的动态重定位,不过在指令中,它解析到GOT槽的绝对地址,而不是到GOT头的相对地址。

GNU版本使用一个,计算变量在TLS块中的负偏移,而不是正偏移的重定位。这是一个重大的改进,偏移可以直接嵌入在一个内存地址中(参见下面)。

那么在Sun模式下,加载x的内容(而不是它的地址),可以使用以下的代码序列:

 

初始可执行模式代码序列,IV

初始重定位                    符号

0x00 movl  x@tpoff (%ebx), %edx

0x06 movl  %gs:0, %eax

0x0c subl   %edx, %eax

0x0emovl  (%eax), %eax

R_386_TLS_IE_32                x

 

GOT [n]

未解决的重定位

R_386_TLS_TPOFF32             x

 

这是前面的代码序列在末尾加上一个额外载入。相比较下,GNU序列不需要那么长。位置无关版本看起来就这样:

 

初始可执行模式代码序列,V

初始重定位                    符号

0x00 movl x@gotntpoff (%ebx), %eax

0x06 movl %gs: (%eax), %eax

R_386_TLS_GOTIE                x

 

GOT [n]

未解决的重定位

R_386_TLS_TPOFF               x

 

版本IITCB之前就是静态TLS,因而通过%gs寄存器指向的内存位置负的偏移,直接访问它。对于非位置无关代码,代码序列就像:

 

初始可执行模式代码序列,VI

初始重定位                    符号

0x00 movl x@indntpoff, %ecx

0x06 movl %gs: (%ecx), %eax

R_386_TLS_IE                   x

 

GOT [n]

未解决的重定位

R_386_TLS_TPOFF               x

 

在这个最后的序列中,如果使用%eax寄存器,而不是上面的%ecx寄存器,第一条指令可能是56字节长。

4.3.3.SPARC初始可执行TLS模式

这里给出的SPARC初始可执行代码序列,依赖于在%l7寄存器中的GOT指针及在%g7中的线程指针。如果这些寄存器可用的话,代码序列是简单的。对于3264位平台,我们有两个不同的版本,因为我们要从内存载入一个GOT项,而这个项在3264位机器有不同的大小。

 

初始可执行模式代码序列

初始重定位                    符号

0x00 sethi  %hi (@tpoff (x)), %o0

0x04 or    %o0, %lo (@tpoff (x)), %o0

0x08 ld    [%l7 + %o0], %o0

0x0c add  %g7, %o0, %o0

R_SPARC_TLS_IE_HI22            x

R_SPARC_TLS_IE_LO10           x

R_SPARC_TLS_IE_LD             x

R_SPARC_TLS_IE_ADD            x

 

GOT [n]

未解决的重定位

R_SPARC_TLS_TPOFF32          x

 

代码把GOT项的偏移常量载入%o0寄存器。操作符@tpoff(x)构建重定位R_SPARC_TLS_IE_HI22R_SPARC_TLS_IE_LO10,它们指示链接器分配GOT项,并附加重定位R_SPARC_TLS_TPOFF32。然后指令ld载入这个GOT项。为了使链接器能识别这个指令,加入了一个R_SPARC_TLS_IE_LD重定位。最后指令add计算x的地址。这个指令具有R_SPARC_TLS_IE_ADD重定位标记。注意到由动态链接器产生的偏移,希望是负的,使得它可以与线程指针相加。

 

初始可执行模式代码序列

初始重定位                    符号

0x00 sethi  %hi (@tpoff (x)), %o0

0x04 or    %o0, %lo (@tpoff (x)), %o0

0x08 ld    [%l7 + %o0], %o0

0x0c add  %g7, %o0, %o0

R_SPARC_TLS_IE_HI22            x

R_SPARC_TLS_IE_LO10           x

R_SPARC_TLS_IE_LDX            x

R_SPARC_TLS_IE_ADD            x

 

GOT [n]

未解决的重定位

R_SPARC_TLS_TPOFF64          x

 

64位版本基本上一样,除了GOT项按64位计算及载入。标记ld指令的重定位也相应地不同。

4.3.4.SH初始可执行TLS模式

这个初始可执行代码序列没有新奇之处。它是一个RISC机器,在有限的偏移及没有直接可用的线程指针的限制下,能产生的最简单的代码。

 

初始可执行模式代码序列

初始重定位                    符号

0x00 mov.l  lf, r0

0x02 stc    gbr, r1

0x04 mov.l  @(r0, r12), r0

0x06 bar    2f

0x08  add  r1, r0

      .align 2

1:    .long x@gottpoff

2:   

 

 

 

 

 

 

R_SH_TLS_IE_32                x

 

GOT [n]

未解决的重定位

R_SH_TLS_TPOFF32             x

 

首先载入的是x相对于线程指针的偏移。像往常一样,这是间接完成的。标记1:的字仅具有与代码序列关联的重定位。链接器将填入GOT项的偏移,这个项将包含静态TLS块中变量的偏移。这个GOT项将由动态链接器填写。在0x04的指令把该GOT项的值载入r0寄存器,并向它加入线程寄存器的值。对于一个加法,线程寄存器的值不是直接可用的,因此它首先被移入一个正式的寄存器。

对于这个初始可执行代码序列,再一次的,它们不能改动。链接器必须找到使用由x@gottpoff所产生的重定位的指令。

4.3.5.Alpha初始可执行TLS模式

这个初始可执行模式要求线程指针从PCB载入一个通用寄存器。并可预计这应该在函数的一开始就完成,之后这个值被重用。但为了完整性,在例子的序列中包括了PALcall

 

初始可执行模式代码序列

初始重定位                    符号

0x00 call_pal PAL_rduniq

0x04 mov   $0, $tp

     ...

0x10 ldq    $1, x ($gp)   ¡gottprel

0x14 addq   $tp, $1, $1

 

 

 

R_ALPHA_GOTTPREL            x

 

GOT [n]

未解决的重定位

R_ALPHA_TPREL64              x

 

重定位指示符!gottprel指导链接器构建一个包含R_ALPHA_TPREL64重定位的GOT项。这个重定位,在程序启动期间,由动态链接器处理,为这个变量产生相对于TCB块的偏移。这个偏移仅需要被载入,并加上线程指针的值,来得到绝对地址。

4.3.6.x86-64初始可执行TLS模式

x86-64初始可执行模式的代码使用%fs段寄存器来定位TCB。使用这个段寄存器,访问偏移为0处的内存,能够载入TCB的地址。

 

初始可执行模式代码序列

初始重定位                    符号

0x00 movq %fs:0, %rax

0x09 addq  x@gottpoff (%rip), %rax

 

R_X86_64_GOTTPOFF            x

 

GOT [n]

未解决的重定位

R_X86_64_TPOFF64              x

 

汇编器根据表达式x@gottpoff (%rip),为符号x产生一个R_X86_64_GOTTPOFF重定位,它要求链接器构建一个具有R_X86_64_TPOFF64重定位的GOT项。接着该GOT项相对于该指令结尾的偏移被用在这个指令里。重定位R_X86_64_TPOFF64,在程序启动期间,由动态链接器在那时载入的模块中查找符号x,得到处理。这个偏移被写入这个GOT项,并随后为addq指令载入。

为了载入x的内容(而不是其地址),可用一个同样长的序列:

 

初始可执行模式代码序列,II

初始重定位                    符号

0x00 movq x@gottpoff (%rip), %rax

0x09 movq %fs: (%rax), %rax

R_X86_64_GOTTPOFF            x

 

GOT [n]

未解决的重定位

R_X86_64_TPOFF64              x

 

4.3.7.s390初始可执行TLS模式

用于这个初始可执行模式的代码小而快。代码需要从GOT得到相对于线程指针的偏移,并加上线程指针。这里有3个不同的版本。使用小GOT-fpic)的位置无关的版本是:

 

初始可执行模式代码序列

初始重定位                    符号

ear %r7, %a0

 

 

R_390_TLS_GOTIE12              x

l   %r9, x@gotntpoff (%r12)

la  %r10, 0 (%r9, %r7) # %r10 = &x

 

GOT [n]

未解决的重定位

R_390_TLS_TPOFF32              x

 

为表达式x@gotntpoff所创建的重定位R_390_TLS_GOTIE12,使得链接器产生一个具有R_390_TLS_TPOFF重定位的GOT项。链接器用,从GOT头到这个生成的GOT项的12位偏移,来代替x@gotntpoff。重定位R_390_TLS_TPOFF,在程序启动期间,由动态链接器处理。

使用大GOT-fPIC)的位置无关版本是:

 

初始可执行模式代码序列

初始重定位                    符号

ear %r7, %a0

 

 

 

R_390_TLS_LOAD              x

 

 

R_390_TLS_GOTIE32            x

l   %r8, .L1-.L0 (%r13)

l   %r9, 0 (%r8, %r12)

la  %r10, 0 (%r9, %r7) # %r10 = &x

...

.L0 : # literal pool, address in %r13

.L1: .long x@gotntpoff

 

GOT [n]

未解决的重定位

R_390_TLS_TPOFF32              x

 

重定位R_390_TLS_GOTIE32R_390_TLS_GOTIE12的作用相同,区别在于链接器使用32位而不是12GOT偏移来替代x@gotntpoff

没有GOT指针的版本是:

 

初始可执行模式代码序列

初始重定位                    符号

ear %r7, %a0

 

 

 

R_390_TLS_LOAD              x

 

 

R_390_TLS_IE32                 x

l   %r8, .L1-.L0 (%r13)

l   %r9, 0 (%r8)

la  %r10, 0 (%r9, %r7) # %r10 = &x

...

.L0 : # literal pool, address in %r13

.L1: .long x@indntpoff

 

GOT [n]

未解决的重定位

R_390_TLS_TPOFF32              x

 

重定位R_390_TLS_IE32指示链接器,像为R_390_TLS_GOTIE{12, 32}那样,构建相同的GOT项,不过链接器使用所创建GOT项的绝对地址来替代x@indntpoff表达式。这使得没有GOT指针的版本,对于位置无关代码是不充分可用的。

4.3.8.s390x初始可执行TLS模式

s390x的初始可执行模式与s390的以相似的方式工作。使用小GOT-fpic)的位置无关版本是:

 

初始可执行模式代码序列

初始重定位                    符号

ear %r7, %a0

sllg %r7, %r7, 32

ear %r7, %a1

 

 

 

R_390_TLS_GOTIE12             x

lg  %r9, x@gotntpoff (%r12)

la  %r10, 0 (%r9, %r7) # %r10 = &x

 

GOT [n]

未解决的重定位

R_390_TLS_TPOFF32              x

 

使用大GOT-fPIC)的位置无关版本是:

 

初始可执行模式代码序列

初始重定位                    符号

ear %r7, %a0

sllg %r7, %r7, 32

ear %r7, %a1

 

 

 

 

R_390_TLS_LOAD              x

 

 

 

R_390_TLS_GOTIE64            x

lg  %r8, .L1-.L0 (%r13)

lg  %r9, 0 (%r8, %r12)

la  %r10, 0 (%r9, %r7) # %r10 = &x

...

.L0: # literal pool, address in %r13

.L1: .quad x@gotntpoff

 

GOT [n]

未解决的重定位

R_390_TLS_TPOFF64              x

 

对于R_390_TLS_GOTIE64,链接器使用64GOT偏移替代x@gotntpoff。不使用GOT指针的版本是:

 

初始可执行模式代码序列

初始重定位                    符号

ear %r7, %a0

sllg %r7, %r7, 32

ear %r7, %a1

 

 

 

R_390_TLS_IEENT              x

R_390_TLS_LOAD              x

larl  %r8, x@indntpoff

lg   %r9, 0 (%r8)

la  %r10, 0 (%r9, %r7) # %r10 = &x

 

GOT [n]

未解决的重定位

R_390_TLS_TPOFF32              x

 

重定位R_390_TLS_IEENT使得x@indntpoff被从larl指令到这个GOT项的相对偏移所替代。因为这个指令是相对于PC的(pc relative),这个不使用GOT指针的版本可以同样用在位置无关代码中。

 

原创粉丝点击