记一次 .NET 某医院门诊软件 卡死分析
一:背景
1. 讲故事
前几天有位朋友找到我,说他们的软件在客户那边卡死了,让我帮忙看下是怎么回事?我就让朋友在程序卡死的时候通过 任务管理器
抓一个 dump 下来,虽然默认抓的是 wow64 ,不过用 soswow64.dll
转还是可以的,参考命令如下:
.load C:\soft\soswow64\soswow64.dll
!wow64exts.sw
接下来就可以分析了哈。
二:WinDbg 分析
1. 为什么会卡死
首先用 !t
简单看一下主线程的 COM Apartment 模式,如果是 STA 那就是窗体程序,比如 WPF,WinForm 之类的,输出如下:
0:000:x86> !t
ThreadCount: 39
UnstartedThread: 0
BackgroundThread: 12
PendingThread: 0
DeadThread: 26
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 1928 01aee0b0 2026020 Preemptive 041D496C:00000000 01ae88a8 2 STA
...
既然是窗体程序那就看主线程吧,使用 ~0s;!clrstack
命令。
0:000:x86> !clrstack
OS Thread Id: 0x1928 (0)
Child SP IP Call Site
0177dff8 0167e1f8 [HelperMethodFrame_1OBJ: 0177dff8] System.Threading.SynchronizationContext.WaitHelper(IntPtr[], Boolean, Int32)
0177e29c 6a6fc693 System.Windows.Threading.DispatcherSynchronizationContext.Wait(IntPtr[], Boolean, Int32)
0177e2b0 71e36d54 System.Threading.SynchronizationContext.InvokeWaitMethodHelper(System.Threading.SynchronizationContext, IntPtr[], Boolean, Int32) [f:\dd\ndp\clr\src\BCL\system\threading\synchronizationcontext.cs @ 349]
0177e4d8 73220076 [GCFrame: 0177e4d8]
0177e5f8 73220076 [GCFrame: 0177e5f8]
0177e6d8 73220076 [GCFrame: 0177e6d8]
0177e6f4 73220076 [HelperMethodFrame_1OBJ: 0177e6f4] System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
0177e770 18078b93 System.Speech.Internal.Synthesis.AudioDeviceOut.Abort()
0177e79c 17270698 System.Speech.Internal.Synthesis.VoiceSynthesis.Abort()
0177e7e8 065ec76b System.Speech.Synthesis.SpeechSynthesizer.SpeakAsyncCancelAll()
0177e7f0 065ec728 xxx.xxx.Speek(System.String)
...
从卦中看是一个 语音模块
,还有 Speek 功能,挺有意思。。。 还 Speek 啥呢?可以用 !mdso
看一下。
0:000:x86> !mdso
Thread 0:
Location Object Type
------------------------------------------------------------
0177e060 04176eb8 System.IntPtr[]
...
0177e7f8 03be9504 System.String "请先登录验证身份"
哈哈,上面只是花絮,继续看线程栈会发现代码卡在 Monitor.ReliableEnter
上,也就是等待 lock 锁,接下来用 kb 把 锁对象提取出来,即 clr!JITutil_MonReliableEnter 方法的第一个参数 03be11b4
,输出如下:
0:000:x86> kb
...
17 0177e768 18078b93 03be11b4 00000000 00000000 clr!JITutil_MonReliableEnter+0xb5
18 0177e794 17270698 0177e7bc 73252799 00000000 0x18078b93
19 0177e7e0 065ec76b 0177e808 065ec728 00000000 0x17270698
1a 0177e7e8 065ec728 00000000 03a0b318 03be9504 0x65ec76b
1b 0177e808 1727e09f 00000000 03be0920 04158b98 0x65ec728
1c 0177e824 69181324 04175c04 041199c0 00000001 0x1727e09f
...
0:000:x86> !do 03be11b4
Name: System.Object
MethodTable: 71f200f4
EEClass: 71a715b0
Size: 12(0xc) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Object
Fields:
None
有了这个对象就可以用 !syncblk
命令观察同步块表,到底是哪个线程在持有不释放?
0:000:x86> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
96 11ac3ee0 3 1 11af0f28 35d4 13 03be11b4 System.Object
-----------------------------
Total 931
CCW 39
RCW 19
ComClassFactory 2
Free 802
0:000:x86> ~13s;!clrstack
ntdll_76fc0000!NtWaitForSingleObject+0xc:
7703159c c20c00 ret 0Ch
OS Thread Id: 0x35d4 (13)
Child SP IP Call Site
17f8f23c 0000002b [InlinedCallFrame: 17f8f23c]
17f8f238 1adf3269 DomainBoundILStubClass.IL_STUB_PInvoke(IntPtr)
17f8f23c 1adf2e82 [InlinedCallFrame: 17f8f23c] System.Speech.Internal.Synthesis.SafeNativeMethods.waveOutClose(IntPtr)
17f8f26c 1adf2e82 System.Speech.Internal.Synthesis.AudioDeviceOut.End()
17f8f298 187a5cd6 System.Speech.Internal.Synthesis.VoiceSynthesis.SpeakText(System.Speech.Internal.Synthesis.SpeakInfo, System.Speech.Synthesis.Prompt, System.Collections.Generic.List`1<System.Speech.Internal.Synthesis.LexiconEntry>)
17f8f304 17271669 System.Speech.Internal.Synthesis.VoiceSynthesis.ThreadProc()
17f8f3b8 71e3710d System.Threading.ThreadHelper.ThreadStart_Context(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\thread.cs @ 74]
17f8f3c4 71e640c5 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 954]
17f8f430 71e63fd6 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 902]
17f8f444 71e63f91 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 891]
17f8f45c 71e37068 System.Threading.ThreadHelper.ThreadStart() [f:\dd\ndp\clr\src\BCL\system\threading\thread.cs @ 111]
17f8f5a0 73220076 [GCFrame: 17f8f5a0]
17f8f784 73220076 [DebuggerU2MCatchHandlerFrame: 17f8f784]
0:013:x86> !t
...
13 14 35d4 11af0f28 2b220 Preemptive 00000000:00000000 01ae88a8 2 MTA
...
从卦中信息看:13号线程持有了 lock 锁,并且它非线程池线程,而是通过 new Thread
出来的,从线程栈看都是sdk函数,综合这些信息,应该是 VoiceSynthesis
创建出来的后台线程,下面的图也可以佐证。
接下来继续看,从线程栈顶上可以观察到最后卡在了 System.Speech.Internal.Synthesis.SafeNativeMethods.waveOutClose
方法处,逆向之后的代码如下:
// System.Speech.Internal.Synthesis.AudioDeviceOut
internal override void End()
{
if (!_deviceOpen)
{
throw new InvalidOperationException();
}
lock (_noWriteOutLock)
{
_deviceOpen = false;
CheckForAbort();
if (_queueIn.Count != 0)
{
SafeNativeMethods.waveOutReset(_hwo);
}
MMSYSERR mMSYSERR = SafeNativeMethods.waveOutClose(_hwo);
}
}
由于这是 Windows 的 VoiceSynthesis 模块封装底层函数,经过千锤百炼,理论上出问题的概率会非常小,除非上层有不合理的调用,这种概率会大一些。
2. 是上层不合理的调用吗
这一块我也没玩过,网上搜一下 waveOutReset
, waveOutClose
,看下有没有同病相怜的人,结果网上一搜一堆,比如下面这样:
不管怎么说,这一块如果处理不好容易出现死锁和卡死的情况,那是不是正如图中所说 waveOutReset
和 waveOutClose
没有匹配造成的呢?
这就取决于代码中的 _queueIn
集合,可以观察这两个函数的汇编代码提取出这个变量。
0:013:x86> !U /d 1adf2e82
Normal JIT generated code
System.Speech.Internal.Synthesis.AudioDeviceOut.End()
...
1adf2e30 8bf1 mov esi,ecx
...
1adf2e69 8b4608 mov eax,dword ptr [esi+8]
1adf2e6c 83780c00 cmp dword ptr [eax+0Ch],0
1adf2e70 7408 je 1adf2e7a
...
0:013:x86> !U /d 187a5cd6
Normal JIT generated code
System.Speech.Internal.Synthesis.VoiceSynthesis.SpeakText(System.Speech.Internal.Synthesis.SpeakInfo, System.Speech.Synthesis.Prompt, System.Collections.Generic.List`1<System.Speech.Internal.Synthesis.LexiconEntry>)
...
187a5cc8 8b45d0 mov eax,dword ptr [ebp-30h]
187a5ccb 8b486c mov ecx,dword ptr [eax+6Ch]
187a5cd3 ff5014 call dword ptr [eax+14h]
>>> 187a5cd6 58 pop eax
...
0:013:x86> kb 10
# ChildEBP RetAddr Args to Child
...
08 17f8f264 1adf2e82 03be11b4 00000001 00000000 0x1adf3269
09 17f8f290 187a5cd6 187a5e16 03be0d24 043f3520 0x1adf2e82
0a 17f8f2f4 17271669 040efa14 040ef9a4 732515d8 0x187a5cd6
0b 17f8f3b0 71e3710d 03ff3e98 17f8f420 71e640c5 0x17271669
...
仔细观察上面的汇编代码:eax 来自于 esi,esi 来自于 ecx,ecx 最终来自于父函数中的 ebp-30h
的位置,串联起来的命令就是 !do poi(poi(poi(17f8f2f4-30)+6c)+0x8)
,接下来我们 do 一下。
0:000:x86> !do poi(poi(poi(17f8f2f4-30)+6c)+0x8)
Name: System.Collections.Generic.List`1[[System.Speech.Internal.Synthesis.AudioDeviceOut+InItem, System.Speech]]
MethodTable: 16cf20ec
EEClass: 71af6f8c
Size: 24(0x18) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
71f2f320 4001891 4 System.__Canon[] 0 instance 03c74538 _items
71f21bb4 4001892 c System.Int32 1 instance 0 _size
71f21bb4 4001893 10 System.Int32 1 instance 1900 _version
71f200f4 4001894 8 System.Object 0 instance 00000000 _syncRoot
71f2f320 4001895 4 System.__Canon[] 0 static <no information>
可以看到此时的 _size=0
,有可能就是因为上层不合理调用导致这里的 _queueIn
意外为 0 ,最终引发的卡死现象。
3. 真相大白
一时之间也找不到上层哪里有不合理的调用,接下来的思路还是自己研读主线程和13号线程的调用栈,最后发现一个可疑的现象,截图如下:
通过仔细研读底层代码,Speek 会将消息丢到底层的queue队列中,后台线程会提取处理,这里的 SpeakAsyncCancelAll
是完全没必要的。
有了这个消息,就让朋友把这个函数去掉观察下试试,据朋友反馈说没有问题了。
三:总结
这个案例中去掉了意外的 speech.SpeakAsyncCancelAll();
语句就搞定了,内部深层逻辑也没有再探究了,大概率就是意外的 _queueIn 为 0,让 waveOutReset
和 waveOutClose
方法没有匹配出现,造成了卡死现象。
记一次 .NET 某医院门诊软件 卡死分析的更多相关文章
- 记一次 .NET 某药品仓储管理系统 卡死分析
一:背景 1. 讲故事 这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下: 从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简 ...
- 记一次 .NET 某金融企业 WPF 程序卡死分析
一:背景 1. 讲故事 前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话. ...
- 记一次 .NET 某数控机床控制程序 卡死分析
一:背景 1. 讲故事 前段时间有位朋友微信上找到我,说它的程序出现了卡死,让我帮忙看下是怎么回事? 说来也奇怪,那段时间求助卡死类的dump特别多,被迫训练了一下对这类问题的洞察力 ,再次声明一下, ...
- “深度评测官”——记2020BUAA软工软件案例分析作业
项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任建) 这个作业的要求在哪里 个人博客作业-软件案例分析 我在这个课程的目标是 完成一次完整的软件开发经历并以博客的方式记录开发 ...
- 软件产品案例分析--K米
软件产品案例分析--K米 第一部分 调研,评测 评测 个人第一次上手体验 使用的第一款点歌软件,以为就是个遥控而已,使用后发现功能还挺多,能点挺久.觉得很方便,不用挤成一堆点歌了.K米的脸蛋(UI)好 ...
- 团队项目2.0软件改进分析MathAPP
软件改进分析 在此基础上,进行软件的改进. 首先,我们把这个软件理解成一个投入市场的.帮助小朋友进行算术运算练习的APP. 从质量保证的角度,有哪些需要改进的BUG? 从用户的角度(把自己当成小学生或 ...
- [团队项目2.0]软件改进分析MathAPP
软件改进分析 在此基础上,进行软件的改进. 首先,我们把这个软件理解成一个投入市场的.帮助小朋友进行算术运算练习的APP. 从质量保证的角度,有哪些需要改进的BUG? 从用户的角度(把自己当成小学生或 ...
- 第五周课后作业——热门软件创新分析+附加题1&附加题3
鉴于我们寝室都热衷于手游,所以本次热门软件创新分析我就来分析一下几款热门的抽卡型手游. 阴阳师(后文简称YYS)——剧情画风唯美,配音引人入胜 作为网易公司研发的一款3D日式和风回合制游戏,YYS ...
- 福州大学软件工程1816 | W班 第10次作业[个人作业——软件产品案例分析]
作业链接 个人作业--软件产品案例分析 评分细则 本次个人项目分数由两部分组成(课堂得分(老师/助教占比60%,学生占比40%)满分40分+博客分满分60分) 课堂得分和博客得分表 评分统计图 千帆竞 ...
- 书评第003篇:《0day安全:软件漏洞分析技术(第2版)》
本书基本信息 丛书名:安全技术大系 作者:王清(主编),张东辉.周浩.王继刚.赵双(编著) 出版社:电子工业出版社 出版时间:2011-6-1 ISBN:9787121133961 版次:1 页数:7 ...
随机推荐
- windows中使用jenkins部署项目,后端无法启动问题
忙活一下午+一上午,问题终于解决了.找了各种办法,最终解决方式如下: 1.jenkins打包成功,到接口会报502异常 原因:后端未成功启动 解决办法: 1.刚开始使用shell命令,无法查杀进程,后 ...
- java.lang.StackOverflowError错误的解决方法
对于java.lang.StackOverflowError认识 如下图所示,报出来这种错误的话,很大概率是有以下几种原因: 现在来看一看我的报错界面: 不难看出,这是无限循环的那种情况,所以,我就去 ...
- 使用python爬虫爬取链家潍坊市二手房项目
使用python爬虫爬取链家潍坊市二手房项目 需求分析 需要将潍坊市各县市区页面所展示的二手房信息按要求爬取下来,同时保存到本地. 流程设计 明确目标网站URL( https://wf.lianjia ...
- Java面试——Netty
一.BIO.NIO 和 AIO [1]阻塞 IO(Blocking I/O):同步阻塞I/O模式,当一条线程执行 read() 或者 write() 方法时,这条线程会一直阻塞直到读取一些数据或者写出 ...
- java多线程基础小白指南--synchronized同步块
sychronized是java多线程非常关键的一个知识点,这篇博客将从synchronized几个用法以及代码来学习. sychronized的作用是能够保证同一时间只有一个线程来运行这块代码,达到 ...
- 二进制安装Kubernetes(k8s) v1.24.2 IPv4/IPv6双栈
二进制安装Kubernetes(k8s) v1.24.2 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes二进制安装 强烈建议在Github ...
- 在 k8s(kubernetes)中使用 Loki 进行日志监控
安装helm环境 [root@hello ~/yaml]# [root@hello ~/yaml]# curl https://baltocdn.com/helm/signing.asc | sudo ...
- kubernetes核心实战(八)--- service
13.service 四层网络负载 创建 [root@k8s-master-node1 ~/yaml/test]# [root@k8s-master-node1 ~/yaml/test]# vim m ...
- mysql 求分组中位数、环比、同比、中位数的环比
说明 中位数.环比.同比概念请自行百度,本文求 字段A中位数.根据字段B分组后字段A中位数.字段A环比.字段A同比.字段A中位数的环比.字段A中位数的同比. 一.表结构如下图 查询条件为 capi ...
- 【Diary】JZSC 2020 旅 游 记(迫真
Day-2 期末考试的day1. 科目是数学 政治 语文.数学25.(3)没动.政治各种抄选择题选项()语文难得写完了.作文压根不知道写的什么 Day-1 期末考试的day2. 科目是英语 物理 历史 ...