一两个月前为产品写了一个独立的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下载到。