博客 / 詳情

返回

TDengine 研發分享:利用 Windbg 解決內存泄漏問題的實踐和經驗

內存泄漏是一種常見的問題,它會導致程序的內存佔用逐漸增加,最終導致系統資源耗盡或程序崩潰。AddressSanitizer (ASan) 和 Valgrind 是很好的內存檢測工具,TDengine 的 CI 過程就使用了 ASan 。不過這次內存泄漏問題發生在 Windows 下,我們 CI 暫時還沒有覆蓋到,因此 TDengine 研發選擇使用 Windbg 來解決問題。結果證明,在 Windows 下,使用 Windbg 也是一個不錯的選擇。

內存泄漏的常用檢測方法

內存泄漏通常會發生在以下情況下:

  • 程序未正確釋放已分配的內存
  • 程序中存在循環引用,導致垃圾收集器無法回收內存
  • 程序中存在內存泄漏的第三方庫或組件

內存泄漏的檢測方法主要包括以下幾種:

  1. 靜態代碼分析工具:未釋放的指針或內存分配錯誤等問題,不能檢測在程序運行時動態分配內存的情況。
  2. 動態分析工具:可以使用內存分配和釋放跟蹤器來跟蹤程序中的內存分配和釋放操作,並檢測是否存在內存泄漏的情況。然而,使用某些工具(如Valgrind)可能會對程序的性能產生一定的影響。
  3. 調試器:WinDbg 和 GDB。

優缺點:

  • 靜態代碼分析工具可以在早期發現問題,但是它們不能檢測程序運行時動態分配內存的情況。
  • 動態分析工具可以在程序運行時檢測問題,但是它們可能會影響程序性能,並且在檢測大型應用程序時可能需要大量的時間和資源。不過在資源充足的測試環境中跑的話,就都不是問題了,比如 ASan 就幫我們發現過不少問題。
  • 調試器可以在程序運行時檢測問題,並提供強大的分析工具。

實踐分析

基本原理

使用 Windbg 定位內存泄露,依賴 glags 組件記錄程序在運行期間所有申請和釋放的內存,同時記錄的還有申請內存時的調用棧信息。這樣在程序運行期間,使用 umdh 組件進行兩次快照記錄,通過比較兩次快照信息的差異,就可以發現兩次快照間隔時間段中申請卻並未釋放的內存申請信息。如果有內存泄露,diff 結果最前邊一般就是泄漏點的調用棧信息。當然,兩次快照期間,要儘量觸發內存泄露,才能更準確的定位。diff 結果中還會有少量正常的申請沒來得及釋放的調用信息,不過 diff 結果中能看到調用次數,比較容易甄別。

問題介紹

taosdump 在 windows 導入數據出錯:

build and install latest TDengine 3.0 branch on Windows
use "taosBenchmark -I stmt -y" to create a lot of tables and data (10000 * 10000).
use "taosdump -D test -o outputFile" to dump out
use "taos -s 'drop database test'" to drop database
use "taosdump -i inputFile" to dump in.

錯誤日誌:taosd “tsem_init failed, errno: 28”

Taosdump: dumpInAvroDataImpl() LN7039 taos_stmt_execute() failed! reason: Out of Memory, timestamp: 1500000009256

定位過程

配置 gflags

gflags 工具應該位於路徑:C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags,如果沒有的話,可以直接前往 Microsoft 的官方網站下載安裝:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/de...

安裝完成後,在命令行執行 gflags.exe /i your_application.exe 可設置跟蹤目標,同時可以設置相關參數。雙擊運行也是可以的,Image File 對應 /i 參數,選擇啓動程序 your_application.exe 後先按 tab 鍵,然後選擇其他配置。

圖片

定位步驟

  1. 啓動 your_application.exe(我要調試的是 taosdump.exe,所以下邊是 taosdump.exe)

“C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags” -i taosdump.exe +ust

  1. 拷貝 pdb 文件到 mysymbols 目錄,pdb 文件存儲了編譯後的程序的調試信息,和可執行程序一起生成,可以在應用程序生成目錄中找到。
  2. Set pdb 目錄

    set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols
  3. 生成第一次內存快照

    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump11.log
  4. 生成第二次內存快照

    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh" -pn:taosdump.exe -f:C:\xstest\umdhlog\taosdump12.log
  5. 生成快照比較結果(umdh)

    "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\umdh"  C:\xstest\umdhlog\taosdump11.log C:\xstest\umdhlog\taosdump12.log -f:C:\xstest\umdhlog\taosdumpdiff11_12.log

分析與解決

結果文件

因為 taosdump 程序啓動後直至退出都在做大量的業務工作,內存泄露很容易發生在兩次快照期間。 988040 – 6ecf0 表示”申請次數 – 釋放次數”, 很明顯發生了內存泄露,泄漏點在 buildRequest 函數的 sem_init 這裏。

+  919350 ( 988040 - 6ecf0)  201b0 allocs        BackTrace9CB6973F
+   1ea5c ( 201b0 -  1754)        BackTrace9CB6973F        allocations

        ntdll!RtlpAllocateHeapInternal+948D5
        taos!heap_alloc_dbg_internal+1F6 (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 359)
        taos!heap_alloc_dbg+4D (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 450)
        taos!_calloc_dbg+6C (minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp, 518)
        taos!calloc+2E (minkernel\crts\ucrt\src\appcrt\heap\calloc.cpp, 30)
        taos!sem_init+5D (C:\workroom\TDengine\contrib\pthread\sem_init.c, 109)
        taos!buildRequest+209 (C:\workroom\TDengine\source\client\src\clientImpl.c, 192)
        taos!stmtCreateRequest+73 (C:\workroom\TDengine\source\client\src\clientStmt.c, 15)
        taos!stmtSetTbName+115 (C:\workroom\TDengine\source\client\src\clientStmt.c, 588)
        taos!taos_stmt_set_tbname+7F (C:\workroom\TDengine\source\client\src\clientMain.c, 1350)
        taosdump!dumpInAvroDataImpl+E25 (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 6260)
        taosdump!dumpInOneAvroFile+3D2 (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 7229)
        taosdump!dumpInAvroWorkThreadFp+20B (C:\workroom\TDengine\tools\taos-tools\src\taosdump.c, 7306)
        taosdump!ptw32_threadStart+CD (C:\workroom\TDengine\contrib\pthread\ptw32_threadStart.c, 233)
        taosdump!thread_start<unsigned int (__cdecl*)(void *),1>+9C (minkernel\crts\ucrt\src\appcrt\startup\thread.cpp, 97)
        KERNEL32!BaseThreadInitThunk+10
        ntdll!RtlUserThreadStart+2B

泄漏點修改

接下來查看代碼並修改,C 語言對內存的使用自由度很高,因此也比較麻煩。可以看到有些路徑遺漏了 tsem_destory 的調用。

圖片

更加詳細的代碼方案請見 https://github.com/taosdata/TDengine/pull/19580

總結

工欲善其事必先利其器,掌握更多的工具和手段,在解決問題時才能比較從容,Windbg 定位內存泄漏的方式非常簡單,但是很有效。不過需要注意,它依賴 pdb 文件,因此,發佈應用程序時要記得保留 pdb 文件。pdb 文件包含了程序的符號信息,能夠幫助我們在調試過程中準確定位問題所在。

另外,從出問題的代碼可以看出,這塊內存的管理方式還是比較容易出錯,RAII 機制能較好的避免資源泄露,C 語言中也可以通過模擬 RAII 來達到類似的效果,雖然沒有 C++ 那麼流暢,也許以後可以考慮優化一下。

RAII(Resource Acquisition Is Initialization)機制是一種重要的資源管理方式,它將資源的獲取和對象的生命週期關聯起來。通過在對象的構造函數中獲取資源,在析構函數中釋放資源,我們可以確保資源的正確管理,防止資源泄漏和內存泄漏等問題。RAII 機制在 C++ 等編程語言中得到廣泛應用,是一種有效的資源管理方式。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.