记一次 .NET 某医院门诊软件 卡死分析

这篇具有很好参考价值的文章主要介绍了记一次 .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 创建出来的后台线程,下面的图也可以佐证。

记一次 .NET 某医院门诊软件 卡死分析

接下来继续看,从线程栈顶上可以观察到最后卡在了 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 ,看下有没有同病相怜的人,结果网上一搜一堆,比如下面这样:

记一次 .NET 某医院门诊软件 卡死分析
记一次 .NET 某医院门诊软件 卡死分析

不管怎么说,这一块如果处理不好容易出现死锁和卡死的情况,那是不是正如图中所说 waveOutResetwaveOutClose 没有匹配造成的呢?

这就取决于代码中的 _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号线程的调用栈,最后发现一个可疑的现象,截图如下:

记一次 .NET 某医院门诊软件 卡死分析

通过仔细研读底层代码,Speek 会将消息丢到底层的queue队列中,后台线程会提取处理,这里的 SpeakAsyncCancelAll 是完全没必要的。

有了这个消息,就让朋友把这个函数去掉观察下试试,据朋友反馈说没有问题了。

记一次 .NET 某医院门诊软件 卡死分析

三:总结

这个案例中去掉了意外的 speech.SpeakAsyncCancelAll(); 语句就搞定了,内部深层逻辑也没有再探究了,大概率就是意外的 _queueIn 为 0,让 waveOutResetwaveOutClose 方法没有匹配出现,造成了卡死现象。文章来源地址https://www.toymoban.com/news/detail-445148.html

记一次 .NET 某医院门诊软件 卡死分析

到了这里,关于记一次 .NET 某医院门诊软件 卡死分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 记一次 .NET 某工控电池检测系统 卡死分析

    前几天有位朋友找到我,说他的窗体程序有卡死现象,让我帮忙看下怎么回事,解决这种问题就需要在卡死的时候抓一个dump下来,拿到dump之后就可以分析了。 窗体程序的卡死,需要观察主线程此时正在做什么,可以用 !clrstack 命令观察。 从卦中的线程栈数据来看,貌似是卡

    2024年02月05日
    浏览(41)
  • 记一次 .NET某医疗器械清洗系统 卡死分析

    前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧。 因为是窗体程序,理所当然就是看主线程此时正在做什么? 可以用 ~0s ; k 看一下便知。 从线程栈来看,当前的方法卡在 win32u!NtUserPeekMessage 上

    2023年04月18日
    浏览(37)
  • 记一次 .NET某MES自动化桌面程序 卡死分析

    前些天有位朋友在微信上找到我,说他们的客户端程序卡死了,让我帮忙看下是什么原因导致的?dump也拿到了手,既然有了dump就开始正式分析吧。 客户端的程序卡死比较好找原因,入手点就是主线程,看下它此时正在做什么,可以用 k 命令。 从卦中信息看,代码正在托管层

    2024年01月16日
    浏览(31)
  • 记一次 .NET 某医院预约平台 非托管泄露分析

    前几天有位朋友找到我,说他的程序有内存泄露,让我帮忙排查一下,截图如下: 说实话看到 32bit, 1.5G 这些之后,职业敏感告诉我,他这个可能是虚拟地址紧张所致,不管怎么说,有了 Dump 就可以上马分析。 要看是不是虚拟地址紧张,可以用 !address -summary 观察下内

    2024年02月12日
    浏览(34)
  • 记一次 .NET某股票交易软件 灵异崩溃分析

    在dump分析的旅程中也会碰到一些让我无法解释的灵异现象,追过这个系列的朋友应该知道,上一篇我聊过 宇宙射线 导致的程序崩溃,后来我又发现了一例,而这一例恰恰是高铁的 列控连锁一体化 程序,所以更加让我确定这是由于 电离辐射 干扰了计算机的 数字信号 导致程

    2024年02月04日
    浏览(30)
  • 记一次 .NET某账本软件 非托管泄露分析

    中秋国庆长假结束,哈哈,在老家拍了很多的短视频,有兴趣的可以上B站观看:https://space.bilibili.com/409524162 ,今天继续给大家分享各种奇奇怪怪的.NET生产事故,希望能帮助大家在未来的编程之路上少踩坑。 话不多说,这篇看一个 .NET程序集泄露 导致的CLR私有堆泄露的案例,

    2024年02月08日
    浏览(29)
  • 记一次 .NET某收银软件 非托管泄露分析

    在我的分析之旅中,遇到过很多程序的故障和杀毒软件扯上了关系,有杀毒软件导致的程序卡死,有杀毒软件导致的程序崩溃,这一篇又出现了一个杀毒软件导致的程序非托管内存泄露,真的是分析多了什么鬼都能撞上。 前几天有位朋友找到过,我他们的程序内存在慢慢的泄

    2024年02月03日
    浏览(26)
  • 记一次 Visual Studio 2022 卡死分析

    最近不知道咋了,各种程序有问题都寻上我了,你说 .NET 程序有问题找我能理解,Windows 崩溃找我,我也可以试试看,毕竟对 Windows 内核也知道一丢丢,那 Visual Studio 有问题找我就说不过去了,但又不好拒绝,就让朋友发下卡死的 dump 我看一看。 因为 VS 是窗体程序,所以在卡

    2024年02月05日
    浏览(40)
  • 记一次 .NET某防伪验证系统 崩溃分析

    昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障,因为看小伙子昨天在群里问了一天也没搞定,干脆自己亲自上阵吧,抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式,省去了很多沟通成本。 windbg有一个非常强大的点就是当你双击打开后,会自动帮你切换到

    2024年03月28日
    浏览(49)
  • 记一次 .NET 某企业采购平台 崩溃分析

    前段时间有个朋友找到我,说他们的程序有偶发崩溃的情况,让我帮忙看下怎么回事,针对这种 crash 的程序,用 AEDebug 的方式抓取一个便知,有了 dump 之后接下来就可以分析了。 既然是程序的崩溃,我们可以像看蓝屏一下看dump文件,使用 !analyze -v 命令即可。 从上面的信息

    2024年02月11日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包