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
......