靜態(tài)區(qū)析構(gòu)時引發(fā)的線程安全 heap-use-after-free

靜態(tài)區(qū)析構(gòu)時引發(fā)的線程安全

背景

給openssl 1.0.2 是非線程安全的,需要CRYPTO_set_locking_callback設(shè)置函數(shù)來控制加鎖和解鎖.
example.cpp

std::vector<std::mutex> g_openssl_locks{static_cast<size_t>(CRYPTO_num_locks())};

//可能是多個線程在調(diào)用這個函數(shù)
void openssl_locking_function(int mode, int n, const char * /* file */, int /* line */) {
    if(mode & CRYPTO_LOCK) {
        g_openssl_locks[n].lock();
    }
    else {
        g_openssl_locks[n].unlock();
    }
}

CRYPTO_set_locking_callback(openssl_locking_function);

如果此時程序強行退出可能出現(xiàn)線程安全錯誤.
比如用instrument ThreadSanitizer運行測試的話就會報錯heap-use-after-free

原因

當(dāng)程序退出的時候,會銷毀全局/靜態(tài)對象. 此時別的線程可能還沒有終止,最后訪問了一個已經(jīng)被析構(gòu)的對象從而引發(fā)未知的問題.
調(diào)用 exit() 函數(shù)時阎肝,程序的終止流程通常遵循以下步驟:

  1. 調(diào)用 exit() 函數(shù):這可以發(fā)生在程序的任何地方放可,不限于主線程。
  2. 執(zhí)行 exit() 的初始操作:開始終止程序觉义,但不立即關(guān)閉所有線程。
  3. 銷毀靜態(tài)存儲期對象:全局對象和靜態(tài)局部對象會被銷毀,調(diào)用它們的析構(gòu)函數(shù)棍郎。
  4. 調(diào)用 atexit() 注冊的函數(shù):如果有通過 atexit() 注冊的函數(shù),這些函數(shù)會按照注冊的逆序被調(diào)用银室。
  5. 其他線程的強制終止:程序中的其他線程將被強制性地終止涂佃。這些線程不會正常完成它們的執(zhí)行路徑。
  6. 清理和關(guān)閉:進(jìn)行最終的清理操作蜈敢,包括關(guān)閉所有打開的文件和釋放其他系統(tǒng)資源辜荠。
  7. 程序終止:最后,控制權(quán)返回操作系統(tǒng)抓狭,程序完全結(jié)束伯病。

在這個過程中,并沒有為線程提供一個完整的否过、有序的終止機制午笛。這是因為 exit() 的設(shè)計是為了迅速終止程序,而不是等待或協(xié)調(diào)線程的安全退出苗桂。因此药磺,當(dāng)使用多線程時,如果需要優(yōu)雅地關(guān)閉線程煤伟,通常建議使用其他同步機制來確保線程能夠安全地完成它們的工作癌佩,而不是依賴 exit() 來結(jié)束程序木缝。

The primary problem with destruction of static-duration objects is access to static-duration objects after their destructors have executed, thus resulting in undefined behavior. To prevent this problem, we require that all user threads finish before destruction begins. For threads that do not naturally finish, mechanisms to terminate threads are proposed in N2447 Multi-threading Library for Standard C++ and its initial incorporation in N2521 Working Draft, Standard for Programming Language C++.

這也是為什么C++標(biāo)準(zhǔn)中也提及了,需要在釋放全局/靜態(tài)變量之前,要保證所有線程都結(jié)束了.

編譯器自帶線程安全優(yōu)化

各編譯器也正對這一點有了優(yōu)化.
對于GCC >=4.3的版本已經(jīng)默認(rèn)屬性fthreadsafe-static保證了靜態(tài)變量線程安全. 此屬性默認(rèn)是開啟的,大概就是能保證在線程都停止之后再析構(gòu). 有興趣可以繼續(xù)研究
參考鏈接:
https://gcc.gnu.org/onlinedocs/gcc/gcc-command-options/options-controlling-c%2B%2B-dialect.html#cmdoption-fthreadsafe-statics
各編譯器所支持的功能如下:

image.png

解決方案

如果你不能保證代碼會被什么編譯器和版本運行. 要兼容的話可以考慮這2個方案.

  • 指定順序

遵循C++標(biāo)準(zhǔn). 如果你能控制子線程的話. 你只需要按照C++標(biāo)準(zhǔn)規(guī)定的順序即可. 先結(jié)束子線程再析構(gòu).

  • 利用堆的特性. 通過將對象new出來放在堆區(qū). 堆區(qū)的生命周期是等待delete操作來釋放. 它不受exit()影響.
std::vector<std::mutex>* g_openssl_locks = new std::vector<std::mutex>(static_cast<size_t>(CRYPTO_num_locks()));

void openssl_locking_function(int mode, int n, const char * /* file */, int /* line */) { 
    if(mode & CRYPTO_LOCK) {
        (*g_openssl_locks)[n].lock();
    }
    else {
        (*g_openssl_locks)[n].unlock();
    }
}

int g_sslinit = SetSslLocking();

此方式,沒人調(diào)用釋放g_openssl_locks. 它會等待程序完全結(jié)束后被操作系統(tǒng)回收. 保證了順序.

參考:

https://developer.aliyun.com/article/793257
https://zhuanlan.zhihu.com/p/656683028

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市围辙,隨后出現(xiàn)的幾起案子氨肌,更是在濱河造成了極大的恐慌,老刑警劉巖酌畜,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怎囚,死亡現(xiàn)場離奇詭異,居然都是意外死亡桥胞,警方通過查閱死者的電腦和手機恳守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贩虾,“玉大人催烘,你說我怎么就攤上這事《邪眨” “怎么了伊群?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長策精。 經(jīng)常有香客問我舰始,道長,這世上最難降的妖魔是什么咽袜? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任丸卷,我火速辦了婚禮,結(jié)果婚禮上询刹,老公的妹妹穿的比我還像新娘谜嫉。我一直安慰自己,他們只是感情好凹联,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布沐兰。 她就那樣靜靜地躺著,像睡著了一般蔽挠。 火紅的嫁衣襯著肌膚如雪住闯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天象泵,我揣著相機與錄音寞秃,去河邊找鬼。 笑死偶惠,一個胖子當(dāng)著我的面吹牛春寿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忽孽,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绑改,長吁一口氣:“原來是場噩夢啊……” “哼谢床!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厘线,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤识腿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后造壮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渡讼,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年耳璧,在試婚紗的時候發(fā)現(xiàn)自己被綠了成箫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡旨枯,死狀恐怖蹬昌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情攀隔,我是刑警寧澤皂贩,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站昆汹,受9級特大地震影響明刷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筹煮,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一遮精、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧败潦,春花似錦、人聲如沸准脂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狸膏。三九已至沟饥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湾戳,已是汗流浹背贤旷。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留砾脑,地道東北人幼驶。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像韧衣,于是被迫代替她去往敵國和親盅藻。 傳聞我的和親對象是個殘疾皇子购桑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容