天天看點

WinDebug查找記憶體洩露

引言

對于C++的開發者來說,記憶體洩露是一類耗時的bug。檢查記憶體洩露總是讓人很抓狂,如果出問題的代碼不是你寫的,或者代碼量很大的時候,事情就更糟糕了。

現在市場上有很多工具可以用于檢查分析記憶體洩露的問題,但是一般都不是免費的。Windbg是一款功能強大又可免費使用的記憶體洩露檢查工具,通過Windbg可以初步定位懷疑有記憶體洩露的代碼位置。COM接口相關的問題在本文檔中不涉及。

Windbg是微軟提供的一款功能強大的使用者核心空間調試器,可以從以下位址下載下傳

http://msdn.microsoft.com/en-us/library/windows/hardware/ff551063(v=vs.85).aspx

使用Windbg

使用Windbg的準備:

  1. 在symbol file 中配置Microsoft Symbol Servers的 目錄

    在Windbg的File==>Symbol File Path中輸入:

    SRV*d:\symbols*http://msdl.microsoft.com/download/symbols

    d:\symbols表示從Microsoft Symbol Servers下載下傳的符号檔案存放目錄,若從未下載下傳過系    統的符号檔案,Windbg會自動下載下傳。

  2. 在Symbol File Path中輸入自己程式的PDB檔案目錄(包括DLL的PDB),可添加    多個,以分号隔開如:

    D:\myApp\pdb;SRV*d:\symbols*http://msdl.microsoft.com/download/symbols

    自己程式的PDB目錄放在前面,Windbg查找較快。

  3. 配置作業系統标記,開啟可能有記憶體洩露程式的使用者堆棧跟蹤。利用gflags工具很    容易實作(gflags同樣也是微軟Windbg工具之一),安裝Windbg時會同時安裝該工    具。使用如下的指令行實作

    gflags.exe /i MemoryLeak.exe +ust

    MemoryLeak.exe是懷疑有記憶體洩露的程式,隻需要exe名稱,不用路徑。

  4. 配置Source File Path,輸入相關的程式代碼目錄,多個目錄用分号隔開
  5. 配置Image File Path,輸入相關程式的2進制檔案目錄,包括exe\dll,多個目錄用分    号隔開

    以下利用Test2.exe作為測試程式,相關代碼如下:

    int _tmain(int argc, _TCHAR* argv[])

    { while(1)

    {

    AllocateMemory();

    }

    return 0;

    }

    void AllocateMemory()

    {

    int* a = new int[2000];

    ZeroMemory(a, 8000);

    Sleep(1);

    }

    下圖是運作gflags的截圖

    WinDebug查找記憶體洩露
    配置好Windbg後,運作存在記憶體洩露的程式,在Windbg中加載該程式,File==>Attach to a Process,或者使用快捷鍵F6,如下圖
    WinDebug查找記憶體洩露

    加載Test2.exe後,Test2.exe會暫停運作,在Windbg中輸入指令(有關heap的指令參考Windbg的幫助文檔,有詳細介紹)

    !heap -s

    會得到類似如下的輸出内容

    0:001> !heap -s

    NtGlobalFlag enables following debugging aids for new heaps:

    validate parameters

    stack back traces

    Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast

    (k) (k) (k) (k) length blocks cont. heap

    -----------------------------------------------------------------------------

    00150000 58000062 1024 12 12 1 1 1 0 0 L

    00250000 58001062 64 24 24 15 1 1 0 0 L

    00260000 58008060 64 12 12 10 1 1 0 0

    00330000 58001062 64576 47404 47404 13 4 1 0 0

    -----------------------------------------------------------------------------

    繼續運作Test2.exe一段時間後,在Windbg中暫停,再次運作指令

    !heap -s

    得到輸出

    0:001> !heap -s

    NtGlobalFlag enables following debugging aids for new heaps:

    validate parameters

    stack back traces

    Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast

    (k) (k) (k) (k) length blocks cont. heap

    -----------------------------------------------------------------------------

    00150000 58000062 1024 12 12 1 1 1 0 0 L

    00250000 58001062 64 24 24 15 1 1 0 0 L

    00260000 58008060 64 12 12 10 1 1 0 0

    00330000 58001062 261184 239484 239484 14 4 1 0 0

    -----------------------------------------------------------------------------

    加粗字段部分顯示了有增長的堆資訊,上面的輸出顯示堆00330000 有增長。

    執行指令

    !heap -stat –h 00330000

    該指令會顯示有增長堆的統計資訊,輸出如下:

    0:001> !heap -stat -h 00330000

    heap @ 00330000

    group-by: TOTSIZE max-display: 20

    size #blocks total ( %) (percent of total busy bytes)

    1f64 76c6 - e905f58 (99.99)

    1800 1 - 1800 (0.00)

    824 2 - 1048 (0.00)

    238 2 - 470 (0.00)

    244 1 - 244 (0.00)

    4c 5 - 17c (0.00)

    b0 2 - 160 (0.00)

    86 2 - 10c (0.00)

    50 3 - f0 (0.00)

    74 2 - e8 (0.00)

    38 4 - e0 (0.00)

    48 3 - d8 (0.00)

    c4 1 - c4 (0.00)

    62 2 - c4 (0.00)

    be 1 - be (0.00)

    b8 1 - b8 (0.00)

    ae 1 - ae (0.00)

    ac 1 - ac (0.00)

    55 2 - aa (0.00)

    a4 1 - a4 (0.00)

    加粗部分顯示有0x76c6個1f64 (8036即代碼中2000*4)大小的記憶體塊被配置設定,如此大量同樣大小的記憶體塊配置設定,可以猜測這就可能是記憶體洩露的地方。

    下一步是擷取到這些被配置設定記憶體的位址,使用指令

    !heap -flt s 1f64

    該指令掉其它的記憶體塊,隻顯示大小為1f64 的記憶體塊資訊

    輸出結果如下:

    0:001> !heap -flt s 1f64

    _HEAP @ 150000

    _HEAP @ 250000

    _HEAP @ 260000

    _HEAP @ 330000

    HEAP_ENTRY Size Prev Flags UserPtr UserSize - state

    003360e0 03f0 0000 [07] 003360e8 01f64 - (busy)

    00338060 03f0 03f0 [07] 00338068 01f64 - (busy)

    00339fe0 03f0 03f0 [07] 00339fe8 01f64 - (busy)

    0033bf60 03f0 03f0 [07] 0033bf68 01f64 - (busy)

    0033dee0 03f0 03f0 [07] 0033dee8 01f64 - (busy)

    01420040 03f0 03f0 [07] 01420048 01f64 - (busy)

    01421fc0 03f0 03f0 [07] 01421fc8 01f64 - (busy)

    01423f40 03f0 03f0 [07] 01423f48 01f64 - (busy)

    01425ec0 03f0 03f0 [07] 01425ec8 01f64 - (busy)

    01427e40 03f0 03f0 [07] 01427e48 01f64 - (busy)

    01429dc0 03f0 03f0 [07] 01429dc8 01f64 - (busy)

    0142bd40 03f0 03f0 [07] 0142bd48 01f64 - (busy)

    0142dcc0 03f0 03f0 [07] 0142dcc8 01f64 - (busy)

    0142fc40 03f0 03f0 [07] 0142fc48 01f64 - (busy)

    01431bc0 03f0 03f0 [07] 01431bc8 01f64 - (busy)

    01433b40 03f0 03f0 [07] 01433b48 01f64 - (busy)

    01435ac0 03f0 03f0 [07] 01435ac8 01f64 - (busy)

    01437a40 03f0 03f0 [07] 01437a48 01f64 - (busy)

    014399c0 03f0 03f0 [07] 014399c8 01f64 - (busy)

    0143b940 03f0 03f0 [07] 0143b948 01f64 - (busy)

    0143d8c0 03f0 03f0 [07] 0143d8c8 01f64 - (busy)

    0143f840 03f0 03f0 [07] 0143f848 01f64 - (busy)

    014417c0 03f0 03f0 [07] 014417c8 01f64 - (busy)

    01443740 03f0 03f0 [07] 01443748 01f64 - (busy)

    014456c0 03f0 03f0 [07] 014456c8 01f64 - (busy)

    01447640 03f0 03f0 [07] 01447648 01f64 - (busy)

    014495c0 03f0 03f0 [07] 014495c8 01f64 - (busy)

    0144b540 03f0 03f0 [07] 0144b548 01f64 - (busy)

    0144d4c0 03f0 03f0 [07] 0144d4c8 01f64 - (busy)

    0144f440 03f0 03f0 [07] 0144f448 01f64 - (busy)

    014513c0 03f0 03f0 [07] 014513c8 01f64 - (busy)

    01453340 03f0 03f0 [07] 01453348 01f64 - (busy)

    014552c0 03f0 03f0 [07] 014552c8 01f64 - (busy)

    01457240 03f0 03f0 [07] 01457248 01f64 - (busy)

    014591c0 03f0 03f0 [07] 014591c8 01f64 - (busy)

    0145b140 03f0 03f0 [07] 0145b148 01f64 - (busy)

    0145d0c0 03f0 03f0 [07] 0145d0c8 01f64 - (busy)

    0145f040 03f0 03f0 [07] 0145f048 01f64 - (busy)

    01460fc0 03f0 03f0 [07] 01460fc8 01f64 - (busy)

    01462f40 03f0 03f0 [07] 01462f48 01f64 - (busy)

    01464ec0 03f0 03f0 [07] 01464ec8 01f64 - (busy)

    01466e40 03f0 03f0 [07] 01466e48 01f64 - (busy)

    01468dc0 03f0 03f0 [07] 01468dc8 01f64 - (busy)

    任意找到一行UsrPtr 對應列的值,使用如下指令顯示該UsrPtr 對應的調用堆棧

    !heap -p -a UsrPtr

    選擇上述輸出中加粗的一行,執行指令!heap -p -a 0143d8c8後可以得到如下的輸出

    0:001> !heap -p -a 0143d8c8

    address 0143d8c8 found in

    _HEAP @ 330000

    HEAP_ENTRY Size Prev Flags UserPtr UserSize - state

    0143d8c0 03f0 0000 [07] 0143d8c8 01f64 - (busy)

    Trace: 0025

    7c96d6dc ntdll!RtlDebugAllocateHeap+0x000000e1

    7c949d18 ntdll!RtlAllocateHeapSlowly+0x00000044

    7c91b298 ntdll!RtlAllocateHeap+0x00000e64

    102c103e MSVCR90D!_heap_alloc_base+0x0000005e

    102cfd76 MSVCR90D!_heap_alloc_dbg_impl+0x000001f6

    102cfb2f MSVCR90D!_nh_malloc_dbg_impl+0x0000001f

    102cfadc MSVCR90D!_nh_malloc_dbg+0x0000002c

    102db25b MSVCR90D!malloc+0x0000001b

    102bd691 MSVCR90D!operator new+0x00000011

    102bd71f MSVCR90D!operator new[]+0x0000000f

    4113d8 Test2!AllocateMemory+0x00000028

    41145c Test2!wmain+0x0000002c

    411a08 Test2!__tmainCRTStartup+0x000001a8

    41184f Test2!wmainCRTStartup+0x0000000f

    7c816fd7 kernel32!BaseProcessStart+0x00000023

    加粗字型部分即為代碼中出現記憶體洩露的位置。

    PS:有時!heap -s指令并不能找出有明顯增長的堆。這時可以先用 !heap -stat -h 指令将所有堆的block 和 size列出來,找到可能有增長的堆,再用指令 !heap -flt s SIZE (SIZE即為懷疑的堆大小)