记一次 .NET 某医院预约平台 非托管泄露分析

news2024/11/23 14:57:28

一:背景

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 的泄露,算是完美的补充了我的案例库!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/721861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker快速部署Hadoop环境

Docker安装部署Hadoop环境&#xff0c;通过三个容器来模拟三个节点&#xff0c;最后只保留Master节点实现搭建。 安装环境 Ubuntu 22.04.1 LTS 和Docker 23.0.1 安装过程 拉取镜像 docker pull registry.cn-hangzhou.aliyuncs.com/hadoop_test/hadoop_base在Docker中创建网…

供应链管理系统有哪些模块?

先弄搞清楚&#xff1a;供应链管理的概念与定义 供应链管理(Supply Chain Management ,简称SCM)&#xff1a;就是指在满足一定的客户服务水平的条件下&#xff0c;为了使整个供应链系统成本达到最小而把供应商、制造商、仓库、配送中心和渠道商等有效地组织在一起来进行的产品…

Kubernetes(k8s)容器编排Pod调度策略

目录 1 节点调度1.1 创建资源清单1.2 应用部署1.3 删除pod 2 定向调度(标签调度)2.1 创建标签2.1.1 添加标签2.1.2 显示标签 2.3 创建资源清单2.4 应用部署2.5 删除pod 1 节点调度 ​ 一般而言pod的调度都是通过RC、Deployment等控制器自动完成&#xff0c;但是仍可以通过手动配…

自然语言处理的分词与词嵌入

1 分词 1.1 什么是分词 分词是把自然语言语句进行数字化的过程。 1.2 为什么要分词 自然语言是字符串序列&#xff0c;机器没办法直接处理&#xff0c; 需要处理成数字的形式。 1.3 如何进行分词 以英文为例&#xff1a; 1 按空格划分 这应该是最简单也最直观的做法了。这…

Vue3setup的参数说明

setup的两个参数 setup包含两个参数&#xff0c;一个为props、一个为context &#xff08;均为形参&#xff09; props&#xff1a;值为对象&#xff0c;包含&#xff1a;组件外部传递过来&#xff0c;且组件内部声明接收了的属性。context&#xff1a;上下文对象 <scrip…

26-DOM常见的操作(了解)

一、DOM &#x1f37f;&#x1f37f;&#x1f37f;文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口 它提供了对文档的结构化的表述&#xff0c;并定义了一种方式可以使从程序中对该结构进行访问&#xff0c;从而改变文档的结构&#xff0c;样式和内容 例如&#xff1a;随着…

2023再更新下百度蜘蛛最新UA(User Agent)

其实百度蜘蛛的UA一直没什么变化&#xff0c;有不少朋友以为百度蜘蛛修改特征了&#xff0c;我查了下日志&#xff0c;把最新的UA整理出来给大家。 百度UA信息&#xff1a; Mozilla/5.0 (compatible; Baiduspider/2.0; http://www.baidu.com/search/spider.html) 神码ai在了…

LVS负载均衡群集——DR直接路由模式

一.LVS数据包流向分析 1.数据包流向 &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xff08;2&#xff09;Director Ser…

Spring Bean 的生命周期快速记忆

引言 “请你描述下 Spring Bean 的生命周期&#xff1f;”&#xff0c;这是面试官考察 Spring 的常用问题&#xff0c;可见是 Spring 中很重要的知识点。 我之前在准备面试时&#xff0c;去网上搜过答案&#xff0c;大多以下图给出的流程作为答案。 如何记忆 Spring Bean 的…

电影《消失的她》观后感

上周看了电影《消失的她》&#xff0c;也许是和朋友一起看的原因吧&#xff0c;这次电影的体验感觉比以往更好&#xff0c;这或许就是共同经历的缘故&#xff0c;同时看完电影&#xff0c;还可以大家一起讨论下。本部电影讲述一个富商国外旅游&#xff0c;女友莫名消失&#xf…

LVS负载均衡群集博客

文章目录 LVS负载均衡群集一.什么是集群1.群集的含义 二.集群使用在那个场景三.集群的分类1.负载均衡器群集2.高可用群集3.高性能运算群集 四.负载集群的架构1.第一层&#xff0c;负载调度器2.第二层&#xff0c;服务器池3.第三层&#xff0c;共享存储 五.负载均衡集群的工作模…

人工智能在航天领域中有哪些应用?

随着科技的不断进步&#xff0c;人工智能已经成为各个领域中的重要驱动力。在航天领域中&#xff0c;人工智能的应用正日益展现出巨大的潜力。航天领域对精确性、自动化和高效性的需求&#xff0c;使得人工智能成为实现这些目标的关键技术之一。人工智能正在以其独特的优势和算…

使用MQL4编写自己的交易策略:技巧与经验分享

随着技术的发展&#xff0c;越来越多的投资者开始使用程序化交易系统进行交易&#xff0c;其中MQL4语言是广泛应用于MetaTrader 4平台上编写交易策略的一种语言。本文将分享一些技巧和经验&#xff0c;帮助读者利用MQL4编写自己的交易策略。 策略开发流程 首先&#xff0c;我…

如何用ChatGPT写Shell脚本

因为最近下班前都要拿机子搞压测&#xff0c;所以这段时间对shell脚本比较感兴趣&#xff0c;用chatGPT写shell脚本很方便。 如下是一些案列 比如我需要写一个批处理&#xff1a;写一个批处理在当前文件夹下建立20个文件夹每个文件夹里面有一个文本文档文本文档的第一句话是hel…

实现分类标签展示的魔力——gradio库中的Label模块

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

第40节:cesium 温度场效果(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><!

JAVA POI 图片插入excel保存导出,可多图,多种插入样式

JAVA POI 图片插入excel保存导出,可多图,多种插入样式 JAVA POI 图片插入excel保存导出,可多图,多种插入样式 import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.util.Date;i…

RK3588平台开发系列讲解(Camera篇)OV569摄像头调试

文章目录 一、摄像头识别检测二、查看摄像头支持的格式三、摄像头采集格式查询四、摄像头采集格式查询沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解OV569摄像头调试。 OV5695 是一种图像传感器,用于摄像头设备。要进行 OV5695 摄像头的调试,通常涉…

如何使用 Oracle 的 Operator 在 Kubernetes 上运行 MySQL

过去几年,Kubernetes 对有状态应用程序的支持已经相当成熟。现在可以将数据库放置在集群内,从而使其能够受益于与其他工作负载相同的可扩展性。 MySQL 是最流行的关系数据库引擎之一,现在由官方Kubernetes 运营商对其进行了增强。Oracle 主导的开源项目提供了一种在 Kubern…

Java学习笔记(static静态变量)

static static表示静态&#xff0c;是Java中的一个修饰符&#xff0c;可以修饰成员方法&#xff0c;成员变量 被static修饰的成员变量&#xff0c;叫做静态变量&#xff0c;被static修饰的成员方法&#xff0c;叫做静态方法 静态变量的特点&#xff1a; 被该类所有对象共享…