找回密码
 立即注册
首页 业界区 业界 记一次 .NET 某放射治疗光学定位软件 卡死分析 ...

记一次 .NET 某放射治疗光学定位软件 卡死分析

俞瑛瑶 5 小时前
一:背景

1. 讲故事

前段时间微信上有位非调试训练营学员找到我,说他们的医疗软件有点问题,有时候卡了一会就好了,有时候卡了很久,让我帮忙看下怎么回事,我让这位朋友在卡的时候抓一个dump给我,我分析看看。
二:卡死分析

1. 为什么会卡死

对于窗体程序的卡死,主要就是看主线程此时正在做什么,使用 ~0s;k 命令即可,输出如下:
  1. 0:000> ~0s;k
  2. PresentationCore_ni!string+0x1e6968:
  3. 00007ffb`f4407e60 250000e0ff      and     eax,0FFE00000h
  4. # Child-SP          RetAddr               Call Site
  5. 00 0000005f`849fc7c0 00007ffb`f4407da7     PresentationCore_ni!`string'+0x1e6968
  6. 01 0000005f`849fc810 00007ffb`f43d1d5a     PresentationCore_ni!System.Windows.ContextLayoutManager.LayoutQueue.Add+0x37
  7. 02 0000005f`849fc860 00007ffb`f2e5a4b8     PresentationCore_ni!System.Windows.UIElement.InvalidateMeasure+0xda
  8. 03 0000005f`849fc8b0 00007ffb`f2ead5bd     PresentationFramework_ni!System.Windows.FrameworkElement.OnPropertyChanged+0x8b8
  9. 04 0000005f`849fcb10 00007ffc`1ae9c394     PresentationFramework_ni!System.Windows.Controls.TextBlock.OnPropertyChanged+0x5d
  10. ...
  11. 26 0000005f`849fe460 00007ffc`1ae9044f     WindowsBase_ni!System.Windows.Threading.Dispatcher.ProcessQueue+0x1fd
  12. 27 0000005f`849fe4f0 00007ffc`1ae93314     WindowsBase_ni!System.Windows.Threading.Dispatcher.WndProcHook+0x6f
  13. 28 0000005f`849fe570 00007ffc`1ae93714     WindowsBase_ni!MS.Win32.HwndWrapper.WndProc+0xc4
  14. 29 0000005f`849fe600 00007ffc`1ae93d58     WindowsBase_ni!MS.Win32.HwndSubclass.DispatcherCallbackOperation+0x84
  15. 2a 0000005f`849fe650 00007ffc`1ae93c56     WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall+0x68
  16. 2b 0000005f`849fe6c0 00007ffc`1ae91262     WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.TryCatchWhen+0x36
  17. 2c 0000005f`849fe710 00007ffc`1ae93082     WindowsBase_ni!System.Windows.Threading.Dispatcher.LegacyInvokeImpl+0x172
  18. 2d 0000005f`849fe7b0 00007ffc`1b053b82     WindowsBase_ni!MS.Win32.HwndSubclass.SubclassWndProc+0x152
  19. 2e 0000005f`849fe8b0 00007ffc`2f0e224e     WindowsBase_ni+0x323b82
  20. 2f 0000005f`849fe920 00007ffc`505ce7e8     clr!UMThunkStub+0x6e
  21. 30 0000005f`849fe9b0 00007ffc`505ce229     user32!UserCallWinProcCheckWow+0x2f8
  22. 31 0000005f`849feb40 00007ffc`1aeb4479     user32!DispatchMessageWorker+0x249
  23. ...
  24. 43 0000005f`849ffc60 00000000`00000000     ntdll!RtlUserThreadStart+0x21
复制代码
从卦中的 ProcessQueue, TextBlock.OnPropertyChanged 等函数来看,当前主线程正在忙碌处理,如果你想看主线程的执行流细节,可以将dmp拖到vs中,让vs帮我们解读,拖进去后是不是一下子就清晰多了。。。截图如下:
1.png

接下来的问题是这玩意会导致UI的卡死吗? 经验上告诉我,这个概率不大,毕竟 PresentationCore.dll 中的代码固若金汤,那问题出在哪里呢?大概率就是窗体的Queue队列积压过多导致。
2. Queue队列积压过多吗

要想找到这个问题的答案,可以深挖调度类Dispatcher,使用 !dso xxx 到当前线程栈里去捞。
  1. 0:000> !dso
  2. OS Thread Id: 0x34d0 (0)
  3. RSP/REG          Object           Name
  4. ...
  5. 0000005F849FED20 0000020c4a784dc8 System.Windows.Threading.Dispatcher
  6. ...
  7. 0:000> !do 0000020c4a784dc8
  8. Name:        System.Windows.Threading.Dispatcher
  9. MethodTable: 00007ffc1ad53e30
  10. EEClass:     00007ffc1ad8a6f0
  11. Size:        232(0xe8) bytes
  12. File:        C:\Windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll
  13. Fields:
  14.               MT    Field   Offset                 Type VT     Attr            Value Name
  15. ...
  16. 00007ffc1ad4caf8  4001284       a0 ...on, WindowsBase]]  0 instance 0000020c4a784f08 _queue
  17. ...
  18. 0:000> !do 0000020c4a784f08
  19. Name:        System.Windows.Threading.PriorityQueue`1[[System.Windows.Threading.DispatcherOperation, WindowsBase]]
  20. MethodTable: 00007ffc1ad4caf8
  21. EEClass:     00007ffc1adab1e8
  22. Size:        56(0x38) bytes
  23. File:        C:\Windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll
  24. Fields:
  25.               MT    Field   Offset                 Type VT     Attr            Value Name
  26. 0000000000000000  4001240        8                       0 instance 0000020c4a784f40 _priorityChains
  27. 0000000000000000  4001241       10                       0 instance 0000020c4a785078 _cacheReusableChains
  28. 00007ffc1b18f340  4001242       18 ...Canon, mscorlib]]  0 instance 0000020c31b84d68 _head
  29. 00007ffc1b18f340  4001243       20 ...Canon, mscorlib]]  0 instance 0000020ce073ca68 _tail
  30. 00007ffc2b4d85a0  4001244       28         System.Int32  1 instance           889015 _count
复制代码
这卦象很不吉利,UI 队列居然积压了高达 88w 的未处理任务,难怪这位朋友说软件卡死了,其实UI线程在忙碌的任务处理,看样子没个几天几夜搞不定哈。
接下来的问题是为什么会积压这么多,要想找到这个问题的答案,可以从 88w 的queue队列中抽选几个任务,看看大概都是些什么,展开上面的 _tail 节点即可。
  1. 0:000> !DumpObj /d 0000020ce073ca68
  2. Name:        System.Windows.Threading.PriorityItem`1[[System.Windows.Threading.DispatcherOperation, WindowsBase]]
  3. MethodTable: 00007ffc1ad4e5e0
  4. EEClass:     00007ffc1adb28a0
  5. Size:        64(0x40) bytes
  6. File:        C:\Windows\Microsoft.Net\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll
  7. Fields:
  8.               MT    Field   Offset                 Type VT     Attr            Value Name
  9. 00007ffc2b4da238  400123a        8       System.__Canon  0 instance 0000020ce073c898 _data
  10. ...
  11. 0:000> !DumpObj /d 0000020ce073c840
  12. Name:        System.Action
  13. MethodTable: 00007ffc2b55aff0
  14. EEClass:     00007ffc2b665440
  15. Size:        64(0x40) bytes
  16. File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
  17. Fields:
  18.               MT    Field   Offset                 Type VT     Attr            Value Name
  19. 00007ffc2b4d5dd8  40002f3        8        System.Object  0 instance 0000020c4ba28050 _target
  20. 00007ffc2b4d5dd8  40002f4       10        System.Object  0 instance 0000000000000000 _methodBase
  21. 00007ffc2b5531f8  40002f5       18        System.IntPtr  1 instance     7ffbcfb85b10 _methodPtr
  22. ...
  23. 0:000> !U 7ffbcfb85b10
  24. Unmanaged code
  25. 00007ffb`cfb85b10 e9db38e700      jmp     00007ffb`d09f93f0
  26. 00007ffb`cfb85b15 5f              pop     rdi
  27. 00007ffb`cfb85b16 61              ???
  28. 00007ffb`cfb85b17 0040dc          add     byte ptr [rax-24h],al
  29. 00007ffb`cfb85b1a c8cffb7f        enter   0FBCFh,7Fh
  30. 00007ffb`cfb85b1e 0000            add     byte ptr [rax],al
  31. 00007ffb`cfb85b20 e80bea555f      call    clr!PrecodeFixupThunk (00007ffc`2f0e4530)
  32. 00007ffb`cfb85b25 5e              pop     rsi
  33. 00007ffb`cfb85b26 0000            add     byte ptr [rax],al
  34. 00007ffb`cfb85b28 68e1c8cffb      push    0FFFFFFFFFBCFC8E1h
  35. 0:000> !U 00007ffb`d09f93f0
  36. Normal JIT generated code
  37. WpfApp.ViewModel.CalculatedIsocenterShiftViewModel.<UpdateLineGraph>b__93_0()
  38. Begin 00007ffbd09f93f0, size f54
  39. >>> 00007ffb`d09f93f0 55              push    rbp
  40. ...
复制代码
从卦中可以看到有一个 b__93_0 方法,看样子这是一个匿名方法,接下来用 ilspy 打开观察源代码,截图如下:
2.png

从卦中的代码看,尼玛,这是兵家大忌哈。。。居然让UI线程做什么复杂的业务逻辑,这怎么不让 UI线程 累死。。。
为了佐证,可以使用 ~*e !clrstack 观察此时的各个线程栈,可以发现目前有两个线程正在通过 Dispatcher 给UI发通知并等待UI线程响应,截图如下:
3.png

到这里基本就真相大白了,这位朋友应该是高频的往UI打数据(画图),导致软件卡死。
三:总结

这次卡死事故是这位朋友犯了兵家大忌,UI线程只用来更新UI,不要将复杂的业务逻辑丢给UI去做。
4.jpeg

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册