讓Visual Leak Detector使用最新10.0版本的dbghelp.dll
介紹
VLD(Visual Leak Detector)是一個檢測Windows C++程式記憶體泄漏的老牌神器,但好幾年沒維護了,
網址:https://github.com/KindDragon/vld/
需求
這個工具通過SxS manifest系結了只能使用它工程目錄下自帶的dbghelp.dll來處理pdb符號,版本是6.11.1.404,
這個版本目前比較老了,所以在決議VS2019/VS2022生成的pdb檔案時,有時候會崩掉或者無法決議出呼叫堆疊的符號,導致無法報出來完整的記憶體泄漏,影響基本功能,所以需要升級它所使用的dbghelp.dll,
VLD的實作機制
在首次進入vld_x64.dll的PE入口時,inline hook掉ntdll.dll的LdrpCallInitRoutine()函式,因為此時可以假定vld_x64.dll是被ntdll.dll的LdrpCallInitRoutine()函式呼叫的,
這樣后續ntdll.dll呼叫當前行程中的任何dll的入口函式時,都會先呼叫vld_x64.dll提供的一個LdrpCallInitRoutine() hook函式,
完成hook后,會執行vld_x64.dll中的各個全域物件的構造,vld_x64.dll提供了一個全域物件g_vld,這個物件的建構式會呼叫dbghelp.dll的SymInitializeW()來初始化MS的符號庫函式,
__declspec(dllexport) VisualLeakDetector g_vld;
在LdrpCallInitRoutine() hook函式中,VLD會重繪當前行程所加載的模塊串列,呼叫dbghelp.dll的SymLoadModuleExW()加載新dll的pdb符號,
BOOLEAN WINAPI LdrpCallInitRoutine(IN PVOID BaseAddress, IN ULONG Reason, IN PVOID Context, IN PDLL_INIT_ROUTINE EntryPoint) { LoaderLock ll; if (Reason == DLL_PROCESS_ATTACH) { g_vld.RefreshModules(); } return EntryPoint(BaseAddress, Reason, (PCONTEXT)Context); }
問題
這樣看起來并無問題,但是10.0版本的dbghelp.dll相比6.11版本有一個改動,導致VLD現有的pdb符號決議功能失敗,
那就是10.0版本的SymInitializeW()的內部代碼會去加載某些DLL,這會導致走到LdrpCallInitRoutine() hook函式中去重繪模塊串列,并最終呼叫SymLoadModuleExW(),
也就是說SymInitializeW()在成功回傳之前會去呼叫SymLoadModuleExW(),這顯然不符合MS的debug help API的約定,所以此時的SymLoadModuleExW()都會回傳失敗,導致匯報泄漏時無法決議符號,
解決辦法
1、設定一個全域的bool標志變數,在呼叫SymInitializeW()之前置位,呼叫完SymInitializeW()之后清除,
dbghelp.h:
extern volatile bool init; BOOL SymInitializeW(_In_ HANDLE hProcess, _In_opt_ PCWSTR UserSearchPath, _In_ BOOL fInvadeProcess) { init = true; CriticalSectionLocker<CriticalSection> cs(m_lock); const auto r = ::SymInitializeW(hProcess, UserSearchPath, fInvadeProcess); init = false; return r; }
2、在LdrpCallInitRoutine() hook函式中判斷一下,如果標志被置位,則本次就不要重繪模塊串列了,也就不會去呼叫SymLoadModuleExW(),
vld.cpp:
volatile bool init = false; BOOLEAN WINAPI LdrpCallInitRoutine(IN PVOID BaseAddress, IN ULONG Reason, IN PVOID Context, IN PDLL_INIT_ROUTINE EntryPoint) { LoaderLock ll; if (Reason == DLL_PROCESS_ATTACH) { if (!init) g_vld.RefreshModules(); } return EntryPoint(BaseAddress, Reason, (PCONTEXT)Context); }
3、相應地,要刪掉VLD工程屬性中添加的和SxS有關的設定,如
- vld.dll.dependency.x64.manifest
- vld.dll.dependency.x86.manifest
- dbghelp.dll (6.11版本的)
- Microsoft.DTfW.DHL.manifest (6.11版本的)
- $(SolutionDir)\lib\dbghelp\lib\$(PlatformName) (不要依賴這個目錄下的lib)
這樣編譯出來的vld_x64.dll默認會加載system32下的dbghelp.dll,
也可以復制Windows SDK、VS2019/VS2022、windbg目錄下的dbghelp.dll,但不要忘了也復制同目錄下的那一堆api-ms-win-crt-runtime-l1-1-0.dll之類的CRT dll,
附贈
幾個防止記憶體泄漏的tips:
1、靜態鏈接到openssl時,需要在DLL_THREAD_DETACH時呼叫OPENSSL_thread_stop()釋放PTD(即per-thread-data),
2、靜態鏈接到log4cplus時,需要在DLL_THREAD_DETACH時呼叫log4cplus::threadCleanup()釋放per-thread-data,
3、libzip有兩個坑(其檔案寫得不甚清楚):
一、zip_close()會順帶將關聯的zip source的句柄也關閉,所以對應的zip source句柄不要再單獨關閉,
如果要保留zip source句柄另作他用,需要在zip_close()之前先用zip_source_keep()將zip source的句柄參考計數加1,
二、zip source句柄用zip_source_free()釋放,而不是用zip_source_close(),
zip_source_free()還有限制,參看其檔案,
參考:
https://github.com/KindDragon/vld/issues/86
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/539152.html
標籤:其他
下一篇:動態代理與責任鏈模式
