昨晚給訓(xùn)練營(yíng)里面的一位朋友分析了一個(gè)程序崩潰的故障,因?yàn)榭葱』镒幼蛱煸谌豪飭?wèn)了一天也沒(méi)搞定,干脆自己親自上陣吧,抓取的dump也是我極力推薦的用 procdump 注冊(cè) AEDebug 的方式,省去了很多溝通成本。
windbg有一個(gè)非常強(qiáng)大的點(diǎn)就是當(dāng)你雙擊打開(kāi)后,會(huì)自動(dòng)幫你切換到崩潰的線程以及崩潰處的匯編代碼,省去了 !analyze -v 命令的龜速輸出,參考信息如下:
...................................................................................................................This dump file has an exception of interest stored in it.The stored exception information can be accessed via .ecxr.(10f4.f58): Access violation - code c0000005 (first/second chance not available)For analysis of this file, run !analyze -veax=00000000 ebx=00000000 ecx=00000040 edx=00000000 esi=004c1b98 edi=07a8ed4ceip=7008508f esp=07a8ec74 ebp=07a8ec80 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246clr!Thread::GetSafelyRedirectableThreadContext+0x7c:7008508f 8038eb cmp byte ptr [eax],0EBh ds:002b:00000000=??...
從卦中可以看到,當(dāng)前崩潰是因?yàn)?eax=0 導(dǎo)致的,那為什么 eax 等于 0 呢?要想尋找這個(gè)答案,需要觀察崩潰前的線程棧上下文,可以使用命令 .ecxr;k 9 即可。
0:009> .ecxr;k 9eax=00000000 ebx=00000000 ecx=00000040 edx=00000000 esi=004c1b98 edi=07a8ed4ceip=7008508f esp=07a8ec74 ebp=07a8ec80 iopl=0 nv up ei pl zr na pe nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246clr!Thread::GetSafelyRedirectableThreadContext+0x7c:7008508f 8038eb cmp byte ptr [eax],0EBh ds:002b:00000000=?? # ChildEBP RetAddr 00 07a8ec80 6fe7f6cd clr!Thread::GetSafelyRedirectableThreadContext+0x7c01 07a8f030 6fe7f2f3 clr!Thread::HandledJITCase+0x3102 07a8f0a4 6fee23da clr!Thread::SuspendRuntime+0x26003 07a8f184 6fedf72d clr!WKS::GCHeap::SuspendEE+0x1fe04 07a8f1b0 6fe309ca clr!WKS::GCHeap::GarbageCollectGeneration+0x16805 07a8f1c0 6fe30a2e clr!WKS::GCHeap::GarbageCollectTry+0x5606 07a8f1e4 6fe30a90 clr!WKS::GCHeap::GarbageCollect+0xa507 07a8f230 6f058b01 clr!GCInterface::Collect+0x5d08 07a8f26c 055fa4b1 mscorlib_ni+0x3b8b01
從卦中信息看,尼瑪,真無(wú)語(yǔ)了 GCInterface::Collect 說(shuō)明有人用 GC.Collect() 手工觸發(fā)GC,不知道為什么要這么做來(lái)污染GC內(nèi)部的統(tǒng)計(jì)信息,不管怎么說(shuō)這個(gè)肯定不是崩潰的原因。
我們繼續(xù)觀察線程棧,可以看到它的邏輯大概是這樣的,通過(guò) SuspendRuntime 把所有的托管線程進(jìn)行邏輯上暫停,在暫停其中的一個(gè)線程時(shí)拋出了異常。
稍微提醒一下,這個(gè) HandledJITCase 方法是用 ip 劫持技術(shù)將代碼引入到 coreclr 中進(jìn)行 GC完成等待,這種神操作有些殺毒軟件會(huì)認(rèn)為是病毒!!!
有些朋友肯定會(huì)說(shuō),有沒(méi)有代碼支撐。。。這里我就找一下 coreclr 的源碼貼一下吧。
void ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason){ while ((thread = ThreadStore::GetThreadList(thread)) != NULL) { ... if (workingOnThreadContext.Acquired() && thread->HandledJITCase()) { ... } ... }}
結(jié)合源碼分析思路就非常清晰了,這里的 thread->HandledJITCase() 中的 thread 到底是哪一個(gè)線程?可以觀察 kb 輸出然后用 !t 去做比對(duì)。
圖片
從卦中看,當(dāng)前 GC 正在 Suspend 主線程,并且還看到了主線程有一個(gè) System.AccessViolationException 異常,無(wú)語(yǔ)了。。。
主線程進(jìn)入到視野之后,那就重點(diǎn)關(guān)注一下它,可以用 k 看一下輸出。
0:009> ~0seax=00000000 ebx=0029ea50 ecx=0029ea90 edx=00000000 esi=7efdb800 edi=000d0000eip=00000000 esp=0029ea4c ebp=75146381 iopl=0 nv up ei pl nz na po nccs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=0021020200000000 ?? ???0:000> k00 75146381 7efdb800 0x001 75146381 7517fa04 0x7efdb80002 0029ea80 7736013a user32!__fnHkINLPKBDLLHOOKSTRUCT+0x2803 0029eae4 7514908d ntdll!KiUserCallbackDispatcher+0x2e04 0029eae4 076e3912 user32!CallNextHookEx+0x8405 0029eb28 076e3064 0x76e391206 0029eb5c 0011d48f xxx!xxx.ScanerHook.KeyboardHookProc+0xe407 0029eb8c 75146381 0x11d48f08 0029eba8 7517fa04 user32!DispatchHookW+0x3809 0029ebd8 7736013a user32!__fnHkINLPKBDLLHOOKSTRUCT+0x280a 0029ec3c 751406eb ntdll!KiUserCallbackDispatcher+0x2e0b 0029ec3c 75140751 user32!_PeekMessage+0x880c 0029ec68 6d8af3bf user32!PeekMessageW+0x108...
從卦象看,這卦非常奇怪,有如下兩點(diǎn)信息:
熟悉 eip 的朋友應(yīng)該知道,它相當(dāng)于一輛車的方向盤(pán),一輛高速行駛的車突然沒(méi)了方向盤(pán),真的太可怕了,最后必然車毀人亡。
在匯編中是因?yàn)閑ax=0導(dǎo)致,而這里eip恰好也等于0,仿佛冥冥之中自有牽連,帶著強(qiáng)烈的好奇心我們來(lái)反匯編下 GetSafelyRedirectableThreadContext 方法邏輯,簡(jiǎn)化后如下:
0:000> uf 7008508fclr!Thread::GetSafelyRedirectableThreadContext:6fe7f60e 55 push ebp6fe7f60f 8bec mov ebp,esp6fe7f611 53 push ebx6fe7f612 56 push esi6fe7f613 57 push edi6fe7f614 8bf1 mov esi,ecx...7008506d ffe9 jmp rcx7008506f fd std70085070 c1daff rcr edx,0FFh70085073 f6450801 test byte ptr [ebp+8],170085077 0f84efa5dfff je clr!Thread::GetSafelyRedirectableThreadContext+0xcc (6fe7f66c)7008507d 8b8604010000 mov eax,dword ptr [esi+104h]70085083 3987b8000000 cmp dword ptr [edi+0B8h],eax70085089 0f85dda5dfff jne clr!Thread::GetSafelyRedirectableThreadContext+0xcc (6fe7f66c)7008508f 8038eb cmp byte ptr [eax],0EBh
從上面的匯編代碼看eax的取值鏈條是: eax <- esi+104h <- ecx ,很顯然這里的 ecx 是 thiscall 協(xié)議中的 Thread=004c1b98 參數(shù),可以用 dp 驗(yàn)證下。
0:000> dp 004c1b98+0x104 L1004c1c9c 00000000
從卦中看果然是 0,有些朋友好奇這個(gè) 104 偏移到底是個(gè)什么東西,參考 coreclr 源碼其實(shí)就是 m_LastRedirectIP 字段,參考如下:
BOOL Thread::GetSafelyRedirectableThreadContext(DWORD dwOptions, CONTEXT* pCtx, REGDISPLAY* pRD){ if (!EEGetThreadContext(this, pCtx)) { return FALSE; } ... if (GetIP(pCtx) == m_LastRedirectIP) { const BYTE short_jmp = 0xeb; const BYTE self = 0xfe; BYTE* ip = (BYTE*)m_LastRedirectIP; if (ip[0] == short_jmp && ip[1] == self) m_LastRedirectIP = 0; return FALSE; }}
結(jié)合匯編代碼其實(shí)我們崩潰在 ip[0] == short_jmp 這一句上,仔細(xì)分析上面的C++代碼會(huì)發(fā)現(xiàn)一個(gè)很奇怪的信息,那就是為什么 GetIP(pCtx)= 0,接下來(lái)用 dt 觀察下寄存器上下文。
0:009> kb 2 # ChildEBP RetAddr Args to Child 00 07a8ec80 6fe7f6cd 00000003 07a8ed4c 07a8ecf0 clr!Thread::GetSafelyRedirectableThreadContext+0x7c01 07a8f030 6fe7f2f3 004c1b98 0b367326 76a016a1 clr!Thread::HandledJITCase+0x310:009> dt _CONTEXT 07a8ed4cntdll!_CONTEXT +0x000 ContextFlags : 0x10007 ... +0x01c FloatSave : _FLOATING_SAVE_AREA +0x08c SegGs : 0x2b +0x090 SegFs : 0x53 +0x094 SegEs : 0x2b +0x098 SegDs : 0x2b +0x09c Edi : 0xd0000 +0x0a0 Esi : 0x7efdb800 +0x0a4 Ebx : 0x29ea50 +0x0a8 Edx : 0 +0x0ac Ecx : 0x29ea90 +0x0b0 Eax : 0 +0x0b4 Ebp : 0x75146381 +0x0b8 Eip : 0 +0x0bc SegCs : 0x23 +0x0c0 EFlags : 0x210202 +0x0c4 Esp : 0x29ea4c ...
從卦中看果然 eip=0,這是一個(gè)非常錯(cuò)誤的信息,還有一點(diǎn)就是 m_LastRedirectIP 字段一般用來(lái)處理一些比較詭異的兼容性問(wèn)題,所以這里兩個(gè)字段都是 0 導(dǎo)致崩潰的產(chǎn)生。
有了上面的信息,我們就知道了前因后果,原來(lái)是主線程車毀人亡(eip=0),導(dǎo)致GC無(wú)法暫停它,在內(nèi)部拋出了代碼異常,你可以說(shuō)是 CLR 的bug,也可以說(shuō)是主線程的Bug,所以給到的解決方案就是:
說(shuō)實(shí)話要想解釋這個(gè)程序?yàn)槭裁磿?huì)崩潰,需要分析者對(duì)GC的SuspendRuntime運(yùn)作邏輯有一定的了解,否則真抓瞎了,所以.NET調(diào)試訓(xùn)練營(yíng)中的GC理論知識(shí)一定是分析這些 dump 的基石。
本文鏈接:http://www.tebozhan.com/showinfo-26-80192-0.html記一次 .NET某防偽驗(yàn)證系統(tǒng)崩潰分析
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: 負(fù)載均衡原理最全詳解