一般要捕获异常只需要两个函数:SetUnhandledExceptionFilter截获异常;MiniDumpWriteDump写dump文件。但是由于CRT函数可能会在内部调用SetUnhandledExceptionFilter(NULL),解除我们程序设置的异常处理,这导致我们的程序无法完整捕获崩溃。另外,还有一部分非异常的CRT错误,不属于SEH异常捕获的范畴,需要通过_set_invalid_parameter_handler、_set_purecall_handler拦截,否则会弹出很丑陋的Runtime Error提示框。为保证所有异常都能由我们捕获,需要把SetUnhandledExceptionFilter函数Hook掉,不让“其他人”去设置自己的Exception处理,有Exception我们自己搞定;还有,对CRT错误做拦截,避免弹出错误窗口:_set_invalid_parameter_handler、_set_purecall_handler。
chromium的breakpad当前只是使用了上边提到的_set_invalid_parameter_handler、_set_purecall_handler函数,并没有屏蔽“其他人”的SetUnhandledExceptionFilter行为,可能导致了部分Crash无法捕获,为什么不这么做呢?有待考察。(stackoverflow也有人提到这个问题:http://stackoverflow.com/questions/11350801/why-does-google-breakpad-not-handle-all-crashes-how-can-i-debug-these-cases)。
进程内捕获dump示例代码:
.h
1 namespace CatchDumpFile 2 { 3 4 void simple_log(const std::wstring& log_msg); 5 6 class CDumpCatch 7 { 8 public: 9 CDumpCatch(); 10 ~CDumpCatch(); 11 private: 12 13 static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI TempSetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); 14 static BOOL ReleaseDumpFile(const std::wstring& strPath, EXCEPTION_POINTERS *pException); 15 static LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException); 16 static void MyPureCallHandler(void); 17 static void MyInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved); 18 19 20 BOOL AddExceptionHandle(); 21 BOOL RemoveExceptionHandle(); 22 BOOL PreventSetUnhandledExceptionFilter(); 23 void SetInvalidHandle(); 24 void UnSetInvalidHandle(); 25 private: 26 LPTOP_LEVEL_EXCEPTION_FILTER m_preFilter; 27 _invalid_parameter_handler m_preIph; 28 _purecall_handler m_prePch; 29 }; 30 };
.cc
1 namespace CatchDumpFile 2 { 3 void simple_log(const std::wstring& log_msg) 4 { 5 std::wstring strLogWnd = L"cswuyg_simple_debug_log"; 6 HWND hSend = ::FindWindow(NULL, strLogWnd.c_str()); 7 COPYDATASTRUCT copydate; 8 copydate.cbData = (DWORD)(log_msg.length() + 1) * sizeof(wchar_t); 9 copydate.lpData = (PVOID)log_msg.c_str(); 10 ::SendMessage(hSend, WM_COPYDATA, 0, (LPARAM)©date); 11 } 12 13 void CDumpCatch::MyPureCallHandler(void) 14 { 15 //simple_log(L"MyPureCallHandler"); 16 throw std::invalid_argument(""); 17 } 18 19 void CDumpCatch::MyInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) 20 { 21 //simple_log(L"MyPureCallHandler"); 22 //The parameters all have the value NULL unless a debug version of the CRT library is used. 23 throw std::invalid_argument(""); 24 } 25 26 void CDumpCatch::SetInvalidHandle() 27 { 28 #if _MSC_VER >= 1400 // MSVC 2005/8 29 m_preIph = _set_invalid_parameter_handler(MyInvalidParameterHandler); 30 #endif // _MSC_VER >= 1400 31 m_prePch = _set_purecall_handler(MyPureCallHandler); //At application, this call can stop show the error message box. 32 } 33 void CDumpCatch::UnSetInvalidHandle() 34 { 35 #if _MSC_VER >= 1400 // MSVC 2005/8 36 _set_invalid_parameter_handler(m_preIph); 37 #endif // _MSC_VER >= 1400 38 _set_purecall_handler(m_prePch); //At application this can stop show the error message box. 39 } 40 41 LPTOP_LEVEL_EXCEPTION_FILTER WINAPI CDumpCatch::TempSetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) 42 { 43 return NULL; 44 } 45 46 BOOL CDumpCatch::AddExceptionHandle() 47 { 48 m_preFilter = ::SetUnhandledExceptionFilter(UnhandledExceptionFilterEx); 49 PreventSetUnhandledExceptionFilter(); 50 return TRUE; 51 } 52 53 BOOL CDumpCatch::RemoveExceptionHandle() 54 { 55 if(m_preFilter != NULL) 56 { 57 ::SetUnhandledExceptionFilter(m_preFilter); 58 m_preFilter = NULL; 59 } 60 return TRUE; 61 } 62 63 CDumpCatch::CDumpCatch() 64 { 65 SetInvalidHandle(); 66 AddExceptionHandle(); 67 } 68 69 CDumpCatch::~CDumpCatch() 70 { 71 UnSetInvalidHandle(); 72 RemoveExceptionHandle(); 73 } 74 75 BOOL CDumpCatch::ReleaseDumpFile(const std::wstring& strPath, EXCEPTION_POINTERS *pException) 76 { 77 HANDLE hDumpFile = ::CreateFile(strPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 78 if (hDumpFile == INVALID_HANDLE_VALUE) 79 { 80 return FALSE; 81 } 82 MINIDUMP_EXCEPTION_INFORMATION dumpInfo; 83 dumpInfo.ExceptionPointers = pException; 84 dumpInfo.ThreadId = ::GetCurrentThreadId(); 85 dumpInfo.ClientPointers = TRUE; 86 // ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL); 87 BOOL bRet = ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemory, &dumpInfo, NULL, NULL); 88 ::CloseHandle(hDumpFile); 89 return bRet; 90 } 91 92 LONG WINAPI CDumpCatch::UnhandledExceptionFilterEx( struct _EXCEPTION_POINTERS *pException ) 93 { 94 //simple_log(L"UnhandledExceptionFilterEx"); 95 wchar_t szPath[MAX_PATH] = { 0 }; 96 ::GetModuleFileName(NULL, szPath, MAX_PATH); 97 ::PathRemoveFileSpec(szPath); 98 std::wstring strPath = szPath; 99 strPath += L"\CrashDumpFile.dmp"; 100 BOOL bRelease = ReleaseDumpFile(strPath.c_str(), pException); 101 //::FatalAppExit(0, L"Error"); 102 if (bRelease) 103 { 104 return EXCEPTION_EXECUTE_HANDLER; 105 } 106 return EXCEPTION_CONTINUE_SEARCH; 107 } 108 109 BOOL CDumpCatch::PreventSetUnhandledExceptionFilter() 110 { 111 HMODULE hKernel32 = LoadLibrary(L"kernel32.dll"); 112 if (hKernel32 == NULL) 113 { 114 return FALSE; 115 } 116 void *pOrgEntry = ::GetProcAddress(hKernel32, "SetUnhandledExceptionFilter"); 117 if(pOrgEntry == NULL) 118 { 119 return FALSE; 120 } 121 122 unsigned char newJump[5]; 123 DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; 124 dwOrgEntryAddr += 5; //jump instruction has 5 byte space. 125 126 void *pNewFunc = &TempSetUnhandledExceptionFilter; 127 DWORD dwNewEntryAddr = (DWORD)pNewFunc; 128 DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; 129 130 newJump[0] = 0xE9; //jump 131 memcpy(&newJump[1], &dwRelativeAddr, sizeof(DWORD)); 132 SIZE_T bytesWritten; 133 DWORD dwOldFlag, dwTempFlag; 134 ::VirtualProtect(pOrgEntry, 5, PAGE_READWRITE, &dwOldFlag); 135 BOOL bRet = ::WriteProcessMemory(::GetCurrentProcess(), pOrgEntry, newJump, 5, &bytesWritten); 136 ::VirtualProtect(pOrgEntry, 5, dwOldFlag, &dwTempFlag); 137 return bRet; 138 } 139 140 }
能引发pure function called 错误的代码:
class IPureCall { public: virtual ~IPureCall(){}; IPureCall() { //indirect call the virtual function, the compiler would not treat as "static binding", it is "dynamic binding". //At this time, the CPureCall class hasn't been constructed, the virtual table didn't has the pure_call function's point, so it cause "pure virtual function called exception". call_by_constructor(); }; virtual void pure_call() = 0; void call_by_constructor() { pure_call(); } }; class CPureCall : public IPureCall { public: CPureCall() { } void pure_call() { } };
pure virtual function called在之前的文章里介绍过(http://www.cnblogs.com/cswuyg/archive/2012/08/22/2650610.html)。
进程外捕获崩溃的做法是使用进程间通信(IPC,内存映射文件或者管道都行),把EXCEPTION_POINTERS指针数据等信息通知捕获进程,让捕获进程去写dump(windows下捕获dump之Google breakpad_client的理解)。进程外捕获dump是比较推荐的做法,chromium的breakpad文档解释说,“一般认为在崩溃进程内部写minidump是不安全的:关键的进程数据结构可能会被破坏掉,或者异常处理程序获取到的堆栈可能是被覆盖了的”(原文:http://code.google.com/p/google-breakpad/wiki/GettingStartedWithBreakpad)。
可复用源码分享:https://github.com/cswuyg/simple_win/tree/master/dump_catch/dump_catch
多模块dump处理相关补充:
1、如果CRT是/MD,那么CRT错误捕获EXE、DLL共用;dump捕获多EXE、DLL共用,只需要在EXE里加上处理就ok;
2、如果CRT是/MT,那么CRT错误捕获各PE文件独立,EXE、DLL必须有自己的处理;dump捕获多EXE、DLL共用。
这方面的知识MSDN也稍有提及:
http://technet.microsoft.com/zh-cn/library/t296ys27(v=vs.71)
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680634(v=vs.85).aspx
不错的编程资料:
不错的抓dump工具介绍:
breakpad相关代码: