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

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 寄存器中得到最终的地址。

为了改进行为,这些指令可以和其它指令任意混合( the instructions 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

 

版本 II TCB 之前就是静态 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 寄存器,第一条指令可能是 5 6 字节长。

4.3.3. SPARC 初始可执行 TLS 模式

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

 

初始可执行模式代码序列

初始重定位                     符号

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_HI22 R_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_GOTIE32 R_390_TLS_GOTIE12 的作用相同,区别在于链接器使用 32 位而不是 12 GOT 偏移来替代 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 ,链接器使用 64 GOT 偏移替代 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 指针的版本可以同样用在位置无关代码中。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值