记一次 .NET某机械臂上位系统 卡死分析

一:背景

1. 讲故事

前些天有位朋友找到我,说他们的程序会偶发性的卡死一段时间,然后又好了,让我帮忙看下怎么回事?窗体类的程序解决起来相对来说比较简单,让朋友用procdump自动抓一个卡死时的dump,拿到dump之后,上 windbg 说话。

二:WinDbg 分析

1. 主线程在做什么

要想看主线程在做什么,很显然用 k 命令观察非托管栈即可。


0:000> k
 # Child-SP          RetAddr               Call Site
00 000000ef`11d1cb70 00007ffc`e65ddc4a     ntdll!RtlSetLastWin32Error+0x38
01 000000ef`11d1cbc0 00007ffc`e660e1a4     clr!JIT_RareDisableHelperWorker+0xca
02 000000ef`11d1cd00 00007ffc`b5c4ea25     clr!JIT_RareDisableHelper+0x14
03 000000ef`11d1cd40 00007ffc`b5c41d35     System_Drawing_ni+0x6ea25
04 000000ef`11d1ce00 00007ffc`87948876     System_Drawing_ni!System.Drawing.StringFormat..ctor+0x15
....
10 000000ef`11d1d8b0 00007ffc`881fc86f     xxx!xxx.AutoResizeColumns+0x106
...

从卦中数据看,托管的栈顶上有一个 RtlSetLastWin32Error 函数,看样子 JIT_RareDisableHelperWorker 方法中某一个函数返回错误码了,那这个错误码是多少呢?要知道这个答案,先要知道它的签名是什么样的,参考链接: https://source.winehq.org/WineAPI/RtlSetLastWin32Error.html


void RtlSetLastWin32Error
(
    DWORD err
)

从签名可以看到,这个 err 是一个 int ,接下来观察 RtlSetLastWin32Error 方法的 rcx 寄存器,有没有存到 线程栈上,如果有的话直接提取即可。


0:000> uf ntdll!RtlSetLastWin32Error
ntdll!RtlSetLastWin32Error:
00007ffd`01a00780 894c2408        mov     dword ptr [rsp+8],ecx
00007ffd`01a00784 4883ec48        sub     rsp,48h
00007ffd`01a00788 488b05813d1300  mov     rax,qword ptr [ntdll!_security_cookie (00007ffd`01b34510)]
00007ffd`01a0078f 4833c4          xor     rax,rsp
....

0:000> k
 # Child-SP          RetAddr               Call Site
00 000000ef`11d1cb70 00007ffc`e65ddc4a     ntdll!RtlSetLastWin32Error+0x38
01 000000ef`11d1cbc0 00007ffc`e660e1a4     clr!JIT_RareDisableHelperWorker+0xca

0:000> dd 000000ef`11d1cbc0 L1
000000ef`11d1cbc0  00000006

从卦中看这个 err=6 ,那这个错误码是什么意思呢?继续查 MSDN: https://learn.microsoft.com/zh-cn/windows/win32/debug/system-error-codes–0-499-

从卦中可以清晰的看到,原来是 无效的句柄 导致的,那这个错误会导致程序的卡死吗?

2. 无效的句柄会卡程序吗

按照我的过往经验没有这么一说,其实 win32api 不像编程语言直接用 try catch 与 SEH 集成,返回错误码的这种方式编码起来虽然麻烦,但性能是最高的,所以这玩意导致程序卡死基本上是不可能的,那接下来的分析方向在哪里呢? 其实在这种场景下抓多dump就尤为重要了,毕竟多个dump之间可以相互参考来观察程序的走势,目前是没有这个条件的,那就从其他的路子上探究吧。

接下来我们试探性的观察所有的托管线程栈,看看他们此时都在做什么。


0:000> ~*e !clrstack
OS Thread Id: 0x555c (98)
        Child SP               IP Call Site
000000ef180fd0b8 00007ffd01a4dc04 [HelperMethodFrame_1OBJ: 000000ef180fd0b8] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
000000ef180fd1e0 00007ffce4e2ddfc System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
000000ef180fd210 00007ffce4e2ddcf System.Threading.WaitHandle.WaitOne(Int32, Boolean)
000000ef180fd250 00007ffcb5573d74 System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
000000ef180fd2c0 00007ffcb4dc0d54 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
000000ef180fd400 00007ffcb5577674 System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
000000ef180fd470 00007ffc882040d4 xxxx.backgroundWorker_ProgressChanged(System.Object, System.ComponentModel.ProgressChangedEventArgs)
000000ef180fe060 00007ffce4e1ae56 System.Threading.ThreadPoolWorkQueue.Dispatch() 

OS Thread Id: 0x4528 (101)
        Child SP               IP Call Site
000000ef183fe1d8 00007ffd01a4dc04 [HelperMethodFrame_1OBJ: 000000ef183fe1d8] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
000000ef183fe300 00007ffce4e2ddfc System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
000000ef183fe330 00007ffce4e2ddcf System.Threading.WaitHandle.WaitOne(Int32, Boolean)
000000ef183fe370 00007ffcb5573d74 System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
000000ef183fe3e0 00007ffcb4dc0d54 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
000000ef183fe520 00007ffcb5577674 System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
000000ef183fe590 00007ffc882040d4 xxxx.backgroundWorker_ProgressChanged(System.Object, System.ComponentModel.
000000ef183ff180 00007ffce4e1ae56 System.Threading.ThreadPoolWorkQueue.Dispatch() 
000000ef183ff608 00007ffce66112c3 [DebuggerU2MCatchHandlerFrame: 000000ef183ff608] 
...

观察上面的线程栈之后,发现有两个线程在 MarshaledInvoke 上等待,而且都是 backgroundWorker_ProgressChanged 方法,看样子有一个 backgroundWorker 控件在这里,其实这个信息还是值得警惕的,为什么这么说呢? 因为它往往会预示着这个 Control 的 Queue 队列可能有很多的数据积压,那就往这个方向走。

3. Queue 队列有积压吗

要找到这个答案,需要观察主线程的线程栈上是否有 Queue 队列。


0:000> !dso
OS Thread Id: 0x918 (0)
RSP/REG          Object           Name
r13              000002a70660a860 System.Drawing.StringFormat
...
000000EF11D1D9B0 000002a7008b5a18 System.ComponentModel.BackgroundWorker
...
000000EF11D1E328 000002a7004ea8b8 System.Collections.Queue
...
0:000> !do 000002a7004ea8b8
Name:        System.Collections.Queue
MethodTable: 00007ffce48cd9d0
EEClass:     00007ffce49f5fd0
Size:        56(0x38) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffce48d0ba0  40018c3        8      System.Object[]  0 instance 000002a7039d3278 _array
00007ffce48d32c0  40018c4       18         System.Int32  1 instance              103 _head
00007ffce48d32c0  40018c5       1c         System.Int32  1 instance               74 _tail
00007ffce48d32c0  40018c6       20         System.Int32  1 instance              227 _size
00007ffce48d32c0  40018c7       24         System.Int32  1 instance              200 _growFactor
00007ffce48d32c0  40018c8       28         System.Int32  1 instance             1366 _version
00007ffce48d0b08  40018c9       10        System.Object  0 instance 0000000000000000 _syncRoot

从卦中数据看,这个 BackgroundWorker.Queue 当前有 227 个任务在队列积压,这说明主线程是没有问题的,只不过是在忙碌的处理任务而已,再回答最后一个问题,为什么会卡一阵子?

4. 为什么会卡一阵子

这是朋友提到的一个疑问,要想找到这个问题的答案,我们再回头看下主线程,看下它是如何从 Queue 中取数据的。


0:000> !clrstack
OS Thread Id: 0x918 (0)
        Child SP               IP Call Site
000000ef11d1cd70 00007ffd01a007b8 [InlinedCallFrame: 000000ef11d1cd70] System.Drawing.SafeNativeMethods+Gdip.GdipCreateStringFormat(System.Drawing.StringFormatFlags, Int32, IntPtr ByRef)
...
000000ef11d1d970 00007ffc87911aac xxx.backgroundWorker_ProgressChanged(System.Object, System.ComponentModel.ProgressChangedEventArgs)
000000ef11d1d9e0 00007ffcdeab652b System.ComponentModel.BackgroundWorker.OnProgressChanged(System.ComponentModel.ProgressChangedEventArgs)
000000ef11d1dc30 00007ffce66112c3 [DebuggerU2MCatchHandlerFrame: 000000ef11d1dc30] 
000000ef11d1dea8 00007ffce66112c3 [HelperMethodFrame_PROTECTOBJ: 000000ef11d1dea8] System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean)
000000ef11d1e020 00007ffce4dfcf58 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[])
000000ef11d1e080 00007ffce4dcbd20 System.Delegate.DynamicInvokeImpl(System.Object[])
000000ef11d1e0d0 00007ffcb4dc702d System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry)
000000ef11d1e110 00007ffcb4dc6f49 System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(System.Object)
000000ef11d1e160 00007ffce4ddfbe8 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000ef11d1e230 00007ffce4ddfad5 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000ef11d1e260 00007ffce4ddfaa5 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
000000ef11d1e2b0 00007ffcb4dc6ecc System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry)
000000ef11d1e300 00007ffcb4dc6c36 System.Windows.Forms.Control.InvokeMarshaledCallbacks()
000000ef11d1e370 00007ffcb4db06fb System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
000000ef11d1e430 00007ffcb4dafa72 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
000000ef11d1e4d0 00007ffcb553d682 DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int64, Int32, Int64, Int64)
000000ef11d1e810 00007ffce660fc9e [InlinedCallFrame: 000000ef11d1e810] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
...
000000ef11d1ea30 00007ffcb4dc5982 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
000000ef11d1ecd0 00007ffc86e308e0 xxx.Program.Main(System.String[])
000000ef11d1ef08 00007ffce66112c3 [GCFrame: 000000ef11d1ef08] 

从卦中看,线程栈上的 InvokeMarshaledCallback 方法就是取数据的函数,接下来用 ILSpy 反编译下这段代码,简化后如下:


private void InvokeMarshaledCallbacks()
{
	ThreadMethodEntry threadMethodEntry = null;
	lock(threadCallbackList)
	{
		if (threadCallbackList.Count > 0)
		{
			threadMethodEntry = (ThreadMethodEntry)threadCallbackList.Dequeue();
		}
	}
	while (threadMethodEntry != null)
	{
		try
		{
			InvokeMarshaledCallback(threadMethodEntry);
		}
		catch (Exception ex)
		{
			threadMethodEntry.exception = ex.GetBaseException();
		}

		lock(threadCallbackList)
		{
			threadMethodEntry = ((threadCallbackList.Count <= 0) ? null : ((ThreadMethodEntry)threadCallbackList.Dequeue()));
		}
	}
}

从代码中的while true来看,这方法真的很轴,不懂得变通,要么不取,要么就是一次性的取完,当 Queue=threadCallbackList 中的数据较多时,主线程就会非常的忙碌,所以这就是卡死一阵子的真正底层原因。

有了前因后果之后,建议朋友做如下两点修改:

  1. Invoke 的逻辑尽量简单,让 UI 可以秒杀。
  2. Invoke 的逻辑是否可以批量化,来减少 Queue 的积压。

三:总结

这次朋友的生产事故,对我们做分析的人来说还是有很大的教训意义,有时候主线程的一些抛错或者阻塞假象会诱导我们陷入分析误区,这就需要调试人员具有一双慧眼识别,及时的浪子回头。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows anaconda 安装 Labelme

安装 # 创建环境 conda create -n labelme python3.6 #激活环境 conda activate labelme # 安装依赖 conda install pyqt conda install pillow # 安装labelme conda install labelme3.16.2 # 启动labelme labelme右键选择标注类型&#xff0c;从上到下为多边形&#xff08;常…

Git分支的状态存储——stash命令的详细用法

文章目录 6.6 Git的分支状态存储6.6.1 git stash命令6.6.2 Git存储的基本使用6.6.3 Git存储的其他用法6.6.4 Git存储与暂存区6.6.5 Git存储的原理 6.6 Git的分支状态存储 有时&#xff0c;当我们在项目的一部分上已经工作一段时间后&#xff0c;所有东西都进入了混乱的状态&am…

机器学习课程复习——朴素贝叶斯

1. 定义 是一种基于贝叶斯定理与特征条件独立假设的生成式分类方法。 2. 公式 原版公式 简化版公式 由于上述公式无法计算&#xff0c;引入条件独立假设 条件独立版公式 3. 贝叶斯分类器 由上述公式可得贝叶斯分类器 化简为 4. 参数估计 4.1. 极大似然估计 4.2. 学习与分…

电子竞赛5——作息时间控制器

一 . 题目要求 用单片机制作作息时间控制器&#xff1b;用四位数码管显示实时时钟&#xff08;时、分&#xff0c;24小时制、12小时制&#xff09;&#xff0c;有秒闪&#xff0c;小时十位有零消隐&#xff1b;可用数字键或、-键校时&#xff08;可快速、-&#xff09;被校位&…

离线安装zabbix-agent,自制yum源方式安装

1&#xff0c;机器准备 现在有2台机器 机器A&#xff0c;能上网&#xff0c;ip&#xff1a;192.168.10.131&#xff1b; 机器B&#xff0c;不能上网&#xff0c;ip&#xff1a;192.168.10.133。 我想在机器B上面安装zabbix-agent-5.0.42版本。 大致思路 在机器A上面制作好我…

Midjourney和Stable Diffusion哪个更适合商业应用?

midjourney的绘画&#xff0c;在撰写有效的prompt需要精确地定义你想要展现的画面&#xff0c;详细描述越准确&#xff0c;生成出的图片结果也会越吻合你的预期。为了提升你midjourney的写作prompt的技巧&#xff0c;可以通过模仿他人的成功案例&#xff0c;亲自尝试编写&#…

C++ 68 之 类模版作函数的参数

#include <iostream> // #include <cstring> #include <string> using namespace std;template<class T1, class T2> // 可以设置默认的类型值&#xff0c;后面在使用的时候&#xff0c;就不用再指定类型了 class Students08{ public:T1 m_name;T2 m_a…

【昇思25天学习打卡营打卡指南-第一天】基本介绍与快速入门

昇思MindSpore介绍 昇思MindSpore是一个全场景深度学习框架&#xff0c;旨在实现易开发、高效执行、全场景统一部署三大目标。 其中&#xff0c;易开发表现为API友好、调试难度低&#xff1b;高效执行包括计算效率、数据预处理效率和分布式训练效率&#xff1b;全场景则指框架…

ICMAN触摸芯片——大金属触摸

ICMAN触摸芯片之大金属触摸 按照工业级别设计 稳定性与抗干扰能力强 单颗芯片支持 1~12按键 可以轻松实现 金属灯触摸方案的触摸开关&#xff0b;无极调光 高性能ICMAN触摸芯片在金属LED触摸台灯的应用

Java23种设计模式(四)

1、备忘录模式 备忘录模式&#xff08;Memento Pattern&#xff09;保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象&#xff0c;备忘录模式属于行为型模式。 备忘录模式允许在不破坏封装性的前提下&#xff0c;捕获和恢复对象的内部状态。 实现方式 创建备忘录…

反激式开关电源是如何工作的

反激的变压器可以看作一个带变压功能的电感&#xff0c;是一个buck-boost电路。 反击式开关变压器 反激式开关电源是指使用反激高频变压器隔离输入输出回路的开关电源。“反激”指的是在开关管接通的情况下&#xff0c;当输入为高电平时输出线路中串联的电感为放电状态&#x…

硬盘格式化NTFS好还是exFAT好 U盘存储文件用哪个格式好? 硬盘用exfat还是ntfs mac不能读取移动硬盘怎么解决

在计算机世界中&#xff0c;文件系统是数据管理的基石&#xff0c;而NTFS和exFAT无疑是这块基石上的两大巨头。它们各自拥有独特的特点和优势&#xff0c;并在不同的使用场景中发挥着重要作用。 什么是文件系统 文件系统提供了组织驱动器的方法。它规定了如何在驱动器上存储数…

2024/06/18--代码随想录算法8/17| 股票问题

121.买卖股票的最佳时机 力扣链接 动规五部曲 确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i][0] 表示第i天持有股票所得最多现金,dp[i][1] 表示第i天不持有股票所得最多现金确定递推公式 dp[i][0] max(dp[i-1][0], -price[i]) dp[i][1]max(dp[i-1][1], …

spark独立集群搭建

spark独立集群搭建(不依赖Hadoop) 1、上传spark-2.4.5-bin-hadoop2.7.tgz至 /usr/local/moudel &#xff0c;再解压到 /usr/local/soft tar -zxvf spark-2.4.5-bin-hadoop2.7.tgz -C /usr/local/soft/ 重命名 mv spark-2.4.5-bin-hadoop2.7/ spark-2.4.5 配…

深入理解计算机系统 CSAPP 家庭作业6.45

CS:APP3e, Bryant and OHallaron 可以参考这里

mediasoup源码分析(三)channel创建及信令交互

mediasoup源码分析--channel创建及信令交互 概述跨职能图业务流程图代码剖析 概述 在golang实现mediasoup的tcp服务及channel通道一文中&#xff0c;已经介绍过信令服务中tcp和channel的创建&#xff0c;本文主要讲解c中mediasoup的channel创建&#xff0c;以及信令服务和medi…

深圳比创达电子EMC|EMC与EMI滤波器:守护电子设备的电磁防火墙

随着科技的飞速发展&#xff0c;电子设备在我们日常生活中的普及率越来越高&#xff0c;从智能手机到大型工业设备&#xff0c;无一不体现出电子技术的重要地位。然而&#xff0c;随之而来的电磁兼容性问题&#xff08;EMC&#xff09;和电磁干扰问题&#xff08;EMI&#xff0…

基于稀疏学习现代信号处理方法的旋转机械故障诊断(MATLAB)

通过对滚动轴承故障诊断研究现状及稀疏表示方法在滚动轴承故障诊断领域中应用现状的调研&#xff0c;发现稀疏表示方法与故障特征提取和故障分类的关联&#xff0c;针对故障诊断问题&#xff0c;通过构造合理的故障稀疏表示模型&#xff0c;选取适合的模型优化算法&#xff0c;…

华为HCIP Datacom H12-821 卷6

1.单选题 下面是一台路由器的部分配置&#xff0c;关于该部分配置描述正确的是&#xff0c;[HUAWEllJip ip-prefix plpermit 10.0.192.0 8 greater-equal 17 less-equal 18 A、10.0.192.0/8 网段内&#xff0c;掩码长度为 20 的路由会匹配到该前缀列表&#xff0c;匹配规则为…

[保姆级教程]uniapp配置vueX

文章目录 注意新建文件简单的使用 注意 uniapp是支持vueX的只需配置一下就好 新建文件 在src文件中&#xff0c;新建一个store&#xff08;如果有的话跳过&#xff09; 在store中新建一个js文件&#xff0c;修改js文件名称和选择模板为default 在 uni-app 项目根目录下&…