eclipse/cdt:-fPIC引起的執行緒區域性變數(__thread)的SIGSEGV異常問題
最近在 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的文章
我們只需要知道這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除錯跟蹤靜態庫的程式碼