介绍
本文主要介绍一种通过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;
完全符合!! 问题解决