一:背景

1. 讲故事

前段时间有位朋友微信上找到我,说它的程序出现了卡死,让我帮忙看下是怎么回事? 说来也奇怪,那段时间求助卡死类的dump特别多,被迫训练了一下对这类问题的洞察力 ,再次声明一下,我分析 dump 是免费的,没有某软高额的分析费用,你要问我图什么,图技术的精进。

回到正题,卡死类的问题分析入口点在于主线程此时在做什么,导致它不能处理自己的任务队列的原因是各种各样的,接下来上 windbg 分析一下。

二:WinDbg 分析

1. 主线程此时在做什么

看主线程很简单,直接一个 k 命令搞定。


0:000> k
# ChildEBP RetAddr
00 00f3ec2c 74ef6439 ntdll!NtWaitForSingleObject+0xc
01 00f3ec2c 70293370 KERNELBASE!WaitForSingleObjectEx+0x99
02 00f3ec5c 702933cc clr!CLREventWaitHelper2+0x33
03 00f3ecac 70293319 clr!CLREventWaitHelper+0x2a
04 00f3ece4 7037bbcf clr!CLREventBase::WaitEx+0x14b
05 00f3ecfc 70374f08 clr!WKS::GCHeap::WaitUntilGCComplete+0x35
06 00f3ed44 7032c2a2 clr!Thread::RareDisablePreemptiveGC+0x20b
07 00f3edc8 6d453712 clr!JIT_RareDisableHelper+0x22
08 00f3ee4c 6d4530d1 System_Windows_Forms_ni!System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)$##600550A+0x48e
09 00f3eea0 6d452f23 System_Windows_Forms_ni!System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)$##6005539+0x175
0a 00f3eecc 6d42b83d System_Windows_Forms_ni!System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)$##6005538+0x4f
0b 00f3eee4 02d90c83 System_Windows_Forms_ni!System.Windows.Forms.Application.Run(System.Windows.Forms.Form)$##60005FA+0x35
...

从卦上信息看,主线程正在等待(WaitUntilGCComplete) GC 处理完成,接下来看下是哪一个线程触发了 GC 操作,并看下它正在 GC 三阶段的哪一步?


0:000> !t
ThreadCount: 33
UnstartedThread: 3
BackgroundThread: 29
PendingThread: 3
DeadThread: 0
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 17a0 0113ecd0 26020 Preemptive 00000000:00000000 01139490 0 STA
2 2 2074 0114e0e0 2b220 Preemptive 00000000:00000000 01139490 0 MTA (Finalizer)
5 3 27f4 011ddaa8 102a220 Preemptive 00000000:00000000 01139490 0 MTA (Threadpool Worker)
8 4 2568 05bf0b90 1029220 Cooperative 00000000:00000000 01139490 1 MTA (GC) (Threadpool Worker) 0:000> ~8s
eax=00000000 ebx=05bf0b90 ecx=00000000 edx=00000000 esi=00000000 edi=00000f6c
eip=77e5aa5c esp=098ce03c ebp=098ce0ac iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
ntdll!NtWaitForSingleObject+0xc:
77e5aa5c c20c00 ret 0Ch
0:008> k
# ChildEBP RetAddr
00 098ce0ac 74ef6439 ntdll!NtWaitForSingleObject+0xc
01 098ce0ac 70293370 KERNELBASE!WaitForSingleObjectEx+0x99
02 098ce0dc 702933cc clr!CLREventWaitHelper2+0x33
03 098ce12c 70293319 clr!CLREventWaitHelper+0x2a
04 098ce164 7029333b clr!CLREventBase::WaitEx+0x14b
05 098ce17c 7032a9b7 clr!CLREventBase::Wait+0x1a
06 098ce1fc 7032a9ef clr!`anonymous namespace'::CreateSuspendableThread+0x165
07 098ce378 704322bd clr!GCToEEInterface::CreateThread+0x15c
08 098ce3a8 704332e5 clr!WKS::gc_heap::prepare_bgc_thread+0x36
09 098ce3a8 70378585 clr!WKS::gc_heap::garbage_collect+0x215
0a 098ce3c8 7037868a clr!WKS::GCHeap::GarbageCollectGeneration+0x1bd
0b 098ce3ec 70378703 clr!WKS::gc_heap::trigger_gc_for_alloc+0x1f
0c 098ce424 70396f6a clr!WKS::gc_heap::try_allocate_more_space+0x152
0d 098ce46c 70397311 clr!WKS::gc_heap::allocate_large_object+0x51
0e 098ce478 702fde2d clr!WKS::GCHeap::AllocLHeap+0x11
0f 098ce494 7063f1af clr!AllocLHeap+0x4b
10 098ce4e8 7028f598 clr!FastAllocatePrimitiveArray+0xa7
11 098ce58c 0e2d3fb6 clr!JIT_NewArr1+0x126
12 098cf26c 6f1f1b5a 0xe2d3fb6
13 098cf274 6f188604 mscorlib_ni!System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext(System.Object)$##6006FCB+0x1a
...

从卦中的 clr!GCToEEInterface::CreateThread 方法看,如果你没有这方面的分析经验,或许你已经无能为力了,以我的经验来说,既然卡死,那这里的 CreateThread 自然无法创建成功,创建不成功的原因在于 进程加载锁 LdrpLoaderLock 被别人持有了,导致它获取不到这把锁而一直茫然等待。

2. 谁持有了加载锁

要想找到谁持有了加载锁,可以在 ntdll 中搜 LdrpLoaderLock 字段,接下来使用 x 命令搜索。


0:008> x ntdll!LdrpLoaderLock
77f053b8 ntdll!LdrpLoaderLock = <no type information>
0:008> !cs 77f053b8
-----------------------------------------
Critical section = 0x77f053b8 (ntdll!LdrpLoaderLock+0x0)
DebugInfo = 0x77f0573c
LOCKED
LockCount = 0x0
WaiterWoken = No
OwningThread = 0x00001314
RecursionCount = 0x1
LockSemaphore = 0x0
SpinCount = 0x04000000

从卦中的 OwningThread = 0x00001314 迹象看,当前是线程 1314 持有了加载锁,看样子是要一生一世的持有。。。。 难怪程序不卡死。。。。 接下来切到这个线程看看到底咋这么痴情?


0:043> ~~[1314]s
eax=00000000 ebx=0589f784 ecx=00000000 edx=00000000 esi=00000000 edi=0589f770
eip=77e5c6bc esp=0589f72c ebp=0589f748 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ntdll!NtWaitForAlertByThreadId+0xc:
77e5c6bc c20800 ret 8
0:043> kb
# ChildEBP RetAddr Args to Child
00 0589f748 77e1336b 0ad114e8 00000000 00000000 ntdll!NtWaitForAlertByThreadId+0xc
01 0589f748 77e50630 00000000 00000000 ffffffff ntdll!RtlpWaitOnAddressWithTimeout+0x33
02 0589f78c 77e13299 00000004 00000000 00000000 ntdll!RtlpWaitOnAddress+0xa5
03 0589f7c8 77e2ed76 00000000 0ad114e0 0ad114e4 ntdll!RtlpWaitOnCriticalSection+0xaf
04 0589f7f0 77e2ec99 0589f814 0ac5d1b4 0ad114e4 ntdll!RtlpEnterCriticalSectionContended+0xd6
05 0589f7f8 0ac5d1b4 0ad114e4 00000003 0ac50000 ntdll!RtlEnterCriticalSection+0x49
06 0589f814 0ac5a890 00001314 0acddd2a 0ac50000 fwlibe1!xxx+0x13e3
07 0589f83c 77e5a9f6 0ac50000 00000003 00000000 fwlibe1!xxx+0x290
...

从卦中看,当前是 fwlibe1 这个非托管dll 准备获取 临界区 时卡死,接下来可以用 !cs 把 临界区变量 给提取出来看看到底谁在持有这个 临界区变量。


0:043> !cs 0ad114e4
-----------------------------------------
Critical section = 0x0ad114e4 (fwlibe1!xxxx+0x3A1C2)
DebugInfo = 0x0ff43360
LOCKED
LockCount = 0x2
WaiterWoken = No
OwningThread = 0x00002378
RecursionCount = 0x1
LockSemaphore = 0xFFFFFFFF
SpinCount = 0x020007d0

从卦中可以清晰的看到,当前临界区变量被 2378 号线程持有,接下来切到这个线程看看到底啥情况。


0:043> ~~[2378]s
eax=00000036 ebx=00002378 ecx=00000400 edx=00000157 esi=0ad114fc edi=0ad114e4
eip=0ac5cf88 esp=0caddf18 ebp=0cade7fc iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
fwlibe1!xxx+0x11b7:
0ac5cf88 8d45f0 lea eax,[ebp-10h] 0:028> k
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0cade7fc 0ac5a8df fwlibe1!xxx+0x11b7
01 0cade938 0ac5abae fwlibe1!xxx+0x22
02 0cadea20 09417ccd fwlibe1!xxx+0x17
03 0cadebfc 09412c11 0x9417ccd
04 0caded2c 6f1f1b5a 0x9412c11
05 0caded34 6f188604 mscorlib_ni!System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.InvokeMoveNext(System.Object)$##6006FCB+0x1a
... 0:028> !clrstack
OS Thread Id: 0x2378 (28)
Child SP IP Call Site
0cade9b4 0ac5cf88 [InlinedCallFrame: 0cade9b4]
0cade998 0941a0e9 *** WARNING: Unable to verify checksum for System.Data.ni.dll
DomainBoundILStubClass.IL_STUB_PInvoke(System.Object, UInt16, Int32, UInt16 ByRef)
0cade9b4 09417ccd [InlinedCallFrame: 0cade9b4] Focas.xxx(System.Object, UInt16, Int32, UInt16 ByRef)

从卦中看,托管层调用了 Focas.xxx 函数进入了 fwlibe1.dll ,最后有一句 WARNING: Stack unwind information not available. Following frames may be wrong. ,可能是展开有一定的问题,不过排查到这里,应该就是这个 Focas.xxx 的问题了。

3. 真相大白

接下来将我的排查结果跟朋友反馈了下,朋友排查下来说是最近改了这里面的连接方式所致,修改之后就搞定了。

三:总结

这个案例告诉我们:只要代码还能跑,就不要动它,它要是不动了,你得要做好动的准备。

记一次 .NET 某数控机床控制程序 卡死分析的更多相关文章

  1. 记一次 .NET 某药品仓储管理系统 卡死分析

    一:背景 1. 讲故事 这个月初,有位朋友wx上找到我,说他的api过一段时间后,就会出现只有请求,没有响应的情况,截图如下: 从朋友的描述中看样子程序是被什么东西卡住了,这种卡死的问题解决起来相对简 ...

  2. 记一次 .NET 某金融企业 WPF 程序卡死分析

    一:背景 1. 讲故事 前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话. ...

  3. 远程监控显示brother数控机床数据

    最近几个月公司太忙了,到现在已经连续出差两个多月了. 这个项目这要做mes系统,涉及到产品在机床的加工过程监控,然后led看板显示产品进度. 这里的主角是日本的brother数控机床,服务器按照一定频 ...

  4. 记一次ORACLE的UNDO表空间爆满分析过程

    这篇文章是记录一次ORACLE数据库UNDO表空间爆满的分析过程,主要整理.梳理了同事分析的思路.具体过程如下所示: 早上收到一数据库服务器的UNDO表空间的告警邮件,最早一封是7:55发出的(监控作 ...

  5. 记java应用linux服务单个CPU使用率100%分析

    之前在做项目的过程中,项目完成后在linux服务器上做性能测试,当服务跑起来的时候发现cpu使用率很奇怪,java应用把单个cpu跑满了,其他cpu利用率0%. 刚开始遇到这问题的时候我第一时间反应使 ...

  6. 记一次vue长列表的内存性能分析和优化

    好久没写东西,博客又长草了,这段时间身心放松了好久,都没什么主题可以写了 上周接到一个需求,优化vue的一个长列表页面,忙活了很久也到尾声了,内存使用和卡顿都做了一点点优化,还算有点收获 写的有点啰嗦 ...

  7. 记一次:Windows的Socket编程学习和分析过程

    Socket编程依赖于:WS2_32.dll --- 服务端 --- .导入我们需要的函数 #incldue <windows.h> //#include<WinSock2.h> ...

  8. 记一次 .NET 医院CIS系统 内存溢出分析

    一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的,不过对于内存方面出的问 ...

  9. 记一次 .NET 车联网云端服务 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下. 这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看, ...

随机推荐

  1. Obsidian基础教程

    Obsidian基础教程 相关链接 2021年新教程 - Obsidian中文教程 - Obsidian Publish 软通达 基础设置篇 1. 开启实时预览 开启实时预览模式,所见即所得 打开设置 ...

  2. python简单处理验证码,三分钟,不能再多了

    序言 大家好鸭, 又是我小熊猫啦 我们在做采集数据的时候,过快或者访问频繁,或者一访问就给弹出验证码,然后就蚌珠了~今天就给大家来一个简单处理验证码的方法 环境模块 Python和pycharm如果还 ...

  3. Tensorflow2 深度学习十必知

    博主根据自身多年的深度学习算法研发经验,整理分享以下十条必知. 含参考资料链接,部分附上相关代码实现. 独乐乐不如众乐乐,希望对各位看客有所帮助. 待回头有时间再展开细节说一说深度学习里的那些道道. ...

  4. Conversation Modeling on Reddit Using a Graph-Structured LSTM

    publish: Transactions of the Association for Computational Linguistics,2016 tasks:  predicting popul ...

  5. labview从入门到出家5(进阶篇)--程序调试以及labview函数库的运用

    跟了前面几章的操作流程,相信大家对labview有了一定的认识.其实只要了解了labview的编程思路,再熟悉地运用各个变量,函数以及属性,那么我们就可以打开labview的大门了.跟其他编程语言一样 ...

  6. 40+倍提升,详解 JuiceFS 元数据备份恢复性能优化之路

    JuiceFS 支持多种元数据存储引擎,且各引擎内部的数据管理格式各有不同.为了便于管理,JuiceFS 自 0.15.2 版本提供了 dump 命令允许将所有元数据以统一格式写入到 JSON 文件进 ...

  7. day05 Java_循环_基本类型数组

    精华笔记: 循环结构: for结构:应用率高.与次数相关的循环 三种循环结构的选择规则: 先看循环是否与次数相关: 若相关----------------------------直接上for 若无关, ...

  8. React中useEffect的简单使用

    学习hooks 在 React 的世界中, 组件有函数组件和类组件 UI 组件我们可以使用函数,用函数组件来展示 UI. 而对于容器组件,函数组件就显得无能为力. 我们依赖于类组件来获取数据,处理数据 ...

  9. 基于ABP实现DDD--仓储实践

      由于软件系统中可能有着不同的数据库,不同的ORM,仓储思想的本质是解耦它们.在ABP中具体的实现仓储接口定义在领域层,实现在基础设施层.仓储接口被领域层(比如领域服务)和应用层用来访问数据库,操作 ...

  10. 【每天学一点-01】 在SpringBoot项目中使用Swagger2

    今天在做毕设的时候,发现在前后端分离的情况下,去调用接口数据时很不方便,然后回想过去,和同学一起做项目的时候,他负责后端,我负责前端,当时调用他的弄好的接口可以说是非常方便,主要是可以通过UI页面直接 ...