记一次 .NET某医疗器械清洗系统 卡死分析
一:背景
1. 讲故事
前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧。
二:WinDbg 分析
1. 为什么会卡死
因为是窗体程序,理所当然就是看主线程此时正在做什么? 可以用 ~0s ; k 看一下便知。
0:000> k
# ChildEBP RetAddr
00 00aff168 75e3bb0a win32u!NtUserPeekMessage+0xc
01 00aff168 75e3ba7e USER32!_PeekMessage+0x2a
02 00aff1a4 6a5d711c USER32!PeekMessageW+0x16e
03 00aff1f0 6a5841a6 System_Windows_Forms_ni+0x23711c
...
17 00afffbc 00000000 ntdll!_RtlUserThreadStart+0x1b
从线程栈来看,当前的方法卡在 win32u!NtUserPeekMessage 上, 熟悉 Windows 窗体消息的朋友都知道这是提取 消息队列 的常规逻辑,这个方法的下一步就是通过 Wow64SystemServiceCall 进入到 Windows内核态,可以用 u 命令验证一下。
0:000> ub win32u!NtUserPeekMessage+0xc
761d1010 b801100000 mov eax,1001h
761d1015 ba10631d76 mov edx,offset win32u!Wow64SystemServiceCall (761d6310)
761d101a ffd2 call edx
朋友也给我截了图,确实出现了卡死,那接下来的问题就是看下当前线程在 内核态 到底在做什么?
2. 真的卡在内核态吗
幸好朋友可以在卡死的机器上安装 windbg,让朋友在卡死的时候使用 Attch to kernel 的方式观察内核态,截图如下:

附加成功后,可以用 !process 0 f xxxx.exe 看到主线程的线程栈。
lkd> !process 0 f xxxx.exe
PROCESS ffffab8ebea75080
SessionId: 1 Cid: 0f78 Peb: 009f1000 ParentCid: 1134
...
THREAD ffffab8ecad14540 Cid 0f78.38f8 Teb: 00000000009f3000 Win32Thread: ffffab8ecd5dabc0 WAIT: (WrUserRequest) UserMode Non-Alertable
ffffab8ecb31bcc0 QueueObject
IRP List:
ffffab8ecad82b20: (0006,0478) Flags: 00060000 Mdl: 00000000
Not impersonating
DeviceMap ffffd400aa7eed50
Owning Process ffffab8ebea75080 Image: xxxx.exe
Attached Process N/A Image: N/A
Wait Start TickCount 1117311 Ticks: 9265 (0:00:02:24.765)
Context Switch Count 60628 IdealProcessor: 2 NoStackSwap
UserTime 00:00:10.796
KernelTime 00:00:06.593
Win32 Start Address 0x00000000006e16aa
Stack Init ffffa88b5b18fb90 Current ffffa88b5b18e780
Base ffffa88b5b190000 Limit ffffa88b5b189000 Call 0000000000000000
Priority 10 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
ffffa88b`5b18e7c0 fffff806`6627e370 nt!KiSwapContext+0x76
ffffa88b`5b18e900 fffff806`6627d89f nt!KiSwapThread+0x500
ffffa88b`5b18e9b0 fffff806`6627d143 nt!KiCommitThreadWait+0x14f
ffffa88b`5b18ea50 fffff806`6628679b nt!KeWaitForSingleObject+0x233
ffffa88b`5b18eb40 ffffa9d4`bdd32b12 nt!KeWaitForMultipleObjects+0x45b
ffffa88b`5b18ec50 ffffa9d4`bdd352d9 win32kfull!xxxRealSleepThread+0x362
ffffa88b`5b18ed70 ffffa9d4`bdd33f8a win32kfull!xxxInterSendMsgEx+0xdd9
ffffa88b`5b18eee0 ffffa9d4`bdd37870 win32kfull!xxxSendTransformableMessageTimeout+0x3ea
ffffa88b`5b18f030 ffffa9d4`bdf1e088 win32kfull!xxxSendMessage+0x2c
ffffa88b`5b18f090 ffffa9d4`bdf1e0e9 win32kfull!xxxCompositedTraverse+0x40
ffffa88b`5b18f0e0 ffffa9d4`bdf1e0e9 win32kfull!xxxCompositedTraverse+0xa1
ffffa88b`5b18f130 ffffa9d4`bdf1e0e9 win32kfull!xxxCompositedTraverse+0xa1
ffffa88b`5b18f180 ffffa9d4`bdf1e0e9 win32kfull!xxxCompositedTraverse+0xa1
ffffa88b`5b18f1d0 ffffa9d4`bdf1e2a7 win32kfull!xxxCompositedTraverse+0xa1
ffffa88b`5b18f220 ffffa9d4`bde5a013 win32kfull!xxxCompositedPaint+0x37
ffffa88b`5b18f2b0 ffffa9d4`bdd2e438 win32kfull!xxxInternalDoPaint+0x12bce3
ffffa88b`5b18f300 ffffa9d4`bdd2e03a win32kfull!xxxInternalDoPaint+0x108
ffffa88b`5b18f350 ffffa9d4`bdd30f1c win32kfull!xxxDoPaint+0x52
ffffa88b`5b18f3b0 ffffa9d4`bdd2ff08 win32kfull!xxxRealInternalGetMessage+0xfac
ffffa88b`5b18f880 ffffa9d4`be1871ce win32kfull!NtUserPeekMessage+0x158
ffffa88b`5b18f940 fffff806`6640d8f5 win32k!NtUserPeekMessage+0x2a
ffffa88b`5b18f990 00007ffe`1816ff74 nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffffa88b`5b18fa00)
00000000`0077e558 00000000`00000000 0x00007ffe`1816ff74
如果线程信息很少的话,可以用 .process 将此进程作为当前上下文,然后加载用户符号,输出如下:
lkd> .process ffffab8ebea75080
Implicit process is now ffffab8e`bea75080
lkd> .reload
Connected to Windows 10 19041 x64 target at (Tue Mar 21 13:21:21.213 2023 (UTC + 8:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
................................................................
.................
Loading User Symbols
PEB is paged out (Peb.Ldr = 00000000`009f1018). Type ".hh dbgerr001" for details
Loading unloaded module list
从刚才的线程栈上看,很明显有一个 win32kfull!xxxSendMessage+0x2c 方法,熟悉 SendMessage 的朋友都知道这个是用来向某个窗体发消息的,那到底是哪一个窗体呢?
3. 到底给哪个窗体发消息
要想获取发送窗体的句柄,需要提取 win32kfull!xxxSendMessage 方法的第一个参数,在 x64 的调用协定下,它是用 rcx 传递的,需要分析下汇编代码,如果 rcx 没有放到栈里,那就无法提取了。
为了少点麻烦,建议让朋友看下 32bit 的操作系统上是否也有这个问题?结果反馈说也存在,使用 !thread xxx 切到目标线程,使用 kb 提取第一个参数地址上的值,即:00010598,截图如下:

丢了一个 sdbgext 插件让朋友看下窗体句柄信息,发现是个 64bit 的,其实除了它还可以用 Spy++ 观察窗体句柄,重点就是找到这个神秘窗体 是由哪个进程下的线程创建的,当把句柄号丢进去后还真给找到了,有点黑暗中寻找到了曙光。截图如下:

从 Spy++ 看当前窗体是由进程号:000016E0下的线程号0000109C 创建的,经过比对,这个线程就是本进程的某个线程号。
分析到这里其实就很明朗了,是因为这个线程 0000109C 创建了一个用户控件,导致内核态 在某种情况下给它发消息,接下来就是寻找到底是什么控件创建的。
4. 罪魁祸首
关于非主线程创建用户控件导致的卡死,我感觉都已经说破嘴皮了,还是有非常多的人犯这个毛病,无语哈,解决办法就是用 bp 去拦截 System.Windows.Forms.Application+MarshalingControl..ctor 方法,具体方案可参考我的文章:【一个超经典 WinForm 卡死问题的再反思】https://www.cnblogs.com/huangxincheng/p/16868486.html
接下来就是朋友的苦苦调试,终于给找到了,截图如下:

对,就是这么一句 Intptr handle =this.Handle 代码,内核句柄的获取让它在这个线程上生根了。
三:总结
就是这么一句代码,来来回回兜了好几圈,花费了朋友个把星期,终于给解决了,也算是一个好结果吧,这个案例需要实时观察程序的内核态和用户态,看 dump 效果不大,造成了这么多时间的浪费。
相信这个案例也让公司老板对他 刮目相看。
记一次 .NET某医疗器械清洗系统 卡死分析的更多相关文章
- 记一次 .NET 某医疗器械 程序崩溃分析
一:背景 1.讲故事 前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用 AEDebug 在程序崩溃的时候自动抽一 ...
- 记一次 .NET 某物管后台服务 卡死分析
一:背景 1. 讲故事 这几个月经常被朋友问,为什么不更新这个系列了,哈哈,确实停了好久,主要还是打基础去了,分析 dump 的能力不在于会灵活使用 windbg,而是对底层知识有一个深厚的理解,比如 ...
- 记一次 .NET 某工控自动化控制系统 卡死分析
一:背景 1. 讲故事 前段时间遇到了好几起关于窗体程序的 进程加载锁 引发的 程序卡死 和 线程暴涨 问题,这种 dump 分析难度较大,主要涉及到 Windows操作系统 和 C++ 的基础知识, ...
- 记一次 .NET 某企业OA后端服务 卡死分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...
- 智软科技医疗器械GSP监管软件通过多省市药监局检查
提供医疗器械GSP监管软件,通过多省市药监局检查,符合2016年最新GSP监管条例的要求. 企业客户列表 温岭市万悦医疗器械有限公司 杭州市上善医疗器械有限公司 武汉明德生物科技股份有限公司 http ...
- 医疗器械c#上位机开发指引教程
此教程面向的读者:对医疗器械上位机编程有兴趣,或者急需了解医疗器械(尿常规.血液分析.生化.心电.B超等医疗下位仪器)的编程流程.编程细节的程序员. 1.得到仪器协议 当我们需要与医疗器械等下位机数据 ...
- 医疗器械软件产品经理必读的法规及标准-YY/T0664(二)
上节主要讲了软件开发策划.软件需求分析.软件系统结构设计三个阶段,这节来分析以下几个阶段. 1.软件单元实现 2.软件集成和集成测试 3.软件系统测试 软件开发过程由若干个活动组成,主要包括软件开发策 ...
- 医疗器械软件产品经理必读的法规及标准-YY/T0664(一)
医疗器械软件产品经理必读的法规及标准-YY/T0664(一) 医疗器械软件的产品经理,需要熟读医药行业标准,在软件设计开发的整个生存周期过程中,我们需要根据<YY/T 0664 医疗器械软件 软 ...
- 【阿里聚安全·安全周刊】双十一背后的“霸下-七层流量清洗”系统| 大疆 VS “白帽子”,到底谁威胁了谁?
关键词:霸下-七层流量清洗系统丨大疆 VS "白帽子"丨抢购软件 "第一案"丨企业安全建设丨Aadhaar 数据泄漏丨朝鲜APT组织Lazarus丨31款违规A ...
- 记一次 .NET 某纺织工厂 MES系统 API 挂死分析
一:背景 1. 讲故事 这个月中旬,有位朋友加我wx求助他的程序线程占有率很高,寻求如何解决,截图如下: 说实话,和不同行业的程序员聊天还是蛮有意思的,广交朋友,也能扩大自己的圈子,朋友说他因为这个b ...
随机推荐
- 第12组 Beta冲刺 (4/5)
1.1基本情况 ·队名:美少女战士 ·组长博客:https://www.cnblogs.com/yaningscnblogs/p/14016973.html ·作业博客:https://edu.cnb ...
- VueUse实用工具
1.安装 npm i @vueuse/core 2.使用 useClipboard 剪贴板 <script setup lang="ts"> import { ref ...
- axis2 WebService 请求参数xml格式
方法定义 public class GetBillInfoService { public String getBillList(String xmlData, String temp ){} 传入接 ...
- Windows server 防火墙开放oracle监听端口
Windows server 防火墙开放oracle监听端口 Windows server 2008 开放1521端口 Windows server 2003 开放监听程序例外先开防火墙,再开监听例外 ...
- rust-must-know-crates-5ad8 100DayOfRust
https://dev.to/cad97/rust-must-know-crates-5ad8 https://dev.to/search?q=100DayOfRust https://fastert ...
- FreeType 矢量字体 测试移植(1)
之前有做过 ascii 和汉字库的字体点阵在lcd上显示的例子,都是按照指定大小的字库的点阵来显示的,所以一但选定了字体文件后,就固定了大小,不可变化,当然也可以存放各种 大小的字体文件,但这样的话就 ...
- keshe第三周
本周尝试在openeuler中运行java代码 实现bc库和crypto.gmsm 如下: 实现gmlib C语言库
- loadrunner入门(关联)
左右边界:提取第一个id web_reg_save_param_ex( "ParamName=Id", "LB=//OK[ ...
- 【LuckyFrame研究】环境准备
LuckyFrame官方使用手册:http://www.luckyframe.cn/book/yhsc/syschyy-24.html LuckyFrame在码云平台或是GitHub上都是分成二个项目 ...
- 第二章启动引导器GRUB2
第二章启动引导器GRUB2grub的配置文件路径:vim /boot/grub2/grub.cfg (不建议直接编辑)vim /etc/default/grub (可编辑的文件)将编辑的操作刷新到/b ...