无标准库C++调用thread_local变量的类方法时为何出现段错误?
问题根因
你遇到的段错误是因为没有遵守x86_64 Linux平台的TLS ABI约定,缺少TCB(线程控制块)自引用字段的初始化。
x86_64 Linux的静态链接TLS实现有明确的ABI要求:fs寄存器的基址指向TCB的起始地址,而TCB偏移0的位置必须存储TCB自身的基地址(也就是fsbase寄存器的值)。
现象解释
- 直接读写
thread_local变量的成员时,编译器会直接用fs基址 + 固定负偏移的方式寻址,不需要读取TCB的自引用字段,所以只要你把fsbase设对了,这部分操作就能正常执行,对应你代码里t1.val =7、打印Hello2的逻辑都没问题。 - 调用
thread_local对象的非静态成员函数时,需要隐式传递对象的this指针,这时候编译器计算TLS对象地址的逻辑是:先从fs:0x0读取TCB自引用的值,再加上对象的固定偏移得到对象地址。你只修改了fsbase寄存器的值,但没有初始化TCB偏移0位置的自引用字段,这个位置存储的是buf的初始垃圾值,所以计算出来的this指针是非法地址,访问时触发段错误。
你贴的汇编代码也验证了这个逻辑:调用Try1::Get()前的几行指令,就是先读fs:0x0的值,再减8得到this指针存入rdi寄存器,和上面的描述完全一致。
修复方案
在设置完fsbase后,补充初始化TCB自引用字段即可,修改后的_start代码如下:
extern "C" int _start() { auto tls_size = 4096*2; auto originalFS = _readfsbase_u64(); u64 new_fsbase = (u64)(buf+4096); _writefsbase_u64(new_fsbase); // 新增:初始化TCB自引用字段,符合ABI要求 *(u64*)new_fsbase = new_fsbase; global.val = 1; global.Get(); //Executes fine sanity_check=6; t1.val = 7; my_write(1, "Hello\n", sanity_check); my_write(1, "Hello2\n", t1.val); //Still fine my_write(1, "Hello3\n", t1.Get()); // 现在可以正常执行 my_exit(0); return 0; }
内容的提问来源于stack exchange,提问作者Eric Stotch
相关产品推荐
相关产品推荐

