內存泄露、死鎖檢測工具來了
掃描二維碼
隨時隨地手機看文章
項目背景
現(xiàn)實困境
做C、C++開發(fā)的朋友應該都知道,C、C++中的內存是手動管理的,手動內存管理是一把雙刃劍,雖然提供了極致性能,但可能由于開發(fā)者的一點點疏忽,就導致內存泄露。據(jù)非官方統(tǒng)計,全球每年因內存泄露導致的系統(tǒng)崩潰事故超過120萬次。
C、C++開發(fā)者面臨以下痛點時經(jīng)常束手無策:
- 幽靈式內存泄露:程序運行數(shù)天后,出現(xiàn)內存耗盡,因為程序是一點點釋放的,不太容易發(fā)現(xiàn)具體問題所在。
- 多線程競態(tài)問題:死鎖導致的服務假死,并且不好復現(xiàn)。
現(xiàn)有方案的局限
傳統(tǒng)工具,Asan、valgrind、gdb功能非常強大,可以檢測基本的問題,但也恰恰是因為功能太過豐富且強大,所以性能損耗非常高,無法用于線上環(huán)境,并且難以捕獲隨機出現(xiàn)的死鎖場景。
項目目標
開發(fā)一個零侵入、高性能、全維度的運行時診斷系統(tǒng):
- 內存監(jiān)控:可以實時追蹤每個內存塊的完整生命周期。
- 死鎖檢測:可以檢測出死鎖,并能檢測出哪個線程的哪幾把鎖出現(xiàn)了死鎖,哪個線程由于等待的哪把鎖而出現(xiàn)的死鎖,可以精確關聯(lián)源代碼位置。
- 內存泄露檢測:可以檢測出具體哪塊內存出現(xiàn)了泄露,并精確關聯(lián)到源代碼位置。
項目介紹
整體架構如圖:
內存檢測
直接看代碼,下面代碼會發(fā)生內存泄露:
extern "C"int TestMemoryLeak() { int *ptr = (int *)malloc(100); printf("TestMemoryLeak: %p\n", ptr); free(ptr); return 0; } extern"C"int TestMemoryLeak2() { int *ptr = (int *)malloc(110); printf("TestMemoryLeak2: %p\n", ptr); int *p = newint[10]; auto q = std::make_unique<int>(10); return 0; }
集成了工具后:
int main() { OpenDynamicExample(); MemoryDetector detect("/mnt/d/project/camping/detector/libdynamic_example.so"); detect.StartTracking(); UseDynamicExample(); detect.StopTracking(); // 會打印 lib1.so 的內存使用情況 CloseDynamicExample(); return 0; }
直接就可以檢測這個動態(tài)庫的內存情況:
本工具可以檢測出程序申請了多少內存,申請了多少塊內存,以及具體哪里發(fā)生了內存泄露,可以精確到具體的源代碼位置。
它不僅可以檢測malloc、free申請和釋放的內存,即便是C++的new、delete、new[]、delete[]、std::make_unique、std::make_shared,也可以,不管程序是通過哪種方式申請和釋放的內存,只要發(fā)生了內存泄露,工具都可以檢測到。
整體采用Hook方案,基本流程如圖:
死鎖檢測
看這段發(fā)生死鎖的代碼:
static void *ThreadFunc1(void *) { pthread_mutex_lock(&mutexA); std::cout << "Thread 1: Locked A\n"; sleep(1); std::cout << "Thread 1: Trying to lock B\n"; pthread_mutex_lock(&mutexB); std::cout << "Thread 1: Locked B\n"; pthread_mutex_unlock(&mutexB); pthread_mutex_unlock(&mutexA); return nullptr; } static void *ThreadFunc2(void *) { pthread_mutex_lock(&mutexB); std::cout << "Thread 2: Locked B\n"; sleep(1); std::cout << "Thread 2: Trying to lock A\n"; pthread_mutex_lock(&mutexA); std::cout << "Thread 2: Locked A\n"; pthread_mutex_unlock(&mutexA); pthread_mutex_unlock(&mutexB); return nullptr; } static void *ThreadFunc3(void *) { std::mutex mtx; std::cout << "Thread 3: Trying to lock mutex\n"; mtx.lock(); std::cout << "Thread 3: Locked mutex\n"; sleep(1); mtx.unlock(); return nullptr; } // 導出的函數(shù),用于創(chuàng)建死鎖場景 static void CreateDeadlock() { pthread_t t1, t2, t3; pthread_attr_t attr; // 初始化線程屬性 pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 創(chuàng)建分離的線程 pthread_create(&t1, &attr, ThreadFunc1, nullptr); pthread_create(&t2, &attr, ThreadFunc2, nullptr); pthread_create(&t3, &attr, ThreadFunc3, nullptr); // 銷毀線程屬性 pthread_attr_destroy(&attr); // 等待一段時間讓死鎖發(fā)生 sleep(3); }
從代碼中可以看到,Thread1和Thread2會發(fā)生死鎖,集成工具后:
LockHook lock_hook("./libdynamic_example.so"); if (!lock_hook.StartTracking()) { std::cerr << "Failed to start lock tracking\n"; dlclose(handle); return 1; } lock_hook.StopTracking();
結果如圖:
工具可以檢測出哪里發(fā)生了死鎖、哪個線程持有了哪把鎖、以及哪把鎖被哪個線程持有了。
且無論你是通過pthread_lock、還是mutex.lock、還是unique_lock或者lock_guard,只要發(fā)生了死鎖,工具都可以檢測到,并且可以定位到源代碼位置。
整體也采用Hook方案,流程如圖所示:
項目收獲
項目代碼量不大,核心代碼大概2000行左右,但涉及到的技術內容非常豐富且硬核。
通過本項目,你可以收獲到:
- 提升C、C++的編碼能力、內存管理黑科技、多線程調試技巧
- ELF 文件結構,包括section 和 segment的概念以及具體作用等。
- 編譯鏈接技術,動態(tài)鏈接與靜態(tài)鏈接的區(qū)別。
- 動態(tài)鏈接與加載,了解動態(tài)鏈接器如何在運行時解析符號和加載動態(tài)庫。
- PLT機制,與GOT之間的關系。
- GOT作用,如何存儲動態(tài)鏈接的函數(shù)地址。
- 函數(shù)調用約定,不同架構下的函數(shù)調用約定。
- 內存保護機制,了解Linux上的內存保護機制(如DEP、ASLR),以及如何影響代碼注入和鉤子技術。
- 調試工具,使用工具(如objdump、gdb)分析二進制文件,理解如何定位和修改PLT。
- Hook技術,如何將自定義代碼注入到目標進程中,以實現(xiàn)鉤子功能。
- 鉤子的安全性,鉤子技術是否有風險。
- 編寫和測試,學習如何編寫鉤子代碼,并在不同環(huán)境中進行測試。
- 鉤子技術的性能分析。
- 動態(tài)庫的加載過程,詳細了解共享庫的加載過程,包括如何在運行時解析依賴關系。
- 符號解析與重定位,符號解析的機制以及重定位表的作用。
- 內存管理和分配機制,內存管理機制,特別是如何安全地分配和修改內存以實現(xiàn)鉤子。
- 內存泄漏檢測技術,理解了內存管理分配機制,可以實現(xiàn)檢測內存泄漏的能力。
- 鎖機制,鎖的底層實現(xiàn)原理,如何實現(xiàn)加解鎖相關的鉤子。
- 死鎖檢測技術,理解了加解鎖的底層機制,可以實現(xiàn)檢測程序是否產(chǎn)生了死鎖。
- 編譯鏈接技術,Debug模式和Release模式的區(qū)別。
- 符號管理機制,調試符號信息的作用。
- 調用棧技術,如何獲取線程的調用堆棧,如何根據(jù)地址解析出對應代碼函數(shù)名和行號。





