当前位置:首页 > CN2资讯 > 正文内容

记一次 .NET 某自动化智能制造软件 卡死分析

1天前CN2资讯


一:背景

1. 讲故事

前些天有位朋友找到我,说他们工厂里面的程序不知道怎么就突然卡死了,让我帮忙看下怎么回事?dump也拿到了,对于这类程序,其实我还是非常有信心的,接下来就来分析吧。

二:卡死分析

1. 为什么会卡死

因为是窗体程序,所以我们直接看主线程,使用 ~0s;!clrstack 观察托管栈即可,输出如下:

0:000> ~0s;!clrstack win32u!NtUserShowWindow+0x14: 00007ff8`1ac31b24 c3 ret OS Thread Id: 0x2ac8 (0) Child SP IP Call Site 000000b4ddbfde08 00007ff81ac31b24 System.Windows.Forms.SafeNativeMethods.ShowWindow(System.Runtime.InteropServices.HandleRef, Int32) 000000b4ddbfde08 00007fffc2f0aaf5 System.Windows.Forms.SafeNativeMethods.ShowWindow(System.Runtime.InteropServices.HandleRef, Int32) 000000b4ddbfdde0 00007fffc2f0aaf5 DomainBoundILStubClass.IL_STUB_PInvoke(System.Runtime.InteropServices.HandleRef, Int32) 000000b4ddbfde90 00007fffc2e7f395 System.Windows.Forms.Control.SetVisibleCore(Boolean) 000000b4ddbfdf60 00007fffc2e8e4d2 System.Windows.Forms.Form.SetVisibleCore(Boolean) 000000b4ddbfdfd0 00007fffc2e9801a System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) 000000b4ddbfe070 00007fffc2e97dd2 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) 000000b4ddbfe0d0 00007fffc3659438 System.Windows.Forms.Form.ShowDialog(System.Windows.Forms.IWin32Window) 000000b4ddbfe1d0 00007fff67dad59f xxx.ShowConfirmDialog(System.String, xxx.InfoType)

从卦象看,应用程序通过 ShowConfirmDialog 方法来显示弹出窗,从托管栈来看没啥毛病,大家都知道托管部分最后要调用非托管代码,所以改成 k 进一步探究底层,输出如下:

0:000> k # Child-SP RetAddr Call Site 00 000000b4`ddbfddd8 00007fff`c2f0aaf5 win32u!NtUserShowWindow+0x14 01 000000b4`ddbfdde0 00007fff`c2e7f395 System_Windows_Forms_ni+0x33aaf5 02 000000b4`ddbfde90 00007fff`c2e8e4d2 System_Windows_Forms_ni!System.Windows.Forms.Control.SetVisibleCore+0x115 03 000000b4`ddbfdf60 00007fff`c2e9801a System_Windows_Forms_ni!System.Windows.Forms.Form.SetVisibleCore+0xb2 04 000000b4`ddbfdfd0 00007fff`c2e97dd2 System_Windows_Forms_ni!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner+0x20a 05 000000b4`ddbfe070 00007fff`c3659438 System_Windows_Forms_ni!System.Windows.Forms.Application.ThreadContext.RunMessageLoop+0x52 06 000000b4`ddbfe0d0 00007fff`67dad59f System_Windows_Forms_ni!System.Windows.Forms.Form.ShowDialog+0x428 07 000000b4`ddbfe1d0 00007fff`67da1f51 xxx!xxx.FuncLib.ShowConfirmDialog+0x4bf

从卦中可以看到当前调用栈止步于用户态 win32u!NtUserShowWindow 函数,看样子当前执行流已经通过syscall进入内核态了,可以通过 Disassembly 窗口来验证,截图如下:

2. NtUserShowWindow 到底怎么了?

至于为什么调用栈在 NtUserShowWindow 中不返回,说实话在用户态层面真的看不出来,只能获取到当前执行流的内核态栈才知道到底发生了什么事情,那如何获取执行流的内核态栈呢?有两种办法:

  • 抓此时操作系统的内核态dump。
  • 借助 procdump -mk 获取此时的线程内核态栈。

由于第一种办法比较笨重,这里就告诉朋友使用第二种方法抓取,关于procdump更多的细节,可参见:https://learn.microsoft.com/en-us/sysinternals/downloads/procdump

很快朋友就拿到了线程kernel级的调用栈文件,截图如下:

双击打开之后切到0号线程观察调用栈,输出如下:

0: kd> ~0s 0: kd> k # Child-SP RetAddr Call Site 00 ffff9005`b03be3b0 fffff805`06e8adbb nt!DbgkpLkmdSnapThreadInContext+0x95 01 ffff9005`b03be8f0 fffff805`068c83bb nt!DbgkpLkmdSnapThreadApc+0x3b 02 ffff9005`b03be920 fffff805`06841657 nt!KiDeliverApc+0x27b 03 ffff9005`b03be9e0 fffff805`0684085f nt!KiSwapThread+0x827 04 ffff9005`b03bea90 fffff805`06840103 nt!KiCommitThreadWait+0x14f 05 ffff9005`b03beb30 fffff805`068d21bd nt!KeWaitForSingleObject+0x233 06 ffff9005`b03bec20 fffff805`068c84d1 nt!KiSchedulerApc+0x3bd 07 ffff9005`b03bed50 fffff805`06841657 nt!KiDeliverApc+0x391 08 ffff9005`b03bee10 fffff805`0684085f nt!KiSwapThread+0x827 09 ffff9005`b03beec0 fffff805`06840103 nt!KiCommitThreadWait+0x14f 0a ffff9005`b03bef60 fffff805`068cadbb nt!KeWaitForSingleObject+0x233 0b ffff9005`b03bf050 ffff8de3`c59156f2 nt!KeWaitForMultipleObjects+0x45b 0c ffff9005`b03bf160 ffff8de3`c5917ea9 win32kfull!xxxRealSleepThread+0x362 0d ffff9005`b03bf280 ffff8de3`c5916b5a win32kfull!xxxInterSendMsgEx+0xdd9 0e ffff9005`b03bf3f0 ffff8de3`c5968e33 win32kfull!xxxSendTransformableMessageTimeout+0x3ea 0f ffff9005`b03bf540 ffff8de3`c596a830 win32kfull!xxxCalcValidRects+0x3b7 10 ffff9005`b03bf6e0 ffff8de3`c596e5a3 win32kfull!xxxEndDeferWindowPosEx+0x1ac 11 ffff9005`b03bf7c0 ffff8de3`c596e3dd win32kfull!xxxSetWindowPosAndBand+0xc3 12 ffff9005`b03bf850 ffff8de3`c5936c36 win32kfull!xxxSetWindowPos+0x79 13 ffff9005`b03bf8d0 ffff8de3`c59369ca win32kfull!xxxShowWindowEx+0x22a 14 ffff9005`b03bf980 ffff8de3`c5d6866a win32kfull!NtUserShowWindow+0xda 15 ffff9005`b03bf9d0 fffff805`06a11235 win32k!NtUserShowWindow+0x16 16 ffff9005`b03bfa00 00007ff8`1ac31b24 nt!KiSystemServiceCopyEnd+0x25 17 000000b4`ddbfddd8 00000000`00000000 0x00007ff8`1ac31b24

从内核态调用栈来看,有一个函数 InterSendMsgEx 特别刺眼,对,它就是翻版的 SendMessage,也就是说当前执行流在内核态中给其他窗口发消息时,对方窗口不响应导致当前线程直接卡死在内核态。。。

那到底是给哪一个窗口发消息呢?看下用户态 NtUserShowWindow 方法的 rcx 参数即可,输出如下:

0:000> r rax=0000000000001057 rbx=0000028fe4171560 rcx=0000000000290476 rdx=0000000000000005 rsi=000000b4ddbfdee8 rdi=0000000000000005 rip=00007ff81ac31b24 rsp=000000b4ddbfddd8 rbp=000000b4ddbfde80 r8=0000028fe5f26b44 r9=00007fffc4c75dd8 r10=0000000000000000 r11=0000028fe4171560 r12=0000028fe4171560 r13=0000000000000202 r14=0000028fe7879eb0 r15=0000000000000010 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 win32u!NtUserShowWindow+0x14: 00007ff8`1ac31b24 c3 ret

从卦中可以看到是因为主线程给窗口 wnd=290476 打消息导致的程序系统性卡死,这个窗体可能是本程序创建的,也有可能不是本程序创建的,接下来该如何找到 wnd=290476 窗口句柄呢? 这是一个老生常谈的问题了哈,这里我就不赘述了,参见我的几篇文章即可。

  • Spy++ : ()[记一次 .NET某医疗器械清洗系统 卡死分析]
  • harmony:()[.NET外挂系列:7. harmony在高级调试中的一些实战案例]
  • MinHook : ()[MinHook 对.NET底层的 SendMessage 拦截真实案例反思

三:总结

这次事故最值得学习的一个点,那就是当用户态层面找不出卡死的祸根时,巧妙的使用 procdump -mk 获取线程的内核态栈,最终找到问题祸根,轻量又实用。


    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/31653.html

    分享给朋友:

    “记一次 .NET 某自动化智能制造软件 卡死分析” 的相关文章

    香港虚拟主机CN2怎么用的:全面解析与实用指南

    香港虚拟主机CN2怎么用的香港虚拟主机CN2的选型指南与核心优势在全球化的今天,选择一个合适的虚拟主机服务对于网站的运营至关重要。而香港虚拟主机CN2凭借其独特的地理位置和高速稳定的网络连接,成为了众多站长和企业的首选。但面对市场上琳琅满目的选项,如何选择一款适合自己的香港虚拟主机CN2呢?本文将带...

    eno VPS:掌握网络接口命名规则与性能优化技巧

    在了解eno VPS之前,我们先来看看什么是eno命名规则。ena作为一种网络接口命名方式,通过特定的规则来表示Linux系统中的网络设备。这种规则帮助用户更容易地识别和管理各种网络接口。具体来说,eno采用的是eno[n|d]的格式,主要用于板载设备。而对于热插拔设备,则使用ens[f][n|d]...

    全面了解扩容:定义、分类及最佳实践

    扩容的定义与重要性 扩容这个词听起来似乎很简单,但它其实蕴含了很多技术细节和实际应用。简单来说,扩容就是对已有系统或设备的能力进行增强,尤其是在存储或处理能力上。想象一下,当你的业务正在快速增长,客户数量激增,原本的系统可能会面临压力,这时扩容就显得尤为重要。通过扩容,我可以在需要的时候增加更多的存...

    HostDare VPS主机服务评测:高性价比与用户体验的完美结合

    在与HostDare的互动中,我感受到这家公司在客户支持与用户体验方面的一些亮点和不足。正如我们所知,HostDare是一家致力于提供高性价比VPS主机服务的公司,尤其在针对中国大陆用户的优化上表现突出。虽然服务上有一些待改善之处,但总体而言,它的客户支持体系还是相对高效的。 首先,HostDare...

    如何选择国外便宜域名注册网站,降低创业成本

    在当今互联网的时代,拥有一个适合自己的域名几乎是每个企业和个人的基本需求。便宜域名,是指那些注册费用相对较低的域名。它们并不等于价格低廉的劣质选择。相反,这些域名能够为无数初创企业与个人项目提供一个良好的起点。便宜域名的定义不仅仅是价格上的划分,更是对资源的有效管理和合理利用。 选择便宜域名的优势体...

    完全解析免费云服务器:优缺点与选购指南

    在数字化时代,云计算技术的发展改变了我们处理数据和应用的方式。想象一下,如果有一种资源可以按需使用,而且不需要为此支付高额费用,那将是多么方便!这就是免费云服务器的魅力所在。简单来说,免费云服务器就是那些允许用户在一定范围内免费使用的云计算资源。 免费云服务器的特点一般包括易于访问和灵活性。用户可以...