vc++程序崩潰後不生成dump文件

原文鏈接:https://blog.csdn.net/lixiangminghate/article/details/50413924

轉自https://blog.csdn.net/lixiangminghate/article/details/50413924
    這幾天給自己的程序通過SetUnhandleExceptionFilte加dump機制。實測時發現不是所有的崩潰,都能生成dump文件:比如assert(false);語句就沒有生成dump文件。上網查了一下發現跟我有相同困惑的人還不少,比如這篇

"

很多軟件通過設置自己的異常捕獲函數,捕獲未處理的異常,生成報告或者日誌(例如生成mini-dump文件),達到Release版本下追蹤Bug的目的。但是,到了VS2005(即VC8),Microsoft對CRT(C運行時庫)的一些與安全相關的代碼做了些改動,典型的,例如增加了對緩衝溢出的檢查。新CRT版本在出現錯誤時強制把異常拋給默認的調試器(如果沒有配置的話,默認是Dr.Watson),而不再通知應用程序設置的異常捕獲函數,這種行爲主要在以下三種情況出現。

(1)       調用abort函數,並且設置了_CALL_REPORTFAULT選項(這個選項在Release版本是默認設置的)。

(2)       啓用了運行時安全檢查選項,並且在軟件運行時檢查出安全性錯誤,例如出現緩存溢出。(安全檢查選項 /GS 默認也是打開的)

(3)       遇到_invalid_parameter錯誤,而應用程序又沒有主動調用

_set_invalid_parameter_handler設置錯誤捕獲函數。

所以結論是,使用VS2005(VC8)編譯的程序,許多錯誤都不能在SetUnhandledExceptionFilter捕獲到。這是CRT相對於前面版本的一個比較大的改變,但是很遺憾,Microsoft卻沒有在相應的文檔明確指出。

"

這篇文章的作者其實已經寫的很好了。這裏順帶提一下,他爲了解決SetUnhandledExceptionFilter再次被調用,通過inline hook的方式使得SetUnhandledExceptionFilter什麼都不做直接返回。我在此處引用作者代碼並略作註解

void DisableSetUnhandledExceptionFilter()
{
void addr = (void)GetProcAddress(LoadLibrary(_T(“kernel32.dll”)),
“SetUnhandledExceptionFilter”);
if (addr)
{
unsigned char code[16];
int size = 0;
 //下面兩個字節是xor eax,eax的Opcode
code[size++] = 0x33;
code[size++] = 0xC0;
//下面三個字節是ret 0x0004的Opcode
code[size++] = 0xC2;
code[size++] = 0x04;
code[size++] = 0x00;

           DWORD dwOldFlag, dwTempFlag;
          VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
          WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
          VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
   }

}

當然,如果在這篇文章的基礎上再深挖一點,會明白因爲vs crt庫自作主張的通過CxxUnhandledExceptionFilter調用了SetUnhandledExceptionFilter覆蓋了前面自定義的UEH,會導致c++異常和win異常走了兩條路:
下面內容節選自widows用戶態程序高效排錯第二章:

案例分析:華生醫生(Dr. Watson)在什麼情況下不能記錄Dump文件

問題描述

客戶聲稱用VC開發的程序偶爾會崩潰。爲了獲取詳細信息,客戶激活了Dr. Watson,以便程序崩潰的時候可以自動獲取dump文件。但是問題再次發生後,Dr. Watson並沒有記錄dump文件。

背景知識

dump文件包含的是內存鏡像信息。在Windows系統上,dump文件分爲內核dump和用戶態dump兩種。前者一般用來分析內核相關的問題,比如驅動程序;後者一般用來分析用戶態程序的問題。如果不作說明,本書後面所指的dump都表示用戶態dump。用戶態的dump又分成mini dump和full dump。前者尺寸小,只記錄一些常用信息;後者則是把目標進程用戶態的所有內容都記錄下來。Windows提供了MiniDumpWriteDump API可供程序調用來生成mini dump。通過調試器和相關工具,可以抓取目標程序的full dump。拿到dump後,可以通過調試器檢查dump中的內容,比如call stack,memory,exception等等。關於dump和調試器的更詳細信息,後面會有更多介紹。跟Dr. Watson相關的文檔是:

Description of the Dr. Watson for Windows (Drwtsn32.exe) Tool

http://support.microsoft.com/?id=308538

Specifying the Debugger for Unhandled User Mode Exceptions

http://support.microsoft.com/?id=121434

INFO: Choosing the Debugger That the System Will Spawn

http://support.microsoft.com/?id=103861

也就是說,通過設定註冊表中的AeDebug項,可以在程序崩潰後,選擇調試器進行調試。選擇Dr. Watson就可以直接生成dump文件。

問題分析

回到這個問題,客戶並沒有獲取到dump文件,可能性有兩個:

1.         Dr. Watson工作不正常。

2.         客戶的程序根本沒有崩潰,不過是正常退出而已。

爲了測試第1點,提供瞭如下的代碼給客戶測試:

int *p=0;

*p=0;

測試上面的代碼,Dr. Watson成功地獲取了dump文件。也就是說,Dr. Watson工作是正常的。那看來客戶聲稱的崩潰可能並不是unhandled exception導致的。說不定在非預料情況下調用了ExitProcess,被客戶誤認爲是崩潰。所以,抓取信息不應該侷限於unhandled exception,而應該檢查進程退出的原因。

當程序在Windbg調試器中退出的時候,系統會觸發調試器的進程退出消息,可以在這個時候抓取dump來分析進程退出的原因。

如果讓客戶每次都先啓動Windbg,然後用Windbg啓動程序,操作起來很複雜。最好有一個自動的方法。Windows提供了讓指定程序隨調試器啓動的選項。設定註冊表後,當設定的進程啓動的時候,系統先啓動指定的調試器,然後把目標進程的地址和命令行作爲參數傳遞給調試器,調試器再啓動目標進程調試。這個選項在無法手動從調試器中啓動程序的時候特別有用,比如調試先於用戶登錄而啓動Windows Service程序,就必須使用這個方法:

How to debug Windows services

http://support.microsoft.com/?kbid=824344

有趣的是,好多惡意程序也通過這個方法來達到加載進程的目的。很多人把這個方法叫做IFEO 劫持(Image File Execution Option Hacking)。

在Windbg目錄下,有一個叫做adplus.vbs的腳本可以方便地調用Windbg來獲取dump文件。所以這裏可以借用這個腳本:

How to use ADPlus to troubleshoot “hangs” and “crashes”

http://support.microsoft.com/kb/286350/EN-US/

腳本的詳細說明可以參考adplus /?的幫助。

新的做法

結合上面的信息,具體做法是:

1.         在客戶機器的Image File Execution Options註冊表下面創建跟問題程序同名的鍵。

2.         在這個鍵的下面創建Debugger字符串類型子鍵。

3.         設定Debugger= C:/Debuggers/autodump.bat。

4.         編輯C:/Debuggers/autodump.bat文件的內容爲如下:

cscript.exe C:/Debuggers/adplus.vbs -crash -o C:/dumps -quiet -sc %1

通過上面的設置,當程序啓動的時候,系統自動運行cscript.exe來執行adplus.vbs腳本。Adplus.vbs腳本的-sc參數指定需要啓動的目標進程路徑(路徑作爲參數又系統傳入,bat文件中的%1代表這個參數),-crash參數表示監視進程退出,-o參數指定dump文件路徑,-quiet參數取消額外的提示。可以用notepad.exe作爲小白鼠做一個實驗,看看關閉notepad.exe的時候,是否有dump產生。

根據上面的設定,問題再次發生後,C:/dumps目錄生成了兩個dump文件。文件名分別是:

PID-0__Spawned0__1st_chance_Process_Shut_Down__full_178C_DateTime_0928.dmp

PID-0__Spawned0__2nd_chance_CPlusPlusEH__full_178C_2006-06-21_DateTime_0928.dmp

注意看第二個的名字,這個名字表示發生2nd chance的C++ exception!打開這個dump後找到了對應的call stack,發現的確是客戶忘記了catch潛在的C++異常。修改代碼添加對應的catch後,問題解決。

問題解決了,可是爲什麼華生醫生(Dr. Watson)抓不到dump呢

當然疑問並沒有隨着問題的解決而結束。既然是unhandled exception導致的crash,爲什麼Dr. Watson抓不到呢?首先創建兩個不同的程序來測試Dr. Watson的行爲:

int _tmain(int argc, _TCHAR* argv[])

{

throw 1;

return 0;

}

int _tmain(int argc, _TCHAR* argv[])

{

int *p=0;

*p=0;

return 0;

}

果然,對於第一個程序,Dr. Watson並沒有保存dump文件。對於第二個,Dr. Watson工作正常。看來的確跟異常類型相關。

仔細回憶一下。當AeDebug下的Auto設定爲0的時候,系統會彈出前面提到的紅色框框。對於上面這兩個程序,框框的內容是不一樣的。

在我這裏,看到的對話框分別是(對話框出現的時候用Ctrl+C保存的信息):


Microsoft Visual C++ Debug Library


Debug Error!

Program: d:/xiongli/today/exceptioninject/debug/exceptioninject.exe

This application has requested the Runtime to terminate it in an unusual way.

Please contact the application’s support team for more information.

(Press Retry to debug the application)


Abort   Retry   Ignore



exceptioninject.exe - Application Error


The instruction at “0x00411908” referenced memory at “0x00000000”. The memory could not be “written”.

Click on OK to terminate the program

Click on CANCEL to debug the program


OK   Cancel


兩者行爲完全不一樣!如果做更多的測試,會發現對話框的細節還跟編譯模式release/debug 相關。

程序可以通過SetUnhandledExceptionFilter函數來修改unhanded exception的默認處理函數。這裏,C++運行庫在初始化CRT(C Runtime)的時候,傳入了CRT的處理函數 (msvcrt!CxxUnhandledExceptionFilter)。如果發生unhandled exception,該函數會判斷異常的號碼,如果是C++異常,就會彈出第一個對話框,否則就交給系統默認的處理函數(kernel32!UnhandledExceptionFilter)處理。第一種情況的call stack 如下:

USER32!MessageBoxA

MSVCR80D!__crtMessageBoxA

MSVCR80D!__crtMessageWindowA

MSVCR80D!_VCrtDbgReportA

MSVCR80D!_CrtDbgReportV

MSVCR80D!_CrtDbgReport

MSVCR80D!_NMSG_WRITE

MSVCR80D!abort

MSVCR80D!terminate

MSVCR80D!__CxxUnhandledExceptionFilter

kernel32!UnhandledExceptionFilter

MSVCR80D!_XcptFilter

第二種情況CRT交給系統處理。Callstack如下:

ntdll!KiFastSystemCallRet

ntdll!ZwRaiseHardError+0xc

kernel32!UnhandledExceptionFilter+0x4b4

release_crash!_XcptFilter+0x2e

release_crash!mainCRTStartup+0x1aa

release_crash!_except_handler3+0x61

ntdll!ExecuteHandler2+0x26

ntdll!ExecuteHandler+0x24

ntdll!KiUserExceptionDispatcher+0xe

release_crash!main+0x28

release_crash!mainCRTStartup+0x170

kernel32!BaseProcessStart+0x23

詳細的信息可以參考:

SetUnhandledExceptionFilter

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/setunhandledexceptionfilter.asp

UnhandledExceptionFilter

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/unhandledexceptionfilter.asp

上面觀察到的信息能解釋Dr. Watson的行爲嗎?看起來似乎有關係。爲了進一步確認這個問題,可以通過下面的測試,使用Windbg代替Dr. Watson,看看是否可以獲取dump。如果僅僅換一個調試器就可以獲取dump,那說明問題是跟調試器相關,跟程序拋出的異常無關。具體做法是:

1.         運行drwtsn32.exe –i註冊Dr. Watson。

2.         打開AeDebug註冊表,找到Debugger項,裏面應該是drwtsn32 -p %ld -e %ld -g。

3.         修改Debugger爲: C:/debuggers/windbg.exe -p %ld -e %ld -c “.dump /mfh C:/myfile.dmp ;q”。

當unhanded exception發生後,系統會啓動windbg.exe作爲調試器加載到目標進程。但是windbg.exe不會自動獲取dump,所以需要用-c參數來指定初始命令。命令之間可以用分開分割。這裏的.dump /mfh C:/myfile.dmp命令就是用來生成dump文件的。接下來的q命令是讓windbg.exe在dump生成完畢後自動退出。用這個方法,對於unhandled C++ exception,windbg.exe是可以獲取dump文件的。所以我認爲Dr. Watson這個工具在獲取dump的時候是有缺陷的。研究的發現在:

http://eparg.spaces.msn.com/blog/cns!59BFC22C0E7E1A76!1213.entry
————————————————
版權聲明:本文爲CSDN博主「Yuri800」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lixiangminghate/article/details/50413924

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章