记一次 .NET 某桌面奇侠游戏 非托管内存泄漏分析
一:背景
1. 讲故事
说实话,这篇dump我本来是不准备上一篇文章来解读的,但它有两点深深的感动了我。
无数次的听说用 Unity 可做游戏开发,但百闻不如一见。
游戏中有很多金庸武侠小说才有的名字,太赏心悦目了。
000000df315978a8 0 3 玉骨扇
000000df31597cd8 0 3 云龙枪
000000df31596d88 0 3 阴风爪
000000df315967a8 0 4 雪魂丝链
000000df31596ad0 0 4 乙木神剑
000000df31596040 0 3 星耀冠
000000df31595328 0 3 乌金锤
...
所以说这么好的一个dump,我得给它留下点什么。
好了,话说回来这个缘分起于上个月有位朋友说它的程序虚拟内存占用非常大,咨询如何解决,如下图:
先甭管是什么问题,多抓几个dump总不会错的,几经折腾后发了一个dump过来。
二: Windbg 分析
1. 到底是哪里的泄漏
分析内存方面的问题,还是那句话,一分为二看一下到底是哪一块的内存泄漏(托管还是非托管)。
先看一下进程总内存,使用 !address -summary 命令。
0:087> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 458 7ffe`9e6a8000 ( 127.995 TB) 100.00%
Heap 48514 1`005fd000 ( 4.006 GB) 72.51% 0.00%
<unknown> 2504 0`2c6ad000 ( 710.676 MB) 12.56% 0.00%
Stack 504 0`2a000000 ( 672.000 MB) 11.88% 0.00%
Image 410 0`0a971000 ( 169.441 MB) 3.00% 0.00%
Other 18 0`001dc000 ( 1.859 MB) 0.03% 0.00%
TEB 168 0`00150000 ( 1.312 MB) 0.02% 0.00%
PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 51581 1`5130f000 ( 5.269 GB) 95.36% 0.00%
MEM_IMAGE 416 0`0aa6b000 ( 170.418 MB) 3.01% 0.00%
MEM_MAPPED 122 0`05bce000 ( 91.805 MB) 1.62% 0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 458 7ffe`9e6a8000 ( 127.995 TB) 100.00%
MEM_COMMIT 51465 1`1c741000 ( 4.445 GB) 80.45% 0.00%
MEM_RESERVE 654 0`45207000 ( 1.080 GB) 19.55% 0.00%
从卦中得知 MEM_COMMIT=4.4G, 接下来再看下托管堆的内存占用,可以用命令 !eeheap -gc 命令。
0:087> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x000000df3118dc48
generation 1 starts at 0x000000df3118b098
generation 2 starts at 0x000000df30fc1000
ephemeral segment allocation context: none
segment begin allocated size
000000df30fc0000 000000df30fc1000 000000df3178cae0 0x7cbae0(8174304)
Large object heap starts at 0x000000df40fc1000
segment begin allocated size
000000df40fc0000 000000df40fc1000 000000df410637b8 0xa27b8(665528)
Total Size: Size: 0x86e298 (8839832) bytes.
------------------------------
GC Heap Size: Size: 0x86e298 (8839832) bytes.
从卦中得知 GC Heap Size= 8839832 Byte = 8M,我去,才这么点,有点开玩笑哈!!! ,很明显这是非托管内存泄漏,既然方向已定,那就排查下非托管区域吧!
2. 探究非托管泄漏
按照经验,寻找非托管泄漏,首先看下 loader 堆,很多程序往往是因为动态创建了太多程序集所致,比如经典的 Castle, XmlSerializer ,有兴趣的朋友可以网上找下这方面的资料,这里使用 !eeheap -loader 命令查看。
0:087> !eeheap -loader
--------------------------------------
Jit code heap:
LoaderCodeHeap: 0000000000000000(0:0) Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Module Thunk heaps:
Module 00007ffda5fa1000: Size: 0x0 (0) bytes.
Module 00007ffd485c4148: Size: 0x0 (0) bytes.
Module 00007ffda2631000: Size: 0x0 (0) bytes.
Module 00007ffda5331000: Size: 0x0 (0) bytes.
Module 00007ffdac621000: Size: 0x0 (0) bytes.
Module 00007ffdac4e1000: Size: 0x0 (0) bytes.
Module 00007ffda48b1000: Size: 0x0 (0) bytes.
Module 00007ffda1791000: Size: 0x0 (0) bytes.
Module 00007ffd487b1858: Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Module Lookup Table heaps:
Module 00007ffda5fa1000: Size: 0x0 (0) bytes.
Module 00007ffd485c4148: Size: 0x0 (0) bytes.
Module 00007ffda2631000: Size: 0x0 (0) bytes.
Module 00007ffda5331000: Size: 0x0 (0) bytes.
Module 00007ffdac621000: Size: 0x0 (0) bytes.
Module 00007ffdac4e1000: Size: 0x0 (0) bytes.
Module 00007ffda48b1000: Size: 0x0 (0) bytes.
Module 00007ffda1791000: Size: 0x0 (0) bytes.
Module 00007ffd487b1858: Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size: Size: 0x99000 (626688) bytes total, 0x2000 (8192) bytes wasted.
=======================================
从输出看: Total LoaderHeap size= 626K,看样子这次踏空了,那就进困难模式看看 Windows NT 堆,这里使用 !heap -s 命令。
0:087> !heap -s
************************************************************************************************************************
NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key : 0xb6c37b3e3a4a189e
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-------------------------------------------------------------------------------------
000000df2e680000 00000002 4145084 4130108 4144304 1537 775 260 1 4 LFH
000000df2e1f0000 00008000 64 4 64 2 1 1 0 0
000000df2e830000 00001002 1860 172 1080 15 5 2 0 0 LFH
000000df2ec80000 00001002 1860 236 1080 5 7 2 0 0 LFH
000000df309e0000 00001002 60 8 60 2 1 1 0 0
000000df30bb0000 00041002 60 8 60 5 1 1 0 0
000000df49bd0000 00001002 840 44 60 3 3 1 0 0 LFH
000000df49b20000 00041002 1860 96 1080 8 3 2 0 0 LFH
000000df30b40000 00001002 60 20 60 9 2 1 0 0
000000df30b30000 00001002 1860 152 1080 11 8 2 0 0 LFH
000000df4bbb0000 00001002 3904 1292 3124 49 6 3 0 0 LFH
000000df89920000 00001002 1860 372 1080 14 7 2 0 0 LFH
000000df89be0000 00001006 1860 280 1080 23 2 2 0 0 LFH
000000df56f40000 00001006 32372 26204 31592 1434 21 6 0 6b LFH
000000df56f10000 00001006 1860 176 1080 21 3 2 0 0 LFH
000000df89ac0000 00001006 3904 2160 3124 67 4 3 0 2e LFH
-------------------------------------------------------------------------------------
从输出信息看:原来程序的内存都被 heap=000000df2e680000 给吸走了,那就深挖它吧,这里用 !heap -stat -h 000000df2e680000 命令看一下该heap的统计信息。
0:087> !ext.heap -stat -h 000000df2e680000
heap @ 000000df2e680000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
2000 4cfd2 - 99fa4000 (68.76)
58 9d7492 - 36201230 (24.17)
12c 267e8 - 2d1c3e0 (1.26)
21d1 c46 - 19f0b26 (0.72)
4020 634 - 18dc680 (0.69)
a0 26d00 - 1842000 (0.68)
a 1d3ebb - 124734e (0.51)
10 f8d99 - f8d990 (0.43)
6 16adae - 881214 (0.24)
b b3508 - 7b4758 (0.22)
7 115125 - 793803 (0.21)
5 17b833 - 7698ff (0.21)
c 86027 - 6481d4 (0.18)
9 afef9 - 62f6c1 (0.17)
d 6a80f - 5688c3 (0.15)
f 4f5a9 - 4a64e7 (0.13)
e 54814 - 49f118 (0.13)
8 8b092 - 458490 (0.12)
13 3139b - 3a7481 (0.10)
15 25d06 - 31a17e (0.09)
从输出信息看,这块heap主要是被 size=2000 和 size=58 给填满了,毕竟他们占比 68.76 + 24.17 = 92.93,所以挖他们很有必要,接下来用命令 !heap -flt s 2000 找出heap中所有的这些block的首地址。
0:087> !ext.heap -flt s 2000
_HEAP @ df2e680000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
000000df2e702dd0 0201 0000 [00] 000000df2e702de0 02000 - (busy)
000000df2e72c7e0 0201 0201 [00] 000000df2e72c7f0 02000 - (busy)
000000df517400c0 0201 0201 [00] 000000df517400d0 02000 - (busy)
000000df517420d0 0201 0201 [00] 000000df517420e0 02000 - (busy)
000000df517440e0 0201 0201 [00] 000000df517440f0 02000 - (busy)
000000df517460f0 0201 0201 [00] 000000df51746100 02000 - (busy)
000000df51748100 0201 0201 [00] 000000df51748110 02000 - (busy)
000000df5174a110 0201 0201 [00] 000000df5174a120 02000 - (busy)
000000df5174c120 0201 0201 [00] 000000df5174c130 02000 - (busy)
000000df5174e130 0201 0201 [00] 000000df5174e140 02000 - (busy)
000000df51750140 0201 0201 [00] 000000df51750150 02000 - (busy)
...
上面的 HEAP_ENTRY 就是block的首地址,由于这样的block大概有 4cfd2=31.5w 个,没法一一列出,接下来就是用 dc 去观察这些 block 的内存块内容来发现其中规律,手工肯定太麻烦了,还是得借助下脚本,这里还是取前1w条查看。
function show_all_blocksize() {
var output = exec("!ext.heap -flt s 58").Take(10000);
for (var line of output) {
var heap_entry_address = line.trim().split(' ')[0];
if (heap_entry_address.indexOf("00") == -1) continue;
show_heap_entry(heap_entry_address);
}
}
function show_heap_entry(heap_entry_address) {
var pageIndex = (index++);
var path = ".writemem D:\\file\\"+ pageIndex + ".txt " + heap_entry_address + " L?0x58";
var output = exec(path);
log("pageIndex=" + pageIndex);
}
执行脚本生成到txt之后,截图如下:
通过观察发现,这个heap中有大量的用户信息,然后就拿这些信息求证朋友了。
和朋友简单沟通后,我也只能帮到这里,到此结案。
三:总结
本次事故的原因是由于 C# 调用 Lua 后,Lua 未作合理的内存释放造成的非托管泄漏,具体怎么在代码层进行释放,这个要看朋友的造化了。
最后上一个小彩蛋,朋友太客气了。
没见过这么大的红包,我居然收了 ,反手就给公司研发小伙伴一人一杯下午茶,在这里对朋友说一声感谢

记一次 .NET 某桌面奇侠游戏 非托管内存泄漏分析的更多相关文章
- 记一次 .NET 某智慧水厂API 非托管内存泄漏分析
一:背景 1. 讲故事 七月底的时候有位朋友在wx上找到我,说他的程序内存占用8G,托管才占用1.5G,询问剩下的内存哪里去了?截图如下: 从求助内容看,这位朋友真的太客气了,动不动就谈钱,真伤感情, ...
- 记一次 .NET 某打印服务 非托管内存泄漏分析
一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的程序出现了内存泄漏,能不能帮他看一下,这个问题还是比较经典的,加上好久没上非托管方面的东西了,这篇就和大家分享一下,话不多说,上 WinD ...
- 记一次 .NET 某HIS系统后端服务 内存泄漏分析
一:背景 1. 讲故事 前天那位 his 老哥又来找我了,上次因为CPU爆高的问题我给解决了,看样子对我挺信任的,这次另一个程序又遇到内存泄漏,希望我帮忙诊断下. 其实这位老哥技术还是很不错的,他既然 ...
- 记一次 .NET 某外贸Web站 内存泄漏分析
一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬. 沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了 ...
- 记一次 .NET 某风控管理系统 内存泄漏分析
一:背景 1. 讲故事 上个月中旬,星球里的一位朋友在微信找我,说他的程序跑着跑着内存会不断的缓慢增长并无法释放,寻求如何解决 ? 得,看样子星球还得好好弄!!! 不管怎么说,先上 windbg 说话 ...
- 记一次 .NET 某智能服装智造系统 内存泄漏分析
一:背景 1. 讲故事 上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下: 朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧. 二:Windbg 分析 1. ...
- 记一次 WinDbg 分析 .NET 某工厂MES系统 内存泄漏分析
一:背景 1. 讲故事 上个月有位朋友加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下: 从聊天内容看,这位朋友压力还是蛮大的,话说这貌似是我分析的第三个 MES 系统了,看样子 . ...
- 记一次 .NET 某消防物联网 后台服务 内存泄漏分析
一:背景 1. 讲故事 去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了...截图如下: 时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了 二 ...
- 记一次 .NET 某电厂Web系统 内存泄漏分析
一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...
随机推荐
- Distance Queries 距离咨询 (LCA倍增模板)
农夫约翰有N(2<=N<=40000)个农场,标号1到N.M(2<=M<=40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地 ...
- Jarvis OJ部分逆向
Jarvis OJ部分逆向题解 很久没有写博客了,前天上Jarvis OJ刷了几道逆向,保持了一下感觉.都是简单题目,写个writeup记录一下. easycrackme int __cdecl ma ...
- C++ //继承中构造和析构顺序
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 class Base 6 { 7 pu ...
- 说说 VARCHAR 背后的那些事
在使用MySQL的过程中,在存储字符串时,大家或许都有过这样或那样的困惑,譬如: 1. 对于固定长度的字符串,为什么推荐使用 CHAR 来存储? 2. VARCHAR 可设置的最大长度是多少? 3 ...
- [TensorFow2.0]-MNIST手写数字识别
本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...
- Linux命令(九)之安装mysql
.personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...
- Redis奇怪的姿势
Redis奇怪的姿势 写在前面 之前渗透 摸鱼 时和小伙伴发现了一个redis,存在未授权,是win服务器但是没有路径,度娘了之后发现了这个姿势,特此学习记录一下. 写入启动项 环境搭建 window ...
- 一个命令搞定 Web 国际化
背景 随着出海的业务越来越多,web 应用面临越来越多的国际化的工作.如何高效,高质量的完成 Web 前端国际化工作,已经是摆在 web 前端同学的急需解决的问题. i18n-helper-cli 是 ...
- BUUCTF刷题系列(1)5.25日记
前面的题目都不太难,就直接从SQL注入开始了. 这个样子的话,明显就是注入,我们先拿出SQL语句:http://fb415201-6634-4fc3-a6bc-a67b84ea1ed2.node3.b ...
- noip 模拟9 题解
rp++==文化课报废 考试经过 先看T1,有被1e12吓到,但根据经验这很可能是水题,经过一番观察后直接打表,似乎看出了规律,觉得应该有了正解,写完之后顺利过掉大样例,但似乎时间稍慢一点,写上快读交 ...