一:背景
1. 講故事
前幾天有位朋友微信上找到我,說(shuō)他的程序會(huì)偶發(fā)性崩潰,一直找不到原因,讓我?guī)兔匆幌略趺椿厥拢瑢?duì)于這種崩潰類(lèi)的程序,最好的辦法就是丟dump過(guò)來(lái)看一下便知,話不多說(shuō),上windbg說(shuō)話。
二:WinDbg 分析
1. 到底是哪里的崩潰
對(duì)于一個(gè)崩潰類(lèi)的dump,尋找崩潰點(diǎn)非常重要,常用的命令就是 !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
從卦中的崩潰點(diǎn)來(lái)看,很奇怪,怎么 cs:eip
所處的地址沒(méi)有機(jī)器碼? 先不管了,看下異常狀態(tài) 80000004
,在微軟的官方文檔查一查:
從圖中信息看,應(yīng)該是 F11 這種單步跟蹤造成的,這就很奇葩了,分析了200+ 的dump,這種崩潰還是第一次遇到,無(wú)語(yǔ),一下子陷入了迷茫。
2. 還有突破口嗎
雖然 windbg 的自動(dòng)化分析給出的信息很不盡如意,但沒(méi)關(guān)系,根據(jù)強(qiáng)大的臨場(chǎng)經(jīng)驗(yàn),我們直接切到異常前的狀態(tài),看看異常前的上下文有沒(méi)有什么新的線索,刪減后如下:
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
從卦中的線程棧信息來(lái)看,邏輯還是非常清楚的,終結(jié)器線程析構(gòu)一個(gè)C#的 SafeWaitHandle 對(duì)象時(shí),在網(wǎng)關(guān)函數(shù) ntdll!NtClose
中拋出了異常,這個(gè)函數(shù)再往下就是 內(nèi)核層 了。
線程既然拋了異常,那 C# 層面有沒(méi)有接到呢?可以用 !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: 外部組件發(fā)生異常。
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
從卦中信息看,果然是在析構(gòu) SafeHandle.Finalize
時(shí)異常了,但這個(gè)異常信息 Message:外部組件發(fā)生異常
對(duì)我們來(lái)說(shuō)一點(diǎn)作用都沒(méi)有,到這里貌似又進(jìn)行不下去了。
3. 從 handle 上突破
托管層沒(méi)法挖了,那就繼續(xù)挖非托管層,也就是異常前的最后一個(gè)函數(shù) ntdll!NtClose
,這個(gè)函數(shù)其實(shí)沒(méi)什么特別的,也就是釋放句柄,這個(gè)函數(shù)一般來(lái)說(shuō)固若金湯,不會(huì)有異常的,不管怎么說(shuō),先把句柄值找出來(lái)看看,簽名如下:
NTSTATUS NTAPI NtClose(
HANDLE Handle
);
如何提取出 handle 呢?非常簡(jiǎn)單,用 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
我去,卦中顯示這個(gè) handle=664 句柄值居然不在進(jìn)程中,難怪調(diào)用 ntdll!NtClose
會(huì)報(bào)錯(cuò),接下來(lái)的問(wèn)題就是這個(gè) handle 到底怎么了?要找到這個(gè)答案,需要從線程棧上把 _EXCEPTION_RECORD
結(jié)構(gòu)體給提取出來(lái),它的內(nèi)部記錄了 ExceptionCode
,而且剛好線程棧上的 ntdll!ExecuteHandler
方法的第一個(gè)參數(shù)就是這個(gè)結(jié)構(gòu)體。
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
接下來(lái)就是找下 ExceptionCode=0xc0000008
代表什么意思,這個(gè)簡(jiǎn)單,網(wǎng)上搜一下便知,截圖如下:
到這里就很好理解了,然來(lái)是在釋放一個(gè)已經(jīng)釋放的句柄,這個(gè)肯定會(huì)報(bào)錯(cuò)的,據(jù)朋友所說(shuō),他們的程序是 C# 和 C++ 混合編程的,那大概率就是 handle=664
被 C++ 給提前釋放了。
有些朋友肯定要問(wèn)了,那我怎么找到釋放這個(gè) handle 的代碼呢?要尋找這個(gè)答案,需要通過(guò) perfview 對(duì) handle 進(jìn)行全程監(jiān)控,參見(jiàn):https://www.cnblogs.com/huangxincheng/p/17559370.html文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-711548.html
三:總結(jié)
這個(gè)崩潰還是挺有意思的,需要你對(duì) Windows 層面的知識(shí)有一定的了解,否則很難找出前因后果,所以請(qǐng)善待做大工控
的朋友。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-711548.html

到了這里,關(guān)于記一次 .NET某新能源檢測(cè)系統(tǒng) 崩潰分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!