gisdemo

分享,更是学习

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

原文:http://www.codeproject.com/Articles/31382/Memory-Leak-Detection-Using-Windbg

引言

对于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的截图

        

    配置好Windbg后,运行存在内存泄露的程序,在Windbg中加载该程序,File==>Attach to a Process,或者使用快捷键F6,如下图

    加载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

    得到输出

     1 0:001> !heap -s
     2 
     3 NtGlobalFlag enables following debugging aids for new heaps:
     4 
     5 validate parameters
     6 
     7 stack back traces
     8 
     9 Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
    10 
    11 (k) (k) (k) (k) length blocks cont. heap
    12 
    13 -----------------------------------------------------------------------------
    14 
    15 00150000 58000062 1024 12 12 1 1 1 0 0 L
    16 
    17 00250000 58001062 64 24 24 15 1 1 0 0 L
    18 
    19 00260000 58008060 64 12 12 10 1 1 0 0
    20 
    21 00330000 58001062 261184 239484 239484 14 4 1 0 0
    22 
    23 -----------------------------------------------------------------------------

     

     

    加粗字段部分显示了有增长的堆信息,上面的输出显示堆00330000 有增长。

    执行命令

    !heap -stat h 00330000

    该命令会显示有增长堆的统计信息,输出如下:

      1 0:001> !heap -stat -h 00330000
      2 
      3 heap @ 00330000
      4 
      5 group-by: TOTSIZE max-display: 20
      6 
      7 size #blocks total ( %) (percent of total busy bytes)
      8 
      9 1f64 76c6 - e905f58 (99.99)
     10 
     11 1800 1 - 1800 (0.00)
     12 
     13 824 2 - 1048 (0.00)
     14 
     15 238 2 - 470 (0.00)
     16 
     17 244 1 - 244 (0.00)
     18 
     19 4c 5 - 17c (0.00)
     20 
     21 b0 2 - 160 (0.00)
     22 
     23 86 2 - 10c (0.00)
     24 
     25 50 3 - f0 (0.00)
     26 
     27 74 2 - e8 (0.00)
     28 
     29 38 4 - e0 (0.00)
     30 
     31 48 3 - d8 (0.00)
     32 
     33 c4 1 - c4 (0.00)
     34 
     35 62 2 - c4 (0.00)
     36 
     37 be 1 - be (0.00)
     38 
     39 b8 1 - b8 (0.00)
     40 
     41 ae 1 - ae (0.00)
     42 
     43 ac 1 - ac (0.00)
     44 
     45 55 2 - aa (0.00)
     46 
     47 a4 1 - a4 (0.00)
     48 
     49  
     50 
     51 加粗部分显示有0x76c6个1f64 (8036即代码中2000*4)大小的内存块被分配,如此大量同样大小的内存块分配,可以猜测这就可能是内存泄露的地方。
     52 
     53 下一步是获取到这些被分配内存的地址,使用命令
     54 
     55 !heap -flt s 1f64
     56 
     57 该命令掉其它的内存块,只显示大小为1f64 的内存块信息
     58 
     59 输出结果如下:
     60 
     61 0:001> !heap -flt s 1f64
     62 
     63 _HEAP @ 150000
     64 
     65 _HEAP @ 250000
     66 
     67 _HEAP @ 260000
     68 
     69 _HEAP @ 330000
     70 
     71 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
     72 
     73 003360e0 03f0 0000 [07] 003360e8 01f64 - (busy)
     74 
     75 00338060 03f0 03f0 [07] 00338068 01f64 - (busy)
     76 
     77 00339fe0 03f0 03f0 [07] 00339fe8 01f64 - (busy)
     78 
     79 0033bf60 03f0 03f0 [07] 0033bf68 01f64 - (busy)
     80 
     81 0033dee0 03f0 03f0 [07] 0033dee8 01f64 - (busy)
     82 
     83 01420040 03f0 03f0 [07] 01420048 01f64 - (busy)
     84 
     85 01421fc0 03f0 03f0 [07] 01421fc8 01f64 - (busy)
     86 
     87 01423f40 03f0 03f0 [07] 01423f48 01f64 - (busy)
     88 
     89 01425ec0 03f0 03f0 [07] 01425ec8 01f64 - (busy)
     90 
     91 01427e40 03f0 03f0 [07] 01427e48 01f64 - (busy)
     92 
     93 01429dc0 03f0 03f0 [07] 01429dc8 01f64 - (busy)
     94 
     95 0142bd40 03f0 03f0 [07] 0142bd48 01f64 - (busy)
     96 
     97 0142dcc0 03f0 03f0 [07] 0142dcc8 01f64 - (busy)
     98 
     99 0142fc40 03f0 03f0 [07] 0142fc48 01f64 - (busy)
    100 
    101 01431bc0 03f0 03f0 [07] 01431bc8 01f64 - (busy)
    102 
    103 01433b40 03f0 03f0 [07] 01433b48 01f64 - (busy)
    104 
    105 01435ac0 03f0 03f0 [07] 01435ac8 01f64 - (busy)
    106 
    107 01437a40 03f0 03f0 [07] 01437a48 01f64 - (busy)
    108 
    109 014399c0 03f0 03f0 [07] 014399c8 01f64 - (busy)
    110 
    111 0143b940 03f0 03f0 [07] 0143b948 01f64 - (busy)
    112 
    113 0143d8c0 03f0 03f0 [07] 0143d8c8 01f64 - (busy) 
    114 
    115 0143f840 03f0 03f0 [07] 0143f848 01f64 - (busy)
    116 
    117 014417c0 03f0 03f0 [07] 014417c8 01f64 - (busy)
    118 
    119 01443740 03f0 03f0 [07] 01443748 01f64 - (busy)
    120 
    121 014456c0 03f0 03f0 [07] 014456c8 01f64 - (busy)
    122 
    123 01447640 03f0 03f0 [07] 01447648 01f64 - (busy)
    124 
    125 014495c0 03f0 03f0 [07] 014495c8 01f64 - (busy)
    126 
    127 0144b540 03f0 03f0 [07] 0144b548 01f64 - (busy)
    128 
    129 0144d4c0 03f0 03f0 [07] 0144d4c8 01f64 - (busy)
    130 
    131 0144f440 03f0 03f0 [07] 0144f448 01f64 - (busy)
    132 
    133 014513c0 03f0 03f0 [07] 014513c8 01f64 - (busy)
    134 
    135 01453340 03f0 03f0 [07] 01453348 01f64 - (busy)
    136 
    137 014552c0 03f0 03f0 [07] 014552c8 01f64 - (busy)
    138 
    139 01457240 03f0 03f0 [07] 01457248 01f64 - (busy)
    140 
    141 014591c0 03f0 03f0 [07] 014591c8 01f64 - (busy)
    142 
    143 0145b140 03f0 03f0 [07] 0145b148 01f64 - (busy)
    144 
    145 0145d0c0 03f0 03f0 [07] 0145d0c8 01f64 - (busy)
    146 
    147 0145f040 03f0 03f0 [07] 0145f048 01f64 - (busy)
    148 
    149 01460fc0 03f0 03f0 [07] 01460fc8 01f64 - (busy)
    150 
    151 01462f40 03f0 03f0 [07] 01462f48 01f64 - (busy)
    152 
    153 01464ec0 03f0 03f0 [07] 01464ec8 01f64 - (busy)
    154 
    155 01466e40 03f0 03f0 [07] 01466e48 01f64 - (busy)
    156 
    157 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即为怀疑的堆大小)

posted on 2013-12-18 19:49  gisdemo  阅读(1862)  评论(0编辑  收藏  举报