windbg分析内存泄漏

介绍

本文主要介绍一种通过windbg分析内存泄漏的方法。

现象

后台检测程序在某天上报了告警,大概就是某程序的提交内存达到了1.0G。登陆后台查看,该进程已经运行了90天,提交内存每天都在持续上涨,从启动到目前为止大概累计上升了800M。应该是存在内存泄漏。
让运维通过工具保存了fulldump

准备工作

  • 下载地址(提取码:11bg)
  • 设置好系统的pdb
e:\mylocalsymbols;SRV*e:\mylocalsymbols*http://msdl.microsoft.com/download/symbols

查找堆块

打印所有堆块信息

!heap -s

显示如下

0:000> !heap -s
HEAPEXT: Unable to read ntdll!RtlpDisableHeapLookaside
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
006f0000 00000002 1246976 1241928 1246976    982   236    81    0      a   LFH
00190000 00001002    3136   1564   3136    390     7     3    0      0   LFH
    External fragmentation  24 % (7 free blocks)
00110000 00001002     256      4    256      1     1     1    0      0      
02050000 00001002     256    176    256      1    18     1    0      0   LFH
02240000 00001002     256      4    256      2     1     1    0      0      
006a0000 00001002      64     12     64      4     2     1    0      0      
044f0000 00001002     256    216    256      7     4     1    0      0   LFH
119d0000 00001002    7424   5820   7424    134   133     4    0     c8   LFH
14290000 00001003     256      4    256      2     1     1    0    bad      
141d0000 00001003     256      4    256      2     1     1    0    bad      
17f20000 00001003     256      4    256      2     1     1    0    bad      
19030000 00001003     256      4    256      2     1     1    0    bad      
191b0000 00001003     256      4    256      2     1     1    0    bad      
19380000 00001003     256      4    256      2     1     1    0    bad      
19300000 00001003     256      4    256      2     1     1    0    bad      
155f0000 00001003     256      4    256      2     1     1    0    bad      
-----------------------------------------------------------------------------

通过观察,我们知道了是006f0000堆块占用了大量内存

HEAPEXT: Unable to read ntdll!RtlpDisableHeapLookaside
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
006f0000 00000002 1246976 1241928 1246976    982   236    81    0      a   LFH

查看堆块内存百分比

内存持续上涨可能是某块固定大小内存被重复申请,所以统计下该堆块中各个内存大小的分配次数

!heap -stat -h 006f0000  

查找堆中各个内存大小占用的百分比

0:000> !heap -stat -h 006f0000
unable to resolve ntdll!RtlpStackTraceDataBase
 heap @ 006f0000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    14 23acbbe - 2c97ead8  (92.78)
    a4 2ba0c - 1bf2fb0  (3.63)
    1000 8f5 - 8f5000  (1.16)
    1a4 3b9c - 61cbf0  (0.79)
    20c 15fb - 2cfdc4  (0.37)
    25 b77d - 1a8511  (0.22)
    64 3ba0 - 174a80  (0.19)
    24 75ae - 108c78  (0.13)
    11c e4a - fda18  (0.13)
    84c 164 - b89b0  (0.09)
    400 172 - 5c800  (0.05)
    234 265 - 54684  (0.04)
    1c 2c2e - 4d508  (0.04)
    1c0 287 - 46c40  (0.04)
    c00 4b - 38400  (0.03)
    20 1a12 - 34240  (0.03)
    3bc ce - 30148  (0.02)
    50 8da - 2c420  (0.02)
    800 4c - 26000  (0.02)
    2ba d2 - 23c94  (0.02)

    size     #blocks     total     ( %) (percent of total busy bytes)
    14 23acbbe - 2c97ead8  (92.78)

TOP 20 中显示,最多的一个大小为 0x014 的分配次数为 0x23acbbe 次, 总共大概有700M左右。基本接近内存泄漏的总数。那么我们就需要来确定这个内存是谁申请的。

定位内存来源

找到了大量的内存是0x014字节大小的,但是根据这个条件我们也找不到具体的代码啊?下面是几个思路

思路1 根据大小

根据内存大小(0x14)去代码中查找大小为(0x14)的类、结构体、宏等等相关代码,然后找到原因。
难!!!
1)、进程包含了很多其他组的dll,有的我没代码权限,无法遍历
2)、结构体、类太多了,人眼遍历太难了(针对这个问题我开发了一个工具,后续章节讲解)

思路2 内存内容

显示所有大小为(0x14)内存的地址,看它的地址内容有没有什么特点,比如是否有特殊的字符串、固定的二进制头??? 显示所有分配大小为 0x14的内存

!heap -flt s 14 

0:000> !heap -flt s 14 
unable to resolve ntdll!RtlpStackTraceDataBase
    _HEAP @ 6f0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0071c038 0004 0000  [00]   0071c040    00014 - (busy)
        0071c2e8 0004 0004  [00]   0071c2f0    00014 - (busy)
        0071e498 0004 0004  [00]   0071e4a0    00014 - (busy)
        0071e4f8 0004 0004  [00]   0071e500    00014 - (busy)
        0071e518 0004 0004  [00]   0071e520    00014 - (busy)
        0071e5f8 0004 0004  [00]   0071e600    00014 - (busy)
        0071e638 0004 0004  [00]   0071e640    00014 - (busy)
        0071e658 0004 0004  [00]   0071e660    00014 - (busy)
        0071e798 0004 0004  [00]   0071e7a0    00014 - (busy)
        007374f0 0004 0004  [00]   007374f8    00014 - (busy)
        00737510 0004 0004  [00]   00737518    00014 - (busy)
        00737530 0004 0004  [00]   00737538    00014 - (busy)
        00737550 0004 0004  [00]   00737558    00014 - (busy)
        00737570 0004 0004  [00]   00737578    00014 - (busy)
        00737590 0004 0004  [00]   00737598    00014 - (busy)
        007375b0 0004 0004  [00]   007375b8    00014 - (busy)
        007375d0 0004 0004  [00]   007375d8    00014 - (busy)
        007375f0 0004 0004  [00]   007375f8    00014 - (busy)
        00737610 0004 0004  [00]   00737618    00014 - (busy)
        00737630 0004 0004  [00]   00737638    00014 - (busy)
        00737650 0004 0004  [00]   00737658    00014 - (busy)
        00737670 0004 0004  [00]   00737678    00014 - (busy)
        00737690 0004 0004  [00]   00737698    00014 - (busy)
            ..............
            ..............

随机抽查几个地址,看下地址内存


image

大都是这样的值,实在是看不出规律。

建议

一般公司都会封装malloc、new函数,并分配一个模块号,每个内存地址头部都会携带id号,如下:

xxx_malloc(int nModleID,size_t size);

这样通过地址空间内容也可以找到分配的模块。

思路3 分配次数

大小0x14的内存在90天时间内总共分配了23acbbe 次, 0x23acbbe = 37407678/(90(天)*24(小时) ≈ 17318次/小时。 这个内存几乎每小时被申请17318次。公司的服务器有个基本功能:每个小时会统计收到的消息次数,那分析下数量级在1w~3w左右的消息即可,大概是4个消息类型,然后通过代码review发现内存泄漏点

if(total_fee){
    LPADD_FEE pAddFee = new ADD_FEE;
    ZeroMemory(pAddFee, sizeof(ADD_FEE));
    pAddFee->nFee = total_fee;
    gdt.nTotalFee = total_fee;
}

结构体 ADD_FEE ,刚好是20字节

typedef struct _tagADD_FEE{
    int nFee;
    int nReserved[4];
}ADD_FEE, *LPADD_FEE;

完全符合!! 问题解决

总结

这个一个低级错误导致的。为了避免类视问题,引入代码静态检测
1)、cppcheck
2)、pclint
最后选了pclint。配合jenkins,每天凌晨进行代码静态检查,并输出和上个版本的diff文件,下次就不会出现这么低级的问题。

你可能感兴趣的:(windbg分析内存泄漏)