一般要捕获异常只需要两个函数: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无法捕获,为什么不这么做呢?有待考察。(*也有人提到这个问题:http://*.com/questions/11350801/why-does-google-breakpad-not-handle-all-crashes-how-can-i-debug-these-cases)。
进程内捕获dump示例代码:
.h
namespace CatchDumpFile
{ void simple_log(const std::wstring& log_msg); class CDumpCatch
{
public:
CDumpCatch();
~CDumpCatch();
private: static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI TempSetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
static BOOL ReleaseDumpFile(const std::wstring& strPath, EXCEPTION_POINTERS *pException);
static LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException);
static void MyPureCallHandler(void);
static void MyInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved); BOOL AddExceptionHandle();
BOOL RemoveExceptionHandle();
BOOL PreventSetUnhandledExceptionFilter();
void SetInvalidHandle();
void UnSetInvalidHandle();
private:
LPTOP_LEVEL_EXCEPTION_FILTER m_preFilter;
_invalid_parameter_handler m_preIph;
_purecall_handler m_prePch;
};
};
.cc
namespace CatchDumpFile
{
void simple_log(const std::wstring& log_msg)
{
std::wstring strLogWnd = L"cswuyg_simple_debug_log";
HWND hSend = ::FindWindow(NULL, strLogWnd.c_str());
COPYDATASTRUCT copydate;
copydate.cbData = (DWORD)(log_msg.length() + ) * sizeof(wchar_t);
copydate.lpData = (PVOID)log_msg.c_str();
::SendMessage(hSend, WM_COPYDATA, , (LPARAM)©date);
} void CDumpCatch::MyPureCallHandler(void)
{
//simple_log(L"MyPureCallHandler");
throw std::invalid_argument("");
} void CDumpCatch::MyInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved)
{
//simple_log(L"MyPureCallHandler");
//The parameters all have the value NULL unless a debug version of the CRT library is used.
throw std::invalid_argument("");
} void CDumpCatch::SetInvalidHandle()
{
#if _MSC_VER >= 1400 // MSVC 2005/8
m_preIph = _set_invalid_parameter_handler(MyInvalidParameterHandler);
#endif // _MSC_VER >= 1400
m_prePch = _set_purecall_handler(MyPureCallHandler); //At application, this call can stop show the error message box.
}
void CDumpCatch::UnSetInvalidHandle()
{
#if _MSC_VER >= 1400 // MSVC 2005/8
_set_invalid_parameter_handler(m_preIph);
#endif // _MSC_VER >= 1400
_set_purecall_handler(m_prePch); //At application this can stop show the error message box.
} LPTOP_LEVEL_EXCEPTION_FILTER WINAPI CDumpCatch::TempSetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter )
{
return NULL;
} BOOL CDumpCatch::AddExceptionHandle()
{
m_preFilter = ::SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
PreventSetUnhandledExceptionFilter();
return TRUE;
} BOOL CDumpCatch::RemoveExceptionHandle()
{
if(m_preFilter != NULL)
{
::SetUnhandledExceptionFilter(m_preFilter);
m_preFilter = NULL;
}
return TRUE;
} CDumpCatch::CDumpCatch()
{
SetInvalidHandle();
AddExceptionHandle();
} CDumpCatch::~CDumpCatch()
{
UnSetInvalidHandle();
RemoveExceptionHandle();
} BOOL CDumpCatch::ReleaseDumpFile(const std::wstring& strPath, EXCEPTION_POINTERS *pException)
{
HANDLE hDumpFile = ::CreateFile(strPath.c_str(), GENERIC_WRITE, , NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDumpFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = ::GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
// ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
BOOL bRet = ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemory, &dumpInfo, NULL, NULL);
::CloseHandle(hDumpFile);
return bRet;
} LONG WINAPI CDumpCatch::UnhandledExceptionFilterEx( struct _EXCEPTION_POINTERS *pException )
{
//simple_log(L"UnhandledExceptionFilterEx");
wchar_t szPath[MAX_PATH] = { };
::GetModuleFileName(NULL, szPath, MAX_PATH);
::PathRemoveFileSpec(szPath);
std::wstring strPath = szPath;
strPath += L"\\CrashDumpFile.dmp";
BOOL bRelease = ReleaseDumpFile(strPath.c_str(), pException);
//::FatalAppExit(0, L"Error");
if (bRelease)
{
return EXCEPTION_EXECUTE_HANDLER;
}
return EXCEPTION_CONTINUE_SEARCH;
} BOOL CDumpCatch::PreventSetUnhandledExceptionFilter()
{
HMODULE hKernel32 = LoadLibrary(L"kernel32.dll");
if (hKernel32 == NULL)
{
return FALSE;
}
void *pOrgEntry = ::GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
if(pOrgEntry == NULL)
{
return FALSE;
} unsigned char newJump[];
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
dwOrgEntryAddr += ; //jump instruction has 5 byte space. void *pNewFunc = &TempSetUnhandledExceptionFilter;
DWORD dwNewEntryAddr = (DWORD)pNewFunc;
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; newJump[] = 0xE9; //jump
memcpy(&newJump[], &dwRelativeAddr, sizeof(DWORD));
SIZE_T bytesWritten;
DWORD dwOldFlag, dwTempFlag;
::VirtualProtect(pOrgEntry, , PAGE_READWRITE, &dwOldFlag);
BOOL bRet = ::WriteProcessMemory(::GetCurrentProcess(), pOrgEntry, newJump, , &bytesWritten);
::VirtualProtect(pOrgEntry, , dwOldFlag, &dwTempFlag);
return bRet;
} }
能引发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() = ;
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相关代码: