一般要捕获异常只需要两个函数: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)&copydate);
 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相关代码:
 
posted on 2013-07-22 22:13  烛秋  阅读(12439)  评论(1编辑  收藏  举报