ELF对线程局部储存的处理(3)
来源:互联网 发布:mac popo登录408 编辑:程序博客网 时间:2024/06/06 14:17
4.2.局部动态TLS模式
局部动态TLS模式是常规动态TLS模式的一个优化。如果编译器认识到,这个线程局部变量的引用所在的对象与其定义所在对象相同,它可以产生遵循这个模式的代码。这包括,比如,具有文件域的线程局部变量,或被定义为保护或隐藏的变量(更多这方面的信息参考:通用ELF ABI规范)。这里我们把这些类型的变量援引为受保护的。
提醒一下,一个线程局部变量由模块ID及在该模块的TLS块中的偏移来定义。在确信变量定义在引用它的模块中时,其偏移在链接时刻是已知的。模块ID未知(除非它是主应用,这种情况下可以执行更多的优化)。因此仍然需要调用__tls_get_addr来获得模块ID,并最终分配TLS块。如果__tls_get_addr的参数,通过传入0作为偏移,可以使得函数计算TLS块的起始地址。那么有可能多次重用这个值,通过把受保护的线程局部变量的偏移加上TLS块的起始地址,来访问多个变量。编译器可以很容易且高效地产生这样的代码。
但要记住,通常,如果只有一个受保护的线程局部变量,使用局部动态模式没有真正的好处。[1]这将意味着像常规动态模式的一次__tls_get_addr调用,加上额外的加法来计算地址。但如果多个变量以这个方式处理,天平就开始倾斜了。我们仍然只有一个函数调用,而每个变量增加一个加法运算。因为常规及局部动态模式之间的差别不只是替代部分指令,而是产生相当不同的代码,因此从常规到局部动态模式的优化不能由链接器来执行。必须需要编译器来完成,可能是在程序员的协助下。
在架构特定的描述中例子的实现等同于这些代码片段:
static __thread int x1;
static __thread int x2;
&x1;
&x2;
4.2.1.IA-64局部动态TLS模式
IA-64的指令集使得确定,使用局部动态模式变量的地址的,代码序列比常规动态模式的代码序列要短。另外。变量的偏移不需要由动态链接器计算,并且GOT可以少一个元素。
局部动态模式代码序列
初始重定位 符号
0x00 mov loc0=gp
0x06 addl t1=@ltoff (@dtpmod (x)), gp
0x0c addl out1=@dtprel (x), r0
;;
0x10 ld8 out0=[t1]
0x16 br.callrp=__tls_get_addr
R_IA_64_LTOFF_DTPMOD22 x
R_IA_64_DTPREL22 x
GOT [n]
未解决的重定位
R_IA_64_DTPMOD64LSB x
与常规动态模式的区别在于,不需要通过把@ltoff (@dtprel(x))的值到gp,然后从这个地址载入,来找出变量的偏移。取而代之,使用地址0x0c的addl指令来直接计算偏移(这正是IA-64如何载入一个立即值)。这意味着需要的ld8指令可以少一个,编译器可以把这个槽用于别的东西。
然而这个代码序列有一个限制。在TLS块中的偏移仅有21位。如果线程局部数据的数量超过了221字节(2M),就要使用另外的代码。更大的偏移必须使用长的move指令来载入,这个指令允许载入完整的64位偏移。另外,编译器可以进一步优化addl指令,如果它被知会线程局部变量的需求不会超过213字节(8K)。这些情形下使用的重定位分别将是R_IA_64_DTPREL64I及R_IA_64_DTPREL14。不管编译器如何选择,通常不可能由链接器来确定最好或必须的指令,因此选择将由用户使用编译选项来决定。上面例子的代码序列是一个好的折衷,可作为缺省设定。
在一个函数必须访问多个受保护的线程局部变量的情形下,节省会更大。在这种情况下,不调用__tls_get_addr来计算任何变量的地址,而是仅计算TLS块的起始地址。
局部动态模式代码序列,II
初始重定位 符号
0x00 mov loc0=gp
0x06 addl t1=@ltoff (@dtpmod (x)), gp
0x0c addl out1=@dtprel (x), r0
;;
0x10 ld8 out0=[t1]
0x16 br.callrp=__tls_get_addr
;;
0x20 mov gp=loc0
0x26 mov r2=ret0
;;
0x30 addl loc1=@dtprel (x1), r2
0x36 addl loc2=@dtprel (x2), r2
R_IA_64_LTOFF_DTPMOD22 x1
R_IA_64_DTPREL22 x1
R_IA_64_DTPREL22 x2
GOT [n]
未解决的重定位
R_IA_64_DTPMOD64LSB x
这个代码的第一部分与前面的代码非常相似,前面的代码只使用了一个变量。唯一的区别在于显式指定的0作为第二个参数传递给了__tls_get_addr。这是计算找到x1的模块,即也是这个代码所在模块,的TLS块的开头。
为了完成计算,需要额外的代码,这些代码始于把该函数调用的返回值保存到一个随后可以被使用的地方(寄存器r2)。最后我们看到真正计算变量地址的代码。它非常简单,因为我们仅要把变量的偏移加上TLS块的基址。这个偏移,在链接时刻,是替换了重定位为R_IA_64_DTPREL22的代码的,已知的立即数。正如在上面的代码中,这个重定位是大小和复杂度的一个折衷。这里,编译器同样可以使用短的add指令或长的move指令。
4.2.2.IA-32局部动态TLS模式
局部动态模式的代码序列没有提供比常规动态模式更多的好处,除非使用了多个变量。原因很清楚。调用__tls_get_addr的代码完全没有改变,因为它只是计算GOT项的地址。GOT项必须由两个字组成,尽管字ti_offset在链接时刻已知。在需要多个变量的情况下,使用这个模式会带来好处。下面是Sun版本的代码序列。
局部动态模式代码序列
初始重定位 符号
0x00 leal x1@tmdnx (%ebx), %edx
0x06 pushl %edx
0x07 call x@TLSPLT
0x0c popl %edx
…
0x10 movl $x1@dtpoff, %edx
0x15 addl %eax, %edx
…
0x20 movl $x2@dtpoff, %edx
0x25 addl %eax, %edx
R_386_TLS_LDM_32 x1
R_386_TLS_LDM_PUSH x1
R_386_TLS_LDM_CALL x1
R_386_TLS_LDM_POP x1
R_386_TLS_LDO_32 x1
R_386_TLS_LDO_32 x2
GOT [n]
未解决的重定位
R_386_TLS_DTPMOD32 x1
在第一条指令中,表达式x1@tmdnx (%ebx)指示汇编器产生一个R_386_TLS_LDM_32。这进而告诉链接器在GOT上构建一个特殊的tls_index对象,其中成员ti_offset是0。这就是为什么在上面的代码中,GOT只有一个未解决的重定位。当处理R_396_TLS_DTPMOD32重定位时,成员ti_module将被填入代码所在模块的ID。
当__tls_get_addr的调用返回时,寄存器%eax包含了,对于当前线程,变量所在模块的TLS块的地址。这时所需要做的是,通过加上变量偏移来完成地址计算。在地址0x10及0x15的指令,通过把偏移加上寄存器%eax的内容,计算变量x1的地址。为此,使用表达式$x1@dtpoff,它产生一个R_386_TLS_LDO_32重定位。这个重定位引用变量x1,并且其偏移可以由链接器算出,并填入指令。
使用第二个变量仅要求重复加法,它的工作要比函数调用少。并且虽然使用了两个变量,在GOT中仅构建了一个tls_index元素。
对于GNU版本,其代码序列带来的好处更明显。
局部动态模式代码序列
初始重定位 符号
0x00 leal x@tlsldm (, %ebx, 1), %eax
0x06 call ___tls_get_addr@plt
…
0x10 leal x1@dtpoff (%eax), %edx
…
0x20 leal x2@dtpoff (%eax), %edx
R_386_TLS_LDM x1
R_386_PLT32 ___tls_get_addr
R_386_TLS_LDO_32 x1
R_386_TLS_LDO_32 x2
GOT [n]
未解决的重定位
R_386_TLS_DTPMOD32 x
TLS基址的计算遵循Sun的版本,其改进归究于__tls_get_addr的调用规范。GOT包含一个特殊的tls_index项,其中成员ti_offset将是0。唯一的区别在于把表达式x1@tlsldm (%ebx)用于GOT项的地址。这个表达式的处理与x1@tmdnx(%ebx)相似,除了为指令构建的重定位是R_386_TLS_LDM,而不是R_386_TLS_LDM_32。
不过调用规范不是仅有的好处。计算最终地址的指令也优化了。利用leal指令的强大功能,在Sun版本值所需的2条指令被合并为1条。该指令的重定位保持不变。不过这还没完。如果不计算变量的地址,这个值就需要是已载入的值,通过使用
movlx1@dtpoff (%eax), %edx
这个指令将得到与原来leal指令相同的重定位。在这样的一个变量中存入值,其行为是相同的。
只要TLS块的基址在寄存器中保护得好好的,保存或计算其地址受保护线程局部变量,只是一条指令的事。
4.2.3.SPARC局部动态TLS模式
对于SPARC,就像IA-32,当仅使用一个变量时,局部动态模式没有好处。对SPARC其坏处甚至更大,这归究于其RISC指令集的本质。如果使用了多个变量,产生的代码看起来如下:
局部动态模式代码序列
初始重定位 符号
0x00 sethi %hi (@tmdnx (x1)), %o0
0x04 add %o0, %lo (@tmndx (x1)), %o0
0x08 add %l7, %o0, %o0
0x0c call __tls_get_addr
...
0x10 sethi %hix (@dtpoff (x1)), %l1
0x14 xor %l1, %lox (@dtpoff (x1)), %l1
0x18 add %o0, %l1, %l1
...
0x20 sethi %hix (@dtpoff (x2)), %l2
0x24 xor %l2, %lox (@dtpoff (x2)), %l2
0x28 add %o0, %l2, %l2
R_SPARC_TLS_LDM_HI22 x1
R_SPARC_TLS_LDM_LO10 x1
R_SPARC_TLS_LDM_ADD x1
R_SPARC_TLS_LDM_CALL x1
R_SPARC_TLS_LDO_HIX22 x1
R_SPARC_TLS_LDO_LOX22 x1
R_SPARC_TLS_LDO_ADD x1
R_SPARC_TLS_LDO_HIX22 x2
R_SPARC_TLS_LDO_LOX22 x2
R_SPRAC_TLS_LDO_ADD x2
GOT [n]
GOT [n]
未解决的重定位,32位
R_SPARC_TLS_DTPMOD32 x1
未解决的重定位,64位
R_SPARC_TLS_DTPMOD64 x1
前四条指令基本上与常规动态模式的代码序列等效。不过这个代码使用tmndx(x1),而不是@dtlndx(x),来为符号x产生一个tls_index项,tmndx (x)构建一个特殊的偏移为0引用当前模块(包含x1)的索引。链接器将为这个对象构建一个重定位,依赖于平台,它或者是R_SPARC_TLS_DTPMOD32,或者是R_SPARC_TLS_DTPMOD64。重定位DTPREL是不需要的。
这样做的原因是,偏移是分别载入的。表达式@dtpoff (x1)用于访问符号x1的偏移。使用地址0x10及0x14的两条指令,完整的偏移被载入,加上在%o0中__tls_get_addr的调用结果,产生的结果在%l1中。表达式@dtpoff (x1)分别为%hix ()及%lox ()部分,创建重定位R_SPARC_TLS_LDO_HIX22及R_SPARC_TLS_LDO_LOX22。指令add被标记上重定位R_SAPCR_TLS_LDO_ADD,因此链接器可以识别它。
使用局部动态模式的好处是,每增加一个变量,只需要添加三条指令,而不需要增加GOT项及运行时重定位。总之,如果运行时处理运行时重定位的开销可以避免,即便只有一个变量,这个模式可能也是可取的。
4.2.4.SH局部动态TLS模式
如同其他架构,在SH中为局部动态模式产生的代码,与常规动态模式的不同之处在于,查找第一个局部符号需要额外的努力。对于第二及以后的查找要轻松得多,尤其对于SH。
局部动态模式代码序列
初始重定位 符号
0x00 mov.l lf, r4
0x02 mova 2f, r0
0x04 mov.l 2f, r1
0x06 add r0, r1
0x08 jsr @r1
0x0a add r12, r4
0x0c bra 3f
0x0e nop
.align 2
1: .long x@tlsgd
2: .long __tls_get_addr@plt
3: …
mov.l .Lp, r1
mov.l r0, r1
…
mov.l .Lq, r1
mov.l r0, r1
…
.Lp: .long x1@dtpoff
.Lq: .long x2@dtpoff
R_SH_TLS_LD_32 x1
R_SH_TLS_LDO_32 x1
R_SH_TLS_LDO_32 x2
GOT [n]
未解决的重定位
R_SH_TLS_DTPMOD32 x1
前7条指令与常规动态模式的相同。只是这一次符号查找是特殊的,因为它使用模块的TLS数据段为0的偏移。这与在SPARC及IA-32上做的一样。与常规动态模式不同之处在于2个GOT项只有一个需要附加重定位。域ti_offset总是0。
在这些准备完成后,确定局部变量地址的代码是简单的。它包含在TLS段中载入变量为链接时刻常量的偏移,并加上先前找到的,模块及当前线程的TLS段的起始地址。
与常规动态模式的代码序列比较,两个变量的一次查找节省了三条指令,一个GOT项,及一个函数调用。对于三个TLS变量的查找,节约的将是八条指令,两个GOT项,及两次函数调用。显而易见,一旦使用多个变量,选择局部动态模式有好处。
值得注意的是,在这个代码序列中,对标记着.Lp及.Lq的变量偏移的内存分配,可以被推迟并最终与其它数据合并(正如上面的例子代码那样)。在构建了它们之后,就不再需要修改mov.l及add指令。从局部动态模式到局部可执行模式的优化不会触及这些指令。因此它们可以被编译器自由移动,在代码中它们不需要固定的位置。
4.2.5.Alpha局部动态TLS模式
对于Alpha,正如IA-32,当仅使用一个变量时,局部动态模式没有优势。如果使用多个变量,所产生的代码看起来如下:
局部动态模式代码序列
初始重定位 符号
0x00 lad $16, x($gp) !tlsldm!1
0x04 ldq $27, __tls_get_addr ($gp) !literal!1
0x08 jsr $26, ($27), 0 !lituse_tlsldm!1
0x0c ldah $29, 0 ($26) !gpdisp!2
0x10 lda $29, 0 ($29) !gpdisp!2
…
0x20 lda $1, x1 ($0) !dtprel
…
0x30 ldah $1, x2 ($0) !dtprelhi
0x34 lda $1, x2 ($1) !dtprello
…
0x40 ldq $1, x3 ($gp) !gotdtprel
0x44 addq $0, $1, $1
R_ALPHA_TLSLDM x
R_ALPHA_LITERAL __tls_get_addr
R_ALPHA_LITUSE 5
R_ALPHA_GPDISP 4
R_ALPHA_DTPREL16 x1
R_ALPHA_DTPRELHI x2
R_ALPHA_DTPRELLO x2
R_ALPHA_GOTDTPREL x3
GOT [n]
未解决的重定位
R_ALPHA_DTPMOD64 x
在0x00及0x14之间的指令基本上与于常规局部模式相同。不同之处在于使用!tlsldm而不是!tlsgd,它将为当前对象构建一个具有0偏移的tls_index项。
这个偏移随后被加上dtprel重定位中的一个。为此,依赖于期望的TLS数据段的大小,我们有三个代码生成选项可选择。在0x20的序列可用于15位的正位移(32K);在0x30的序列可用于31位的正位移(2G);而在0x40的序列则是用于64位的位移(displacement)。
4.2.6.x86-64局部动态TLS模式
类似于IA-32及SPARC,如果仅有一个局部变量以这个方式访问,这个访问模式对常规动态模式没有优势可言。
局部动态模式代码序列
初始重定位 符号
0x00 leaq x1@tlsld (%rip), %rdi
0x07 call __tls_get_add@plt
…
0x10 leaq x1@dtpoff (%rax), %rcx
…
0x20 leaq x2@dtpoff (%rax), %r9
R_X86_64_TLSGD x1
R_X86_64_PLT32 __tls_get_addr
R_X86_64_DTPOFF32 x1
R_X86_64_DTPOFF32 x2
GOT [n]
未解决的重定位
R_X86_64_DTPMOD64 x1
前两条指令与常规动态模式的基本相同,虽然少了填充位。这两条指令必须相连。代码使用x1@tlsld *%rip),而不是x1@tlsgd (%rip),来为符号x1构建tls_index项,它创建了一个特殊的,带有偏移0的引用当前模块(包含x1)的索引。链接器将为这个对象仅创建一个重定位,R_X86_64_DTPMOD64。重定位R_X86_64_DTPOFF64不需要。
这样的原因是偏移是被另外载入的。表达式x1@dtpoff用于访问符号x1的偏移。使用在0x10的指令,完整的偏移被载入,并加上在%rax中__tls_get_addr调用的结果,产生的结果则在%rcx中。表达式x1@dtpoff构建了重定位R_X86_64_DTPOFF32。如果不计算变量的地址,这个值就需要是已载入的值,通过使用
movqx1@dtpoff (%rax), %r11
这个指令将得到与原来的leaq指令相同的重定位。在这样的一个变量中存入值,其行为是相同的。
只要TLS块的基址在寄存器中保护得好好的,保存或计算其地址受保护线程局部变量,只是一条指令的事。
使用局部动态模式的好处是,对于每个增加的变量,只需要增加3条新指令,不需要增加GOT项或运行时重定位。总之,如果运行时处理运行时重定位的开销可以避免,即便只有一个变量,这个模式可能也是可取的。
4.2.7.s390局部动态TLS模式
对于s390,如果仅访问一个变量,局部动态模式的代码序列,对于常规动态模式,没有优势。它甚至会稍微更糟糕一些,因为需要载入一个额外的字常数库(literal pool)的项(x@tlsldm及x@dtpoff,而不仅是x@ltsgd),并加上__tls_get_offset函数调用的返回值。如果访问多个局部变量,局部动态模式要好于常规动态模式,因为每增加一个变量,仅需要一个简单的字常数库的载入,而不是一整个函数调用。
局部动态模式代码序列
初始重定位 符号
l %r6, .L1-.L0 (%r13)
ear %r7, %a0
R_390_TLS_LDCALL x1
R_390_TLS_LDM32 x1
R_390_TLS_LDO32 x1
R_390_TLS_LDO32 x2
l %r2, .L2-.L0 (%r13)
bas %r14, 0 (%r6, %r13)
la %8, 0 (%r2, %r7)
l %r9, .L3-.L0 (%r13)
la %r10, 0 (%r10, %r8) # %r10 = &x1
l %r9, .L4-.L0 (%r13)
la %r10, 0 (%r10, %r18) # %r10 = &x2
...
.L0 : # literal pool, address in %r13
.L1: .long __tls_get_offset@plt-.L0
.L2: .long x1@ltsldm
.L3: .long x1@dtpoff
.L4: .long x2@dtpoff
GOT [n]
未解决的重定位
R_390_TLS_DTPMOD x1
正如IA-32局部动态TLS模式,表达式x1@tlsldm在字常数库中的语义是,指示汇编器发布一个R_390_TLS_LDM32重定位。链接器将为它在GOT上构建一个特殊的tls_index对象,其中成员ti_offset置为0。当处理重定位R_390_TLS_LDM32时,成员ti_module将被填入代码所在模块的ID。字常数库的项x1@dtpoff及x2@dtpoff被汇编器翻译为重定位R_390_TSL_LDO32。链接器将为该模块计算TLS块中x1及x2的偏移,并写入字常数库。
这个指令序列被分成四部分。第一部分类似于常规动态模式的第一部分。第二部分,使用凭借字常数库项x@tlsldm创建的tls_index对象的偏移,调用__tls_get_offset。在这个调用之前,必须设立GOT寄存器%r12。在第二部分的第三条指令之后,%r8包含了代码所在模块的线程局部内存的地址。代码序列的第三部分显示了如何计算线程局部变量x1及x2的地址。第四部分显示了代码序列所需要的字常数库的项。
局部动态访问模式的所有指令都可以被编译器自由地安排,只要满足明显的数据依赖,并且考虑到了bas指令的函数调用语义。
4.2.8.s390x局部动态TLS模式
s390x的局部动态访问模式类似于s390的版本。在s390及s390s的常规动态模式之间的差别依然存在。线程指针的提取要求3条指令而不是1条,使用bras1指令来完成到__tls_get_offset的跳转,并且偏移是64位的而不是32位。
局部动态模式代码序列
初始重定位 符号
ear %r7, %a0
sllg %r7, %r7, 32
ear %r7, %a1
R_390_TLS_LDCALL x1
R_390_TLS_LDM64 x1
R_390_TLS_LDO64 x1
R_390_TLS_LDO64 x2
lg %r2, .L2-.L0 (%r13)
bras1 %r14, __tls_get_offset@plt
la %r8, 0 (%r2, %r7)
lg %9, .L2-.L0 (%r13)
la %r10, 0 (%r9, %r8) # %r10 = &x1
lg %r9, .L3-.L0 (%r13)
la %r10, 0 (%r9, %r8) # %r10 = &x2
...
.L0 : # literal pool, address in %r13
.L1: .quad x1@tlsldm
.L2: .quad x1@dtpoff
.L3: .quad x2@dtpoff
GOT [n]
未解决的重定位
R_390_TLS_DTPMOD x1
[1] IA-64具有优势
- ELF对线程局部储存的处理(3)
- ELF对线程局部储存的处理(1)
- ELF对线程局部储存的处理(2)
- ELF对线程局部储存的处理(4)
- ELF对线程局部储存的处理(5)
- ELF对线程局部储存的处理(6)
- ELF对线程局部储存的处理(7)
- ELF对线程局部储存的处理(8)
- ELF对线程局部储存的处理(1) __thread
- Windows核心编程笔记(十七) 线程局部储存
- Bfd对elf文件头的处理
- TLS 局部线程储存 一个演示小例子
- 局部的变量与成员变量对线程的影响
- 线程局部存储,Part 3:编译器和链接器对隐式TLS的支持
- 消息线程,对MFC消息机制的局部模拟
- java实现线程安全的栈(链式储存)
- 线程局部存储(TLS)的使用
- java服务对线程并发的处理
- 正式开启技术博客
- ELF对线程局部储存的处理(1)
- Oracle 知识备份(二)2011年4月4号
- ELF对线程局部储存的处理(2)
- Foxmail中设置Gmail教程
- ELF对线程局部储存的处理(3)
- ELF对线程局部储存的处理(4)
- Oracle 知识备份(三)2011年4月4号
- ELF对线程局部储存的处理(5)
- 品牌战略
- Ubuntu 10.10 下面禁用触摸板
- JSP Servlet 小记
- 如何在各种字符串类型之间进行转换(VS2010)
- FastWei is born...