.net新能源汽车锂电池检测程序ui挂死问题分析-kb88凯时官网登录

来自:网络
时间:2024-06-10
阅读:
免费资源网 - https://freexyz.cn/

一:背景

1. 讲故事

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

.net新能源汽车锂电池检测程序ui挂死问题分析

.net新能源汽车锂电池检测程序ui挂死问题分析

.net新能源汽车锂电池检测程序ui挂死问题分析

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

说点题外话,我本人对 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将会被执行,比如刷新自身。

觉得文字比较拗口的话,我试着画一张图来阐明一下。

.net新能源汽车锂电池检测程序ui挂死问题分析

从本质上来说,它就是一个观察者模式,但这和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 线程哦,有了这些基础知识后,再来分析下为什么会被卡死。

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 \"x\", poi(poi(poi(poi("   address   " 0x4) 0xc) 0x4))";
        var output = exec(commandtext).first();
        if (parseint(output) == 0) continue; //not exists thread info
        commandtext = ".printf \"x\", 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 踢皮球,截图如下:

.net新能源汽车锂电池检测程序ui挂死问题分析

咳,到这里貌似就查不下去了,有其他资料上说 control 在跨线程注册 handler 时会经过 marshalingcontrol ,所以在这个控件设置bp断点是能够抓到的,参考命令如下:

bp xxx ".echo marshalingcontrol creation detected. callstack follows.;!clrstack;.echo

这里我就没法验证了。

三:总结

虽然知道这三起事故都是由于非ui线程创建control所致,但很遗憾的是我尽了最大的知识边界还没有找到最重要的罪魁祸首,不过值得开心的是基于现有线索有一位朋友终于找到了问题代码,真替他开心???,解决办法也很简单,将 创建控件 通过 invoke 调度到 ui线程 执行。截图如下:

.net新能源汽车锂电池检测程序ui挂死问题分析

通过这个案例,我发现高级调试真的是一场苦行之旅,且调且珍惜!

以上就是.net新能源汽车锂电池检测程序ui挂死问题分析 的详细内容,更多关于.net锂电池ui挂死的资料请关注其它相关文章!

免费资源网 - https://freexyz.cn/
返回顶部
顶部
网站地图