1. 程式人生 > >windowsclient崩潰分析和調試

windowsclient崩潰分析和調試

而在 ros lag this指向 eas 位置 call 即使 多個實例

本文介紹windows上崩潰分析的一些手段,順便提多進程調試、死鎖等。



1.崩潰分析過程
1.1 確認錯誤碼
不管是用windbg還是用vs。首先應該註意的是錯誤碼,而90%以上的崩潰都是非法訪問。


在非法訪問時。能夠看一下訪問的目標地址。

地址是0,或者離0非常近(0x00000008或0xfffffffc)。
一般和空指針相關。假設是一個貌似正常的地址,通常是對象已析構後訪問其數據,或者堆破壞。

1.2確認崩潰相應的C++操作
什麽是確認崩潰相應的C++操作:
比方非法訪問,通常得有個mov指令才會觸發內存訪問,然後導致崩潰。而mov指針相應於C++的哪一步呢?
比方a->b->c->foo();
在看到源代碼時,會定位於這一行,可是,並不清楚是哪一步訪問失敗。

所以這個時候要查看相應匯編代碼。
大概會有好幾個mov,簡單的分析就知道是哪一步時訪問失敗。



對編碼的影響:
這就要求,不要在單個語句中寫太復雜的東西比方
x ? b[i] : y > 0 ? c->member[8] : *ptr;
這種代碼崩潰。要還原到錯誤的地方非常難。



虛函數調用:
通常
mov edx, dword ptr [ecx]
mov edx, dword ptr [edx+0x??

]
call edx
意味著虛函數調用。每一行都可能是崩潰位置(在call內崩潰時,vs會標註出下一條語句的位置)。
在第一行崩潰意味著拿到一個非法指針,可能是空,也能夠指向非法地址。


在第二行崩潰意味著對象已經析構,ecx指向能夠訪問,可是值不正確。所以拿到的虛函數表不正確。


在最後一行崩潰一般另一個崩潰棧,可是看不到棧幀,在vs中相應的棧楨顯示一個地址。沒其他內容。
也一般意味著對象析構。

對象及析構函數:
析構函數是常常發生崩潰的地方,假設沒實用戶提供的析構函數,會定位到幾行匯編。所以沒事,就寫個
析構函數吧,至少能定位到是析構函數。
在析構函數外部還會有一大堆匯編代碼,裏面是對象成員析構的代碼。崩潰在裏面的時候,難以確認
是哪個對象析構。
假設用指針,在析構函數中主動調用Release或delete,這樣能夠顯示調用,不用去猜是誰在析構,當然
用指針或值對象。在邏輯上各有其優點。在此不表。
假設崩潰位置是call或jmp到某個A::~A()的位置。能夠推測到析構的對象的類型是A。
對象析構順序是從後往前,從子類到基類,依據這點,結合崩潰位置。能夠推測誰析構。
利用對象布局,比方成員在對象中的偏移,可能有得於推測誰析構出問題。
能夠人為地在對象布局中引入一些填充的字節,使得能看到this對象(線上崩潰沒有堆上的數據,由於
截取fulldump並上報有操作上的難度。所以this指向堆上時可能看不到,而在棧上則有可能),有利於分析。

還原上下文:
線上拿到的dump的信息少,不能調試。所以能夠依據崩潰所在模塊。崩潰在模塊中的偏移量,在本地
調試相應的bin,找到相應的模塊。偏移量。打上斷點,能夠在本地還原出崩潰時的運行環境。

當然,
在本地運行到相應位置時不一定崩潰,可是,有了很多其它上下文信息。能夠比較easy確定相應的C++操作。



使用IDA:
能夠使用IDA讓匯編代碼更好看,較easy分析流程。



關閉alsr。指定建議模塊載入地址:
這樣可能使得更easy分析。

可是會使得安全性減少,能夠用於小流量版本號。

ln指令:
windbg中ln指令能夠依據地址。還原出相應的信息,比方該地址是在某個類的某個方法中。

有時可能會
還原出幾個信息:A::foo() + 0x?

?, B::foo1() + 0x??。這就須要自己依據上下文推斷了。

代碼優化:
代碼優化使得分析更難,能夠嘗試改變一些編譯選項,減少優化級別,保留棧楨,關閉應用程序全局優化,
使得在release下的分析easy些。

所見並不是真實:
windbg和vs看到的棧幀可能是假的:可能在中間某一些可能已經亂了,可能棧楨省略使得vs分析的結果不正確。


(通常是vs分析得不正確。另外也有windbg杯具的時候)
對於在中間已經亂掉的棧。能夠依據返回地址。棧參數,棧楨省略數據等,又一次還原出棧。只是在90%的情況
下,即使還原出來,也不知道下一步怎麽辦。



對象還原:
線上崩潰沒有堆,能夠將感興趣的對象拷貝復制到棧上(自己得控制深拷貝)。然後崩潰上報中就能夠看到
對象的狀態了。

(註意代碼優化可能使得拷貝無效)

1.3C++上的邏輯
在確定崩潰和C++操作的關系後,就是自己邏輯上的問題了,基本上能遇到的問題都是對象生命周期管理
不當,進而造成非法訪問。
指針的判空能規避一處的非法訪問,可是能夠把錯誤進一步擴散。指針判空。且用且珍惜。

在設計或編碼時,應當考慮代碼的可調試性。比方chromium中的線程池中,加入任務時,會生成當前調用
信息。和task綁定,以使於定位錯誤。

1.4堆破壞
基本無解,崩潰現場和引入錯誤的點相差太遠。

僅僅能盡人事,聽天命了。
比方,開一下頁堆。存在一定概率使得崩潰出現,看人品。


比方。換一個CRT堆。或者自己寫個,增強錯誤檢測。
比方。CRT本身。尤其是調試的堆,堆上有些填充信息。使得在看到的時候或多或少嘆口氣:大概認識這些
填充信息,想要很多其它的信息,難啊。。


比方。能夠自己寫個調試器,自己插入頁堆,或者使用系統的頁堆。使得檢測自己主動化,然後通過大規模數據
使之重現。

2.其他
多進程調試:
能夠通過在
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
建立關心的進程名的項。填上debugger鍵值,值為調試器路徑,使得進程創建時就attach。(gflags也是
改這裏)。

可是問題是,有的模塊是按需載入的,在這個時候還不能在相應模塊中下斷點。
另外能夠自己在關心的位置加上MessageBox或ATLASSERT之類的代碼,等彈出對話框時再attach到相應進程。

activex:
attach方法同理。

可是,IE的多進程模型會使得attach不方便。


在IE9及以上,其進程模型是一個主進程,控制多個tab進程,按一定規則創建tab進程,將任務分派到
tab上。同一個網頁打開兩次,可能分配到不同的進程上,也可能是同樣的進程。在同一個進程中,同一
個activex可能有多個實例,並且每一個實例相應的主線程還不一定是同一個線程。
通常會控制僅僅開一個tab,使得調試更加easy。另外能夠開多tab。然後關閉,然後再開,來測試同一個進程
有多外activex實例的情況。更進一步,能夠自己調用IWebBrowser2,來模擬很多其它的情況。

np插件:
chrome中這個簡單些。一個插件一個進程,多個實例。共享主線程。
還有些開源工具將activex適配為np插件,使得能夠在chrome中調用ax。調試。

多機調試:
前面說過,windbg,vs都支持。



死鎖:
死鎖現場不會是線上問題(能夠通過一定手段,使得在線上發生死鎖時報告。可是基本上沒用過,相應的
手段在線下有玩過)。線下問題通常會有現場,或者能拿到full dump。

一般使用windbg來看,用~*kb或者
這系列的命令看看線程都在幹什麽。而重點關註的則是WaitForSingleObjectEx之類的調用。

通過分析調用
相應的參數。進一步能還原出,拿到 分發器 對象後不歸還的線程是誰。

或者也能夠!runaway找到占用CPU
高的線程,然後看看該線程在幹什麽。線程循環等待,則死鎖了。線程一直在那裏跑,可能是死循環了。

線下的死鎖檢測,一般能夠向主線程發一個消息來實現。

warning:有可能某個線程拿到分發器對象,可是該線程已經掛掉了。

windowsclient崩潰分析和調試