記一次 .NET某實(shí)驗(yàn)室自動(dòng)進(jìn)樣系統(tǒng)崩潰分析
一、背景
1. 講故事
前些天有位朋友在微信上聯(lián)系到我,說(shuō)他們的程序在客戶那邊崩掉了,讓我?guī)兔聪略趺椿厥?,dump也拿到了,那就上手分析吧。
二、WinDbg 分析
1. 哪里的崩潰
既然是程序的崩潰,自然是有原因的,皮褲套棉褲,必定有緣故,不是皮褲太薄就是棉褲沒(méi)毛,用 !analyze -v 觀察下異常信息。
0:107> !analyze -v
CONTEXT: (.ecxr)
rax=0000005e0dc7c4a0 rbx=0000005e0dc7c400 rcx=0000005e0dc7c4a0
rdx=0000000000000000 rsi=0000005e0dc7c3f0 rdi=0000005e0dc7c4a0
rip=00007ffb1ecfc223 rsp=0000005e0dc7c3c0 rbp=0000005e0dc7c4c0
r8=00000000000004d0 r9=0000000000000000 r10=0000000000000000
r11=0000005e0dc7c4a0 r12=0000000000000000 r13=000002079d450220
r14=000002079b93aba0 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000200
coreclr!EEPolicy::HandleFatalError+0x7f:
00007ffb`1ecfc223 488d442440 lea rax,[rsp+40h]
Resetting default scope
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00007ffb1ec6d70f (coreclr!ProcessCLRException+0x00000000000d9f7f)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000001
NumberParameters: 0
從卦中信息看這是一個(gè)經(jīng)典的 訪問(wèn)違例,但崩潰在 EEPolicy::HandleFatalError 處就有點(diǎn)匪夷所思了,HandleFatalError 方法主要是用來(lái)在拋異常之前修整異常上下文的,這個(gè)方法固若金湯,一般不會(huì)出問(wèn)題的,但不管怎么樣,還是看下 rsp+40h 到底是什么東西。
0:107> dp rsp+40h L1
0000005e`0dc7c400 00000001`c0000005
上面的 c0000005 很顯然是訪問(wèn)違例,看樣子這里有點(diǎn)混亂,也不是第一崩潰現(xiàn)場(chǎng),這里就不過(guò)多糾結(jié)了,那怎么去找真正的崩潰點(diǎn)呢?還有一個(gè)方法就是去找 RaiseException 或者 KiUserExceptionDispatch 返回點(diǎn)之前的有用函數(shù),參考如下:
0:107> .ecxr
0:107> k
*** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 0000005e`0dc7c3c0 00007ffb`1ec6d72e coreclr!EEPolicy::HandleFatalError+0x7f [D:\a\_work\1\s\src\coreclr\vm\eepolicy.cpp @ 776]
01 0000005e`0dc7c9d0 00007ffb`5235292f coreclr!ProcessCLRException+0xd9f9e [D:\a\_work\1\s\src\coreclr\vm\exceptionhandling.cpp @ 1036]
02 0000005e`0dc7cc00 00007ffb`52302554 ntdll!RtlpExecuteHandlerForException+0xf
03 0000005e`0dc7cc30 00007ffb`5235143e ntdll!RtlDispatchException+0x244
04 0000005e`0dc7d340 00000000`6c942893 ntdll!KiUserExceptionDispatch+0x2e
05 0000005e`0dc7daf0 00007ffa`c066ed7b libxxx_manage!get_clean_xxx
06 0000005e`0dc7db70 00007ffa`c06b73a4 0x00007ffa`c066ed7b
...
從卦中看,程序崩潰在 libxxx_manage!get_clean_xxx 中,看樣子是一個(gè) C++ 寫(xiě)的動(dòng)態(tài)鏈接庫(kù),這就有點(diǎn)無(wú)語(yǔ)了。。。
2. C++ 庫(kù)為什么會(huì)崩
要想尋找答案,最好的辦法就是觀察 000000006c942893 處的匯編代碼,參考如下:
0:107> ub 00000000`6c942893
libxxx_manage!get_clean_xxx:
00000000`6c942876 55 push rbp
00000000`6c942877 53 push rbx
00000000`6c942878 4883ec68 sub rsp,68h
00000000`6c94287c 488dac2480000000 lea rbp,[rsp+80h]
00000000`6c942884 48894d00 mov qword ptr [rbp],rcx
00000000`6c942888 c745dc00000000 mov dword ptr [rbp-24h],0
00000000`6c94288f 488b4500 mov rax,qword ptr [rbp]
0:107> u 00000000`6c942893
00000000`6c942893 488b00 mov rax,qword ptr [rax]
0:107> dp rbp L1
0000005e`0dc7c4c0 00000000`00000000
從上面的匯編代碼來(lái)看,這是 get_clean_xxx 方法的序幕代碼,問(wèn)題出在 rbp 的內(nèi)容為0上,但 rbp 又來(lái)自于 rcx,根據(jù) x64調(diào)用協(xié)定,rcx 即方法的第一個(gè)參數(shù),看樣子是這個(gè)參數(shù)為 null 導(dǎo)致的,參考如下:
0:107> !address rcx
Usage: Stack
Base Address: 0000005e`0dc78000
End Address: 0000005e`0dc80000
Region Size: 00000000`00008000 ( 32.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 0000005e`0db00000
Allocation Protect: 00000004 PAGE_READWRITE
More info: ~107k
0:107> dp rcx L1
0000005e`0dc7c4a0 00000000`00000000
3. get_clean_xxx 參數(shù)為null嗎
這個(gè)問(wèn)題比較簡(jiǎn)單,繼續(xù)用 !clrstack 觀察下 Pinvoke 之上的 C# 代碼。
0:107> !clrstack
OS Thread Id: 0x3508 (107)
Child SP IP Call Site
0000005E0DC7DBA0 00007ffac066ed7b [InlinedCallFrame: 0000005e0dc7dba0] xxx_LibPInvoke.xxx_clean_query(IntPtr)
0000005E0DC7DB70 00007ffac066ed7b ILStubClass.IL_STUB_PInvoke(IntPtr)
0000005E0DC7DC30 00007ffac06b73a4 xx+c__DisplayClass11_0.<xxxQueryClean>b__0(IntPtr)
...
接下來(lái)就是看下托管層的 C# 代碼是如何寫(xiě)的,截圖如下:
從圖中可以清楚的看到,xxxChannel 傳給C++ 的時(shí)候沒(méi)有判斷是否為null,導(dǎo)致崩潰的發(fā)生,那還有沒(méi)有其他的佐證呢?其實(shí)也是有的,如果符號(hào)給力還可以使用 !clrstack -a 去找到 xxxChannel 傳下去的值。
0:107> !clrstack -a
OS Thread Id: 0x3508 (107)
Child SP IP Call Site
0000005E0DC7DBA0 00007ffac066ed7b [InlinedCallFrame: 0000005e0dc7dba0] xxx_LibPInvoke.xxx_clean_query(IntPtr)
0000005E0DC7DB70 00007ffac066ed7b ILStubClass.IL_STUB_PInvoke(IntPtr)
PARAMETERS:
<no data>
0000005E0DC7DC30 00007ffac06b73a4 xxx+c__DisplayClass11_0.<xxxQueryClean>b__0(IntPtr)
PARAMETERS:
this (0x0000005E0DC7DC80) = 0x0000020a9d9ca8d8
xxxChannel (0x0000005E0DC7DC88) = 0x0000000000000000
LOCALS:
0x0000005E0DC7DC6C = 0x0000000000000000
0x0000005E0DC7DC68 = 0x0000000000000000
可以清楚的看到確實(shí)是 0,到這里就一切真相大白,對(duì)參數(shù)加一個(gè)判斷即可,那這東西到底是誰(shuí)的責(zé)任呢?我覺(jué)得雙方都有問(wèn)題吧。
- 寫(xiě)托管層的人有點(diǎn)飄。
- 寫(xiě)非托管層的人未作防御性編程,還是年輕太相信人了。
三、總結(jié)
這次生產(chǎn)事故徹底破壞了兩個(gè)語(yǔ)言團(tuán)隊(duì)之間的相互合作的信任度,信任重建可就難了,不怕神一樣的對(duì)手,就怕豬豬一樣的隊(duì)友,放在這里還是挺合適的,哈哈,開(kāi)個(gè)小玩笑。