一兩個月前為產品寫了一個獨立的exe,由於產品使用的捕獲dump是一個現成的進程外exe,如果以資源的方式集成它容易出現安全警告,由於時間關系沒有尋求新的解決方法,還是遵循舊方案,不捕獲dump。 最近業余看了會兒breakpad client,想到一個解決方案——其實也蠻簡單的,最后exe大概會增加200多KB。下邊從頭分析。
有這樣一種需求,希望一個進程啟動之后,有另一個進程來“守護”它,當它發生crash時,能生成dump,然后把它重啟;還有一個要求,“守護”進程跟工作進程必須是在同一個物理文件里,就像chromium一樣,它是多進程的,但只有exe文件只有一個。借助breakpad client,這是很容易實現的事情,“守護”進程即是breakpad client中的服務進程,工作進程即是breakpad client中的客戶進程。
一、思路
1、dump捕獲,基於breakpad client。
2、守護,在breakpad服務進程的客戶端crash回調里重啟工作進程(不能直接做重啟,需要做些簡單處理)。
3、一個物理文件多種功能進程,代碼都放在一個物理文件里,不同類進程根據命令行參數做區分。
二、需要注意的問題
1、首先啟動的是工作進程,由工作進程啟動守護進程,工作進程需要等待守護進程的初始化完畢(主要是用於進程通信的管道)才開始注冊異常處理。現在我看到的chromium代碼,沒有使用進程外dump,所以沒有等待邏輯。
2、工作進程沒有后退策略,當守護進程被異常結束時,客戶進程崩潰就沒法處理了,可以考慮在客戶進程的crash回調做處理,但這種處理是在crash線程做的,最好是讓異常處理的安全線程在進程外dump觸發的情況下仍然啟動,作為后備策略,demo暫不考慮這特殊情況。
3、進程crash之后,全局C++對象的析構函數不會被調用。
4、守護進程在重啟工作進程之前,需要等到工作進程已經退出、守護進程的管道已經停止,才能重啟。
三、實現
1、工作進程啟動守護進程時,加上命令行參數crash_server=XXX,XXX為GUID Event名,守護進程啟動之后置位該Event,工作進程等待該Event置位再繼續往下執行。
2、crash發生時,守護進程的主線程等待客戶進程退出;接着主線程析構crash_server、啟動客戶進程;接着主線程等待工作線程的dump上傳完畢;最后退出主線程。crash沒有發生時:守護進程在主線程等待客戶進程退出后退出。
3、chromium的crash_service.cc 為防止server進程上傳dump時主線程突然退出,做了幾個防止措施:(1)、在客戶進程退出時,sleep 1000毫秒給dump上傳函數執行的機會,然后再給自己發送WM_CLOSE消息;(2)、dump上傳函數跟CrashService類的析構函數有臨界區鎖,當上傳函數未執行完畢時,CrashService不會析構,所以主線程也就不會先退出。我寫的demo,守護進程沒有創建窗口,也不使用WM_CLOSE消息。
4、chromium也有重啟機制,可以在breakpad_win.cc中看到,是在客戶進程的crash回調中做的。
部分代碼:
Process Entry:

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int /*nCmdShow*/) { CmdlineParser cmd_parse(lpstrCmdLine); std::wstring server_start_event_name = cmd_parse.GetValueByKey(L"crash_server"); if (!server_start_event_name.empty()) { GuardProcess::GuardProcessMain(server_start_event_name); return 0; } else { WorkProcess::WorkProcessMain(hInstance); return 0; } }
Guard process Main Function:

void GuardProcessMain(const std::wstring& server_start_event_name) { if (!CrashServerStart(server_start_event_name)) { return; } while(!g_client_exit) { ::Sleep(1000); } if (g_restart_client) { delete g_crash_server; g_crash_server = NULL; wchar_t lpszFileName[MAX_PATH] = {0}; ::GetModuleFileName(NULL, lpszFileName, MAX_PATH); std::wstring strFullName = lpszFileName; ::ShellExecute(NULL, L"open", strFullName.c_str(), NULL, NULL, SW_SHOWNORMAL); } if (g_uploadover != NULL) { DWORD dwRet = ::WaitForSingleObject(g_uploadover, 15000); if (dwRet != WAIT_OBJECT_0) { //error } } }
Work Process Main Function:

void WorkProcessMain(HINSTANCE hInstance) { StartServerExe(); AddExceptionCatch(); HRESULT hRes = ::CoInitialize(NULL); ATLASSERT(SUCCEEDED(hRes)); ::DefWindowProc(NULL, 0, 0, 0L); AtlInitCommonControls(ICC_BAR_CLASSES); hRes = _Module.Init(NULL, hInstance); ATLASSERT(SUCCEEDED(hRes)); int nRet = 0; { CMainDlg dlgMain; nRet = dlgMain.DoModal(); } _Module.Term(); ::CoUninitialize(); }
四、總結
本demo的主要難點是在細節的考慮上,思路是比較簡單的。breakpad的使用極其方便;一個物理文件多個功能進程的思路也很常見。
五、一些基礎
當然就是breakpad client代碼了,我做了一些學習分享放在:
《windows下捕獲dump》http://www.cnblogs.com/cswuyg/p/3207576.html。
《windows下捕獲dump之Google breakpad_client的理解》http://www.cnblogs.com/cswuyg/p/3286244.html
breakpad client的學習代碼可從https://github.com/cswuyg/google_breakpad_client下載到。