翼度科技»论坛 编程开发 .net 查看内容

记一次 .NET某新能源检测系统 崩溃分析

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
一:背景

1. 讲故事

前几天有位朋友微信上找到我,说他的程序会偶发性崩溃,一直找不到原因,让我帮忙看一下怎么回事,对于这种崩溃类的程序,最好的办法就是丢dump过来看一下便知,话不多说,上windbg说话。
二:WinDbg 分析

1. 到底是哪里的崩溃

对于一个崩溃类的dump,寻找崩溃点非常重要,常用的命令就是 !analyze -v,输出如下:
  1. 0:006> !analyze -v
  2. CONTEXT:  6fbdee65 -- (.cxr 0x6fbdee65)
  3. eax=55d2ebff ebx=5e5f04c0 ecx=e8c434e8 edx=cf8bc35b esi=83008b05 edi=75880000
  4. eip=3d83f98b esp=ce8b0774 ebp=5756ec8b iopl=0 vip     ov up ei pl nz na po nc
  5. cs=4040  ss=0010  ds=81f8  es=00e1  fs=4e8b  gs=ffdb             efl=08758b00
  6. 4040:3d83f98b ??              ???
  7. Resetting default scope
  8. EXCEPTION_RECORD:  049bfbd0 -- (.exr 0x49bfbd0)
  9. ExceptionAddress: 00000000
  10.    ExceptionCode: 049bfbf8
  11.   ExceptionFlags: 6f9b6c38
  12. NumberParameters: 8752248
  13.    Parameter[0]: 00000000
  14.    Parameter[1]: 6f9c92a0
  15.    Parameter[2]: 049bfbdc
  16.    Parameter[3]: 00000008
  17.    Parameter[4]: 00000000
  18.    Parameter[5]: 049bfc34
  19.    Parameter[6]: 6f9b6d0d
  20.    Parameter[7]: a2cc713a
  21.    Parameter[8]: 6f9b6c40
  22.    Parameter[9]: 00000000
  23.    Parameter[10]: 00844c80
  24.    Parameter[11]: a2cc712a
  25.    Parameter[12]: 00000000
  26.    Parameter[13]: 049bfd2c
  27.    Parameter[14]: 049bfc00
  28. PROCESS_NAME:  xxxx.exe
  29. ERROR_CODE: (NTSTATUS) 0x80000004 - {    }
  30. EXCEPTION_CODE_STR:  80000004
  31. FAULTING_THREAD:  ffffffff
复制代码
从卦中的崩溃点来看,很奇怪,怎么 cs:eip 所处的地址没有机器码? 先不管了,看下异常状态 80000004,在微软的官方文档查一查:

从图中信息看,应该是 F11 这种单步跟踪造成的,这就很奇葩了,分析了200+ 的dump,这种崩溃还是第一次遇到,无语,一下子陷入了迷茫。
2. 还有突破口吗

虽然 windbg 的自动化分析给出的信息很不尽如意,但没关系,根据强大的临场经验,我们直接切到异常前的状态,看看异常前的上下文有没有什么新的线索,删减后如下:
  1. 0:006> .ecxr
  2. eax=00000000 ebx=049bec60 ecx=027e1374 edx=0b8024a8 esi=00000000 edi=049bebf8
  3. eip=09fb48b1 esp=049beb98 ebp=049bebe0 iopl=0         nv up ei pl zr na pe nc
  4. cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
  5. xxx!xxx.Program.CurrentDomain_UnhandledException+0x29:
  6. 09fb48b1 cc              int     3
  7. 0:006> k
  8.   *** Stack trace for last set context - .thread/.cxr resets it
  9. # ChildEBP RetAddr      
  10. 00 049bebe0 6f962546     xxx!xxx.Program.CurrentDomain_UnhandledException+0x29
  11. ...
  12. 11 049bf114 77a48962     clr!_except_handler4+0x29
  13. 12 049bf138 77a48934     ntdll!ExecuteHandler2+0x26
  14. 13 049bf200 77a34f86     ntdll!ExecuteHandler+0x24
  15. 14 049bf6f0 77a32b2c     ntdll!KiUserExceptionDispatcher+0x26
  16. 15 049bf6f0 76698d7a     ntdll!NtClose+0xc
  17. 16 049bf6f0 6f96287d     KERNELBASE!CloseHandle+0x4a
  18. ...
  19. 20 049bf970 78a1887b     clr!SafeHandle::Finalize+0x7a
  20. 21 049bf978 78a187e4     mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Dispose+0x1b [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\safehandle.cs @ 263]
  21. 22 049bf998 6f98df99     mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Finalize+0x24 [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\safehandle.cs @ 199]
  22. 23 049bf9ec 6f98e0a7     clr!FastCallFinalize+0x6d
  23. 24 049bfa10 6f98de5c     clr!MethodTable::CallFinalizer+0x150
  24. 25 049bfa78 6f98ded3     clr!CallFinalizer+0xa6
  25. 26 049bfa78 6f9c9263     clr!FinalizerThread::DoOneFinalization+0x132
  26. 27 049bfaa8 6f9c9343     clr!FinalizerThread::FinalizeAllObjects+0xa1
  27. 28 049bfad4 6f973b24     clr!FinalizerThread::FinalizerThreadWorker+0xbe
  28. 29 049bfaec 6f973b9b     clr!ManagedThreadBase_DispatchInner+0x71
  29. 2a 049bfb74 6f973c4b     clr!ManagedThreadBase_DispatchMiddle+0x8f
  30. 2b 049bfbd0 6f9b6c38     clr!ManagedThreadBase_DispatchOuter+0x6d
  31. 2c 049bfbf8 6f9b6d0d     clr!ManagedThreadBase::FinalizerBase+0x33
  32. 2d 049bfc34 6f98eb34     clr!FinalizerThread::FinalizerThreadStart+0xe2
  33. 2e 049bfcd0 76cdfcc9     clr!Thread::intermediateThreadProc+0x58
  34. 2f 049bfce0 77a27b1e     kernel32!BaseThreadInitThunk+0x19
  35. 30 049bfd3c 77a27aee     ntdll!__RtlUserThreadStart+0x2f
  36. 31 049bfd4c 00000000     ntdll!_RtlUserThreadStart+0x1b
复制代码
从卦中的线程栈信息来看,逻辑还是非常清楚的,终结器线程析构一个C#的 SafeWaitHandle 对象时,在网关函数 ntdll!NtClose 中抛出了异常,这个函数再往下就是 内核层 了。
线程既然抛了异常,那 C# 层面有没有接到呢?可以用 !t 观察下。
  1. 0:006> !t
  2. ThreadCount:      10
  3. UnstartedThread:  0
  4. BackgroundThread: 8
  5. PendingThread:    0
  6. DeadThread:       1
  7. Hosted Runtime:   no
  8.                                                                          Lock  
  9.        ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
  10.    0    1 4b9c 0085f088     a6028 Preemptive  0B7FF79C:00000000 00858c78 0     STA
  11.    6    2 5068 008a3708     ab228 Preemptive  0B8024B8:00000000 00858c78 0     MTA (Finalizer) System.Runtime.InteropServices.SEHException 0b800c88
  12.   11    3 293c 0092c0a8   10a9228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker)
  13.   12    4 2eb0 0602ed48   8029228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Completion Port)
  14. XXXX    5    0 07de70a8   1039820 Preemptive  00000000:00000000 00858c78 0     Ukn (Threadpool Worker)
  15.   13    6 7e0c 0a7ada58   102a228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker)
  16.   14    7 7c60 0a773950   1029228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker)
  17.   15    8 5c24 0a775f68   10a9228 Preemptive  0B7EB8CC:00000000 00858c78 0     MTA (Threadpool Worker)
  18.   16    9 698c 008d5b40   1029228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker)
  19.   17   10 7de4 008dea10   1029228 Preemptive  0B7ECE80:00000000 00858c78 0     MTA (Threadpool Worker)
  20. 0:006> !PrintException /d 0b800c88
  21. Exception object: 0b800c88
  22. Exception type:   System.Runtime.InteropServices.SEHException
  23. Message:          外部组件发生异常。
  24. InnerException:   <none>
  25. StackTrace (generated):
  26.     SP       IP       Function
  27.     00000000 00000000 mscorlib_ni!Microsoft.Win32.Win32Native.CloseHandle(IntPtr)+0x1
  28.     049BF760 78ADF5FE mscorlib_ni!Microsoft.Win32.SafeHandles.SafeWaitHandle.ReleaseHandle()+0xe
  29.     00000000 00000001 mscorlib_ni!System.Runtime.InteropServices.SafeHandle.InternalFinalize()+0xffffffff90656c91
  30.     049BF978 78A1887B mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Dispose(Boolean)+0x1b
  31.     049BF980 78A187E4 mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Finalize()+0x24
  32. StackTraceString: <none>
  33. HResult: 80004005
复制代码
从卦中信息看,果然是在析构 SafeHandle.Finalize 时异常了,但这个异常信息  Message:外部组件发生异常  对我们来说一点作用都没有,到这里貌似又进行不下去了。
3. 从 handle 上突破

托管层没法挖了,那就继续挖非托管层,也就是异常前的最后一个函数 ntdll!NtClose,这个函数其实没什么特别的,也就是释放句柄,这个函数一般来说固若金汤,不会有异常的,不管怎么说,先把句柄值找出来看看,签名如下:
  1. NTSTATUS NTAPI NtClose(
  2.   HANDLE Handle
  3. );
复制代码
如何提取出 handle 呢?非常简单,用 kb 即可。
  1. 0:006> kb
  2.   *** Stack trace for last set context - .thread/.cxr resets it
  3. # ChildEBP RetAddr      Args to Child   
  4. ...
  5. 13 049bf200 77a34f86     049bf218 049bf268 049bf218 ntdll!ExecuteHandler+0x24
  6. 14 049bf6f0 77a32b2c     00000664 049bf730 008a3708 ntdll!KiUserExceptionDispatcher+0x26
  7. 15 049bf6f0 76698d7a     00000664 049bf6fc 049bf72c ntdll!NtClose+0xc
  8. 16 049bf6f0 6f96287d     00000664 049bf730 008a3708 KERNELBASE!CloseHandle+0x4a
  9. ...
  10. 0:006> !handle 00000664 f
  11. Handle 00000664
  12.   Type                 <Error retrieving type>
  13. unable to query object information
  14. unable to query object information
  15.   No object specific information available
复制代码
我去,卦中显示这个 handle=664 句柄值居然不在进程中,难怪调用 ntdll!NtClose 会报错,接下来的问题就是这个 handle 到底怎么了?要找到这个答案,需要从线程栈上把 _EXCEPTION_RECORD 结构体给提取出来,它的内部记录了 ExceptionCode ,而且刚好线程栈上的 ntdll!ExecuteHandler 方法的第一个参数就是这个结构体。
  1. 0:006> dt _EXCEPTION_RECORD 049bf218
  2. VCRUNTIME140_CLR0400!_EXCEPTION_RECORD
  3.    +0x000 ExceptionCode    : 0xc0000008
  4.    +0x004 ExceptionFlags   : 0
  5.    +0x008 ExceptionRecord  : (null)
  6.    +0x00c ExceptionAddress : 0x74a70daa Void
  7.    +0x010 NumberParameters : 0
  8.    +0x014 ExceptionInformation : [15] 0
复制代码
接下来就是找下 ExceptionCode=0xc0000008 代表什么意思,这个简单,网上搜一下便知,截图如下:

到这里就很好理解了,然来是在释放一个已经释放的句柄,这个肯定会报错的,据朋友所说,他们的程序是 C# 和 C++ 混合编程的,那大概率就是 handle=664 被 C++ 给提前释放了。
有些朋友肯定要问了,那我怎么找到释放这个 handle 的代码呢?要寻找这个答案,需要通过 perfview 对 handle 进行全程监控,参见:https://www.cnblogs.com/huangxincheng/p/17559370.html
三:总结

这个崩溃还是挺有意思的,需要你对 Windows 层面的知识有一定的了解,否则很难找出前因后果,所以请善待做大工控的朋友。

来源:https://www.cnblogs.com/huangxincheng/archive/2023/10/16/17767410.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具