最近在 ubuntu下用eclipse Neon.3 (4.6.3) 调试一个C工程时遇到一个好奇怪的问题:
一个应用程序A,调用一个静态库B,静态库中用__thread
定义了线程局部变量(TLS,thread local storage),在eclipse跟踪进B的函数,代码执行到访问TLS变量时,程序直接就崩溃了,报了SIGSEGV错误异常(无效的内存引用),但是不跟踪直接运行代码是没有问题的。
以下是lib B的代码 testlib2.c
#include <stdlib.h>
#include "testlib2.h"
static __thread int tls_v = 12345;
void test_tls(){
printf("%d\n",tls_v);
}
对应的头文件testlib2.h
#ifndef TESTLIB2_H_
#define TESTLIB2_H_
void test_tls();
#endif /* TESTLIB2_H_ */
应用程序A代码
#include "testlib2.h"
int main(void) {
test_tls();
return EXIT_SUCCESS;
}
如下图,代码执行到读取tls_v
变量的时候就直接崩溃了,如果调试时如果不跟踪进test_tls()
,程序也能正常执行。
当我把lib B改为动态库时代码,调试正常。
百思不得其解啊,没办法网上仔细翻了关于线程局部变量的相关资料。以前只了解thread local storage的基本概念,知道它是线程独享的变量,并没有深入去研究。通过这次的问题,知道线程局部变量有4种访问模型
General Dynamic (GD)
,Local Dynamic (LD)
,Initial Executable (IE)
,Local Executable (LE)
,关于这4种模型的说明参见下面oracle的文章
Thread-Local Storage Access Models
https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter8-20.html
我们只需要知道这4种模型分类代表不同的tls变量访问能力。一般来说,程序员在编译自己的c/c++代码时是不用关心这个问题的。
然而编译器在编译代码时针对这种不同的访问模型会生成不同的代码。参见下面的关于gcc编译选项的gnu官方手册(《3.16 Options for Code Generation Conventions》)中关于-ftls-model
选项的说明
-ftls-model
选项用于指定tls变量的访问模型,引起我关注不是如何用它来设置tls-model
,而是说明中的最后一行文字(如下图红框标):如果指定了-fpic
则tls-model
的默认值为General Dynamic (GD)
否则为Initial Executable (IE)
。
看到这里我想到了我的静态库B在编译时指定了-fPIC
选项。于是我去掉-fPIC
选项重新编译,再跟踪可以通过了。如下图,可以看出,没有-fPIC
选项时生成的汇编代码与前面有-fPIC
选项时是不一样的。
虽然到目前为止,我还不知道为什么eclipse下对-fPIC
选项编译的静态库中的TLS调试会造成异常,但总算知道这个问题产生的条件,后续开发中就可以避免了。
导致SIGSEGV异常问题出现是在几个条件下都具备的情况下发生的:
1.静态库中使用__thread
变量
2.静态库编译使用了-fPIC
选项
3.eclipse调试跟踪静态库的代码
参考资料
《3.16 Options for Code Generation Conventions》