• 记一次 .NET 某数控机床控制程序 卡死分析


    一:背景

    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
    ...
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    从卦上信息看,主线程正在等待(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
    ...
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

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

    2. 谁持有了加载锁

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

    
    0:008> x ntdll!LdrpLoaderLock
    77f053b8          ntdll!LdrpLoaderLock = 
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    从卦中的 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
    ...
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    从卦中看,当前是 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从卦中可以清晰的看到,当前临界区变量被 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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

    3. 真相大白

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

    三:总结

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

  • 相关阅读:
    微服务介绍
    【北京迅为】《iTOP-3588开发板快速测试手册》第五章 debian系统功能测试
    Spring高手之路11——BeanDefinition解密:构建和管理Spring Beans的基石
    如何在网页中实现动画效果
    基于springboot+vue水务报修处理系统
    大学生静态HTML鲜花网页设计作品 DIV布局网上鲜花介绍网页模板代码 DW花店网站制作成品 web网页制作与实现
    viobot效果可视化
    【Vue+Element-UI】实现登陆注册界面及axios之get、post请求登录功能实现、跨域问题的解决
    图解第一类曲线积分与第二类曲线积分的关系
    32 | 基于LoadRunner实现企业级服务器端性能测试的实践(上)
  • 原文地址:https://blog.csdn.net/huangxinchen520/article/details/126717252