记一次 .NET 某新能源汽车锂电池检测程序 UI挂死分析

一:背景

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:   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.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 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

果然不出所料, 从卦象上看 Thread=7 线程上有 Control 注册了系统事件,那 Thread=7 到底是什么线程呢?可以通过 !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) 

从卦象上看: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 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
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 _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文件,希望我能将你的踩坑经验分享给更多的人。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

这个女生躲在衣柜等男友回家,结果竟是......

1 不要什么都怪爸爸我妈明明也是这样帮我脱的▼2 这就是现实版大女主反杀女二的故事啊▼3 我猜到了开头却没有猜到结尾▼4 ???一定是我被饿出了幻觉▼5 小小的孩子在极短的时间内经历了人生的大戏和大悲▼6 这个令人羡慕的发量啊&#…

mysql savepoint作用_savepoint原理

保存点在MySQL中, 保存点SAVEPOINT属于事务控制处理部分。利用SAVEPOINT可以回滚指定部分事务,从而使事务处理更加灵活和精细。SAVEPOINT相关的SQL语句如下SAVEPOINT identifier设置SAVEPOINT。如果重复设置同名savepoint,新的会覆盖老的.RELEASE SAVEPOINT identif…

路由器笔记 CCNA

配置路由器应用性<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1配置端口1&#xff09; 配置局域网端口&#xff1a;进入&#xff08;config-if&#xff09;#模式 配置ip地址和掩码 &#xff08;config-if&#xff09;#ip a…

图片裁剪和异步上传插件--一步到位(记录)

图片上传裁剪这功能随处可见&#xff0c;有的自己写&#xff0c;不过太耗费时间了&#xff0c;插件的话感觉好多&#xff0c;前段时间就想挑一款好的插件&#xff0c;以后就用那款&#xff0c;可是挑了几款插件用上去&#xff0c;效果很好&#xff0c;问题就出在合并了&#xf…

git checkout 单个文件_git 如何回退单个文件

1.进入到文件所在文件目录&#xff0c;或者能找到文件的路径查看文件的修改记录git log fileName结果&#xff1a;如果文件修改记录太多&#xff0c;则使用git log -number fileName结果&#xff1a;2.回退到指定版本git reset ** fileName1.git reset -mixed&#xff1a;此为默…

未来十年最吃香专业大盘点,有你的吗?

全世界只有3.14 % 的人关注了爆炸吧知识这个世界上理工科到底有多吃香&#xff1f;如果你是理工男/女&#xff0c;先恭喜一下你&#xff0c;不知道喜从何来&#xff1f;不妨先来看这几组数据&#xff1a;高薪职业平均薪资一览表薪资最高的专业前20名这并不只是中国的情况&#…

mysql挂载到iscsi_corosync+pacemaker+iscsi磁盘实现mysql高可用

实验用主机&#xff1a;提供iscsi磁盘:172.16.103.1,提供iscsi磁盘高可用主机:172.16.103.2&#xff0c;172.16.103.3实验拓扑&#xff1a;实验步骤&#xff1a;一、配置172.16.103.1&#xff0c;输出iscsi磁盘&#xff0c;创建的磁盘分区为/dev/sda3&#xff0c;在实际的使用环…

一种在未来互联网中的面向用户的云操作系统体系

本文描述了一个欲将整个网络当做计算机使用&#xff0c;可以极大增强云计算应用能力和提高用户体验的一个类操作系统体系结构。望有识之士能做下去。 价值1&#xff09;用户的痛苦在哪里&#xff1f;对于终端用户&#xff0c;他们的痛苦在于使用Web应用不方便&#xff0c;可以使…

提高「搜商」,挣大钱

大家好&#xff0c;我是Z哥。在之前的一篇讲述数据分析的文章《这个时代最重要的技能之一》中提到了这周要和大家聊聊「搜商」的事情。搜商这个词诞生于互联网时代&#xff0c;体现的是一个人利用搜索引擎查找自己所需信息的能力。我觉得在当下这个时代&#xff0c;搜商的重要性…

idea 配置jdk版本_JDK 11 安装过程(同时已安装了JDK 8)以及Intellij IDEA 配置

电脑上已经安装过 JDK 8 版本(C:Javajdk1.8.0_111)。安装好 JDK 11 版本(C:Program FilesJavajdk-11.0.2)之后&#xff0c;目录如下&#xff0c;分别有&#xff1a;安装过程&#xff1a;1. 开始安装 JDK 11&#xff1b;2. 完成安装JDK 11&#xff1b;配置过程&#xff1a;1. 将…

老师,你和我的文具撞衫了!

全世界只有3.14 % 的人关注了爆炸吧知识来看越南学生拍摄的一组图&#xff0c;主题是老师和文具“撞衫”&#xff01;老师你和我的胶水撞了&#xff01;老师你和我的笔袋撞了&#xff01;老师你和我的手机壳撞了&#xff01;这个铅笔和老师好像啊书皮和老师很像老师今天穿得像面…

中小企业SaaS型软件BI的发展前景

2019独角兽企业重金招聘Python工程师标准>>> 传统企业在实施信息化的过程中&#xff0c;往往遵循着先用ERP把物料管起来&#xff0c;然后再CRM把客户关系管理起来&#xff0c;之后是财务系统&#xff0c;最后才会在数据的压力之下&#xff0c;实 施BI。那么&#xf…

穿透Session 0 隔离(一)

服务&#xff08;Service&#xff09;对于大家来说一定不会陌生&#xff0c;它是Windows 操作系统重要的组成部分。我们可以把服务想像成一种特殊的应用程序&#xff0c;它随系统的“开启&#xff5e;关闭”而“开始&#xff5e;停止”其工作内容&#xff0c;在这期间无需任何用…

navicat循环执行上下两行相减sql语句_SQL语句的优化分析

一、开门见山&#xff0c;问题所在sql语句性能达不到你的要求&#xff0c;执行效率让你忍无可忍&#xff0c;一般会时下面几种情况。网速不给力&#xff0c;不稳定。服务器内存不够&#xff0c;或者SQL 被分配的内存不够。sql语句设计不合理没有相应的索引&#xff0c;索引不合…

想不到,那些让我半夜偷偷收藏的沙雕表情包,竟是出自AI之手

全世界只有3.14 % 的人关注了爆炸吧知识转自&#xff1a;机器之心参与&#xff1a;蛋酱要是收藏夹里没几个独家沙雕表情包&#xff0c;当代网民都无法在朋友圈立足。但有一些「妙不可言」的图片&#xff0c;也许是 AI 生成的结果。一般人很难读出「meme」这个词&#xff0c;它通…

JavaScript中的arguments,callee,caller

2019独角兽企业重金招聘Python工程师标准>>> arguments: arguments 该对象代表正在执行的函数和调用它的函数的参数。 [function.]arguments[n]参数 function&#xff1a;选项。当前正在执行的 Function 对象的名字。 n &#xff1a;选项。要传递给 Function 对象的…

python总结函数图像_PIL使用小结(crop和paste函数)

PIL(Python Imaging Library)是python语言中对图像处理方面的一个开源库&#xff0c;其主要功能模块为Image&#xff0c;对于Image模块&#xff0c;可以使用from PIL import Image或者import Image由于使用了试用版的chartdir库&#xff0c;在生成图片的时候下面会出现一行提示…

js中apply和join

join 分隔符分隔指定数组中的数据,不仅可以分割&#xff0c;还可以考虑用join("")合并&#xff0c;可以将一个array对象数据进行合并 1 <script>2 vark["2","3",4,5];3 alert(k.join(""));4 vars["s",":",,…

12333新农合网上查询_新农合医保查询缴费平台|新农村医疗保险网上缴费平台

这个智慧新农合新农合患者就诊从挂号到缴费&#xff0c;不用再拿着各种票据在报销处和收费处之间奔走&#xff0c;也不用担心票据不完整造成无法正常报销的情况&#xff0c;只需携带本人有效身份证和农合本&#xff0c;凭身份证号码或新农合号码即可实时报销所有费用&#xff0…

晚上我们一起去白码会所玩啊!

1 白学公主和白码王子真般配晚上一起去白码会所玩啊▼2 看完这些图我觉得要重新考虑要不要让男人陪产了▼3 乒乓球拍好难吃啊▼4 论朋友圈美食照片是怎么来的▼5 论推广普通话的重要性▼6 关键是&#xff0c;你吃完一身味忘不掉而且大多数人吃一次还想吃▼7 想知道一天1…