意识形态建设专题网站,如何利用wordpress卖产品,长沙传媒公司排行,手机网站与PC网站一#xff1a;背景 1. 讲故事这世间事说来也奇怪#xff0c;近两个月有三位朋友找到我#xff0c;让我帮忙分析下他的程序hangon现象#xff0c;这三个dump分别涉及#xff1a;医疗#xff0c;新能源#xff0c;POS系统。截图如下#xff1a;那这篇为什么要拿其中的 新… 一背景 1. 讲故事这世间事说来也奇怪近两个月有三位朋友找到我让我帮忙分析下他的程序hangon现象这三个dump分别涉及医疗新能源POS系统。截图如下那这篇为什么要拿其中的 新能源 说事呢因为这位朋友解决的最顺利在提供的一些线索后比较顺利的找出了问题代码。说点题外话我本人对 winform 是不熟的又奈何它三番五次的出现在我的视野里所以我决定写一篇文章好好的总结下介于没有太多的参考资料能力有限只能自己试着解读。二Windbg 分析 1. 程序现象开始之前先吐槽一下这几位大佬抓的dump文件都是 wow64也就是用64bit任务管理器抓了32bit的程序见如下输出
wow64cpu!CpupSyscallStub0x9:
00000000756d2e09 c3 ret所以就不好用 windbg preview 来分析了首先要用 !wow64exts.sw 将 64bit 转为 32bit 本篇用的是 windbg10好了既然是UI卡死首当其冲就是要看一下UI线程到底被什么东西卡住了可以用命令 !clrstack 看一下。
0:000:x86 !clrstack
OS Thread Id: 0x1d90 (0)
Child SP IP Call Site
0019ee6c 0000002b [HelperMethodFrame_1OBJ: 0019ee6c] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
0019ef50 6c4fc7c1 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
0019ef68 6c4fc788 System.Threading.WaitHandle.WaitOne(Int32, Boolean)
0019ef7c 6e094e7e System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
0019efbc 6e463b96 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
0019efc0 6e09722b [InlinedCallFrame: 0019efc0]
0019f044 6e09722b System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
0019f078 6e318556 System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object)
0019f090 6eef65a8 Microsoft.Win32.SystemEventsSystemEventInvokeInfo.Invoke(Boolean, System.Object[])
0019f0c4 6eff850c Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[])
0019f110 6eddb134 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr)
0019f130 6f01f0b0 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr)
0019f134 001cd246 [InlinedCallFrame: 0019f134]
0019f2e4 001cd246 [InlinedCallFrame: 0019f2e4]
0019f2e0 6dbaefdc DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
0019f2e4 6db5e039 [InlinedCallFrame: 0019f2e4] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
0019f318 6db5e039 System.Windows.Forms.ApplicationComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
0019f31c 6db5dc49 [InlinedCallFrame: 0019f31c]
0019f3a4 6db5dc49 System.Windows.Forms.ApplicationThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0019f3f4 6db5dac0 System.Windows.Forms.ApplicationThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0019f420 6db4a7b1 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
0019f434 003504a3 xxx.Program.Main()
0019f5a8 6f191366 [GCFrame: 0019f5a8] 从调用栈上看代码是由于 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged 被触发然后在 System.Windows.Forms.Control.WaitForWaitHandle处被卡死从前者的名字上就能看到OnUserPreferenceChanged用户首选项 是一个系统级别的 Microsoft.Win32.SystemEvents 事件那到底是什么导致了这个系统事件被触发为此我查了下资料大概是说如果应用程序的 Control 注册了这些系统级事件那么当windows发出 WM_SYSCOLORCHANGE, WM_DISPLAYCHANGED, WM_THEMECHANGED主题首选项界面显示 消息时这些注册了系统级事件的 Control 的handle将会被执行比如刷新自身。觉得文字比较拗口的话我试着画一张图来阐明一下。从本质上来说它就是一个观察者模式但这和UI卡死没有半点关系充其量就是解决问题前需要了解的背景知识还有一个重要概念没有说那就是WindowsFormsSynchronizationContext 。2. 理解 WindowsFormsSynchronizationContext为什么一定要了解 WindowsFormsSynchronizationContext 呢理解了它你就搞明白了为什么会卡死我们知道 winform 的UI线程是一个 STA 模型它的一个特点就是单线程其他线程想要更新Control都需要调度到UI线程的Queue队列中不存在也不允许并发更新Control的情况参考如下
0:000:x86 !t
ThreadCount: 207
UnstartedThread: 0
BackgroundThread: 206
PendingThread: 0
DeadThread: 0
Hosted Runtime: noLock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 1d90 003e2430 2026020 Preemptive 00000000:00000000 003db8b8 0 STA 2 2 2804 003f0188 2b220 Preemptive 00000000:00000000 003db8b8 0 MTA (Finalizer) Winform 还有一个特点它会给那些创建 Control 的线程配一个 WindowsFormsSynchronizationContext 同步上下文也就是说如果其他线程想要更新那个 Control那就必须将更新的值通过 WindowsFormsSynchronizationContext 调度到那个创建它的线程上这里的线程不仅仅是 UI 线程哦有了这些基础知识后再来分析下为什么会被卡死。3. 卡死的真正原因再重新看下主线程的调用栈它的走势是这样的OnUserPreferenceChanged - WindowsFormsSynchronizationContext.Send - Control.MarshaledInvoke - WaitHandle.WaitOneNative哈哈有看出什么问题吗眼尖的朋友会发现为什么主线程会调用 WindowsFormsSynchronizationContext.Send 方法呢难道那个注册 handler的 Control 不是由主线程创建的吗要想回答这个问题需要看一下 WindowsFormsSynchronizationContext 类的 destinationThreadRef 字段值源码如下
public sealed class WindowsFormsSynchronizationContext : SynchronizationContext, IDisposable
{private Control controlToSendTo;private WeakReference destinationThreadRef;
}可以用 !dso 命令把线程栈上的 WindowsFormsSynchronizationContext 给找出来简化输出如下
0:000:x86 !dso
OS Thread Id: 0x1d90 (0)
ESP/REG Object Name
0019ED70 027e441c System.Windows.Forms.WindowsFormsSynchronizationContext
0019EDC8 112ee43c Microsoft.Win32.SafeHandles.SafeWaitHandle
0019F078 11098b74 System.Windows.Forms.WindowsFormsSynchronizationContext
0019F080 1107487c Microsoft.Win32.SystemEventsSystemEventInvokeInfo
0019F08C 10fa386c System.Object[] (System.Object[])
0019F090 1107487c Microsoft.Win32.SystemEventsSystemEventInvokeInfo
0019F0AC 027ebf60 System.Object
0019F0C0 10fa386c System.Object[] (System.Object[])
0019F0C8 027ebe3c System.Object
0019F0CC 10fa388c Microsoft.Win32.SystemEventsSystemEventInvokeInfo[]
...0:000:x86 !do 11098b74
Name: System.Windows.Forms.WindowsFormsSynchronizationContext
Fields:MT Field Offset Type VT Attr Value Name
6dbd8f30 4002567 8 ...ows.Forms.Control 0 instance 11098c24 controlToSendTo
6c667c2c 4002568 c System.WeakReference 0 instance 11098b88 destinationThreadRef0:000:x86 !do 11098b88
Name: System.WeakReference
Fields:MT Field Offset Type VT Attr Value Name
6c66938c 4000705 4 System.IntPtr 1 instance 86e426c m_handle0:000:x86 !do poi(86e426c)
Name: System.Threading.Thread
Fields:MT Field Offset Type VT Attr Value Name
6c663cc4 40018a5 24 System.Int32 1 instance 2 m_Priority
6c663cc4 40018a6 28 System.Int32 1 instance 7 m_ManagedThreadId
6c66f3d8 40018a7 2c System.Boolean 1 instance 1 m_ExecutionContextBelongsToOuterScope果然不出所料, 从卦象上看 Thread7 线程上有 Control 注册了系统事件那 Thread7 到底是什么线程呢可以通过 !t 查看。
0:028:x86 !t
ThreadCount: 207
UnstartedThread: 0
BackgroundThread: 206
PendingThread: 0
DeadThread: 0
Hosted Runtime: noLock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 1d90 003e2430 2026020 Preemptive 00000000:00000000 003db8b8 0 STA 2 2 2804 003f0188 2b220 Preemptive 00000000:00000000 003db8b8 0 MTA (Finalizer) 28 7 27f0 0b29cd30 3029220 Preemptive 00000000:00000000 003db8b8 0 MTA (Threadpool Worker) 从卦象上看ID7 是一个线程池线程而且是 MTA 模式按理说它应该将创建控件的逻辑调度给UI线程而不是自己创建所以UI线程一直在 WaitOneNative 处等待 7号线程消息泵响应所以导致了无限期等待。4. 7号线程到底创建了什么控件这又是一个考验底层知识的问题也困扰着我至今太难了我曾今尝试着把 UserPreferenceChangedEventHandler 事件上的所有 handles 捞出来写了一个脚本大概如下
use strict;// 32bit
let arr [xxxx];function initializeScript() { return [new host.apiVersionSupport(1, 7)]; }
function log(str) { host.diagnostics.debugLog(str \n); }
function exec(str) { return host.namespace.Debugger.Utility.Control.ExecuteCommand(str); }function invokeScript() {for (var address of arr) {var commandText .printf \%04x\, poi(poi(poi(poi( address 0x4)0xc)0x4));var output exec(commandText).First();if (parseInt(output) 0) continue; //not exists thread infocommandText .printf \%04x\, poi(poi(poi(poi(poi( address 0x4)0xc)0x4))0x28);output exec(commandText).First();//thread idvar tid parseInt(output);if (tid 1) log(Thread tid ,systemEventInvokeInfo address);}
}输出结果
||2:2:438 !wow64exts.sw
Switched to Guest (WoW) mode
Thread7,systemEventInvokeInfo1107487c从输出中找到了 7号线程 对应的处理事件 systemEventInvokeInfo 然后对其追查如下
0:028:x86 !do 1107487c
Name: Microsoft.Win32.SystemEventsSystemEventInvokeInfo
Fields:MT Field Offset Type VT Attr Value Name
6c65ae34 4002e9f 4 ...ronizationContext 0 instance 11098b74 _syncContext
6c6635ac 4002ea0 8 System.Delegate 0 instance 1107485c _delegate0:028:x86 !DumpObj /d 1107485c
Name: Microsoft.Win32.UserPreferenceChangedEventHandler
Fields:MT Field Offset Type VT Attr Value Name
6c66211c 40002b0 4 System.Object 0 instance 110747bc _target
6c66211c 40002b1 8 System.Object 0 instance 00000000 _methodBase
6c66938c 40002b2 c System.IntPtr 1 instance 6ebdc00 _methodPtr
6c66938c 40002b3 10 System.IntPtr 1 instance 0 _methodPtrAux
6c66211c 40002bd 14 System.Object 0 instance 00000000 _invocationList
6c66938c 40002be 18 System.IntPtr 1 instance 0 _invocationCount0:028:x86 !DumpObj /d 110747bc
Name: DevExpress.LookAndFeel.Design.UserLookAndFeelDefault从输出中可以看到最后的控件是 DevExpress.LookAndFeel.Design.UserLookAndFeelDefault 我以为找到了答案拿着这个结果去 google结果 devExpress 踢皮球截图如下咳到这里貌似就查不下去了有其他资料上说 Control 在跨线程注册 handler 时会经过 MarshalingControl 所以在这个控件设置bp断点是能够抓到的参考命令如下
bp xxx .echo MarshalingControl creation detected. Callstack follows.;!clrstack;.echo这里我就没法验证了。三总结 虽然知道这三起事故都是由于非UI线程创建Control所致但很遗憾的是我尽了最大的知识边界还没有找到最重要的罪魁祸首不过值得开心的是基于现有线索有一位朋友终于找到了问题代码真替他开心????????????解决办法也很简单将 创建控件 通过 Invoke 调度到 UI线程 执行。截图如下通过这个案例我发现高级调试真的是一场苦行之旅且调且珍惜END工作中的你是否已遇到 ... 1. CPU爆高2. 内存暴涨3. 资源泄漏4. 崩溃死锁5. 程序呆滞等紧急事件全公司都指望着你能解决... 危难时刻才能展现你的技术价值作为专注于.NET高级调试的技术博主欢迎微信搜索: 一线码农聊技术免费协助你分析Dump文件希望我能将你的踩坑经验分享给更多的人。