记一次 .NET某工控自动化系统 崩溃分析

一:背景

1. 讲故事

前些天微信上有位朋友找到我,说他的程序偶发崩溃,分析了个把星期也没找到问题,耗费了不少人力物力,让我能不能帮他看一下,给我申请了经费,哈哈,遇到这样的朋友就是爽快,刚好周二晚上给调试训练营的朋友分享 GC标记阶段 相关知识,而这个dump所展示的问题是对这块知识的一个很好的巩固,接下来我们开始分析吧。

二:WinDbg分析

1. 为什么会崩溃

要想找到崩溃原因,还是用老命令 !analyze -v ,输出如下:


0:005> !analyze -v
CONTEXT:  (.ecxr)
eax=063ce258 ebx=07b90000 ecx=0063552e edx=0063552e esi=03070909 edi=03070909
eip=71954432 esp=063ce220 ebp=063ce23c iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
clr!WKS::gc_heap::mark_object_simple+0x12:
71954432 8b0f            mov     ecx,dword ptr [edi]  ds:002b:03070909=????????
Resetting default scopeEXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 71954432 (clr!WKS::gc_heap::mark_object_simple+0x00000012)ExceptionCode: c0000005 (Access violation)ExceptionFlags: 00000001
NumberParameters: 2Parameter[0]: 00000000Parameter[1]: 03070909
Attempt to read from address 03070909STACK_TEXT:  
063ce23c 719543fc     063ce258 0a76cc88 71954260 clr!WKS::gc_heap::mark_object_simple+0x12
063ce25c 71950b62     0a76cc88 063cec88 00000000 clr!WKS::GCHeap::Promote+0xa8
...
063cec28 71950fa3     71950da0 063cec40 00000500 clr!Thread::StackWalkFrames+0x9d
063cec4c 7195103e     063cec88 00000002 00000000 clr!standalone::ScanStackRoots+0x43
063cec68 71954038     0079cb88 063cec88 00080101 clr!GCToEEInterface::GcScanRoots+0xdb
063cecc0 71953225     00080101 00000000 00000001 clr!WKS::gc_heap::mark_phase+0x17e
063cece0 7195355b     71f75da0 00000000 00000001 clr!WKS::gc_heap::gc1+0xae
063cecf8 71953665     71f75fb4 71f75fb4 00000000 clr!WKS::gc_heap::garbage_collect+0x367
063ced18 7195376a     00000000 00000000 71f75fb4 clr!WKS::GCHeap::GarbageCollectGeneration+0x1bd
...

从卦中信息看,当前执行流处于GC标记阶段,并且是在各个线程栈上寻找用户根,在寻找的过程中踩到了坏内存,接下来需要捋一下是什么逻辑踩到的,可以用 u 反汇编一下。


0:005> u WKS::gc_heap::mark_object_simple
clr!WKS::gc_heap::mark_object_simple:
71954420 55              push    ebp
71954421 8bec            mov     ebp,esp
71954423 83ec18          sub     esp,18h
71954426 8b4508          mov     eax,dword ptr [ebp+8]
71954429 57              push    edi
7195442a 8b38            mov     edi,dword ptr [eax]
7195442c 89bde8ffffff    mov     dword ptr [ebp-18h],edi
71954432 8b0f            mov     ecx,dword ptr [edi]
...

从汇编逻辑看,这是将方法的第一个参数进行解引用,参考 coreclr 的源码。


void gc_heap::mark_object_simple(uint8_t** po THREAD_NUMBER_DCL)
{uint8_t* o = *po;if (gc_mark1(o)){...}
}

结合C++代码,edi=03070909 就是上面的o,也就是需要标记的托管对象,但现在这个 o 是一个坏对象,那为什么会坏掉呢?

2. 为什么 o 坏掉了

按照过往经验肯定是托管堆损坏了,可以用 !verifyheap 观察下。


0:005> !verifyheap
No heap corruption detected.

从卦中看,我去,托管堆居然是好的,过往经验在这个dump里被击的粉碎,接下来要往哪里突破呢? 可以观察下这个托管地址和当前的托管segment在空间距离上的特征,命令输出如下:


0:005> !address 03070909Usage:                  <unknown>
Base Address:           02ca2000
End Address:            036f0000
Region Size:            00a4e000 (  10.305 MB)
State:                  00002000          MEM_RESERVE
Protect:                <info not present at the target>
Type:                   00020000          MEM_PRIVATE
Allocation Base:        026f0000
Allocation Protect:     00000004          PAGE_READWRITE0:005> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x06ca7a7c
generation 1 starts at 0x06b91000
generation 2 starts at 0x026f1000
ephemeral segment allocation context: nonesegment     begin  allocated      size
026f0000  026f1000  02c98f8c  0x5a7f8c(5930892)
06b90000  06b91000  0732b3d0  0x79a3d0(7971792)
Large object heap starts at 0x036f1000segment     begin  allocated      size
036f0000  036f1000  03c78da0  0x587da0(5799328)
Total Size:              Size: 0x12ca0fc (19702012) bytes.
------------------------------
GC Heap Size:    Size: 0x12ca0fc (19702012) bytes.0:005> !addressBaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-----------------------------------------------------------------------------------------------
...
+  26f0000  2ca2000   5b2000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unknown>  [..........o.....]2ca2000  36f0000   a4e000 MEM_PRIVATE MEM_RESERVE                                    <unknown>  
...

说实话,有经验的朋友看到这卦中信息马上就知道是怎么回事了,步骤大概是这样的。

  • 03070909 曾经实打实的分配在 SOH 上
  • GC 触发后,03070909 所在的 segment 被收缩,同时对象被移走。
  • 但不知为何,线程栈还保留了这个老地址 03070909,而不是新地址

出现这种情况的原因,大多是 C# 和 C++ 交互时没有把 03070909 给固定住(GCHandle.Alloc),导致GC触发对象移动之后,会存在两种情况的崩溃。

  1. C++ 层面的崩溃:因为此时的C++拿的地址不再有效了,导致在非托管层崩溃。

  2. CLR 层面的崩溃:线程如果在C++层面僵持,托管层GC触发时会误认为这个无效的地址还是一个有效的对象,进而在标记阶段导致程序崩溃。

有些朋友可能被我说懵了,画个简图如下:

由于这个dump属于第二种崩溃,即存在僵死的线程,接下来就是想办法找到这个线程。

3. 僵死的线程在哪里

如果你了解GC标记阶段的底层运作,我相信你很容易找出这个答案的,对,只需要找到 ScanStackRoots 函数的第一个参数即可,参考代码如下:


void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, ScanContext* sc)
{Thread* pThread = NULL;while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL){ScanStackRoots(pThread, fn, sc);}
}

接下来上 windbg 在崩溃的线程栈上实操一下。


0:005> kb 8# ChildEBP RetAddr      Args to Child              
00 063ce23c 719543fc     063ce258 0a76cc88 71954260 clr!WKS::gc_heap::mark_object_simple+0x12
01 063ce25c 71950b62     0a76cc88 063cec88 00000000 clr!WKS::GCHeap::Promote+0xa8
02 063ce274 71951a35     063cec40 0a76cc88 00000000 clr!GcEnumObject+0x37
03 063ce5d8 71950e6f     063ce920 063ce870 00000000 clr!EECodeManager::EnumGcRefs+0x72b
04 063ce628 717bfaa4     063ce650 063cec40 71950da0 clr!GcStackCrawlCallBack+0x139
05 063ce8f4 717bfbaa     063ce920 71950da0 063cec40 clr!Thread::StackWalkFramesEx+0x92
06 063cec28 71950fa3     71950da0 063cec40 00000500 clr!Thread::StackWalkFrames+0x9d
07 063cec4c 7195103e     063cec88 00000002 00000000 clr!standalone::ScanStackRoots+0x430:005> dp 063cec88 L1
063cec88  08debbf80:005> !t
ThreadCount:      30
UnstartedThread:  0
BackgroundThread: 29
PendingThread:    0
DeadThread:       0
Hosted Runtime:   noLock  ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception...30   26 3e98 08debbf8     2b220 Preemptive  00000000:00000000 0079cb88 0     MTA ...

从卦中看,30号线程就是我苦苦寻找的僵死线程,接下来赶紧切过去看看,果然发现了C++的函数xxx.Driver.xxx,由于私密性,我就模糊一下了哈。


0:030> ~30s
eax=00000000 ebx=08debbf8 ecx=00000000 edx=00000000 esi=00000000 edi=00000244
eip=77872aac esp=0a76c9fc ebp=0a76ca6c iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtWaitForSingleObject+0xc:
77872aac c20c00          ret     0Ch
0:030> !clrstack 
OS Thread Id: 0x3e98 (30)
Child SP       IP Call Site
0a76cc18 77872aac [InlinedCallFrame: 0a76cc18] 
0a76cc0c 00aa8047 DomainBoundILStubClass.IL_STUB_PInvoke(UInt32, xxx ByRef)
0a76cc18 00aa6c67 [InlinedCallFrame: 0a76cc18] xxx.Driver.xxx(UInt32, xxx ByRef)
0a76ccc0 00aa6c67 xxx.Driver.xxxFault(UInt32, System.String)
...

既然发现了C++方法,最后还剩一个疑问,就是此时的03070909真的在非托管层吗?这个可以通过搜索它的线程栈地址。


0:030> s-d poi(@$teb+0x8) poi(@$teb+0x4) 03070909
0a76cc88  03070909 728f5d01 68d8c642 5c654b42  .....].rB..hBKe\

从代码中可以看到确实是在xxx.Driver.xxxFault方法里传给了C++,有了这些信息接下来就是告诉朋友,重点关注下这个方法,捋一下逻辑。

三:总结

说实话这个dump分析起来还是有一定难度的,它考验着你对GC标记阶段玩法的底层理解,即使这位朋友是C#编程高手,分析了个把星期找不出问题是能够理解的,毕竟术业有专攻,很开心的是这位朋友因此加了.NET高级调试训练营,哈哈,以dump会友。

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

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

相关文章

Python之数据可视化基础

目录 一 JSON数据格式转换 二 pyecharts模块 三 Pyecharts入门 四 数据可视化之疫情折线图 一 JSON数据格式转换 什么是JSON? JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。它以易于阅读和编写的方式来表示结构化数据。JSO…

机器学习的精髓-梯度下降算法

目 1. 梯度下降算法2. 梯度下降求解3. 总结 1. 梯度下降算法 梯度下降算法是一种优化算法&#xff0c;用于最小化函数的数值方法。它通过沿着函数梯度的反方向来更新参数&#xff0c;以逐步减小函数值。这一过程重复进行直到达到收敛条件。梯度下降算法有多种变体&#xff0c;…

利用Maven获取jar包

我有一个习惯&#xff0c;就是程序不在线依赖网络的任何包。以前用C#时候虽然用Nuget找包&#xff0c;但是添加引用后又马上把Nuget引用删了&#xff0c;再把Nuget下载的dll拷贝到工程再引用dll。 这样做的好处是&#xff1a; 1.别人得到程序代码可以直接编译&#xff0c;不用…

《WebKit 技术内幕》学习之十四(2):调式机制

2 实践——基础和性能调试 Chromium开发者工具基本上沿用了Web Inspector的功能&#xff0c;所以这一节主要以该开发者工具作为介绍的对象&#xff0c;一起了解开发者工具提供的功能和一些基本的用法&#xff0c;有些用法其实在之前已经介绍过&#xff0c;这里可能为了系统性考…

数据类型(下)

数据类型&#xff08;下&#xff09; 1.集合&#xff08;set&#xff09;1.1 定义1.2 独有功能1.3 公共功能1.4 转换1.5 其他1.5.1 集合的存储原理1.5.2 元素必须可哈希1.5.3 查找速度特别快1.5.4 对比和嵌套 练习题 强插&#xff1a;None类型2.字典&#xff08;dict)2.1 定义2…

银行数据仓库体系实践(6)--调度系统

调度系统是数据仓库的重要组成部分&#xff0c;也是每个银行或公司一个基础软件或服务&#xff0c;需要在全行或全公司层面进行规划&#xff0c;在全行层面统一调度工具和规范&#xff0c;由于数据类系统调度作业较多&#xff0c;交易类系统批量优先级高&#xff0c;为不互相影…

基于ssm+vue在线考试系统

摘要 在线考试系统是一种利用现代技术手段实现的教育评估工具&#xff0c;它为学生提供了更灵活、便捷的考试方式&#xff0c;同时为教育机构提供了高效管理和评估学生学业水平的手段。在这个背景下&#xff0c;基于SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架和Vu…

【OCC学习23】使用Draw探索OCC API 【完结】

对于OCC应用开发者来说&#xff0c;OCC的文档虽然不错&#xff0c;但针对具体的需求找到合适的API还是得不断摸索。我发现看Draw的代码是探索OCC API使用的最佳路径。掌握根据Draw命令查找对应代码就能高效找到解决方案。所以这是本系列的最后一篇分享了&#xff0c;个人感觉OC…

重塑网络安全格局:零信任安全架构的崛起与革新

零信任安全架构是一种现代安全模式&#xff0c;其设计原则是“绝不信任&#xff0c;始终验证”。它要求所有设备和用户&#xff0c;无论他们是在组织网络内部还是外部&#xff0c;都必须经过身份验证、授权和定期验证&#xff0c;才能被授予访问权限。简而言之&#xff0c;“零…

Dockerfile里ADD * 保留原来的目录结构

1、问题 给新模块写Dockerfile&#xff0c;很多静态资源分散在各个目录&#xff0c;于是Dockerfile里我直接一句&#xff1a; ADD ./* /dest/镜像出来后&#xff0c;启动容器&#xff0c;进入容器种后发现&#xff1a;文件拷贝成功&#xff0c;但原来的目录结构都不在了&…

【网站项目】基于SSM的251国外摇滚乐队交流和周边售卖系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

用javadoc生成springboot的文档

概述&#xff1a;生成 Spring Boot 项目的 JavaDoc 文档与生成普通的 Java 项目类似。 目录 第一步&#xff1a;创建一个springboot项目 第二步&#xff1a;编写pom文件 第三步&#xff1a;运行 Maven 命令生成 JavaDoc 第四步&#xff1a;查看结果 第一步&#xff1a;创建…

AG32VF407 AGRV2K 串口printf调试输出

视频讲解 [AG32VF407]国产MCUFPGA 串口printf调试输出及演示 原理图 测试代码 新建一个platformio工程&#xff0c;复制如下文件到测试工程目录下 E:\tech\AGM-AG32VF\sdk-release\AgRV_pio\platforms\AgRV\boards\agrv2k_407\board.asf E:\tech\AGM-AG32VF\sdk-release\AgRV_…

RCD负载箱的未来发展趋势和创新技术有哪些?

随着科技的不断发展&#xff0c;RCD负载箱作为电力系统中的重要设备&#xff0c;其未来发展趋势和创新技术也将不断涌现。以下是一些可能的发展趋势和创新技术&#xff1a; 1. 智能化&#xff1a;未来的RCD负载箱将更加智能化&#xff0c;能够实现远程监控、故障诊断和自动调节…

Pyside6在Pycharm下安装和使用

目录 一&#xff1a;安装 二&#xff1a;使用 一&#xff1a;安装 打开Pycharm编辑器&#xff0c;file-setting里Python解释器&#xff0c;点击小号&#xff0c;添加模块&#xff0c;搜索Pyside6,安装 安装报错&#xff0c;可能是默认的库安装超时&#xff0c;用其他的源 p…

Mybatis----分页

1.什么是分页 分页&#xff08;Pagination&#xff09;是指将大量数据划分为多个页面进行展示的一种技术手段。在数据量较大的情况下&#xff0c;将所有数据一次性显示在页面上会导致加载时间过长和页面过于庞大&#xff0c;影响用户体验和系统性能。分页技术通过划分数据为多…

为何外贸公司应该采用CRM客户管理软件?

在外贸行业中&#xff0c;客户关系管理尤为关键。在当下的大数据背景下&#xff0c;所有规模的外贸公司都迫切地需要进行数字化改造。无论是大型公司还是小型业务&#xff0c;他们都希望通过深入分析客户数据&#xff0c;为用户提供更优的体验&#xff0c;并据此调整企业战略。…

Scrum框架的自组织团队

飞行在天空中的鸟群一会排成一个“一”字&#xff0c;一会排成一个“人”字&#xff0c;它们自由飞翔&#xff0c;又根据不同的风向排列不同的队形减少阻力&#xff0c;达到最高的飞行效率。人类社会中也如此&#xff0c;没有一种不需要调整的通用方法可以适用于所有的工作场景…

【开源】基于JAVA+Vue+SpringBoot的民宿预定管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色2.2.2 房主角色2.2.3 系统管理员角色 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿4.3 新增民宿评价4.4 查询留言4.5 新增民宿订单 五、免责说明 一、摘要 1.1 项目介绍 基于…

应急响应-Linux-文件痕迹排查

敏感目录 Linux系统铭感目录如下。 /tmp /tmp目录和命令目录/usr/bin /usr/sbin等经常作为恶意软件下载根目录及相关文件被替换的目录。文件名为crloger8的木马下载到/tmp目录下&#xff0c;如图所示 ~/.ssh及/etc/ssh 需要查看是否存在.ssh或者ssh文件 ls -la /home/on…