记一次 .NET 某工控电池检测系统 卡死分析
一:背景
1. 讲故事
前几天有位朋友找到我,说他的窗体程序有卡死现象,让我帮忙看下怎么回事,解决这种问题就需要在卡死的时候抓一个dump下来,拿到dump之后就可以分析了。
二:为什么会卡死
1. 观察主线程
窗体程序的卡死,需要观察主线程此时正在做什么,可以用 !clrstack 命令观察。
0:000:x86> !clrstack
OS Thread Id: 0x4a08 (0)
Child SP IP Call Site
012fe784 0000002b [HelperMethodFrame_1OBJ: 012fe784] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
012fe868 7115d952 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\waithandle.cs @ 243]
012fe880 7115d919 System.Threading.WaitHandle.WaitOne(Int32, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\waithandle.cs @ 194]
012fe894 711e89bf System.Threading.WaitHandle.WaitOne(Int32) [f:\dd\ndp\clr\src\BCL\system\threading\waithandle.cs @ 220]
012fe89c 6fb186b8 System.Threading.ReaderWriterLockSlim.WaitOnEvent(System.Threading.EventWaitHandle, UInt32 ByRef, TimeoutTracker, EnterLockType)
012fe8e0 6fb17892 System.Threading.ReaderWriterLockSlim.TryEnterReadLockCore(TimeoutTracker)
012fe920 6fb17562 System.Threading.ReaderWriterLockSlim.TryEnterReadLock(TimeoutTracker)
012fe94c 0325f49f xxx.QuyitpjK0dXKR6IyqH(System.Object)
012fe964 0325ee8a xxx.RWAutoLock..ctor(System.Threading.ReaderWriterLockSlim, Boolean)
...
从卦中的线程栈数据来看,貌似是卡在一个读写锁TryEnterReadLock 上,根据读写锁的规则,必然有人执行了一个 WriteLock 并且出不来,接下来就是寻找持有这个 lock 的线程。
2. 到底谁在持有
如果是 lock ,相信很多朋友都知道用 !syncblk 命令,那读写锁用什么命令呢?说实话我也搞不清楚,只能先挖挖 ReaderWriterLockSlim 类本身,看看有没有什么新发现。
0:000:x86> !DumpObj /d 03526f38
Name: System.Threading.ReaderWriterLockSlim
MethodTable: 6f947428
EEClass: 6f9a92dc
Size: 72(0x48) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll
Fields:
MT Field Offset Type VT Attr Value Name
70da878c 40004aa 38 System.Boolean 1 instance 0 _fIsReentrant
6f92fa28 40004ab 3c ...LockSlim+SpinLock 1 instance 03526f74 _spinLock
70dfba4c 40004ac 1c System.UInt32 1 instance 20 _numWriteWaiters
70dfba4c 40004ad 20 System.UInt32 1 instance 1 _numReadWaiters
70dfba4c 40004ae 24 System.UInt32 1 instance 0 _numWriteUpgradeWaiters
70dfba4c 40004af 28 System.UInt32 1 instance 0 _numUpgradeWaiters
6f93d764 40004b0 39 System.Byte 1 instance 0 _waiterStates
70da42a8 40004b1 2c System.Int32 1 instance -1 _upgradeLockOwnerId
70da42a8 40004b2 30 System.Int32 1 instance 11 _writeLockOwnerId
70da6924 40004b3 c ...g.EventWaitHandle 0 instance 034844d0 _writeEvent
70da6924 40004b4 10 ...g.EventWaitHandle 0 instance 042a69c8 _readEvent
70da6924 40004b5 14 ...g.EventWaitHandle 0 instance 00000000 _upgradeEvent
70da6924 40004b6 18 ...g.EventWaitHandle 0 instance 00000000 _waitUpgradeEvent
70da150c 40004b8 4 System.Int64 1 instance 367 _lockID
70da878c 40004ba 3a System.Boolean 1 instance 0 _fUpgradeThreadHoldingRead
70dfba4c 40004bc 34 System.UInt32 1 instance 3221225472 _owners
70da878c 40004c2 3b System.Boolean 1 instance 0 _fDisposed
70da42a8 40004a9 4dc System.Int32 1 static 4 ProcessorCount
70da150c 40004b7 4d4 System.Int64 1 static 1882 s_nextLockID
6f942b7c 40004b9 0 ...ReaderWriterCount 0 TLstatic t_rwc
结合源码分析,发现上面的 _writeLockOwnerId=11 就是持有锁的线程ID,找到持有线程就好办了,把这个 managedid=11 转成 dbgid 再观察。
0:000:x86> !t
13 11 47bc 0a0702c0 1029220 Preemptive 00000000:00000000 01425ed0 0 MTA (Threadpool Worker)
0:013:x86> !clrstack
OS Thread Id: 0x47bc (13)
Child SP IP Call Site
07e4f1ac 0000002b [InlinedCallFrame: 07e4f1ac]
07e4f1a4 09e38597 DomainBoundILStubClass.IL_STUB_PInvoke(IntPtr)
07e4f1ac 09e38334 [InlinedCallFrame: 07e4f1ac] System.Data.SQLite.UnsafeNativeMethods.sqlite3_step(IntPtr)
07e4f1dc 09e38334 System.Data.SQLite.SQLite3.Step(System.Data.SQLite.SQLiteStatement)
07e4f228 09e36fe8 System.Data.SQLite.SQLiteDataReader.NextResult()
07e4f250 09e36ceb System.Data.SQLite.SQLiteDataReader..ctor(System.Data.SQLite.SQLiteCommand, System.Data.CommandBehavior)
07e4f270 09e367ce System.Data.SQLite.SQLiteCommand.ExecuteReader(System.Data.CommandBehavior)
07e4f284 09e36732 System.Data.SQLite.SQLiteCommand.ExecuteNonQuery(System.Data.CommandBehavior)
07e4f2b0 09e366e6 System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()
07e4f2bc 09e350dc SqlSugar.AdoProvider.ExecuteCommand(System.String, SqlSugar.SugarParameter[])
07e4f388 13189518 SqlSugar.InsertableProvider`1[[System.__Canon, mscorlib]].ExecuteCommand()
07e4f420 0181ac4a xxx.OperateLog+d__8.MoveNext()
...
0:013:x86> k
CvRegToMachine(x86) conversion failure for 0x14f
X86MachineInfo::SetVal: unknown register 0 requested
# ChildEBP RetAddr
00 07e4ede0 76c9ad10 ntdll_76ed0000!NtFlushBuffersFile+0xc
01 07e4ede0 6b27af8c KERNELBASE!FlushFileBuffers+0x30
WARNING: Stack unwind information not available. Following frames may be wrong.
02 07e4edf0 6b270256 SQLite_Interop!SI768767362ea03a94+0xf73c
03 07e4ee1c 6b267938 SQLite_Interop!SI768767362ea03a94+0x4a06
04 07e4ee38 6b2599e1 SQLite_Interop!SI83d1cf4976f57337+0x84c8
05 07e4ee80 6b25902b SQLite_Interop!SIa3401e98cbad673e+0x3201
06 07e4ee98 6b25258c SQLite_Interop!SIa3401e98cbad673e+0x284b
07 07e4f168 6b255a05 SQLite_Interop!SI327cfc7a6b1fd1fb+0x633c
08 07e4f19c 09e38597 SQLite_Interop!SI9c6d7cd7b7d38055+0x255
结合卦中的读写信息,大概知道了原来是用写锁来写sqlite,后者卡在缓冲区刷新函数 NtFlushBuffersFile 上,方法签名如下:
NTSTATUS NtFlushBuffersFile(
HANDLE FileHandle,
IO_STATUS_BLOCK *IoStatusBlock
);
有些朋友可能想看一下到底怎么写的,那就简单的反编译一下代码:

到这里基本就搞清楚了,由于 13号 线程持有了 写锁,导致主线程要用读锁操作 sqlite 时进行了长时间等待。
解决办法就比较简单了,主线程尽可能的只做UI更新的操作,不要让他触发各类锁,否则就有等锁的概率发生。
3. NtFlushBuffersFile 怎么了
有些朋友可能要问为什么 NtFlushBuffersFile 函数会卡死不返回,要想找到这个答案,需要看下反汇编。
0:013:x86> uf ntdll_76ed0000!NtFlushBuffersFile
ntdll_76ed0000!NtFlushBuffersFile:
76f41ad0 b84b000000 mov eax,4Bh
76f41ad5 ba7071f576 mov edx,offset ntdll_76ed0000!Wow64SystemServiceCall (76f57170)
76f41ada ffd2 call edx
76f41adc c20800 ret 8
0:013:x86> u 76F57170h
ntdll_76ed0000!Wow64SystemServiceCall:
76f57170 ff252892ff76 jmp dword ptr [ntdll_76ed0000!Wow64Transition (76ff9228)]
0:013:x86> u 76ec7000
wow64cpu!KiFastSystemCall:
76ec7000 ea0970ec763300 jmp 0033:76EC7009
76ec7007 0000 add byte ptr [eax],al
76ec7009 41 inc ecx
76ec700a ffa7f8000000 jmp dword ptr [edi+0F8h]
从汇编代码看,NtFlushBuffersFile 通过 KiFastSystemCall 进入内核态了,用户态dump是没法看内核态的,所以也无法继续追究下去。
不过也可以看下这个线程过往的 GetLastError() 值,可能有些收获,使用 !gle 命令。
0:013:x86> !gle
LastErrorValue: (Win32) 0x26 (38) - <Unable to get error code text>
LastStatusValue: (NTSTATUS) 0xc0000008 - <Unable to get error code text>
根据上面的状态码,去msdn上搜一下具体信息。


从错误说明看,可能是这个sqlite文件有什么问题,又是句柄无效,又是读到头了,怀疑是操作sqlite 的时候出现了文件损坏。
现在回头看看,如果想对 Sqlite 进行并发读写,开启下 Write-Ahead Logging 模式应该就可以了,不需要在程序里面进行读写控制。

所以最终的建议就是:
- 开启WAL模式
- 删掉读写控制
三:总结
这次卡死事故还是挺有意思的,熟悉了下 ReaderWriterLockSlim 又对 sqlite 有了一个新的认识。

记一次 .NET 某工控电池检测系统 卡死分析的更多相关文章
- 记一次 .NET某医疗器械清洗系统 卡死分析
一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...
- 记一次 .NET 某工控软件 内存泄露分析
一:背景 1.讲故事 上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也 ...
- 记一次 .NET 某工控数据采集平台 线程数 爆高分析
一:背景 1. 讲故事 前几天有位朋友在 B站 加到我,说他的程序出现了 线程数 爆高的问题,让我帮忙看一下怎么回事,截图如下: 说来也奇怪,这些天碰到了好几起关于线程数无缘无故的爆高,不过那几个问题 ...
- 记一次 .NET 某工控自动化控制系统 卡死分析
一:背景 1. 讲故事 前段时间遇到了好几起关于窗体程序的 进程加载锁 引发的 程序卡死 和 线程暴涨 问题,这种 dump 分析难度较大,主要涉及到 Windows操作系统 和 C++ 的基础知识, ...
- 记一次 .NET 某工控视觉软件 非托管泄漏分析
一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...
- 记一次 .NET 某工控MES程序 崩溃分析
一:背景 1.讲故事 前几天有位朋友找到我,说他的程序出现了偶发性崩溃,已经抓到了dump文件,Windows事件日志显示的崩溃点在 clr.dll 中,让我帮忙看下是怎么回事,那到底怎么回事呢? 上 ...
- 开源纯C#工控网关+组态软件(十)移植到.NET Core
一. 引子 写这个开源系列已经十来篇了.自从十年前注册博客园以来,关注了张善友.老赵.xiaotie.深蓝色右手等一众大牛,也围观了逗比的吉日嘎啦.精密顽石等形形色色的园友.然而整整十年一篇文章都 ...
- MA8601升级版 PL2586|USB HUB 工控级芯片方案PL2586|可直接替代FE1.1S芯片方案
MA8601升级版 PL2586|USB HUB 工控级芯片方案PL2586|可直接替代FE1.1S芯片方案 旺玖在2022年新推出的一款USB HUB 芯片其性能和参数可以完全替代FE1.1S,是M ...
- Wireshark工控协议
Wireshark是一个强大开源流量与协议分析工具,除了传统网络协议解码外,还支持众多主流和标准工控协议的分析与解码. 序号 协议类型 源码下载 简介 1 Siemens S7 https://git ...
- 【转】工控老鬼】西门子S7200入门&精通【1】S7200硬件大全
转载地址:http://blog.sina.com.cn/s/blog_669692a601016i5f.html 工控老鬼提醒以下的信息和资料可能不全或者不准确,如有疑问可以查阅西门子中国网 ...
随机推荐
- jQuery真伪数组转换
// 真数组转伪数组 [].push.apply(obj,arr); // 伪数组转真数组 [].slice.call(obj);
- vlan与单臂路由
vlan 1,什么是vlan vlan叫做虚拟局域网 (VLAN, Virtual LAN) 虚拟局域网(VLAN)是一组逻辑上的设备和用户,这些设备和用户并不受物理位置的限制,可以根据功能.部门及应 ...
- 静态vlan的划分实验
静态vlan的划分 1,toupu图 2,配置id与子网掩码 2.1,pc,server的ip与子网配置 pc5 pc6 pc7 pc8 server1 server2 3,vlan的静态划分 1,v ...
- Node.js安装中出现的问题及其解决方案
Node.js安装与配置流程,请参考 1.npm -v测试时出现警告 更好的选择是安装一个更完善的版本 问题出现的原因 node更新后是最新版 但是npm的版本没有相应的更新存在版本滞后导致问题出现 ...
- 【动画进阶】神奇的 3D 磨砂玻璃透视效果
最近,群友分享了一个很有意思的效果: 原效果的网址:frosted-glass.该效果的几个核心点: 毛玻璃磨砂效果 卡片的 3D 旋转跟随效果 整体透明度和磨砂感.以及卡片的 3D 形态会随着用户移 ...
- CVE-2021-3156 Linux sudo权限提升漏洞复现
前言: 现在最火的莫不过是这个linux sudo权限提升漏洞了,sudo命令可以说是在linux中十分出名的命令了,在运维时很多时候都需要用到sudo命令,通过此漏洞,可以对具有一定权限的普通用户, ...
- elasticsearch中的数据类型search_as_you_type及查看底层Lucene索引
search_as_you_type字段类型用于自动补全,当用户输入搜索关键词的时候,还没输完就可以提示用户相关内容.as_you_type应该是说当你打字的时候.它会给索引里的这个类型的字段添加一些 ...
- Win11+ VS2022编译 FFmpeg6.0 静态库
目录 编译前言 为什么项目编译? 前期准备 环境配置 ffmpeg外部库 额外的编译选项-for渲染 opengl (需要glext) ffnvcodec (需要nv-codec-headers) A ...
- 第一个程序PingPong
功能需求 如图所示,开启两个ping类型的服务ping1和ping2,ping1给ping2发消息,ping2收到回应ping1,ping1收到再回应ping2,不断循环. 服务模块 Skynet提供 ...
- Windows 虚拟地址 到底是如何映射到 物理地址 的?
一:背景 1. 讲故事 我发现有很多的 .NET程序员 写了很多年的代码都没弄清楚什么是 虚拟地址,更不用谈什么是 物理地址 以及Windows是如何实现地址映射的了?这一篇我们就来聊一聊这两者之间的 ...