记一次 .NET 某医院预约平台 非托管泄露分析
一:背景
1. 讲故事
前几天有位朋友找到我,说他的程序有内存泄露,让我帮忙排查一下,截图如下:
说实话看到 32bit, 1.5G 这些关键词之后,职业敏感告诉我,他这个可能是虚拟地址紧张所致,不管怎么说,有了 Dump 就可以上马分析。
二:WinDbg分析
1. 虚拟地址紧张所致吗
要看是不是虚拟地址紧张,可以用 !address -summary
观察下内存段统计信息,截图如下:
我去,用 WinDbg Preview 尽然分析不了,在加载 ntdll
的过程中死掉了,如果你是我们调试训练营的朋友,应该会深深的有体会,我们分析的第一个dump就存在这个情况,这个加载不了其实就预示着一种非托管泄露,这里暂不剧透。
用 WinDbg Preview
分析不了怎么办呢?可以用 Windbg 的其他版本哈,比如 Windbg10, WinDbg6
等等,这里就采用 WinDbg10 X86
版本打开吧。
0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 179 8cbb1000 ( 2.199 GB) 54.97%
Heap 6598 376f6000 ( 886.961 MB) 48.09% 21.65%
<unknown> 3091 31954000 ( 793.328 MB) 43.02% 19.37%
Image 376 8c0d000 ( 140.051 MB) 7.59% 3.42%
Stack 75 1780000 ( 23.500 MB) 1.27% 0.57%
Other 7 4e000 ( 312.000 kB) 0.02% 0.01%
TEB 25 19000 ( 100.000 kB) 0.01% 0.00%
PEB 1 1000 ( 4.000 kB) 0.00% 0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 179 8cbb1000 ( 2.199 GB) 54.97%
MEM_COMMIT 9821 6bfad000 ( 1.687 GB) 93.68% 42.18%
MEM_RESERVE 352 7492000 ( 116.570 MB) 6.32% 2.85%
从卦中 MEM_COMMIT
的 %ofTotal= 42.18%
来看,提交内存占总的虚拟地址比重还不到一半,这说明我的猜测是错的,不存在虚拟地址紧张的情况,这里稍微提醒一下的是,这里不存在虚拟地址紧张是因为它开的是 Any CPU
模式,默认能吃到 4G 内存。
不管怎么说,现在被当头一棒,既然这条路走不通,那会是什么情况导致的呢?一般来说这个内存量我是不愿意分析的,但既然分析到这里也只能继续分析,接下来用 !eeheap -gc
观察下托管堆内存占用情况。
0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x777c0434
generation 1 starts at 0x77781000
generation 2 starts at 0x01861000
ephemeral segment allocation context: none
segment begin allocated size
01860000 01861000 0285ffdc 0xffefdc(16773084)
...
77780000 77781000 77aa25c0 0x3215c0(3282368)
Large object heap starts at 0x02861000
segment begin allocated size
02860000 02861000 031e5cc0 0x984cc0(9981120)
Total Size: Size: 0x1f7e47e4 (528369636) bytes.
------------------------------
GC Heap Size: Size: 0x1f7e47e4 (528369636) bytes.
从卦中看当前托管堆也才 528M
和 提交内存 1.6G
相距甚远,所以这个 dump 大概率是存在非托管内存泄露,其实 !address -summary
中的 Heap
也能佐证,说到底就是 ntheap
泄露。
2. ntheap 怎么啦
深挖 ntheap 我就不挖了,省的误入歧途,文章开头我说过 ntdll 无法加载的现象预示着一种非托管泄露,对 ,就是 GC 的加载堆泄露,加载堆是 CLR 用来映射 C# 程序集,模块,类型,方法等用途的一块私有内存,那怎么去洞察它呢?可以使用 !eeheap -loader
命令洞察。
0:000> !eeheap -loader
Loader Heap:
--------------------------------------
...
Module 05829f78: Size: 0x0 (0) bytes.
Module 0582a8f8: Size: 0x0 (0) bytes.
Module 0582b278: Size: 0x0 (0) bytes.
Module 0582bbf8: Size: 0x0 (0) bytes.
Module 0582c578: Size: 0x0 (0) bytes.
Module 0582cef8: Size: 0x0 (0) bytes.
Module 0582d878: Size: 0x0 (0) bytes.
...
Module 362ea420: Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size: Size: 0x7e7e000 (132636672) bytes total, 0x28000 (163840) bytes wasted.
=======================================
虽然加载堆只统计到了 132M
,但其中的 module 高达 2.3w
个,其实这里会有一些相关内存是加载堆之外无法统计到的,一般正常的程序不可能有这么多的module,所以这就是我们接下来突破的点,那怎么突破呢?最好的办法就是观察下这个 module 中到底有什么 type,使用 !dumpmodule
命令即可。
0:000> !dumpmodule -mt 0582d878
Name: Unknown Module
Attributes: Reflection
Assembly: 0c229d38
LoaderHeap: 00000000
TypeDefToMethodTableMap: 050676e4
TypeRefToMethodTableMap: 050676f8
MethodDefToDescMap: 0506770c
FieldDefToDescMap: 05067734
MemberRefToDescMap: 00000000
FileReferencesMap: 05067784
AssemblyReferencesMap: 05067798
Types defined in this module
MT TypeDef Name
------------------------------------------------------------------------------
0582dcb0 0x02000002
0582df90 0x02000003
0582e018 0x02000004
0582e0b8 0x02000005
0582e194 0x02000006
Types referenced in this module
MT TypeRef Name
------------------------------------------------------------------------------
从模块中并没有看到类型
的文字描述,那怎么办呢,我们随便抽一个 mt 看下这个 mt 下有什么方法,使用 !dumpmt
命令即可。
0:000> !dumpmt -md 0582dcb0
EEClass: 05068980
Module: 0582d878
Name:
mdToken: 02000002
File: Unknown Module
BaseSize: 0x44
ComponentSize: 0x0
Slots in VTable: 8
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDe JIT Name
739819c8 735e61fc PreJIT System.Object.ToString()
73987850 735e6204 PreJIT System.Object.Equals(System.Object)
7398bd80 735e6224 PreJIT System.Object.GetHashCode()
738ddbe8 735e6238 PreJIT System.Object.Finalize()
0583b529 0582dc8c NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack.InitCallbacks()
0583b52d 0582dc94 NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack..ctor()
0583c7d0 0582dc74 JIT Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack.Write3_root(System.Object)
0583c868 0582dc80 JIT Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCallBack.Write2_CallBack(System.String, System.String, xxx.Models.xxxBack, Boolean, Boolean)
看到卦中的这些信息,我相信有很多朋友知道是怎么回事了,对,就是 Serialization
泄露,那它序列化什么类型呢 ? 从卦中看就是 xxx.Models.xxxBack
类,即 xmlSerializer.Serialize(xxx.Models.xxxBack)
的相关逻辑,接下来就需要逆向看下到底是哪里写的,结果发现是他的底层库封装的,有些方法有问题,有些没问题,真的是无语哈。
//有问题的方法
public static string Serialize(object o, Encoding encoding, string rootName)
{
XmlSerializer xmlSerializer = new XmlSerializer(o.GetType(), new XmlRootAttribute(rootName));
...
xmlSerializer.Serialize(memoryStream, o, xmlSerializerNamespaces);
return encoding.GetString(memoryStream.ToArray());
}
//正确的方法
public static string Serialize(object Obj, Encoding encoding)
{
...
using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, xmlWriterSettings))
{
XmlSerializerNamespaces xmlSerializerNamespaces = new XmlSerializerNamespaces();
xmlSerializerNamespaces.Add("", "");
new XmlSerializer(Obj.GetType()).Serialize(xmlWriter, Obj, xmlSerializerNamespaces);
}
return encoding.GetString(memoryStream.ToArray());
}
这是一个老生常谈的问题,如果你用 new XmlSerializer(o.GetType(), new XmlRootAttribute(rootName));
模式的话,一定要缓存起来,否则就会泄露,只能说是微软造的一个大坑吧,多少人都踩上去了。
三:总结
在我分析的真实dump案例中,见过 Castle ProxyGenerator
的泄露,也见过 CodeAnalysis.CSharp.Scripting
的泄露,还真没见过 XmlSerializer
的泄露,算是完美的补充了我的案例库!
记一次 .NET 某医院预约平台 非托管泄露分析的更多相关文章
- 记一次 .NET 某云采购平台API 挂死分析
一:背景 1. 讲故事 大概有两个月没写博客了,关注我的朋友应该知道我最近都把精力花在了星球,这两个月时间也陆陆续续的有朋友求助如何分析dump,有些朋友太客气了,给了大大的红包,哈哈,手里面也攒了1 ...
- 记一次 .NET 某智慧水厂API 非托管内存泄漏分析
一:背景 1. 讲故事 七月底的时候有位朋友在wx上找到我,说他的程序内存占用8G,托管才占用1.5G,询问剩下的内存哪里去了?截图如下: 从求助内容看,这位朋友真的太客气了,动不动就谈钱,真伤感情, ...
- 记一次 .NET 某工控视觉软件 非托管泄漏分析
一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...
- 记一次 .NET 某电子厂OA系统 非托管内存泄露分析
一:背景 1.讲故事 这周有个朋友找到我,说他的程序出现了内存缓慢增长,没有回头的趋势,让我帮忙看下到底怎么回事,据朋友说这个问题已经困扰他快一周了,还是没能找到最终的问题,看样子这个问题比较刁钻,不 ...
- uniapp医院预约挂号微信小程序
开头感言:最近看小程序很火,也想弄一个看看,用了一些时间从0开始写,也记录了一些笔记,自己用框架写的模板,不是很精美,后面会慢慢优化,功能也是后面慢慢加上去的, 其中功能这块,起初只是一些简单的功能, ...
- 记一次 .NET 某医院HIS系统 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...
- 记一次 .NET 某市附属医院 Web程序 偶发性CPU爆高分析
一:背景 1. 讲故事 这个月初,一位朋友加微信求助他的程序出现了 CPU 偶发性爆高,希望能有偿解决一下. 从描述看,这个问题应该困扰了很久,还是医院的朋友给力,开门就是 100块 红包 ,那既然是 ...
- 攻打医院服务器的SamSam勒索木马分析
攻打医院服务器的SamSam勒索木马分析 近日一款名为SamSam的勒索木马在国外爆发.该木马利用医院系统的服务器漏洞实施入侵,再进行加密勒索钱财.由于医院网络信息安全水平普遍薄弱,SamSam成功感 ...
- Android8.1 MTK平台 SystemUI源码分析之 网络信号栏显示刷新
SystemUI系列文章 Android8.1 MTK平台 SystemUI源码分析之 Notification流程 Android8.1 MTK平台 SystemUI源码分析之 电池时钟刷新 And ...
- 基于JSP开发医院预约挂号系统 Java源码
开发环境: Windows操作系统 开发工具: Eclipse+Jdk+Tomcat+MYSQL数据库 运行效果图: 源码及原文链接:http://javadao.xyz/forum.php?mod= ...
随机推荐
- 由 Base64 展开的知识探讨
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值.. 本文作者:霜序(掘金) 前言 在我们的业务应用中越来越多的应用到编 ...
- 【SpringCloud】(二)Eureka注册中心和Feign远程调用
1 SpringCloud 核心 SpringCloud基于HTTP协议,这是和Dubbo最本质的区别,Dubbo的核心是RPC(远程方法调用) Eureka:注册中心 Ribbon:客户端负载均衡 ...
- 京东小程序接入ARVR的技术方案和性能调优
作者:京东零售 戴旭 京东小程序是一个开放技术平台,正在被越来越多的头部品牌选择,用于站内私域流量的营销和运营.诸如各种日化.奢侈品等品牌对ARVR有较多的诉求,希望京东小程序引擎提供一些底层能力,叠 ...
- 【LeetCode动态规划#14】子序列系列题(最长递增子序列、最长连续递增序列、最长重复子数组、最长公共子序列)
最长递增子序列 力扣题目链接(opens new window) 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度. 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其 ...
- 搭建一个简易框架 3秒创建一个WebApi接口
前端ajax请求数据,传递的参数都是一个json字符串,经过多次解析发现其实都是一个DataSet {"selectA1":[{"Name":"156 ...
- 2023-05-08:我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符, 并返回唯一字符的个数。 例如:s = “LEETCODE“ ,则其中 “L“, “T
2023-05-08:我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符, 并返回唯一字符的个数. 例如:s = "LEETCODE" ,则 ...
- 文心一言 VS chatgpt (13)-- 算法导论3.1 8题 3.2 1题
八.可以扩展我们的记号到有两个参数n和m的情形,其中的n和m可以按不同速率独立地趋于无穷.对于给定的函数g(n, m),用O(g(n, m))来表示以下函数集: O(g(n, m)) = 对Ω(g(n ...
- 2022-11-18:给定一个数组arr,表示连续n天的股价,数组下标表示第几天 指标X:任意两天的股价之和 - 此两天间隔的天数 比如 第3天,价格是10 第9天,价格是30 那么第3天和第9天的指
2022-11-18:给定一个数组arr,表示连续n天的股价,数组下标表示第几天 指标X:任意两天的股价之和 - 此两天间隔的天数 比如 第3天,价格是10 第9天,价格是30 那么第3天和第9天的指 ...
- TypeError: Cannot read property 'getAttribute' of undefined
今天使用echarts + vue 做 图标,运行时提示vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in mounted hook: "Typ ...
- 发布 markdown 小功能:指定图片尺寸
之前园子的 markdown 功能多年落后,这两年我们正在努力赶上. 今天发布一个 markdown 小功能,可以通过 markdown 语法指定图片尺寸. 指定宽度 =200x ![](https: ...