(1) 調(diào)用abort函數(shù),并且設(shè)置了_CALL_REPORTFAULT選項(xiàng)(這個(gè)選項(xiàng)在Release版本是默認(rèn)設(shè)置的)。
(2) 啟用了運(yùn)行時(shí)安全檢查選項(xiàng),并且在軟件運(yùn)行時(shí)檢查出安全性錯(cuò)誤,例如出現(xiàn)緩存溢出。(安全檢查選項(xiàng) /GS 默認(rèn)也是打開(kāi)的)
(3) 遇到_invalid_parameter錯(cuò)誤,而應(yīng)用程序又沒(méi)有主動(dòng)調(diào)用
_set_invalid_parameter_handler設(shè)置錯(cuò)誤捕獲函數(shù)。
所以結(jié)論是,使用VS2005(VC8)編譯的程序,許多錯(cuò)誤都不能在SetUnhandledExceptionFilter捕獲到。這是CRT相對(duì)于前面版本的一個(gè)比較大的改變,但是很遺憾,Microsoft卻沒(méi)有在相應(yīng)的文檔明確指出。
解決方法
之所以應(yīng)用程序捕獲不到那些異常,原因是因?yàn)樾掳姹镜腃RT實(shí)現(xiàn)在異常處理中強(qiáng)制刪除所有應(yīng)用程序先前設(shè)置的捕獲函數(shù),如下所示:
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
UnhandledExceptionFilter(&ExceptionPointers);
解決方法是攔截CRT調(diào)用SetUnhandledExceptionFilter函數(shù),使之無(wú)效。在X86平臺(tái)下,可以使用以下代碼。
#ifndef _M_IX86
#error "The following code only works for x86!"
#endif
void DisableSetUnhandledExceptionFilter()
{
void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),
"SetUnhandledExceptionFilter");
if (addr)
{
unsigned char code[16];
int size = 0;
code[size++] = 0x33;
code[size++] = 0xC0;
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);
}
}
在設(shè)置自己的異常處理函數(shù)后,調(diào)用DisableSetUnhandledExceptionFilter禁止CRT設(shè)置即可。
其它討論
上面通過(guò)設(shè)置api hook,解決了在VS2005上的異常捕獲問(wèn)題,這種雖然不是那么“干凈”的解決方案,確是目前唯一簡(jiǎn)單有效的方式。
雖然也可以通過(guò)_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT), signal(SIGABRT, ...), 和_set_invalid_parameter_handler(...) 解決(1)(3),但是對(duì)于(2),設(shè)置api hook是唯一的方式。
http://blog.csdn.net/WinGeek/article/details/3942995
[Windows編程] 如何捕捉程序異常/crash 并生成 dump 文件
前面介紹如如何用WinDBG 生成crash dump 《WinDBG 技巧:如何生成Dump 文件(.dump 命令) 》,但是用戶(hù)機(jī)器上通常不安裝WinDBG, 而且多數(shù)用戶(hù)也不知道怎么使用WinDBG。 所以最好是自己程序里面能夠捕捉exception/crash,并且生成crash dump,然后通過(guò)網(wǎng)絡(luò)傳回到自己服務(wù)器。
捕捉exception 可以用API 函數(shù) SetUnhandledExceptionFilter 。生成crash dump 可以用DbgHelp.dll 里面的MiniDumpWriteDump 函數(shù)。
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter( __in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter );
BOOL WINAPI MiniDumpWriteDump( __in HANDLE hProcess, __in DWORD ProcessId, __in HANDLE hFile, __in MINIDUMP_TYPE DumpType, __in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, __in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, __in PMINIDUMP_CALLBACK_INFORMATION CallbackParam );
代碼示例:
- #include <dbghelp.h>
- #include <shellapi.h>
- #include <shlobj.h>
-
-
-
- LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pExceptionPointers)
- {
-
- SetErrorMode( SEM_NOGPFAULTERRORBOX );
-
-
- CStringW strBuild;
- strBuild.Format(L"Build: %s %s", __DATE__, __TIME__);
- CStringW strError;
- HMODULE hModule;
- WCHAR szModuleName[MAX_PATH] = L"";
- GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)pExceptionPointers->ExceptionRecord->ExceptionAddress, &hModule);
- GetModuleFileName(hModule, szModuleName, ARRAYSIZE(szModuleName));
- strError.AppenedFormat(L"%s %d , %d ,%d.", szModuleName,pExceptionPointers->ExceptionRecord->ExceptionCode, pExceptionPointers->ExceptionRecord->ExceptionFlags, pExceptionPointers->ExceptionRecord->ExceptionAddress);
-
-
- BOOL bMiniDumpSuccessful;
- WCHAR szPath[MAX_PATH];
- WCHAR szFileName[MAX_PATH];
- WCHAR* szAppName = L"AppName";
- WCHAR* szVersion = L"v1.0";
- DWORD dwBufferSize = MAX_PATH;
- HANDLE hDumpFile;
- SYSTEMTIME stLocalTime;
- MINIDUMP_EXCEPTION_INFORMATION ExpParam;
- GetLocalTime( &stLocalTime );
- GetTempPath( dwBufferSize, szPath );
- StringCchPrintf( szFileName, MAX_PATH, L"%s%s", szPath, szAppName );
- CreateDirectory( szFileName, NULL );
- StringCchPrintf( szFileName, MAX_PATH, L"%s%s//%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
- szPath, szAppName, szVersion,
- stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
- stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
- GetCurrentProcessId(), GetCurrentThreadId());
- hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE,
- FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
-
- MINIDUMP_USER_STREAM UserStream[2];
- MINIDUMP_USER_STREAM_INFORMATION UserInfo;
- UserInfo.UserStreamCount = 1;
- UserInfo.UserStreamArray = UserStream;
- UserStream[0].Type = CommentStreamW;
- UserStream[0].BufferSize = strBuild.GetLength()*sizeof(WCHAR);
- UserStream[0].Buffer = strBuild.GetBuffer();
- UserStream[1].Type = CommentStreamW;
- UserStream[1].BufferSize = strError.GetLength()*sizeof(WCHAR);
- UserStream[1].Buffer = strError.GetBuffer();
-
- ExpParam.ThreadId = GetCurrentThreadId();
- ExpParam.ExceptionPointers = pExceptionPointers;
- ExpParam.ClientPointers = TRUE;
-
- MINIDUMP_TYPE MiniDumpWithDataSegs = MiniDumpNormal
- | MiniDumpWithHandleData
- | MiniDumpWithUnloadedModules
- | MiniDumpWithIndirectlyReferencedMemory
- | MiniDumpScanMemory
- | MiniDumpWithProcessThreadData
- | MiniDumpWithThreadInfo;
- bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
- hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);
-
- ...
-
- return EXCEPTION_CONTINUE_SEARCH;
- }
-
- int _tmain()
- {
-
- SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
- ....
- return 0;
- }
http://blog.csdn.net/mergerly/article/details/6662797
VC++ Runtime Error 異常捕獲之不掛的程序
VC++ Runtime Error, 對(duì)不少朋友來(lái)說(shuō), 這是一個(gè)十分討厭的錯(cuò)誤提示, 您可能不知道如何著手調(diào)試: 產(chǎn)生這個(gè)錯(cuò)誤的原因是什么? 確實(shí)只有知道了產(chǎn)生這個(gè)錯(cuò)誤的直接原因, 才能去調(diào)試這個(gè)錯(cuò)誤.
剛碰到這個(gè)錯(cuò)誤的時(shí)候, 是發(fā)生在視頻解碼的時(shí)候, 由于解碼一直在工作狀態(tài), 所以我也不知道如何去調(diào)試, 當(dāng)出現(xiàn)這個(gè)錯(cuò)誤之后, 我們大多數(shù)時(shí)候就忽略了, 想從其他地方解決, 提高穩(wěn)定性, 甚至懷疑解碼器的穩(wěn)定性; 后來(lái), 我接觸解碼庫(kù)之后, 開(kāi)始調(diào)試這樣的錯(cuò)誤, 剛開(kāi)始這樣的錯(cuò)誤并不容易重現(xiàn), 往往要幾個(gè)小時(shí), 當(dāng)這個(gè)錯(cuò)誤重現(xiàn)之后, 程序還是在運(yùn)行的, 只是其中的某一個(gè)線(xiàn)程中斷了執(zhí)行, 其中的這個(gè)線(xiàn)程彈出了 "VC++ Runtime Error" 這樣的對(duì)話(huà)框, 如果你點(diǎn)擊它, 則整個(gè)應(yīng)用程序會(huì)直接退出. 為了調(diào)試, 我就不能點(diǎn)擊這個(gè)對(duì)話(huà)框, 而是使用VC2005附件到進(jìn)程, 然后再直接中斷進(jìn)程, 這個(gè)時(shí)候, 會(huì)有一個(gè)線(xiàn)程中斷點(diǎn)就在對(duì)話(huà)框的消息循環(huán)中, 仔細(xì)查看堆棧, 發(fā)現(xiàn)了一個(gè)函數(shù): msvcrt.dll!_abort() , 到這里是時(shí)候查看MSDN了:
函數(shù)名: abort
功 能: 異常終止一個(gè)進(jìn)程
用 法: void abort(void);
In a single or multithreaded Windows-based application, abort calls the Windows MessageBox function to create a message box to display the message with anOK button. When the user clicks OK, the program aborts immediately.
我們的程序就是基于WINDOS窗口的多線(xiàn)程應(yīng)用程序, 調(diào)用了abort就會(huì)彈出對(duì)話(huà)框, 在release版本中, 就是一個(gè)確認(rèn)對(duì)話(huà)框, 點(diǎn)擊后程序就提示出錯(cuò)并退出.
在正常的程序里, 我們是不會(huì)調(diào)用abort的, 除非是遇到了嚴(yán)重的, 不能恢復(fù)的錯(cuò)誤. 那么到底這個(gè)abort是怎么被調(diào)用的呢, 我們自己寫(xiě)的代碼顯然是沒(méi)有這個(gè)函數(shù), 再仔細(xì)查看堆棧, 發(fā)現(xiàn)是在一個(gè)C語(yǔ)言版本的開(kāi)源庫(kù)中. 我們的程序是需要7*24小時(shí)運(yùn)行的, 出現(xiàn)了解碼異常應(yīng)該要被我們忽略, 而不是應(yīng)用程序崩潰. 開(kāi)源的跨平臺(tái)解碼庫(kù)是C語(yǔ)言寫(xiě)的, 在出現(xiàn)了嚴(yán)重錯(cuò)誤時(shí), 就直接abort這也是可以理解的, 不過(guò), 這樣的程序在我們的代碼中顯然要避免. 大哥, 現(xiàn)在都是什么年代了, 很多程序都是需要一直跑的, 我只好改的庫(kù)的源代碼來(lái)重新編譯程序才能解決這個(gè)問(wèn)題了, 該怎么改了, 如果去分析解碼的邏輯, 我們沒(méi)有專(zhuān)業(yè)的人才. 我想就干脆從abort函數(shù)這里入手, 直接返回成功值, 但是這樣對(duì)解碼邏輯影響更大, 可能導(dǎo)致更大的錯(cuò)誤, 我想到了操作系統(tǒng)的異常機(jī)制, 由于我們是在WINDOWS平臺(tái)上工作, 所以可以利用WINDOWS結(jié)構(gòu)化異常, 我們可取消abort調(diào)用, 在這里我們使用代碼產(chǎn)生一個(gè)結(jié)構(gòu)化異常(SEH), 結(jié)構(gòu)化異常分為硬件異常和軟件異常, CPU可以檢查到內(nèi)存非法訪(fǎng)問(wèn)和除零錯(cuò)誤等異常, 那么我們就將abort替換成除零語(yǔ)句, 比如 int i = 10/0;
當(dāng)程序執(zhí)行到這里的時(shí)候, CPU會(huì)捕捉這個(gè)異常, 并提示用戶(hù), 我們可以在調(diào)用解碼函數(shù)的地方, 增加SEH捕捉代碼, 來(lái)捕捉這個(gè)錯(cuò)誤, 那么程序就能忽略這個(gè)錯(cuò)誤并繼續(xù)執(zhí)行了. 后來(lái)的事實(shí)也證明了這個(gè)錯(cuò)誤的忽略對(duì)程序并沒(méi)有什么明顯的影響. 怎么寫(xiě)這個(gè)捕捉代碼呢, 操作系統(tǒng)支持的SEH捕捉代碼塊為 __try - __finally 塊和 __try - __except 塊, 而__try - __finnaly塊就可以實(shí)現(xiàn)我們的功能. 寫(xiě)到這里, 可能有朋友要說(shuō)了, 我們平時(shí)見(jiàn)的最多的是try-catch語(yǔ)句, 那么我要解釋一下了, try-catch 是C++異常的處理方式, 而__try-__finnaly是操作系統(tǒng)SEH異常處理方式. 在C++語(yǔ)言的try-catch并不能捕捉操作系統(tǒng)結(jié)構(gòu)化異常(比如CPU異常, 內(nèi)存訪(fǎng)問(wèn)沖突, 除零錯(cuò)誤等). C++異常只能捕獲軟件異常, 通常是調(diào)用throw而產(chǎn)生的異常, 比如MFC異常中常見(jiàn)的CException.
SEH異常和C++異常有本質(zhì)的區(qū)別, SEH是操作系統(tǒng)提供的異常處理技術(shù), 在任何支持該操作系統(tǒng)的編程語(yǔ)言中, 都可以使用, 而C++異常處理只能在編寫(xiě)C++代碼時(shí)使用。然而, 應(yīng)當(dāng)知道WINDOWS的VC++編譯器是使用操作系統(tǒng)結(jié)構(gòu)化異常來(lái)實(shí)現(xiàn)C++異常的. 也就是說(shuō), C++的try塊在VC++下編譯時(shí), 會(huì)變成__try塊, C++的catch塊會(huì)變成SEH的 __except塊: catch測(cè)試則變成SEH異常過(guò)濾器, catch中的代碼則變?yōu)開(kāi)_except中的代碼. 事實(shí)上, C++的throw塊, 在編譯的時(shí)候也會(huì)變成SEH的RaiseException函數(shù)調(diào)用, 由c++異常變?yōu)镾EH異常.
__finnally的好處在于, 有時(shí)更詳細(xì)的異常信息對(duì)我們沒(méi)有更大幫助, 我們只需要捕獲到異常并忽略它。上面提到C++異常在VC++里被轉(zhuǎn)換成SEH異常, 那么在VC下使用try-catch是否能捕獲硬件異常呢? 比如我們常見(jiàn)的 0x0000000C 不可讀或?qū)? VC++編譯器已經(jīng)提供了支持:
try {;} catch(...){;} 這樣的語(yǔ)句就能夠捕獲所有異常:包括CPU異常, 以及C++異常; 不過(guò)需要注意的是, 在VC6.0中, 是默認(rèn)支持的. 但是在VC2005中, 是默認(rèn)不捕獲CPU異常的. 區(qū)別在于一個(gè)C++編譯選項(xiàng)/Eha , 只有這個(gè)選項(xiàng)打開(kāi)才能用上面的try-catch()捕捉SEH異常.
程序偶爾會(huì)出現(xiàn):
Microsoft Visual C++ Runtime Library Runtime Error!
Program: [APPPATH] Abnormal program termination
同時(shí)帶有紅色叉叉的對(duì)話(huà)框。
打開(kāi)VC,附加對(duì)應(yīng)的進(jìn)程,在線(xiàn)程選項(xiàng)卡里查找類(lèi)似MessageBox的字樣,找到的那個(gè)就是彈出對(duì)話(huà)框的線(xiàn)程,右鍵,轉(zhuǎn)到線(xiàn)程,查看調(diào)用堆棧,一般就能找到問(wèn)題了
根據(jù)剛才的函數(shù)調(diào)用堆棧,可以發(fā)現(xiàn),這個(gè)框框是由abort間接產(chǎn)生的,根據(jù)查看abort的源碼,可以發(fā)現(xiàn),產(chǎn)生結(jié)果是有選項(xiàng)_WRITE_ABORT_MSG和_CALL_REPORTFAULT這兩個(gè)標(biāo)志決定的,這兩個(gè)標(biāo)志一個(gè)是產(chǎn)生如上所述的對(duì)話(huà)框,一個(gè)是產(chǎn)生那個(gè)常見(jiàn)的錯(cuò)誤報(bào)告對(duì)話(huà)框。在msdn里,和abort相關(guān)的還有個(gè)函數(shù)_set_abort_behavior,可通過(guò)此函數(shù),改變abort的行為。
還有個(gè)常見(jiàn)的錯(cuò)誤框與此類(lèi)似,是pure virtual function 就是純虛函數(shù)的調(diào)用,它的過(guò)程與abort相類(lèi)似。
測(cè)試?yán)樱?/span>
//SEH的異常處理過(guò)濾器
static LONG __stdcall MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExPtrs)
{
AfxMessageBox("glllll");
return EXCEPTION_EXECUTE_HANDLER;
}
void SignalHandler(int signal)
{
AfxMessageBox("Application aborting...\n");
}
HANDLE handle;
DWORD WINAPI ThreadProc(LPVOID lp)
{
// TODO: Add your control notification handler code here
//int a = 0;
//int b = 6 / a;
char* pBuffer = (char*) (int)(rand());
char szBuff[10];
//StrCpy(pBuffer, "Hello, Crash!");
//MessageBox(pBuffer);
//strcpy(pBuffer, szBuff);
//CloseHandle((HANDLE)handle);
//abort();
//throw bad_alloc("aaaaa");
//terminate();
RaiseException(0, 0, 0, NULL);
AfxMessageBox("good");
return 0;
}
char ppp;
static char pppp;
const char p5 = 'a';
const static char p6 = 'b';
void CCrashTestDlg::OnBnClickedButton1()
{
_set_abort_behavior( 0, _WRITE_ABORT_MSG);
_set_abort_behavior(0, _CALL_REPORTFAULT);
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
typedef void (*SignalHandlerPointer)(int);
SignalHandlerPointer previousHandler;
//previousHandler = signal(SIGABRT, SignalHandler);
//MessageBox("ddd", NULL);
//abort();
DWORD dwID = 0;
handle = CreateThread(NULL, 0, ThreadProc, handle, 0, &dwID);
CloseHandle(handle);
//WaitForSingleObject(handle, INFINITE);
}