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. nodejs图片转换字节保存
  2. Java 洛谷 P1028 数的计算
  3. Spark Streaming
  4. sql 倒数第二个_小白初探SQL(一)
  5. 大数据技术之kafka (第 3 章 Kafka 架构深入 ) 消费者组案例
  6. 构建自己的服务器有什么好处呢?
  7. Asp.net MVC 3实例学习之ExtShop(六)——登录对话框
  8. 发展数字经济面临哪些困难_解决数字音乐制作面临的最大问题之一
  9. java jdbc jar包_大数据从入门到深入:JavaEE 之 数据库技术 JDBC(1)
  10. app.honeycomb.Shell$HomeActivity failed to start
  11. Java中的final、static、this、super 关键字
  12. Linux开机自动挂载Windows分区的两种方法
  13. 让微信保持高度活跃的利器
  14. 山东省智慧城市产业技术创新战略联盟成立
  15. @RequestParam@PathVariable【注解区别】
  16. 自监督论文阅读笔记 Urban feature analysis from aerial remote sensing imagery using self-supervised and semi-s
  17. 七月集训(22,23)字典树,有序集合
  18. JavaScript 的 Math.floor() 函数
  19. 15岁极客王逸翛:希望大家都有“开源精神”
  20. 网易裁员,让保安把身患绝症的员工赶出公司!前网易员工亲述经历的噩梦!...

热门文章

  1. 正则表达式 密码需至少包含数字、字母、符号中的2种
  2. python 通讯录
  3. [引用]心揣“法海”,身嫁“许仙”
  4. springdatajpa配置多数据源
  5. Mac下的vim简单配置
  6. verdi方法fsdbreport将fsdb的某个信号值抽出为可读文件 及波形转换,合并,修改工具,Verdi培训整理笔记
  7. 利用 Python 和 Selenium 自动下载知网期刊文件
  8. adb 安卓模拟器 进程端口_Android开发中遇到的问题(一)——Android模拟器端口被占用问题的解决办法...
  9. 【总结】——工作之前,给自己
  10. 软件编程_Python_cxfreeze