記一次 .NET 某光譜檢測(cè)軟件 內(nèi)存暴漲分析
一:背景
1. 講故事
訓(xùn)練營里的一位學(xué)員找到我,說他們的系統(tǒng)會(huì)出現(xiàn)內(nèi)存暴漲的情況,看了下也不是托管堆的問題,讓我協(xié)助一下到底怎么回事?既然有dump了,那就開始分析之旅吧。
二:內(nèi)存暴漲分析
1. 為什么會(huì)暴漲
在分析之前還是那條原則,不要過分的相信求助者的話,否則容易被他帶溝里去,畢竟人家是業(yè)余的,你是專業(yè)的。。。接下來使用 !address -summary 觀察下提交內(nèi)存,輸出如下。
0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 637 5ffd`2b0ce000 ( 95.989 TB) 74.99%
<unknown> 2783 2001`3b63cc00 ( 32.005 TB) 99.98% 25.00%
Heap 3737 1`509cf000 ( 5.260 GB) 0.02% 0.00%
Image 2615 0`2deb1400 ( 734.692 MB) 0.00% 0.00%
Stack 235 0`12d00000 ( 301.000 MB) 0.00% 0.00%
Other 80 0`082c8000 ( 130.781 MB) 0.00% 0.00%
TEB 78 0`0009c000 ( 624.000 kB) 0.00% 0.00%
PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 6681 2002`9d342000 ( 32.010 TB) 100.00% 25.01%
MEM_IMAGE 2625 0`2deca000 ( 734.789 MB) 0.00% 0.00%
MEM_MAPPED 223 0`09d16000 ( 157.086 MB) 0.00% 0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 637 5ffd`2b0ce000 ( 95.989 TB) 74.99%
MEM_RESERVE 855 2001`4093a000 ( 32.005 TB) 99.98% 25.00%
MEM_COMMIT 8674 1`945e8000 ( 6.318 GB) 0.02% 0.00%
...
從卦中可以看到當(dāng)前 MEM_COMMIT=6.3G 和 Heap=5.2G,卦象里已經(jīng)非常明顯的表明當(dāng)前屬 NT堆 暴漲,縮小范圍之后繼續(xù)使用 !heap -s 下鉆分析。
0:000> !heap -s
************************************************************************************************************************
NT HEAP STATS BELOW
************************************************************************************************************************
NtGlobalFlag enables following debugging aids for new heaps:
stack back traces
LFH Key : 0x7a5897d060b6c4ee
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-------------------------------------------------------------------------------------
0000016214c40000 08000002 4711276 4710972 4710884 9187 1416 295 19 6d LFH
000001620cbd0000 08008000 64 8 64 6 1 1 0 0
0000016214f60000 08001002 1472 132 1080 21 7 2 0 0 LFH
...
從卦象看,這高內(nèi)存是由 Heap=0000016214c40000 貢獻(xiàn)的,其中 Commit=4.71G 也明確的表明當(dāng)前是由海量的 alloc <1M 的 block 引發(fā)的,由于這個(gè)dump已經(jīng)開啟了 stack back traces,所以能夠輕松的拿到分配block的調(diào)用棧。
2. 海量的block是什么
要想找到這個(gè)答案,可以對(duì)heap進(jìn)行分配排序來觀察異常特征,使用 !heap -stat -h 0000016214c40000 命令。
0:000> !heap -stat -h 0000016214c40000
heap @ 0000016214c40000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
4000 40e08 - 103820000 (88.20)
1000 15a1d - 15a1d000 (7.35)
1309800 5 - 5f2f800 (2.02)
e6a900 2 - 1cd5200 (0.61)
ba6a20 1 - ba6a20 (0.25)
3a8000 3 - af8000 (0.23)
4ce500 2 - 99ca00 (0.20)
800027 1 - 800027 (0.17)
...
從卦中可以看到 block=4000byte 的塊有 0x40e08 個(gè),總大小為 0x103820000 = 4.3G,看樣子離真相越來越近了,接下來使用 !heap -flt s 4000 看下這 0x40e08 個(gè)block都是些什么東西,部分輸出如下:
0:000> !heap -flt s 4000
_HEAP @ 16214c40000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
000001622fa684a0 0403 0000 [00] 000001622fa684d0 04000 - (busy)
...
0000016281dcf7b0 0403 0403 [00] 0000016281dcf7e0 04000 - (busy)
0000016281dd37e0 0403 0403 [00] 0000016281dd3810 04000 - (busy)
0000016281dd7810 0403 0403 [00] 0000016281dd7840 04000 - (busy)
0000016281ddb840 0403 0403 [00] 0000016281ddb870 04000 - (busy)
0000016281ddf870 0403 0403 [00] 0000016281ddf8a0 04000 - (busy)
0000016281de38a0 0403 0403 [00] 0000016281de38d0 04000 - (busy)
0000016281de78d0 0403 0403 [00] 0000016281de7900 04000 - (busy)
0000016281deb900 0403 0403 [00] 0000016281deb930 04000 - (busy)
0000016281def930 0403 0403 [00] 0000016281def960 04000 - (busy)
0000016281df3960 0403 0403 [00] 0000016281df3990 04000 - (busy)
0000016281df7990 0403 0403 [00] 0000016281df79c0 04000 - (busy)
...
接下來就是使用 !heap -p -a 0000016281df7990 抽幾個(gè) block 觀察其分配棧,全都是如下輸出:
0:000> !heap -p -a 0000016281df7990
address 0000016281df7990 found in
_HEAP @ 16214c40000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0000016281df7990 0403 0000 [00] 0000016281df79c0 04000 - (busy)
7ffa3dadb49d ntdll!RtlpAllocateHeapInternal+0x0000000000000a7d
7ffa3b44fde6 ucrtbase!_malloc_base+0x0000000000000036
7ff9faacc1a9 xxxWrapper!xxxWrapper::operator=+0x0000000000008819
7ff9faac5474 xxxWrapper!xxxWrapper::operator=+0x0000000000001ae4
7ff9faac3efb xxxWrapper!xxxWrapper::operator=+0x000000000000056b
7ff9c8c06606 +0x00007ff9c8c06606
從卦象看是 0x00007ff9c8c06606 調(diào)用了 xxxWrapper.dll,可能有些人有疑問,前者為什么是地址而不是函數(shù)名,這是因?yàn)樗荍IT動(dòng)態(tài)生成的哈,使用 !ip2md 0x00007ff9c8c06606 讓其現(xiàn)出原形。
0:000> !ip2md 0x00007ff9c8c06606
MethodDesc: 00007ff9c8e00068
Method Name: xxxWrapper.xxxWrapperHelper.GetSpectrum(Int32)
Class: 00007ff9c8ded028
MethodTable: 00007ff9c8e001f8
mdToken: 0000000006000073
Module: 00007ff9c8dbcce8
IsJitted: yes
CodeAddr: 00007ff9c8c06590
Transparency: Safe critical
最后根據(jù) GetSpectrum 找到相應(yīng)的源碼,截圖如下:

從卦中看應(yīng)該只有分配,沒有調(diào)用相關(guān)的釋放 函數(shù)導(dǎo)致的,讓朋友找一下 GetSpectrum 函數(shù)的 C++ 方,定會(huì)提供相關(guān)指導(dǎo)意見。
三:總結(jié)
本次內(nèi)存暴漲事故屬于C++和C#團(tuán)隊(duì)沒有很好的溝通和協(xié)作引發(fā)的,不過像這種多語言項(xiàng)目想不弄出點(diǎn)事都難哈。

浙公網(wǎng)安備 33010602011771號(hào)