news 2026/4/16 11:59:58

记一次 .NET 某理财管理客户端 OOM溢出分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
记一次 .NET 某理财管理客户端 OOM溢出分析

一:背景

1. 讲故事

这是训练营里的学员找到我的,让我帮忙看下为什么他的客户程序会偶发的出现 报错弹框,由于dump比较敏感,这里就不截图发出来了,由于是错误弹框,并不会出现程序崩溃,而且朋友在日志中也看到了 OOM 异常,就是因为这个 OOM 异常导致了后续流程的 报错弹框,说这个程序的内存还行,在业务代码中用了 try catch 吞掉异常了,让我帮忙看下。

由于 OOM dump没到手,而且代码中使用 try catch 吞掉了,有些人可能就没撤了,其实知道 异常两阶段 的朋友应该知道,我们可以在 first chance 的时候抓dump,即 catch 之前,所以就有了下面的捕获脚本。

procdump 20860 -e 1 -f PAVException -ma -o D:\testdump\

顺利拿到dump之后,接下来就是一顿分析了。

二:OOM分析

1. 为什么会 OOM

双击 dump 之后,映入眼帘的就是异常线程的现场信息,参考如下:

This dump file has an exception of interest stored in it.

The stored exception information can be accessed via .ecxr.

(15fc.4fe8): C++ EH exception - code e06d7363 (first/second chance not available)

For analysis of this file, run !analyze -v

eax=2b1aefa0 ebx=19930520 ecx=00000003 edx=00000000 esi=037eebc0 edi=530bb548

eip=77383874 esp=2b1aefa0 ebp=2b1aeffc iopl=0 nv up ei pl nz ac pe nc

cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216

KERNELBASE!RaiseException+0x64:

77383874 8b4c2454 mov ecx,dword ptr [esp+54h] ss:002b:2b1aeff4=224e4fd8

从卦中可以看到 RaiseException 就是托管异常的明证,接下来用 .ecxr ; k 观察异常调用栈。

0:052> .ecxr;k

eax=2b1aefa0 ebx=19930520 ecx=00000003 edx=00000000 esi=037eebc0 edi=530bb548

eip=77383874 esp=2b1aefa0 ebp=2b1aeffc iopl=0 nv up ei pl nz ac pe nc

cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216

KERNELBASE!RaiseException+0x64:

77383874 8b4c2454 mov ecx,dword ptr [esp+54h] ss:002b:2b1aeff4=224e4fd8

*** Stack trace for last set context - .thread/.cxr resets it

# ChildEBP RetAddr

00 2b1aeffc 52e3c8fb KERNELBASE!RaiseException+0x64

01 2b1af02c 52fee8fc coreclr!_CxxThrowException+0x66 [d:\a01\_work\11\s\src\vctools\crt\vcruntime\src\eh\throw.cpp @ 74]

02 2b1af040 52d481a8 coreclr!ThrowOutOfMemory+0x24 [D:\a\_work\1\s\src\coreclr\src\utilcode\ex.cpp @ 1044]

03 2b1af074 30b8f91e coreclr!LargeHeapHandleTable::AllocateHandles [D:\a\_work\1\s\src\coreclr\src\vm\appdomain.cpp @ 381]

WARNING: Frame IP not in any known module. Following frames may be wrong.

04 2b1af074 05990114 0x30b8f91e

05 2b1af074 52d452e7 0x5990114

06 2b1af0c8 52d453e7 coreclr!AllocateSzArray+0x227 [D:\a\_work\1\s\src\coreclr\src\vm\gchelpers.cpp @ 427]

07 2b1af14c 5257296e coreclr!JIT_NewArr1+0xb7 [D:\a\_work\1\s\src\coreclr\src\vm\jithelpers.cpp @ 2723]

08 2b1af160 52581bcf System_Private_CoreLib!System.Text.Encoding.GetBytes+0x22 [_/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs @ 667]

09 2b1af168 263e7ad6 System_Private_CoreLib!System.Text.UTF8Encoding.UTF8EncodingSealed.GetBytes+0x1b

0a 2b1af1a8 263e7a43 xxx!xxx.xxxxHashData+0x46

从卦中可以清晰的看到,原来是在 xxxxHashData 中执行了 GetBytes 时抛出的 OOM 异常, 那为什么 GetBytes 会抛出异常呢?这个只能结合源代码说话了。

2. GetBytes 为什么会抛出 OOM

找到 xxxxHashData 下的 GetBytes 方法,截图如下:

从卦中可以看到参数是一个 string,看样子这就是突破口了,使用 !clrstack -a 观察这个 s 的具体值,参考如下:

0:052> !clrstack -a

OS Thread Id: 0x4fe8 (52)

Child SP IP Call Site

2B1AF0E8 77383874 [HelperMethodFrame: 2b1af0e8]

2B1AF154 5257296e System.Text.Encoding.GetBytes(System.String) [/_/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs @ 667]

PARAMETERS:

this (<CLR reg>) = 0x05b5b674

s (<CLR reg>) = 0x348d1010

LOCALS:

<no data>

<no data>

<no data>

0:052> !DumpObj /d 348d1010

Name: System.String

MethodTable: 0568ec98

EEClass: 0569a8c0

Size: 83886094(0x500000e) bytes

String: <String is invalid or too large to print>

Fields:

MT Field Offset Type VT Attr Value Name

056873b4 4000212 4 System.Int32 1 instance 41943040 _stringLength

056854e0 4000213 8 System.Char 1 instance 54 _firstChar

0568ec98 4000211 60 System.String 0 static 05b512b0 Empty

0:052> ? 0x500000e

Evaluate expression: 83886094 = 0500000e

从卦中看真的是吓一跳,string.length=4194w 真尼玛大,并且 string 的重量高达 83M,就是由于这个 83M 的string,被 clr 直接给屏掉了。。。接下来的问题是为什么 clr 会屏掉呢?

3. clr 为什么会屏掉

有一些 clr 基础知识的朋友应该知道,这种 OOM 异常一般是两种情况。

通过 if 语句判断是否超限,这个在训练营里面都有讲到,参考代码如下:

// Limit the maximum string size to <2GB to mitigate risk of security issues caused by 32-bit integer

// overflows in buffer size calculations.

if (cchStringLength > CORINFO_String_MaxLength)

ThrowOutOfMemory();

向托管堆要指定大小的内存要不到的时候,这个可以用 !ao 命令观察。

0:052> !ao

Didn't have enough memory to allocate an LOH segment

Details: LOH Failed to reserve memory 50,331,648 bytes

从上面的卦数据来看,是 clr 向大对象堆预定50M的连续地址空间时,结果要不到,clr非常无奈抛出了这个OOM异常。

接下来的问题是为什么要不到呢?

4. 为什么托管堆拒绝了

有经验的朋友应该知道是咋回事了,对,就是虚拟地址空间不足导致的。。。 可以用 !address -summary 观察虚拟地址空间大小。

0:052> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal

<unknown> 1091 3e78b000 ( 999.543 MB) 64.47% 48.81%

Free 380 1f183000 ( 497.512 MB) 24.29%

Image 1039 17d37000 ( 381.215 MB) 24.59% 18.61%

Stack 219 6100000 ( 97.000 MB) 6.26% 4.74%

Heap 38 4751000 ( 71.316 MB) 4.60% 3.48%

TEB 73 11a000 ( 1.102 MB) 0.07% 0.05%

Other 21 3d000 ( 244.000 kB) 0.02% 0.01%

PEB 1 3000 ( 12.000 kB) 0.00% 0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal

MEM_PRIVATE 1010 36608000 ( 870.031 MB) 56.12% 42.48%

MEM_IMAGE 1142 17e6c000 ( 382.422 MB) 24.67% 18.67%

MEM_MAPPED 330 129f9000 ( 297.973 MB) 19.22% 14.55%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal

MEM_COMMIT 1937 4fdd5000 ( 1.248 GB) 82.42% 62.40%

MEM_FREE 380 1f183000 ( 497.512 MB) 24.29%

MEM_RESERVE 545 11098000 ( 272.594 MB) 17.58% 13.31%

从卦中可以看到虽然 MEM_RESERVE=272M ,但没有哪一块是大于 50M 的,所以直接导致灾难的发生,到这里该如何解决呢?这其实也是一个经典的问题,即 32bit 程序 2G 地址空间问题,修改办法如下:

使用大地址 LargeAddress,让程序尽量吃 4G 内存。

将程序调整到 64bit,让虚拟地址不再捉襟见肘。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 11:19:58

基于vue的校园电影网站的设计与实现_b3xt0e6w_springboot php python nodejs

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持java、ThinkPHP、Node.js、Spring B…

作者头像 李华
网站建设 2026/4/16 11:04:16

基于vue的网上药房在线买药商城系统_hc349102_springboot php python nodejs

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持java、ThinkPHP、Node.js、Spring B…

作者头像 李华
网站建设 2026/4/16 10:26:01

41、Linux系统全面解析:从基础到高级应用

Linux系统全面解析:从基础到高级应用 1. Linux设计哲学与基础概念 Linux在计算领域有着广泛的应用,这得益于其独特的设计哲学。自由软件和开源软件有所区别,开源是一种开发方法,而自由软件是一场社会运动,强调的是自由而非免费。Linux的高度可扩展性使其能胜任服务器、工…

作者头像 李华