,(www.22223388.com)实时更新发布最新最快最有效的新2网址和新2最新网址,包括新2手机网址,新2备用网址,皇冠最新网址,新2足球网址,新2网址大全。
更多高质量干货:参见我的 GitHub: dotnetfly
一:靠山
1. 讲故事
这世间事说来也新鲜,近两个月有三位同伙找到我,让我协助剖析下他的程序hangon征象,这三个dump划分涉及: 医疗,新能源,POS系统。截图如下:
那这篇为什么要拿其中的 新能源
说事呢? 由于这位同伙解决的最顺遂,在提供的一些线索后对照顺遂的找出了问题代码。
说点题外话,我本人对 winform 是不熟的,又怎样它三番五次的泛起在我的视野里,以是我决议写一篇文章好好的总结下,介于没有太多的参考资料,能力有限,只能自己试着解读。
二: Windbg 剖析
1. 程序征象
最先之前先吐槽一下,这几位大佬抓的dump文件都是 wow64
,也就是用64bit义务治理器抓了32bit的程序,见如下输出:
wow64cpu!CpupSyscallStub+0x9:
00000000`756d2e09 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.SystemEvents+SystemEventInvokeInfo.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.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
0019f31c 6db5dc49 [InlinedCallFrame: 0019f31c]
0019f3a4 6db5dc49 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0019f3f4 6db5dac0 System.Windows.Forms.Application+ThreadContext.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: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 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 线程哦,有了这些基础知识后,再来剖析下为什么会被卡死。
欢迎进入ALLBET官网娱乐平台开户(www.aLLbetgame.us),欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。
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.SystemEvents+SystemEventInvokeInfo
0019F08C 10fa386c System.Object[] (System.Object[])
0019F090 1107487c Microsoft.Win32.SystemEvents+SystemEventInvokeInfo
0019F0AC 027ebf60 System.Object
0019F0C0 10fa386c System.Object[] (System.Object[])
0019F0C8 027ebe3c System.Object
0019F0CC 10fa388c Microsoft.Win32.SystemEvents+SystemEventInvokeInfo[]
...
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 destinationThreadRef
0: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_handle
0: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
果真不出所料, 从卦象上看 Thread=7
线程上有 Control 注册了系统事宜,那 Thread=7 到底是什么线程呢? 可以通过 !t
查看。
0:028:x86> !t
ThreadCount: 207
UnstartedThread: 0
BackgroundThread: 206
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 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)
从卦象上看: ID=7 是一个线程池线程,而且是 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 info
commandText = ".printf \"%04x\", poi(poi(poi(poi(poi(" + address + "+0x4)+0xc)+0x4))+0x28)";
output = exec(commandText).First();
//thread id
var tid = parseInt(output);
if (tid > 1) log("Thread=" + tid + ",systemEventInvokeInfo=" + address);
}
}
输出效果:
||2:2:438> !wow64exts.sw
Switched to Guest (WoW) mode
Thread=7,systemEventInvokeInfo=1107487c
从输出中找到了 7号线程
对应的处置事宜 systemEventInvokeInfo ,然后对其追查如下:
0:028:x86> !do 1107487c
Name: Microsoft.Win32.SystemEvents+SystemEventInvokeInfo
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 _delegate
0: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 _invocationCount
0: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线程 执行。截图如下:
通过这个案例,我发现高级调试真的是一场苦行之旅,且调且珍惜!