一般要捕獲異常隻需要兩個函數: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
不錯的程式設計資料:
http://www.cppblog.com/woaidongmao/archive/2009/10/21/99129.html http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li http://www.cnblogs.com/FCoding/archive/2012/07/05/2578543.html不錯的抓dump工具介紹:
http://wintellect.com/blogs/jrobbins/how-to-capture-a-minidump-let-me-count-the-waysbreakpad相關代碼:
https://code.google.com/p/chromium/codesearch#chromium/src/breakpad/src/client/windows/handler/exception_handler.h