6

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

 6 months ago
source link: https://www.cnblogs.com/huangxincheng/p/17767410.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1. 讲故事

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

二:WinDbg 分析

1. 到底是哪里的崩溃

对于一个崩溃类的dump,寻找崩溃点非常重要,常用的命令就是 !analyze -v,输出如下:


0:006> !analyze -v
CONTEXT:  6fbdee65 -- (.cxr 0x6fbdee65)
eax=55d2ebff ebx=5e5f04c0 ecx=e8c434e8 edx=cf8bc35b esi=83008b05 edi=75880000
eip=3d83f98b esp=ce8b0774 ebp=5756ec8b iopl=0 vip     ov up ei pl nz na po nc
cs=4040  ss=0010  ds=81f8  es=00e1  fs=4e8b  gs=ffdb             efl=08758b00
4040:3d83f98b ??              ???
Resetting default scope

EXCEPTION_RECORD:  049bfbd0 -- (.exr 0x49bfbd0)
ExceptionAddress: 00000000
   ExceptionCode: 049bfbf8
  ExceptionFlags: 6f9b6c38
NumberParameters: 8752248
   Parameter[0]: 00000000
   Parameter[1]: 6f9c92a0
   Parameter[2]: 049bfbdc
   Parameter[3]: 00000008
   Parameter[4]: 00000000
   Parameter[5]: 049bfc34
   Parameter[6]: 6f9b6d0d
   Parameter[7]: a2cc713a
   Parameter[8]: 6f9b6c40
   Parameter[9]: 00000000
   Parameter[10]: 00844c80
   Parameter[11]: a2cc712a
   Parameter[12]: 00000000
   Parameter[13]: 049bfd2c
   Parameter[14]: 049bfc00

PROCESS_NAME:  xxxx.exe

ERROR_CODE: (NTSTATUS) 0x80000004 - {    }

EXCEPTION_CODE_STR:  80000004

FAULTING_THREAD:  ffffffff

从卦中的崩溃点来看,很奇怪,怎么 cs:eip 所处的地址没有机器码? 先不管了,看下异常状态 80000004,在微软的官方文档查一查:

214741-20231016152619936-534662646.png

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

2. 还有突破口吗

虽然 windbg 的自动化分析给出的信息很不尽如意,但没关系,根据强大的临场经验,我们直接切到异常前的状态,看看异常前的上下文有没有什么新的线索,删减后如下:


0:006> .ecxr
eax=00000000 ebx=049bec60 ecx=027e1374 edx=0b8024a8 esi=00000000 edi=049bebf8
eip=09fb48b1 esp=049beb98 ebp=049bebe0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
xxx!xxx.Program.CurrentDomain_UnhandledException+0x29:
09fb48b1 cc              int     3
0:006> k
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr      
00 049bebe0 6f962546     xxx!xxx.Program.CurrentDomain_UnhandledException+0x29
...
11 049bf114 77a48962     clr!_except_handler4+0x29
12 049bf138 77a48934     ntdll!ExecuteHandler2+0x26
13 049bf200 77a34f86     ntdll!ExecuteHandler+0x24
14 049bf6f0 77a32b2c     ntdll!KiUserExceptionDispatcher+0x26
15 049bf6f0 76698d7a     ntdll!NtClose+0xc
16 049bf6f0 6f96287d     KERNELBASE!CloseHandle+0x4a
...
20 049bf970 78a1887b     clr!SafeHandle::Finalize+0x7a
21 049bf978 78a187e4     mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Dispose+0x1b [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\safehandle.cs @ 263] 
22 049bf998 6f98df99     mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Finalize+0x24 [f:\dd\ndp\clr\src\BCL\system\runtime\interopservices\safehandle.cs @ 199] 
23 049bf9ec 6f98e0a7     clr!FastCallFinalize+0x6d
24 049bfa10 6f98de5c     clr!MethodTable::CallFinalizer+0x150
25 049bfa78 6f98ded3     clr!CallFinalizer+0xa6
26 049bfa78 6f9c9263     clr!FinalizerThread::DoOneFinalization+0x132
27 049bfaa8 6f9c9343     clr!FinalizerThread::FinalizeAllObjects+0xa1
28 049bfad4 6f973b24     clr!FinalizerThread::FinalizerThreadWorker+0xbe
29 049bfaec 6f973b9b     clr!ManagedThreadBase_DispatchInner+0x71
2a 049bfb74 6f973c4b     clr!ManagedThreadBase_DispatchMiddle+0x8f
2b 049bfbd0 6f9b6c38     clr!ManagedThreadBase_DispatchOuter+0x6d
2c 049bfbf8 6f9b6d0d     clr!ManagedThreadBase::FinalizerBase+0x33
2d 049bfc34 6f98eb34     clr!FinalizerThread::FinalizerThreadStart+0xe2
2e 049bfcd0 76cdfcc9     clr!Thread::intermediateThreadProc+0x58
2f 049bfce0 77a27b1e     kernel32!BaseThreadInitThunk+0x19
30 049bfd3c 77a27aee     ntdll!__RtlUserThreadStart+0x2f
31 049bfd4c 00000000     ntdll!_RtlUserThreadStart+0x1b

从卦中的线程栈信息来看,逻辑还是非常清楚的,终结器线程析构一个C#的 SafeWaitHandle 对象时,在网关函数 ntdll!NtClose 中抛出了异常,这个函数再往下就是 内核层 了。

线程既然抛了异常,那 C# 层面有没有接到呢?可以用 !t 观察下。


0:006> !t
ThreadCount:      10
UnstartedThread:  0
BackgroundThread: 8
PendingThread:    0
DeadThread:       1
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 4b9c 0085f088     a6028 Preemptive  0B7FF79C:00000000 00858c78 0     STA 
   6    2 5068 008a3708     ab228 Preemptive  0B8024B8:00000000 00858c78 0     MTA (Finalizer) System.Runtime.InteropServices.SEHException 0b800c88
  11    3 293c 0092c0a8   10a9228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker) 
  12    4 2eb0 0602ed48   8029228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Completion Port) 
XXXX    5    0 07de70a8   1039820 Preemptive  00000000:00000000 00858c78 0     Ukn (Threadpool Worker) 
  13    6 7e0c 0a7ada58   102a228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker) 
  14    7 7c60 0a773950   1029228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker) 
  15    8 5c24 0a775f68   10a9228 Preemptive  0B7EB8CC:00000000 00858c78 0     MTA (Threadpool Worker) 
  16    9 698c 008d5b40   1029228 Preemptive  00000000:00000000 00858c78 0     MTA (Threadpool Worker) 
  17   10 7de4 008dea10   1029228 Preemptive  0B7ECE80:00000000 00858c78 0     MTA (Threadpool Worker) 
0:006> !PrintException /d 0b800c88
Exception object: 0b800c88
Exception type:   System.Runtime.InteropServices.SEHException
Message:          外部组件发生异常。
InnerException:   <none>
StackTrace (generated):
    SP       IP       Function
    00000000 00000000 mscorlib_ni!Microsoft.Win32.Win32Native.CloseHandle(IntPtr)+0x1
    049BF760 78ADF5FE mscorlib_ni!Microsoft.Win32.SafeHandles.SafeWaitHandle.ReleaseHandle()+0xe
    00000000 00000001 mscorlib_ni!System.Runtime.InteropServices.SafeHandle.InternalFinalize()+0xffffffff90656c91
    049BF978 78A1887B mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Dispose(Boolean)+0x1b
    049BF980 78A187E4 mscorlib_ni!System.Runtime.InteropServices.SafeHandle.Finalize()+0x24

StackTraceString: <none>
HResult: 80004005

从卦中信息看,果然是在析构 SafeHandle.Finalize 时异常了,但这个异常信息 Message:外部组件发生异常 对我们来说一点作用都没有,到这里貌似又进行不下去了。

3. 从 handle 上突破

托管层没法挖了,那就继续挖非托管层,也就是异常前的最后一个函数 ntdll!NtClose,这个函数其实没什么特别的,也就是释放句柄,这个函数一般来说固若金汤,不会有异常的,不管怎么说,先把句柄值找出来看看,签名如下:


NTSTATUS NTAPI NtClose(
  HANDLE Handle
);

如何提取出 handle 呢?非常简单,用 kb 即可。


0:006> kb 
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr      Args to Child    
...
13 049bf200 77a34f86     049bf218 049bf268 049bf218 ntdll!ExecuteHandler+0x24
14 049bf6f0 77a32b2c     00000664 049bf730 008a3708 ntdll!KiUserExceptionDispatcher+0x26
15 049bf6f0 76698d7a     00000664 049bf6fc 049bf72c ntdll!NtClose+0xc
16 049bf6f0 6f96287d     00000664 049bf730 008a3708 KERNELBASE!CloseHandle+0x4a
...

0:006> !handle 00000664 f
Handle 00000664
  Type         	<Error retrieving type>
unable to query object information
unable to query object information
  No object specific information available

我去,卦中显示这个 handle=664 句柄值居然不在进程中,难怪调用 ntdll!NtClose 会报错,接下来的问题就是这个 handle 到底怎么了?要找到这个答案,需要从线程栈上把 _EXCEPTION_RECORD 结构体给提取出来,它的内部记录了 ExceptionCode ,而且刚好线程栈上的 ntdll!ExecuteHandler 方法的第一个参数就是这个结构体。


0:006> dt _EXCEPTION_RECORD 049bf218
VCRUNTIME140_CLR0400!_EXCEPTION_RECORD
   +0x000 ExceptionCode    : 0xc0000008
   +0x004 ExceptionFlags   : 0
   +0x008 ExceptionRecord  : (null) 
   +0x00c ExceptionAddress : 0x74a70daa Void
   +0x010 NumberParameters : 0
   +0x014 ExceptionInformation : [15] 0

接下来就是找下 ExceptionCode=0xc0000008 代表什么意思,这个简单,网上搜一下便知,截图如下:

214741-20231016152619929-1253962607.png

到这里就很好理解了,然来是在释放一个已经释放的句柄,这个肯定会报错的,据朋友所说,他们的程序是 C# 和 C++ 混合编程的,那大概率就是 handle=664 被 C++ 给提前释放了。

有些朋友肯定要问了,那我怎么找到释放这个 handle 的代码呢?要寻找这个答案,需要通过 perfview 对 handle 进行全程监控,参见:https://www.cnblogs.com/huangxincheng/p/17559370.html

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

图片名称

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK