windbg分析内存泄漏

介绍

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

现象

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

准备工作

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文件,下次就不会出现这么低级的问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容