介绍
本文主要介绍一种通过windbg分析内存泄漏的方法。
现象
后台检测程序在某天上报了告警,大概就是某程序的提交内存达到了1.0G。登陆后台查看,该进程已经运行了90天,提交内存每天都在持续上涨,从启动到目前为止大概累计上升了800M。应该是存在内存泄漏。
让运维通过工具保存了fulldump
准备工作
- 下载地址(提取码:11bg)
 - 设置好系统的pdb
1
e:\mylocalsymbols;SRV*e:\mylocalsymbols*http://msdl.microsoft.com/download/symbols
 
查找堆块
打印所有堆块信息1
!heap -s
显示如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
230: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堆块占用了大量内存1
2
3
4
5HEAPEXT: 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
查看堆块内存百分比
内存持续上涨可能是某块固定大小内存被重复申请,所以统计下该堆块中各个内存大小的分配次数1
!heap -stat -h 006f0000
查找堆中各个内存大小占用的百分比
1  | 0:000> !heap -stat -h 006f0000  | 
1  | size #blocks total ( %) (percent of total busy bytes)  | 
TOP 20 中显示,最多的一个大小为 0x014 的分配次数为 0x23acbbe 次, 总共大概有700M左右。基本接近内存泄漏的总数。那么我们就需要来确定这个内存是谁申请的。
定位内存来源
找到了大量的内存是0x014字节大小的,但是根据这个条件我们也找不到具体的代码啊?下面是几个思路
思路1 根据大小
根据内存大小(0x14)去代码中查找大小为(0x14)的类、结构体、宏等等相关代码,然后找到原因。
难!!!
1)、进程包含了很多其他组的dll,有的我没代码权限,无法遍历
2)、结构体、类太多了,人眼遍历太难了(针对这个问题我开发了一个工具,后续章节讲解)
思路2 内存内容
显示所有大小为(0x14)内存的地址,看它的地址内容有没有什么特点,比如是否有特殊的字符串、固定的二进制头???   显示所有分配大小为 0x14的内存1
!heap -flt s 14
1  | 0:000> !heap -flt s 14  | 
随机抽查几个地址,看下地址内存
大都是这样的值,实在是看不出规律。
建议
一般公司都会封装malloc、new函数,并分配一个模块号,每个内存地址头部都会携带id号,如下:1
xxx_malloc(int nModleID,size_t size);
这样通过地址空间内容也可以找到分配的模块。
思路3 分配次数
大小0x14的内存在90天时间内总共分配了23acbbe 次, 0x23acbbe = 37407678/(90(天)*24(小时) ≈ 17318次/小时。 这个内存几乎每小时被申请17318次。公司的服务器有个基本功能:每个小时会统计收到的消息次数,那分析下数量级在1w~3w左右的消息即可,大概是4个消息类型,然后通过代码review发现内存泄漏点1
2
3
4
5
6if(total_fee){
    LPADD_FEE pAddFee = new ADD_FEE;
    ZeroMemory(pAddFee, sizeof(ADD_FEE));
    pAddFee->nFee = total_fee;
    gdt.nTotalFee = total_fee;
}
结构体 ADD_FEE ,刚好是20字节1
2
3
4typedef struct _tagADD_FEE{
    int nFee;
    int nReserved[4];
}ADD_FEE, *LPADD_FEE;
完全符合!! 问题解决