一:背景

1. 讲故事

前段时间有位朋友找到我,说他的程序内存会出现暴涨,让我看下是怎么事情?而且还告诉我是在 Linux 环境下,说实话在Linux上分析.NET程序难度会很大,难度大的原因在于Linux上的各种开源工具主要是针对 C/C++, 和 .NET 一毛钱关系都没有,说到底微软在 Linux 上的调试领域支持度还远远不够。

虽然知道分析起来难度可能会很大,但该分析还是要分析的,让朋友抓一个 dump 过来,上 WinDbg 说话。

二:WinDbg 分析

1. 到底是哪里的泄露

只要是进程都会有内存段的,所以分析Linux的dump一样可以使用 !address -summary 命令来观察。


0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown> 1607 ffffffff`cd7a9e00 ( 16.000 EB) 100.00% 100.00%
Image 41699 0`31e57200 ( 798.340 MB) 0.00% 0.00% --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
1247 fffffffe`1c910000 ( 16.000 EB) 100.00%
MEM_PRIVATE 42059 1`e2cf1000 ( 7.544 GB) 0.00% 0.00% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
1247 fffffffe`1c910000 ( 16.000 EB) 100.00% 100.00%
MEM_COMMIT 42059 1`e2cf1000 ( 7.544 GB) 0.00% 0.00% --- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE 41067 1`cff54000 ( 7.249 GB) 0.00% 0.00%
PAGE_READONLY 644 0`07268000 ( 114.406 MB) 0.00% 0.00%
PAGE_EXECUTE_READ 223 0`06d1f000 ( 109.121 MB) 0.00% 0.00%
PAGE_EXECUTE_WRITECOPY 125 0`04e16000 ( 78.086 MB) 0.00% 0.00% --- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unknown> 7ffc`78f8e000 ffff8003`86672000 ( 16.000 EB)
Image 7ff8`49102000 0`10000000 ( 256.000 MB)

这里简单提一下,我发现有很多朋友搞不清楚这里的 16.000 EB 是什么意思,它其实是2的64次方,即程序的用户态空间的寻址范围。

从卦中的 MEM_COMMIT=7.544G 来看当前提交内存不小,接下来用 !eeheap -gc 观察下托管堆内存占用。


0:000> !eeheap -gc ========================================
Number of GC Heaps: 1
----------------------------------------
generation 0 starts at 7ff688f78f10
generation 1 starts at 7ff688484e70
generation 2 starts at 7ff8f7fff000
ephemeral segment allocation context: none
Small object heap
segment begin allocated committed allocated size committed size
7ff63fffa000 7ff63fffb000 7ff64fe51d80 7ff64fe5d000 0xfe56d80 (266694016) 0xfe63000 (266743808)
7ff6772c8000 7ff6772c9000 7ff6872c2d38 7ff6872c8000 0xfff9d38 (268410168) 0x10000000 (268435456)
7ff74bffe000 7ff74bfff000 7ff75bffdfc0 7ff75bffe000 0xfffefc0 (268431296) 0x10000000 (268435456)
7ff773ffe000 7ff773fff000 7ff783ffdfc8 7ff783ffe000 0xfffefc8 (268431304) 0x10000000 (268435456)
7ff849102000 7ff849103000 7ff859101fe8 7ff859102000 0xfffefe8 (268431336) 0x10000000 (268435456)
7ff8f7ffe000 7ff8f7fff000 7ff907ffce88 7ff907ffe000 0xfffde88 (268426888) 0x10000000 (268435456)
7ff6872ca000 7ff6872cb000 7ff68a768438 7ff68aa4b000 0x349d438 (55170104) 0x3781000 (58200064)
Large object heap starts at 7ff907fff000
segment begin allocated committed allocated size committed size
7ff733ff8000 7ff733ff9000 7ff73aedd058 7ff73aefe000 0x6ee4058 (116277336) 0x6f06000 (116416512)
7ff743ffc000 7ff743ffd000 7ff744358f10 7ff744379000 0x35bf10 (3522320) 0x37d000 (3657728)
7ff7a3ffe000 7ff7a3fff000 7ff7a9d63ee0 7ff7a9d84000 0x5d64ee0 (97930976) 0x5d86000 (98066432)
7ff7bbffe000 7ff7bbfff000 7ff7c3dc1090 7ff7c3de2000 0x7dc2090 (131866768) 0x7de4000 (132005888)
7ff907ffe000 7ff907fff000 7ff90f048b30 7ff90f069000 0x7049b30 (117742384) 0x706b000 (117878784)
Pinned object heap starts at 7ff90ffff000
segment begin allocated committed allocated size committed size
7ff90fffe000 7ff90ffff000 7ff9102d15b0 7ff9102d2000 0x2d25b0 (2958768) 0x2d4000 (2965504)
------------------------------
GC Allocated Heap Size: Size: 0x7f36bca0 (2134293664) bytes.
GC Committed Heap Size: Size: 0x7f710000 (2138112000) bytes.

从卦中看当前提交内存也仅有 2.13G,这和 7.5G 相距甚远,说明这是最复杂的 非托管内存泄漏

2. 非托管泄露分析

作为一个.NET调试者,需要像医生一样尽自己最大可能救治病人,那接下来我们的研究方向在哪里呢?大家需要知道所有的内存占用的基本盘都在 虚拟地址 上,结果一搜索,发现有大概 4w+ 的 dll,一个程序怎么可能会有这么多动态链接库呢?截图如下:

既然找到了可疑之处那就继续挖吧,接下来就是要考虑这个dll是托管代码创建的还是非托管代码创建的,用排除法就好了,如果是托管代码创建的,那就肯定属于 Assembly 下的某一个module,可以查下加载堆看看。


0:000> !eeheap -loader
Loader Heap:
--------------------------------------
...
Module 00007ff95e265778: Size: 0x0 (0) bytes.
Module 00007ff95e2661e0: Size: 0x0 (0) bytes.
Module 00007ff95e266c48: Size: 0x0 (0) bytes.
Module 00007ff95e2676b0: Size: 0x0 (0) bytes.
Module 00007ff95e268118: Size: 0x0 (0) bytes.
Module 00007ff95e268b80: Size: 0x0 (0) bytes.
Module 00007ff95e2695e8: Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size: Size: 0x4bf4b000 (1274327040) bytes total, 0x4da000 (5087232) bytes wasted.
======================================= 0:000> !dumpmodule 00007ff95e2695e8
Name: *75db8939-8b3a-4075-94ac-e9bb52acf9d1#40147-0.dll
Attributes: PEFile IsInMemory IsFileLayout
...
MetaData start address: 00007FF85BBCD330 (2068 bytes) 0:000> !DumpAssembly /d 00007ff80d71bba0
Parent Domain: 0000559009249080
Name: Unknown
ClassLoader: 00007FF80D71BC00
Module
00007ff95e2695e8 *75db8939-8b3a-4075-94ac-e9bb52acf9d1#40147-0.dll

从卦中看虽然加载堆只有 1.27G,但它还有很多关联的内存,而且动态module高多4w+,接下来用 !dumpmodule -mt 观察内部是什么类型。


0:000> !dumpmodule -mt 00007ff95e2695e8
Name: *75db8939-8b3a-4075-94ac-e9bb52acf9d1#40147-0.dll
Attributes: PEFile IsInMemory IsFileLayout
...
Types defined in this module MT TypeDef Name
------------------------------------------------------------------------------
00007ff95e269d80 0x02000004 Submission#0
00007ff95e269ec0 0x02000005 Submission#0+<>d__0 Types referenced in this module MT TypeRef Name
------------------------------------------------------------------------------
00007ff92d6152a8 0x0200000c System.Object
00007ff92ead4d18 0x0200000d System.Threading.Tasks.Task`1
00007ff93133f298 0x0200000e System.Runtime.CompilerServices.IAsyncStateMachine
00007ff92d6cec90 0x0200000f System.Exception
00007ff93133f648 0x02000010 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 0:000> !dumpmt -md 00007ff95e269ec0
...
MethodDesc Table
Entry MethodDesc JIT Name
00007FF92D620030 00007ff92d615238 JIT System.Object.Finalize()
00007FF92D620038 00007ff92d615248 PreJIT System.Object.ToString()
00007FF92D620040 00007ff92d615258 JIT System.Object.Equals(System.Object)
00007FF92D620058 00007ff92d615298 JIT System.Object.GetHashCode()
00007FF95DFFB0E0 00007ff95e269e58 JIT Submission#0+<>d__0.MoveNext()
00007FF95DFF9B70 00007ff95e269e78 NONE Submission#0+<>d__0.SetStateMachine(System.Runtime.CompilerServices.IAsyncStateMachine)
00007FF95DFF9B60 00007ff95e269e48 JIT Submission#0+<>d__0..ctor()

从卦中看也只能看到一些 Submission 为前缀的类与之相关的状态机类,也看不出来是谁创建的,结果又入了困境。

3. 到底是谁作的孽

要想获取动态程序集的创建事件,有一个好办法就是用跨平台的 dotnet-trace,让它捕获程序集的加载事件即可,详情可参考:https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/collect-details ,然后让朋友跑 30min 看看,参考命令如下:


dotnet-trace collect -p 4108 --clrevents loader --duration 00:00:30:00

有了生成好的 dotnet_xxxx.nettrace 之后就可以用 perfview 观察了,打开 Event视图,搜索 AssemblyLoad 事件,截图如下:

通过 Time MSec 的748前缀来看,这1s种能生成几十个动态程序集,接下来右键选择 Open Any Stacks 观察是什么代码调用的,截图如下:

从 perfivew 的输出看,原来是 XXXCusDis 方法内部调用 Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync 生成了非常多的程序集。

最后就是把 CSharpScript.EvaluateAsync 告诉朋友,能不能给剔除掉做个排查?

三:总结

网上查了下 Microsoft.CodeAnalysis.CSharp.Scripting 可以用来生成C#脚本代码,大家在用的时候小心点吧。

记一次 .NET某报关系统 非托管泄露分析的更多相关文章

  1. 记一次 .NET 某电子厂OA系统 非托管内存泄露分析

    一:背景 1.讲故事 这周有个朋友找到我,说他的程序出现了内存缓慢增长,没有回头的趋势,让我帮忙看下到底怎么回事,据朋友说这个问题已经困扰他快一周了,还是没能找到最终的问题,看样子这个问题比较刁钻,不 ...

  2. 记一次 .NET 某工控视觉软件 非托管泄漏分析

    一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...

  3. 记一次 .NET 某打印服务 非托管内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的程序出现了内存泄漏,能不能帮他看一下,这个问题还是比较经典的,加上好久没上非托管方面的东西了,这篇就和大家分享一下,话不多说,上 WinD ...

  4. 记一次 .NET 某手术室行为信息系统 内存泄露分析

    一:背景 1. 讲故事 昨天有位朋友找到我,说他的程序内存存在泄露导致系统特别卡,大地址也开了,让我帮忙看一下怎么回事?今天上午看了下dump,感觉挺有意思,在我的分析之旅中此类问题也蛮少见,算是完善 ...

  5. 记一次 .NET 某工控软件 内存泄露分析

    一:背景 1.讲故事 上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也 ...

  6. 记一次 .NET 某教育系统API 异常崩溃分析

    一:背景 1. 讲故事 这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有 ...

  7. 记一次 .NET 某新能源系统 线程疯涨 分析

    一:背景 1. 讲故事 前段时间收到一个朋友的求助,说他的程序线程数疯涨,寻求如何解决. 等我分析完之后,我觉得这个问题很有代表性,所以拿出来和大家分享下,还是上老工具 WinDbg. 二: WinD ...

  8. 记一次 .NET 某智慧水厂API 非托管内存泄漏分析

    一:背景 1. 讲故事 七月底的时候有位朋友在wx上找到我,说他的程序内存占用8G,托管才占用1.5G,询问剩下的内存哪里去了?截图如下: 从求助内容看,这位朋友真的太客气了,动不动就谈钱,真伤感情, ...

  9. 记一次 .NET 医院CIS系统 内存溢出分析

    一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的,不过对于内存方面出的问 ...

  10. 记一次 .NET 某HIS系统后端服务 内存泄漏分析

    一:背景 1. 讲故事 前天那位 his 老哥又来找我了,上次因为CPU爆高的问题我给解决了,看样子对我挺信任的,这次另一个程序又遇到内存泄漏,希望我帮忙诊断下. 其实这位老哥技术还是很不错的,他既然 ...

随机推荐

  1. Centos7 部署Django项目 uwsgi + nginx

    启动 首先确保你的django项目是可以在虚拟环境中跑起来的,环境管理窝用的是pyenv,pyenv不知道什么东西的可以参考窝之前写过的Pyenv环境管理的安装文. 项目启动 python manag ...

  2. [Flink] Flink Job运行状态正常,但日志中偶报“FlinkException: The file LOG does not exist on the TaskExecutor.”

    0 序言 Flink : 1.12 job start running time : 2022-12-27 17:40:47 problem throw time : 2023-05-11 16:41 ...

  3. 2022-12-14:给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 给定两个长度为n的数组,powers和rates pow

    2022-12-14:给定一个正数n, 表示从0位置到n-1位置每个位置放着1件衣服 从0位置到n-1位置不仅有衣服,每个位置还摆着1个机器人 给定两个长度为n的数组,powers和rates pow ...

  4. 2022-04-19:A*算法, 过程和Dijskra高度相处, 有到终点的预估函数, 只要预估值<=客观上最优距离,就是对的。 预估函数是一种吸引力: 1)合适的吸引力可以提升算法的速度; 2)吸引

    2022-04-19:A*算法, 过程和Dijskra高度相处, 有到终点的预估函数, 只要预估值<=客观上最优距离,就是对的. 预估函数是一种吸引力: 1)合适的吸引力可以提升算法的速度; 2 ...

  5. 2022-03-06:金币路径。 给定一个数组 A(下标从 1 开始)包含 N 个整数:A1,A2,……,AN 和一个整数 B。 你可以从数组 A 中的任何一个位置(下标为 i)跳到下标 i+1,i+

    2022-03-06:金币路径. 给定一个数组 A(下标从 1 开始)包含 N 个整数:A1,A2,--,AN 和一个整数 B. 你可以从数组 A 中的任何一个位置(下标为 i)跳到下标 i+1,i+ ...

  6. 【GiraKoo】线程本地存储(Thread Local Storage, TLS)

    [技术分享]线程本地存储(Thread Local Storage, TLS) 在项目开发中,遇到了关于TLS相关的问题.为了了解该机制的用途,在微软的官网查找了一些资料. 本文参考官方文档, 简单介 ...

  7. 7-8 切分表达式——写个tokenizer吧 (20 分)

    1.题目描述: [先说点出题背景] 这个题是为低年级同学.学C语言的同学准备的,因为,对这部分同学,这个题目编写起来略有一点复杂.如果是高年级.学过了正则表达式(Regular Expression) ...

  8. es笔记六之聚合操作之指标聚合

    本文首发于公众号:Hunter后端 原文链接:es笔记六之聚合操作之指标聚合 聚合操作,在 es 中的聚合可以分为大概四种聚合: bucketing(桶聚合) mertic(指标聚合) matrix( ...

  9. 微软Build 2023两大主题:Copilots和插件

    在本周大型微软人工智能 2023 开发者大会的开幕式上,人工智能站到了舞台中央--前台和后台以及介于两者之间的所有舞台. 贯穿会议的两个主要主题是Copilots - 涵盖广泛产品和服务的AI助手 - ...

  10. 文心一言 VS 讯飞星火 VS chatgpt (28)-- 算法导论5.1 3题

    三.假设你希望以1/2的概率输出0与 1.你可以自由使用一个输出0或1的过程 BIASED-RANDOM.它以某概率 p 输出1,概率 1-p 输出0,其中 0<p<1 ,但是 p 的值未 ...