TLS变量访问模型

  • Post author:
  • Post category:其他




TLS变量的4种访问模型

ELF中对TLS(Thread Local Storage)变量的4种访问模型,参见:

tls.pdf



gcc中使用的TLS变量访问模型

gcc中关于TLS变量访问模型的控制,可以通过-ftls-model=model [global-dynamic / local-dynamic / initial-exec / local-exec]来指定。当使用-fPIC编译时,默认是使用了”global-dynamic”模型,而当不使用-fPIC编译时,默认是使用了”initial-exec”模型,参见:

gcc.pdf


我们将通过如下的例子展示gcc中的TLS变量访问模型。

对于如下test_tls.c文件:

#include <errno.h>
#include <stdio.h>

int print_date();
__thread int myno = -1;

int print_date()
{
    int y = 2021;
    int m = 12;
    int d = 15;
    int ret = printf("today is %04d-%02d-%02d !\n", y, m, d);

    if(ret > 0)
      myno = ret;

    return 0;
}

当我们使用如下编译命令编译时:

gcc test_tls.c -c -fPIC -o tls.o

查看tls.o中的重定位信息:

readelf -r tls.o :

Relocation section '.rela.text' at offset 0x2f8 contains 6 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
......
000000000046  000a00000013 R_X86_64_TLSGD    0000000000000000 myno - 4
00000000004e  000e00000004 R_X86_64_PLT32    0000000000000000 __tls_get_addr - 4
000000000060  000a00000013 R_X86_64_TLSGD    0000000000000000 myno - 4
000000000068  000e00000004 R_X86_64_PLT32    0000000000000000 __tls_get_addr - 4

可以看到,myno的重定位类型,为R_X86_64_TLSGD,且使用了__tls_get_addr,属于global-dynamic访问模型。而当我们将其编译链接成动态库时:

gcc -shared -o libtls.so tls.o

查看libtls.so中的信息:

readelf -r libtls.so :

Relocation section '.rela.dyn' at offset 0x550 contains 10 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
......
000000200fe0  001000000010 R_X86_64_DTPMOD64 0000000000000000 myno + 0
000000200fe8  001000000011 R_X86_64_DTPOFF64 0000000000000000 myno + 0
......

而当我们使用如下命令编译时:

gcc test_tls.c -c -o tls.o

查看tls.o中的重定位信息:

readelf -r tls.o :

Relocation section '.rela.text' at offset 0x2c0 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
......
000000000047  000a00000017 R_X86_64_TPOFF32  0000000000000000 myno + 0
000000000051  000a00000017 R_X86_64_TPOFF32  0000000000000000 myno + 0

可以看到,myno的重定位类型,为R_X86_64_TPOFF32,属于local-exec访问模型,这与gcc手册中描述的默认值不相同。但同时,gcc手册中还有一句话:

Note that the choice is subject to optimization: the compiler may use a more efficient model for symbols not visible outside of the translation unit, or if ‘-fpic’ is not given on the command line.

也就是说,当gcc在编译的时候,如果发现这个这个TLS变量就是在编译单元内的,没发现它是个外部符号,并且没有-fPIC编译选项的时候,就会做优化。

如果此时,我们有一个主程序以extern外部变量的方式引用myno,那么这个重定位类型如何?

对于这样一段main函数代码test_main.c:

#include "test_tls.c"
extern __thread int myno;

int main()
{
    print_date();
    if(myno > 0)
        return 0;
    
    return -1;
}

使用如下编译命令(为了保留tls_main中的重定位信息,使用 -Wl,-q 链接选项)

gcc test_main.c -I./ -L./ -ltls -o tls_main -Wl,-q

再查看tls_main中对myno的重定位类型:

readelf -r tls_main :

Relocation section '.rela.text' at offset 0x19a0 contains 25 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
......
000000400599  004400000016 R_X86_64_GOTTPOFF 0000000000000000 myno - 4
0000004005a8  004400000016 R_X86_64_GOTTPOFF 0000000000000000 myno - 4
......
0000004005cb  004400000016 R_X86_64_GOTTPOFF 0000000000000000 myno - 4
......

可以看到,myno的重定位类型为R_X86_64_GOTTPOFF,即是initial-exec访问模型。



修改TLS变量访问模型

经测试发现:

当编译选项不含-fPIC时,编译单元内的TLS变量的重定位类型会被统一优化为local-exec访问模型,且无法通过-ftls-model选项达到修改目的。如果引用外部TLS变量,该重定位类型为initial-exec访问模型。

当编译选项含有-fPIC时,可通过-ftls-model任意切换TLS变量访问模型,例如将global-dynamic切换到initial-exec:

gcc test_tls.c -c -fPIC -ftls-model=initial-exec -o tls.o

查看tls.o中的重定位信息:

readelf -r tls.o :

Relocation section '.rela.text' at offset 0x2c8 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
......
000000000045  000a00000016 R_X86_64_GOTTPOFF 0000000000000000 myno - 4
000000000054  000a00000016 R_X86_64_GOTTPOFF 0000000000000000 myno - 4

此时,将其编译链接为动态库:

gcc -shared -o libtls.so tls.o

查看libtls.so中的信息:

readelf -r libtls.so :

Relocation section '.rela.dyn' at offset 0x4e8 contains 9 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
......
000000200fe8  000f00000012 R_X86_64_TPOFF64  0000000000000000 myno + 0
......



版权声明:本文为mscej原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。