记一次使用Windbg分析内存“泄漏”的案例

客户的生产环境里发生怪异的事情,他们发现IBM Spectrum Symphony的集群里的核心进程VEMKD的内存使用时不时会一路飙升且不下降,如果不人工干预(重启VEMKD)的话,VEMKD的内存使用可能会撑爆(64G的物理内存)。且看下图。

记一次使用Windbg分析内存“泄漏”的案例_第1张图片

因为是生产环境,UMDH这个工具是不能上的,会影响cluster,而这个问题又仅仅在生产环境观察到,在QA和UAT环境都没有,DEV环境更没有。所以我们在分析各种VEMKD的log和perf log之后,不能确定怀疑的点在哪,只好让客户配合收集VEMKD的core dump文件,一并把环境里的vemkd.pdb和vemkd.exe上传给我们,试图从dump文件里分析泄漏点在哪。收集dump,可以从task manager找到进程,右键生成dump;也可以用Process Explorer右键生成dump。

WinDbg分析dump,总结下来,在这个case里下面几个命令比较相关和实用。

!heap -s==>  显示堆的概要信息(-s即summary)。一般从概要信息里可以看到哪些heap是占用较多内存的。优先考虑最大的heap,然后用下一个命令查看该heap的更多信息。

!heap -stat -h Handle==> 显示指定堆地址Handle的内存使用统计信息

!heap -flt s size==> flt即filter, 仅显示所有内存分配大小为指定size的分配详情

!heap -p -a address ==>-p显示要看page heap info, -a指定要看的info要包括address这个地址。特别注意,-a可以显示调用栈(Stack traces),当然事先收集core dump的时候需要先用gflag打开

!heap -x -v address ==>-x查找包含有指定address的堆,-v则针对指定的address额外查找当前进程整块的虚拟内存空间

!heap -i heapaddress   && !heap -i memory’s heapentry  ==>-i指显示指定heap的信息

dt -v [module!]Name Address ==> display type,显示指定变量Name的类型,后面跟Name的内存地址
dc address Llen ==> dc,显示double word类型
db address   ==>display byte,显示byte和ASCII码

!sym noisy ==> Activates noisy symbol loading

 先从!heap -s的结果中得知,在某个约2G的core dump里,00000135e2480000堆占用的memory最多,达1.5G;在用!heap -stat -h 00000135e2480000得知,size=19c的block有0x4c66a个,占了0x7af5298 byte即122m。

0:000> !heap -s
                                          NT HEAP STATS BELOW
LFH Key : 0xfddb5bc3d2603fd9
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt     Free List   UCR Virt Lock Fast
(k)   (k)    (k)    (k)  length blocks cont. heap
00000135e2480000 00000002 1507216 1334856 1505660 48225 3479 240 3 980e LFH Virtual address fragmentation 11 % (240 uncommited ranges)
00000135e2310000 00008000 64 4 64 2 1 1 0 0
00000135e40d0000 00001002 1616 68 60 18 3 1 0 0 LFH
00000135e4800000 00001002 60 16 60 13 1 1 0 0

0:000> !heap -stat -h 00000135e2480000
heap @ 00000135e2480000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
19c 4c66a - 7af5298 (22.36)
20 25d2d3 - 4ba5a60 (13.75)
18 314db6 - 49f4910 (13.45)
80 54522 - 2a29100 (7.67)
951 4552 - 285d0f2 (7.34)
38 57d10 - 1335b80 (3.49)
228 74f9 - fc38e8 (2.87)
78 1a17e - c3b310 (2.22)
c8 d7a8 - a87b40 (1.91)
6 1ba743 - a5eb92 (1.89)
238 42d7 - 944d08 (1.69)
58 1a59e - 90ee50 (1.65)
411 1f03 - 7e1b33 (1.43)
188 3ec8 - 602240 (1.09)
28 25c3c - 5e6960 (1.07)
40 164b1 - 592c40 (1.01)
1e 2e208 - 567cf0 (0.98)
46fbc8 1 - 46fbc8 (0.81)
b8 601d - 4514d8 (0.79)
68 7e18 - 3339c0 (0.58)

 下一步,查看一下大小为19c的Stack traces,这样就可以结合代码定位到大小为19c是什么数据。查看代码得知,0x19c大小的内容是从PEM发送的数据块,它的内容是container status update。当VEMKD接收到来自PEM的数据时,该内存被分配到inQueueMsgIn()函数channel内的lpIOContext->Buffer位置。然后这块内存会被一个数据类型为buffer structure的变量链接并赋值。所以查看stack traces对了解code如何运作非常有帮助。

0:012> !heap -p -a 000002194ee146b0
address 000002194ee146b0 found in _HEAP @ 21943e10000
HEAP_ENTRY       Size Prev Flags UserPtr          UserSize - state
000002194ee14680 001d 0000 [00] 000002194ee146b0   0019c   - (busy)
unknown!printable
7ffdbb280133 ntdll!RtlpCallInterceptRoutine+0x000000000000003f
7ffdbb213be4 ntdll!RtlpAllocateHeapInternal+0x0000000000001164
7ffdaf127705 msvcr120!calloc_impl+0x000000000000005d
7ffdaf126c91 msvcr120!calloc+0x0000000000000015
7ff692f33312 vemkd!gcalloc+0x0000000000000072
7ff692f4afcb vemkd!encodeAllocHostList+0x000000000000007b
7ff692f4fbff vemkd!fillAllocationRecord+0x000000000000012f
7ff692f4a21d vemkd!allocation_snapshot+0x00000000000000ad
7ff692f4ed72 vemkd!alloc_consumer_snapshot+0x0000000000000022
7ff692ea68d3 vemkd!main+0x00000000000004c3
7ff693054acb vemkd!__tmainCRTStartup+0x000000000000010f
7ffdbafa84d4 kernel32!BaseThreadInitThunk+0x0000000000000014
7ffdbb241791 ntdll!RtlUserThreadStart+0x0000000000000021

查看一下buffer类型在堆块000001f1`faf00720的内容是什么。

0:000> dt buffer 000001f1`faf00720
vemkd!buffer
+0x000 forw : 0x000001f1`877f9110 buffer
+0x008 back : 0x000001f1`fa725c30 buffer
+0x010 data : 0x000001f1`e1ad0680 “”   <==data是我们需要查看的,data的入口要减去偏移量
+0x018 pos : 0n412
+0x01c len : 0n412
+0x020 elemStartTime : 0x00000486`00dde4b9
+0x028 elemEndTime : 0
+0x030 recvLength : 0n0

 查看一下这个data堆块的相关信息,可知data的requested size正是19c bytes.

0:000> !heap -i 0x000001f1e1ad0670 <— ( 0x000001f1e1ad0680 - 0x10 = the heap entry address of above “buffer->data”)
Detailed information for block entry 000001f1e1ad0670
Assumed heap : 0x000001f1be240000 (Use !heap -i NewHeapHandle to change)
Header content : 0x8400CC0D 0x94012000
Block flags : 0x1 LFH (busy )
Total block size : 0x1b units (0x1b0 bytes)
Requested size : 0x19c bytes (unused 0x14 bytes). <--- size is 19c
Subsegment : 0x000001f1fa7ceb20

 可以看看这个19c bytes的具体内容是什么:


0:000> dc 0x000001f1`e1ad0680  <- the content of above “buffer->data”:
000001f1`e1ad0680 10000000 14000000 80010000 02000000 ................
000001f1`e1ad0690 00000000 00000000 00000000 1d000000 ................
000001f1`e1ad06a0 32776267 35323030 2e353139 2e6c6267  gbw20025915.gbl.
000001f1`e1ad06b0 682e6461 6e616465 656e2e69 00000074  ad.abcdef.net...
000001f1`e1ad06c0 06000000 34333537 00003430 04000000 ....753404......
000001f1`e1ad06d0 44010000 ffffffff 00000000 00000000 ...D............
000001f1`e1ad06e0 00000000 5c200000 5c200000 00000000 ...... .. ....
000001f1`e1ad06f0 00000000 00000000 00000000 11000000 ...............


0:000> db 000001943e2cace0
000001943e2cace0 00 00 00 10 00 00 00 14-00 00 01 80 00 00 00 01 ................
000001943e2cacf0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 1d ................
000001943e2cad00 67 62 77 32 30 30 32 35-39 34 35 2e 67 62 6c 2e gbw20025945.gbl.
000001943e2cad10 61 64 2e 68 65 64 61 6e-69 2e 6e 65 74 00 00 00 ad.abcdef.net...
000001943e2cad20 00 00 00 06 34 31 33 35-31 30 00 00 00 00 00 02 ....413510......
000001943e2cad30 00 00 01 44 ff ff ff ff-00 00 00 00 00 00 00 00 ...D............
000001943e2cad40 00 00 00 00 00 00 41 d8-00 00 41 d8 00 00 00 00 ......A...A.....
000001943e2cad50 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 11 ................

从显示的内容来看,19c大小的内容都跟两个host gbw20025915和gbw20025945有关。所以我们很怀疑这两个机器有问题。于是把上面的所有Symphony的log都收集回来检查,果不其然,这两个机子上的SIM进程不断重启,原因是在application的profile里设定的preExecCmd在这两个机子上找不着。SIM不断重启就导致PEM会不断向VEMKD汇报container status update,一旦VEMKD太忙而不能及时处理这些update,request就会在VEMKD堆积,而PEM因得不到VEMKD的响应,亦会每5分钟重复发送container status update的request,就导致加速堆积,所以导致VEMKD的memory的使用慢慢上升。后来客户把这两个机子从cluster里移出去,问题就没再发生了。

所以,从结果来看,这个case不是内存泄漏,而是数据堆积在VEMKD里没有及时处理。

你可能感兴趣的:(高性能计算HPC,c++,大数据,IBM,Symphony,云计算,WinDbg,heap)