1. 讲故事

这世间事说来也奇怪,近两个月有三位朋友找到我,让我帮忙分析下他的程序hangon现象,这三个dump分别涉及: 医疗,新能源,POS系统。截图如下:

那这篇为什么要拿其中的 新能源 说事呢? 因为这位朋友解决的最顺利,在提供的一些线索后比较顺利的找出了问题代码。

说点题外话,我本人对 winform 是不熟的,又奈何它三番五次的出现在我的视野里,所以我决定写一篇文章好好的总结下,介于没有太多的参考资料,能力有限,只能自己试着解读。

二: Windbg 分析

1. 程序现象

开始之前先吐槽一下,这几位大佬抓的dump文件都是 wow64,也就是用64bit任务管理器抓了32bit的程序,见如下输出:

1

2

wow64cpu!CpupSyscallStub+0x9:

00000000`756d2e09 c3              ret

所以就不好用 windbg preview 来分析了,首先要用 !wow64exts.sw 将 64bit 转为 32bit ,本篇用的是 windbg10,好了,既然是UI卡死,首当其冲就是要看一下UI线程到底被什么东西卡住了,可以用命令 !clrstack 看一下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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的情况,参考如下:

1

2

3

4

5

6

7

8

9

10

11

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 线程哦,有了这些基础知识后,再来分析下为什么会被卡死。

3. 卡死的真正原因

再重新看下主线程的调用栈,它的走势是这样的: OnUserPreferenceChanged -> WindowsFormsSynchronizationContext.Send -> Control.MarshaledInvoke -> WaitHandle.WaitOneNative,哈哈,有看出什么问题吗???

眼尖的朋友会发现,为什么主线程会调用 WindowsFormsSynchronizationContext.Send 方法呢? 难道那个注册 handler的 Control 不是由主线程创建的吗?要想回答这个问题,需要看一下 WindowsFormsSynchronizationContext 类的 destinationThreadRef 字段值,源码如下:

1

2

3

4

5

public sealed class WindowsFormsSynchronizationContext : SynchronizationContext, IDisposable

{

    private Control controlToSendTo;

    private WeakReference destinationThreadRef;

}

可以用 !dso 命令把线程栈上的 WindowsFormsSynchronizationContext 给找出来,简化输出如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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 查看。

1

2

3

4

5

6

7

8

9

10

11

12

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 捞出来,写了一个脚本大概如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

"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 ,然后对其追查如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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断点是能够抓到的,参考命令如下:

1

bp xxx ".echo MarshalingControl creation detected. Callstack follows.;!clrstack;.echo

这里我就没法验证了。

三:总结

虽然知道这三起事故都是由于非UI线程创建Control所致,但很遗憾的是我尽了最大的知识边界还没有找到最重要的罪魁祸首,不过值得开心的是基于现有线索有一位朋友终于找到了问题代码,真替他开心

.NET新能源汽车锂电池检测程序UI挂死问题分析相关推荐

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

    一:背景 1. 讲故事 这世间事说来也奇怪,近两个月有三位朋友找到我,让我帮忙分析下他的程序hangon现象,这三个dump分别涉及:医疗,新能源,POS系统.截图如下: 那这篇为什么要拿其中的 新能 ...

  2. 记一次 .NET 某上市工业智造 CPU+内存+挂死 三高分析

    一:背景 1. 讲故事 上个月有位朋友加wx告知他的程序有挂死现象,询问如何进一步分析,截图如下: 看这位朋友还是有一定的分析基础,可能玩的少,缺乏一定的分析经验,当我简单分析之后,我发现这个dump ...

  3. 探秘新能源汽车锂电池正极材料龙头“高镍三元”

    摘要:随着人们生活水平的提高,城镇化趋势越来越明显,人们对出行工具的要求也越来越高,近些年环保型新能源汽车已经成为人们出行首选,还可以保护环境节约资源.电池作为汽车的核心部件,成为广大学者研究关注的焦 ...

  4. 总线控制内部eep_CAN总线在新能源汽车中的通信网络设计及应用分析

    从事汽车相关行业的小伙伴们,都知道CAN总线,它是当今汽车各电控单元之间通信的总线标准,现在几乎所有的汽车厂家都选择使用CAN总线通信.CAN总线起初便是基于BOSCH公司为了解决汽车的电子控制单元增 ...

  5. 2021年中国机动车、汽车和新能源汽车保有量及驾驶人和驾驶证业务办理情况分析「图」

    一.机动车和汽车 1.机动车新注册登记数量及保有量 根据公安部数据显示,我国新注册登记机动车数量整体表现为波动上涨趋势,2021年为3674万辆,比2020年增加346万辆,增长10.38%.就机动车 ...

  6. VxWorks任务挂死实战分析

    目录 背景描述 根本原因 分析过程 背景描述 操作系统:VxWorks 5.5 CPU:MIPS32 74Kc内核CPU 现象描述:联调代码时发现应用层代码调用以下接口函数必现任务挂死,检查代码发现入 ...

  7. I2C 挂死原因分析及解决方案

    I2C几乎是嵌入系统中最为通用串行总线,MCU周边的各种器件只要对速度要求不高都可以使用.优点是兼容性好(几乎所有MCU都有I2C主机控制器,没有也可以用IO模拟),管脚占用少,芯片实现简单.I2C协 ...

  8. Linux服务器挂死案例分析

    问题现象: 在linux服务器上运行一个指定的脚本时,就会出现无数个相同进程的,而且不停的产生,杀也杀不掉,最后系统就陷入死循环,无法登陆,只能人工去按机器的电源键才可以.这够崩溃的吧? 问题分析过程 ...

  9. 【新能源】这家新能源汽车动力电池工厂车间视频,最近刷爆了朋友圈!!

    很多人都知道,前段时间,欧洲一些国家,相继发布了禁售燃油车的时间和计划,全世界似乎都要跨入新能源汽车的时代,发动机是燃油车的心脏,而新能源汽车的心脏,则是动力电池:新能源汽车的发展正在将中国汽车产业带 ...

最新文章

  1. 后氧传感器正常数据_氧传感器正常数据流
  2. I - Watering Flowers CodeForces - 617C
  3. The application does not contain a valid bundle identifier.解决方法
  4. 微信开发系列之九 - 在微信里直接创建SAP C4C的社交媒体消息
  5. 守护线程 java 1615478655
  6. 关于Windowsn 7因验证未通过被视为“非正版”出现“黑屏”的应急处理预案
  7. 数据结构 Tricks(一)—— 父节点和左右孩子索引号之间的关系
  8. ubuntu 安装 mujoco-py
  9. 用js实现贪吃蛇网页游戏
  10. 快乐大本营html5小游戏,快乐大本营小游戏规则-新年小游戏.doc
  11. 感悟篇:我在B端做数据分析(一)
  12. 004---css样式表(内部样式表、行内样式表、外部样式表)
  13. TCP三次握手中SYN,ACK,seq ack的含义
  14. 实战经验:大数据分析为什么大多数会失败?
  15. 华中科技大学计算机组成,华中科技大学计算机组成原理.pptx
  16. JAVA网络爬爬学习之HttpClient+Jsoup
  17. lintcode落单的数
  18. Unix系统下修改密码
  19. window10专业版下:docker中k8s安装
  20. linux下用shell脚本删除mysql记录数量比较多的表。

热门文章

  1. 第三方接口对接注意事项
  2. WebRTC、Janus卡顿优化实践(SPS、PPS丢包)
  3. React refs
  4. Ubuntu为应用程序Anaconda、PyCharm等程序创建桌面快捷方
  5. 青少年成长管理 第10章 成长计划
  6. IDEA快速导入包、默认设置以及自定义配置maven
  7. 使用Microsoft Speech Object Library自动朗读
  8. sublime使用浏览器快捷键配置问题和谷歌浏览器打开html文件问题(小菜鸟汇总)
  9. 谈绿色溢价的时代发展与建筑节能指导
  10. 【NOIP2013模拟】Freda的传呼机 题解+代码