(1) 调用abort函数,并且设置了_CALL_REPORTFAULT选项(这个选项在Release版本是默认设置的)。
(2) 启用了运行时安全检查选项,并且在软件运行时检查出安全性错误,例如出现缓存溢出。(安全检查选项 /GS 默认也是打开的)
(3) 遇到_invalid_parameter错误,而应用程序又没有主动调用
_set_invalid_parameter_handler设置错误捕获函数。
所以结论是,使用VS2005(VC8)编译的程序,许多错误都不能在SetUnhandledExceptionFilter捕获到。这是CRT相对于前面版本的一个比较大的改变,但是很遗憾,Microsoft却没有在相应的文档明确指出。
解决方法
之所以应用程序捕获不到那些异常,原因是因为新版本的CRT实现在异常处理中强制删除所有应用程序先前设置的捕获函数,如下所示:
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
UnhandledExceptionFilter(&ExceptionPointers);
解决方法是拦截CRT调用SetUnhandledExceptionFilter函数,使之无效。在X86平台下,可以使用以下代码。
#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);
}
}
在设置自己的异常处理函数后,调用DisableSetUnhandledExceptionFilter禁止CRT设置即可。
其它讨论
上面通过设置api hook,解决了在VS2005上的异常捕获问题,这种虽然不是那么“干净”的解决方案,确是目前唯一简单有效的方式。
虽然也可以通过_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT), signal(SIGABRT, ...), 和_set_invalid_parameter_handler(...) 解决(1)(3),但是对于(2),设置api hook是唯一的方式。
http://blog.csdn.net/WinGeek/article/details/3942995
[Windows编程] 如何捕捉程序异常/crash 并生成 dump 文件
前面介绍如如何用WinDBG 生成crash dump 《WinDBG 技巧:如何生成Dump 文件(.dump 命令) 》,但是用户机器上通常不安装WinDBG, 而且多数用户也不知道怎么使用WinDBG。 所以最好是自己程序里面能够捕捉exception/crash,并且生成crash dump,然后通过网络传回到自己服务器。
捕捉exception 可以用API 函数 SetUnhandledExceptionFilter 。生成crash dump 可以用DbgHelp.dll 里面的MiniDumpWriteDump 函数。
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, 对不少朋友来说, 这是一个十分讨厌的错误提示, 您可能不知道如何着手调试: 产生这个错误的原因是什么? 确实只有知道了产生这个错误的直接原因, 才能去调试这个错误.
刚碰到这个错误的时候, 是发生在视频解码的时候, 由于解码一直在工作状态, 所以我也不知道如何去调试, 当出现这个错误之后, 我们大多数时候就忽略了, 想从其他地方解决, 提高稳定性, 甚至怀疑解码器的稳定性; 后来, 我接触解码库之后, 开始调试这样的错误, 刚开始这样的错误并不容易重现, 往往要几个小时, 当这个错误重现之后, 程序还是在运行的, 只是其中的某一个线程中断了执行, 其中的这个线程弹出了 "VC++ Runtime Error" 这样的对话框, 如果你点击它, 则整个应用程序会直接退出. 为了调试, 我就不能点击这个对话框, 而是使用VC2005附件到进程, 然后再直接中断进程, 这个时候, 会有一个线程中断点就在对话框的消息循环中, 仔细查看堆栈, 发现了一个函数: msvcrt.dll!_abort() , 到这里是时候查看MSDN了:
函数名: abort
功 能: 异常终止一个进程
用 法: 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窗口的多线程应用程序, 调用了abort就会弹出对话框, 在release版本中, 就是一个确认对话框, 点击后程序就提示出错并退出.
在正常的程序里, 我们是不会调用abort的, 除非是遇到了严重的, 不能恢复的错误. 那么到底这个abort是怎么被调用的呢, 我们自己写的代码显然是没有这个函数, 再仔细查看堆栈, 发现是在一个C语言版本的开源库中. 我们的程序是需要7*24小时运行的, 出现了解码异常应该要被我们忽略, 而不是应用程序崩溃. 开源的跨平台解码库是C语言写的, 在出现了严重错误时, 就直接abort这也是可以理解的, 不过, 这样的程序在我们的代码中显然要避免. 大哥, 现在都是什么年代了, 很多程序都是需要一直跑的, 我只好改的库的源代码来重新编译程序才能解决这个问题了, 该怎么改了, 如果去分析解码的逻辑, 我们没有专业的人才. 我想就干脆从abort函数这里入手, 直接返回成功值, 但是这样对解码逻辑影响更大, 可能导致更大的错误, 我想到了操作系统的异常机制, 由于我们是在WINDOWS平台上工作, 所以可以利用WINDOWS结构化异常, 我们可取消abort调用, 在这里我们使用代码产生一个结构化异常(SEH), 结构化异常分为硬件异常和软件异常, CPU可以检查到内存非法访问和除零错误等异常, 那么我们就将abort替换成除零语句, 比如 int i = 10/0;
当程序执行到这里的时候, CPU会捕捉这个异常, 并提示用户, 我们可以在调用解码函数的地方, 增加SEH捕捉代码, 来捕捉这个错误, 那么程序就能忽略这个错误并继续执行了. 后来的事实也证明了这个错误的忽略对程序并没有什么明显的影响. 怎么写这个捕捉代码呢, 操作系统支持的SEH捕捉代码块为 __try - __finally 块和 __try - __except 块, 而__try - __finnaly块就可以实现我们的功能. 写到这里, 可能有朋友要说了, 我们平时见的最多的是try-catch语句, 那么我要解释一下了, try-catch 是C++异常的处理方式, 而__try-__finnaly是操作系统SEH异常处理方式. 在C++语言的try-catch并不能捕捉操作系统结构化异常(比如CPU异常, 内存访问冲突, 除零错误等). C++异常只能捕获软件异常, 通常是调用throw而产生的异常, 比如MFC异常中常见的CException.
SEH异常和C++异常有本质的区别, SEH是操作系统提供的异常处理技术, 在任何支持该操作系统的编程语言中, 都可以使用, 而C++异常处理只能在编写C++代码时使用。然而, 应当知道WINDOWS的VC++编译器是使用操作系统结构化异常来实现C++异常的. 也就是说, C++的try块在VC++下编译时, 会变成__try块, C++的catch块会变成SEH的 __except块: catch测试则变成SEH异常过滤器, catch中的代码则变为__except中的代码. 事实上, C++的throw块, 在编译的时候也会变成SEH的RaiseException函数调用, 由c++异常变为SEH异常.
__finnally的好处在于, 有时更详细的异常信息对我们没有更大帮助, 我们只需要捕获到异常并忽略它。上面提到C++异常在VC++里被转换成SEH异常, 那么在VC下使用try-catch是否能捕获硬件异常呢? 比如我们常见的 0x0000000C 不可读或写. VC++编译器已经提供了支持:
try {;} catch(...){;} 这样的语句就能够捕获所有异常:包括CPU异常, 以及C++异常; 不过需要注意的是, 在VC6.0中, 是默认支持的. 但是在VC2005中, 是默认不捕获CPU异常的. 区别在于一个C++编译选项/Eha , 只有这个选项打开才能用上面的try-catch()捕捉SEH异常.
程序偶尔会出现:
Microsoft Visual C++ Runtime Library Runtime Error!
Program: [APPPATH] Abnormal program termination
同时带有红色叉叉的对话框。
打开VC,附加对应的进程,在线程选项卡里查找类似MessageBox的字样,找到的那个就是弹出对话框的线程,右键,转到线程,查看调用堆栈,一般就能找到问题了
根据刚才的函数调用堆栈,可以发现,这个框框是由abort间接产生的,根据查看abort的源码,可以发现,产生结果是有选项_WRITE_ABORT_MSG和_CALL_REPORTFAULT这两个标志决定的,这两个标志一个是产生如上所述的对话框,一个是产生那个常见的错误报告对话框。在msdn里,和abort相关的还有个函数_set_abort_behavior,可通过此函数,改变abort的行为。
还有个常见的错误框与此类似,是pure virtual function 就是纯虚函数的调用,它的过程与abort相类似。
测试例子:
//SEH的异常处理过滤器
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);
}