记一次.net某制造业mes系统崩溃分析(代码片段)

dotNET跨平台 dotNET跨平台     2022-12-06     346

关键词:

一:背景

1.讲故事

前段时间有位朋友微信找到我,说他的程序偶尔会出现内存溢出崩溃,让我帮忙看下是怎么回事,咨询了下程序是 x86 部署,听到这个词其实心里已经有了数,不管怎么样还是用 windbg 分析一下。

二:WinDbg 分析

1. x86 程序意味着什么

x86 程序意味着程序默认只能吃到 2G 的内存,或者说只能用 2G 的虚拟地址,这种类型的程序很容易出现 虚拟地址紧张 造成崩溃,那怎么去验证程序只能吃 2G 内存呢?通常有两种做法:

1) 使用 !dh 查看 PE 头

可以用 lm 找到 exe 模块,然后使用 !dh Device_xxx 观察 PE 头,代码如下:

0:000> lm
start    end        module name
00360000 0099a000   xxxDevice C (service symbols: CLR Symbols without PDB)        
157f0000 15abf000   QQPinyin   (export symbols)       QQPinyin.ime
....

0:000> !dh xxxDevice

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
     14C machine (i386)
       3 number of sections
6305F7E8 time date stamp Wed Aug 24 18:05:28 2022

       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
     102 characteristics
            Executable
            32 bit word machine
...

最后一行的 32 bit word machine 表示是纯纯 x86,但在我的分析旅行中,这种也不是特别准,曾经遇到程序开启了 大地址,最后也只能吃 2G 内存,这就很奇葩了,所以更准的方式就是用 !address 看内存段。

  1. 使用 !address 查看内存段

这种做法万无一失,输出如下:

0:000> !address

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
+        0   360000   360000             MEM_FREE    PAGE_NOACCESS                      Free   
...
+ 7ffe1000 7ffec000     b000             MEM_FREE    PAGE_NOACCESS                      Free       
+ 7ffec000 7ffed000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_READONLY                      <unknown>  [HalT............]
+ 7ffed000 7fff0000     3000             MEM_FREE    PAGE_NOACCESS                      Free       

0:000> ? 7fff0000 /0x100000
Evaluate expression: 2047 = 000007ff

卦中最后一个内存段地址为 7fff0000,也就是 2G 的意思,所以最好的办法就是让朋友开启大地址解决,那大地址怎么开,用 anycpu 编译即可,但有很多朋友反馈用 anycpu 的话,很多 C++ 的链接库会报错,所以更好的做法是参考这篇:https://www.cnblogs.com/huangxincheng/p/15671957.html

到这里,貌似就可以结案了。。。

2. 真的要让 2G 地址背锅吗

开启大地址可以让程序吃到更多的内存,这个不假,但 放之四海而皆准 也不见得,言外之意还得分析下内存是怎么被吃掉的?如果是程序本身的问题,不断的侵蚀内存,再多的内存也不够用,对吧。

作为一个负责任的 调试博主,还是不要简单忽悠过去,接下来用 !address -summary 观察下内存布局。

0:000> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown>                              1221          551c4000 (   1.330 GB)  79.82%   66.49%
Free                                    258          155dd000 ( 341.863 MB)           16.69%
Image                                   916           b1c6000 ( 177.773 MB)  10.42%    8.68%
Stack                                   303           62c0000 (  98.750 MB)   5.79%    4.82%
Heap                                    129           426f000 (  66.434 MB)   3.89%    3.24%
TEB                                     101             f7000 ( 988.000 kB)   0.06%    0.05%
Other                                    12             60000 ( 384.000 kB)   0.02%    0.02%
PEB                                       1              3000 (  12.000 kB)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                            1437          561fd000 (   1.346 GB)  80.77%   67.29%
MEM_IMAGE                              1180           d23f000 ( 210.246 MB)  12.32%   10.27%
MEM_MAPPED                               66           75d7000 ( 117.840 MB)   6.91%    5.75%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT                             2113          5ce03000 (   1.451 GB)  87.10%   72.56%
MEM_FREE                                258          155dd000 ( 341.863 MB)           16.69%
MEM_RESERVE                             570           dc10000 ( 220.062 MB)  12.90%   10.75%

...

从输出看,当前提交内存为:MEM_COMMIT = 1.45G,以我的经验来说, 1.2G 是一个警戒线,一旦过了,程序崩溃的概率会几何倍提升。

<unknown>=1.33G 来看,内存可能都被 GC堆 或者 VirtualAlloc 吃掉了,为了进一步验证,需要看下托管堆。

0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x5ff45f5c
generation 1 starts at 0x5fee1000
generation 2 starts at 0x02d81000
ephemeral segment allocation context: none
 segment     begin  allocated      size
02d80000  02d81000  03d7fc50  0xffec50(16772176)
098c0000  098c1000  0a8bfc10  0xffec10(16772112)
0abf0000  0abf1000  0bbefc90  0xffec90(16772240)
0e0a0000  0e0a1000  0f09ff40  0xffef40(16772928)
13640000  13641000  1463fee8  0xffeee8(16772840)
17e40000  17e41000  18e3fff8  0xffeff8(16773112)
...
5fee0000  5fee1000  603b86e4  0x4d76e4(5076708)
Large object heap starts at 0x03d81000
 segment     begin  allocated      size
03d80000  03d81000  04cd0f70  0xf4ff70(16056176)
3e8f0000  3e8f1000  3f88bd80  0xf9ad80(16362880)
3f8f0000  3f8f1000  4075eeb0  0xe6deb0(15130288)
Total Size:              Size: 0x44c33258 (1153643096) bytes.
------------------------------
GC Heap Size:    Size: 0x44c33258 (1153643096) bytes.

从卦中的 GC Heap Size= 1.15G 来看,原来都是被 GCHeap 给弄没了,它吃掉了这么多内存是正常还是异常现象呢?这个还是取决于程序的业务逻辑,比如人家有一个小缓存什么的。

3. 真的要让程序背锅吗

既然分析到这里,含着泪也得分析下去,可以使用 !dumpheap -stat 看下托管堆使用。

0:000> !dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
71430958  1420366     66249092 System.Int32[]
...
174f4300  6852848    109645568 xxx.Mes.xxxPlatInfo
174f4194  6852848    164468352 xxx.Mes.Platxxx
7142eb40  6923571    210298518 System.String
00dbb9a0  1788917    434963034      Free

如果你有足够的分析经验,一看就能看出问题,比如 xxx.Mes.xxxPlatInfoxxx.Mes.Platxxx 对象高达 685w ,对象之间的排列布局很容易造成大量 Free 块,也叫做堆碎片化,真的很难看,类似下面这样。

0:000> !dumpheap 5cee1000  5dede324
 Address       MT     Size
 ...
5cee17c8 00dbb9a0       38 Free
5cee17f0 174f4194       24     
5cee1808 7142eb40       30     
5cee1828 174f4300       16     
5cee1838 00dbb9a0       10 Free
5cee1844 174f4194       24     
5cee185c 7142eb40       30     
5cee187c 174f4300       16     
5cee188c 00dbb9a0       46 Free
5cee18bc 174f4194       24     
5cee18d4 7142eb40       30     
5cee18f4 174f4300       16     
5cee1904 00dbb9a0       10 Free
5cee1910 174f4194       24     
5cee1928 7142eb40       30     
5cee1948 174f4300       16     
5cee1958 00dbb9a0       50 Free
5cee198c 174f4194       24     
5cee19a4 7142eb40       30     
5cee19c4 174f4300       16     
5cee19d4 00dbb9a0      130 Free
...

接下来在 xxx.Mes.xxxPlatInfo 中抽一个对象观察它的引用根,为什么没有被 GC 回收。

从图中可以看到,它被 xxxPlatInfo 类下的 ConcurrentDictionary 中的 List 持有,我翻看了几个,Size 都比较大,比如下面输出:

0:000> !do 0ea19634
Name:        System.Collections.Generic.List`1[[xxx]]
MethodTable: 174f4350
EEClass:     71006b4c
Size:        24(0x18) bytes
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7143e0fc  400188f        4     System.__Canon[]  0 instance 04bd0f60 _items
71430994  4001890        c         System.Int32  1 instance   200367 _size
71430994  4001891       10         System.Int32  1 instance   200367 _version
7142eee0  4001892        8        System.Object  0 instance 00000000 _syncRoot
7143e0fc  4001893        4     System.__Canon[]  0   static  <no information>

0:000> !DumpObj /d 0e6b9888
Name:       System.Collections.Generic.List`1[[xxx]]
MethodTable: 174f4350
EEClass:     71006b4c
Size:        24(0x18) bytes
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7143e0fc  400188f        4     System.__Canon[]  0 instance 3f78bd70 _items
71430994  4001890        c         System.Int32  1 instance   171806 _size
71430994  4001891       10         System.Int32  1 instance   171806 _version
7142eee0  4001892        8        System.Object  0 instance 00000000 _syncRoot
7143e0fc  4001893        4     System.__Canon[]  0   static  <no information>

将这些信息反馈给朋友后,朋友说 List 这么多是有问题的,排查之后是 List 在多线程情况下有问题,修正之后问题得到解决。

三:总结

这次事故主要是由于朋友在处理线程安全集合 ConcurrentDictionary<xxx, List<xxx>> 的过程中,对其中的 List<xxx> 没有合理的线程安全处理,导致数据的异常暴增,最终把紧张的 2G 虚拟地址用尽。

教训就是:key 线程安全了, value 也要记的安全哦!

记一次.net某纺织工厂mes系统api挂死分析(代码片段)

一:背景1.讲故事这个月中旬,有位朋友加我wx求助他的程序线程占有率很高,寻求如何解决,截图如下:说实话,和不同行业的程序员聊天还是蛮有意思的,广交朋友,也能扩大自己的圈子,... 查看详情

记一次.net某医疗住院系统崩溃分析(代码片段)

一:背景1.讲故事最近收到了两起程序崩溃的dump,查了下都是经典的doublefree造成的,蛮有意思,这里就抽一篇出来分享一下经验供后面的学习者避坑吧。二:WinDbg分析1.崩溃点在哪里windbg带了一个自动化分析... 查看详情

记一次.net某企业erp网站系统崩溃分析(代码片段)

一:背景1.讲故事前段时间收到了一个朋友的求助,说他的ERP网站系统会出现偶发性崩溃,找了好久也没找到是什么原因,让我帮忙看下,其实崩溃好说,用procdump自动抓一个就好,拿到dump之后,接下来就是一顿分析了。二:WinD... 查看详情

记一次.net某医疗器械程序崩溃分析(代码片段)

一:背景1.讲故事前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用AEDebug在程序崩溃的时候自动抽一... 查看详情

记一次某制造业erp系统cpu打爆事故分析(代码片段)

一:背景1.讲故事前些天有位朋友微信找到我,说他的程序出现了CPU阶段性爆高,过了一会就下去了,咨询下这个爆高阶段程序内部到底发生了什么?画个图大概是下面这样,你懂的。按经验来说,这... 查看详情

记一次.net某智能服装智造系统内存泄漏分析(代码片段)

一:背景1.讲故事上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下:朋友这段话已经说的非常言简意赅了,那就上windbg说话吧。二:Windbg分析1.到底是哪一方面的泄漏根据朋友描述,程序... 查看详情

记一次.net某新能源系统线程疯涨分析(代码片段)

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

记一次.net某工控自动化控制系统卡死分析(代码片段)

一:背景1.讲故事前段时间遇到了好几起关于窗体程序的进程加载锁引发的程序卡死和线程暴涨问题,这种dump分析难度较大,主要涉及到Windows操作系统和C++的基础知识,所以有必要简单整理和大家分享一下&... 查看详情

记一次.net某车零件mes系统登录异常分析

一:背景1.讲故事这个案例有点特殊,以前dump分析都是和软件工程师打交道,这次和非业内人士交流,隔行如隔山,从指导dump怎么抓到问题解决,需要一个强大的耐心。前几天有位朋友在微信上找到我,说他们公司采购的MES系... 查看详情

记一次腾讯会议的意外崩溃分析(代码片段)

一:背景1.讲故事前段时间在用腾讯会议直播的时候,居然意外崩溃了,还好不是在训练营上课,不然又得重录了,崩完之后发现腾讯会议的bugreport组件会自动生成一个minidump,截图如下:作为一个.NET高级调试的技术博主,非.NET... 查看详情

记一次.net某wms仓储打单系统内存暴涨分析(代码片段)

一:背景1.讲故事七月中旬有一位朋友加wx求助,他的程序在生产上跑着跑着内存就飙起来了,貌似没有回头的趋势,询问如何解决,截图如下:和这位朋友聊下来,感觉像是自己在小县城当了个小老板... 查看详情

记一次.net某电厂web系统内存泄漏分析(代码片段)

一:背景1.讲故事前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5在内存管理方面做的也不够好,所以有必要给大... 查看详情

记一次.net某智慧物流wcs系统cpu爆高分析(代码片段)

一:背景1.讲故事哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了CPU爆高,让我帮忙看下什么原因,由于那段时间在苦心研究C++,分析和经验分享也就懈怠了,今天就... 查看详情

记一次windows10内存压缩模块崩溃分析(代码片段)

一:背景1.讲故事在给各位朋友免费分析.NET程序各种故障的同时,往往也会收到各种其他类型的dump,比如:Windows崩溃,C++崩溃,Mono崩溃,真的是啥都有,由于基础知识的相对缺乏,分析起来并不是那么的顺利,今天就聊一个Win... 查看详情

记一次.net某手术室行为信息系统内存泄露分析(代码片段)

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

记一次.net某电厂web系统内存泄漏分析

一:背景1.讲故事前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5在内存管理方面做的也不够好,所以有必要给大家分享一下。二:WinDbg分... 查看详情

记一次.net某家装erp系统内存暴涨分析(代码片段)

一:背景1.讲故事前段时间微信上有一位老朋友找到我,说他的程序跑着跑着内存会突然爆高,有时候会下去,有什么会下不去,怀疑是不是某些情况下存在内存泄露,让我帮忙分析一下,其实内存泄露方面的问题还是比较好解... 查看详情

记一次.net某桌面奇侠游戏非托管内存泄漏分析(代码片段)

一:背景1.讲故事说实话,这篇dump我本来是不准备上一篇文章来解读的,但它有两点深深的感动了我。无数次的听说用Unity可做游戏开发,但百闻不如一见。游戏中有很多金庸武侠小说才有的名字,太赏心悦目... 查看详情