概述
0. Google Breakpad介绍
Breakpad是一个库和工具套件,可以让你发布的应用程序(把编译器提供的调试信息剥离掉的)给用户,记录了崩溃紧凑的“dump”文件,发送回您的服务器,并从这些minidump产生C和C++堆栈踪迹。Breakpad可以根据请求使没有崩溃的程序也可以写出minidump。
Breakpad有三个主要组件:
① 客户端是一个库,包含在应用程序中。 它可以获取当前线程的状态和当前加载的可执行文件和共享库的ID写转储文件。您可以配置客户端发生了崩溃时写入一个minidump时,或明确要求时。
② 符号卸载器是一个程序,读取由编译器产生的调试信息,并生成一个使用Breakpad格式的符号文件 。
③ 处理器(minidump processor)是一个程序,读取一个minidump文件,找到相应的版本的符号文件的(可执行文件和共享库的转储提到的),并产生了一个人可读的C / C + +堆栈跟踪。
小型转储文件格式(即minidump)
1.进程内抓取Dump文件
在Windows下使用breakpad的方法很简单,只需要创建一个ExceptionHandler的类即可
进程内抓取Dump文件是最简单的breakpad的用法。使用方法很简单:
const std::wstring s_strCrashDir = L"c:dumps";
bool InitBreakpad()
{
google_breakpad::ExceptionHandler *pCrashHandler = new google_breakpad::ExceptionHandler(s_strCrashDir,
onExceptionFilter,
onMinidumpDumped,
NULL,
google_breakpad::ExceptionHandler::HANDLER_ALL,
MiniDumpNormal,
NULL,
NULL);
if(pCrashHandler == NULL)
return false;
return true;
}
2. 进程外抓取Dump文件
使用进程外抓取Dump时,需要指定服务端和客户端,在服务端中需要创建CrashGenerationServer的实例,而在客户端中则只需要创建ExceptionHandler即可。此外,如果服务端自己需要抓进程内的Dump,请将pipe的参数置为NULL。
const wchar_t s_pPipeName[] = L"\.pipebreakpadcrash_handler_server";
const std::wstring s_strCrashDir = L "c:dumps";
bool InitBreakpad()
{
google_breakpad::CrashGenerationServer *pCrashServer = new google_breakpad::CrashGenerationServer(s_pPipeName,
NULL,
onClientConnected,
NULL,
onClientDumpRequest,
NULL,
onClientExited,
NULL,
true,
&s_strCrashDir);
if(pCrashServer == NULL)
return false;
// 如果已经服务端已经启动了,此处启动会失败
if(!pCrashServer->Start())
{
delete pCrashServer;
pCrashServer = NULL;
}
google_breakpad::ExceptionHandler *pCrashHandler = new google_breakpad::ExceptionHandler(s_strCrashDir,
onExceptionFilter,
onMinidumpDumped,
NULL,
google_breakpad::ExceptionHandler::HANDLER_ALL,
MiniDumpNormal,
(pCrashServer == NULL) ? s_pPipeName : NULL, // 如果是服务端,则直接使用进程内dump
NULL);
if(pCrashHandler == NULL)
return false;
return true;
}
3. 注意事项
使用breakpad的时候,有两个地方需要注意:
① 记得把breakpad的solution下的几个工程,包含到你开发的工程中,或者直接包含他们的lib。
common:基础功能,包含一个对GUID的封装和http上传的类。
exception_handler:用来捕获崩溃的类。
crash_generation_server:breakpad的服务端,用来在产生崩溃时抓取dump。
crash_generation_client:breakpad的客户端,用来捕获当前进程的崩溃。
② 在初始化breakpad之前,记得先创建好dump文件的目录,不然breakpad服务端将不能正常的写dump,这会导致breakpad客户端在崩溃时无限等待服务端dump写完的消息,最后失去响应。
4. breakpad介绍
看完了如何使用breakpad,我们现在看看breakpad在Windows下到底是如何实现的呢?
breakpad代码结构:
Google breakpad的源代码都在src的目录下,分为如下几个文件夹:
client:这下面包含了前台应用程序中捕捉dump的部分代码,里面按照平台分成各个子文件夹
common:前台后台都会用到的部分基础代码,字符串转换,内存读写,md5等
google_breakpad:breakpad中公共的头文件
processor:用于在后台处理崩溃的核心代码
testing:测试工程
third_party:第三方库
tools:一些小工具,用于处理dump文件和符号表
breakpad的崩溃捕获机制
在Windows下捕获崩溃,大家很容易会想到那个捕获结构化异常的Api:SetUnhandledExceptionFilter
breakpad中也使用了这个API来实现的崩溃捕获,另外,breakpad还捕获了另外两种C++运行库提供的崩溃,一种是使用_set_purecall_handler捕获纯虚函数调用产生的崩溃,还有一种是使用_set_invalid_parameter_handler捕获错误的参数调用产生的崩溃。
if(handler_types & HANDLER_EXCEPTION)
previous_filter_ = SetUnhandledExceptionFilter(HandleException);
#if _MSC_VER >= 1400 // MSVC 2005/8
if(handler_types & HANDLER_INVALID_PARAMETER)
previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter);
#endif // _MSC_VER >= 1400
if(handler_types & HANDLER_PURECALL)
previous_pch_ = _set_purecall_handler(HandlePureVirtualCall);
另外由于C++运行库提供的崩溃回调中,并不会提供当前的线程现场和崩溃信息,所以breakpad会自己生成好这些信息,然后请求生成dump。
这里值得一说的是,在非异常崩溃处理中,breakpad获取线程现场使用的函数是RtlCaptureContext而不是GetThreadContext。
RtlCaptureContext只能捕获当前线程的现场,而GetThreadContext可以捕获任意线程的现场,只要有这个线程的句柄即可。
但是GetThreadContext有两个不好的地方:不能获取当前线程的现场;获取现场前必须先用SuspendThread暂停目标线程。
而RtlCaptureContext虽然只能获取当前线程的现场,但是调用他时可以不用暂停线程的运行。
对于breakpad来说,崩溃发生后越早获取现场就越好,所以breakpad使用RtlCaptureContext函数作为他的线程获取函数。
5. breakpad中C/S结构
C/S结构:客户机和服务器结构
由于breakpad是在进程外抓取dump,所以breakpad需要实现一个C/S结构来处理崩溃进程抓取dump的请求。
① breakpad跨进程通信的实现
breakpad中使用了命名管道来实现IPC。
在客户端,初始化ExceptionHandler的时候,如果指定了PipeName,也就表示此时需要使用进程外的dump抓取,ExceptionHandler,会建立一个 CrashGenerationClient的对象,由这个对象连接服务端,将自己注册到服务端上去。
可以参看exception_handler.cc中的ExceptionHandler::Initialize函数。
在服务端,初始化CrashGenerationServer的时候,就会建立一个命名管道,并等待客户端来连接。一旦有客户端连接上来,服务端会为每一个客户端生成一个ClientInfo的对象,之后用这个对象来管理所有的客户端,一旦有崩溃发生,服务端都会从这个对象中取出dump所需要的信息。
可以参看crash_generation_server.cc中的CrashGenerationServer::HandleReadDoneState函数。
② breakpad捕获崩溃生成dump的流程
有两个简单的问题,这里说明一下,:
在服务端如何为客户端生成事件句柄?
使用DuplicateHandle,即可把任意一个内核对象的句柄复制到其他进程,并且可以指定产生的句柄的权限。
如何异步的等待一个事件?
使用RegisterWaitForSingleObject,即可异步的等待一个事件,当事件发生的时候,就可以回调到一个指定的回调函数中,但是要注意的是,RegisterWaitForSingleObject会在一个新的线程中来等待这个事件,此处很容易产生多线程的调用,需要注意线程问题。
③ 服务端关键数据结构:ClientInfo
ClientInfo是服务端中最重要的数据结构,服务端通过它来管理所有的客户端。客户端注册时,会保存或生成里面所有的信息,在客户端请求生成dump的时候,服务端就会通过ClientInfo获取所有客户端的信息。ClientInfo中保存了如下信息:
· 客户端进程pid和句柄
· 生成Minidump的类型
· 自定义的客户端信息
· 客户端崩溃的线程ID
· 客户端崩溃的信息
· 客户端请求崩溃所使用的事件句柄
④ 这里有一个问题:在客户端发生崩溃时,服务器如何通过ClientInfo获取到客户端的崩溃信息呢?
客户端中有几个用于保存崩溃信息的变量,在注册时,客户端会将这几个变量的地址发送至服务端,服务端将其保存在ClientInfo中,然后当崩溃发生的时候,服务端就可以通过ReadProcessMemory读取客户端中的信息,从而生成dump。这样做就避免了每次发生崩溃,都要通过Pipe将崩溃信息传递到服务端中去了。
这些变量分别是:崩溃的线程ID,EXCEPTION_POINTERS和MDRawAssertionInfo。
EXCEPTION_POINTERS和MDRawAssertionInfo的区别在于,异常崩溃的信息会被写入EXCEPTION_POINTERS,非异常崩溃(非法参数和纯虚函数调用)的信息会被写入MDRawAssertionInfo中。
⑤ dump文件上传
在breakpad的工程中,有一个工程叫做:crash_report_sender,里面是一个上传崩溃文件的类,他的实现很简单,他使用Windows Internet Api来完成dump文件的上传。
在使用crash_report_sender时,可以为其指定一个checkpoint_file。
explicit CrashReportSender(const wstring &checkpoint_file);
这个文件只有一个作用,就是用来保存上次上传崩溃的时间和今天上传过的崩溃的次数。通过这个文件,我们就可以来设置每日上传的崩溃的最大数量。
CrashReportSender::CrashReportSender(const wstring &checkpoint_file)
: checkpoint_file_(checkpoint_file),
max_reports_per_day_(-1),
last_sent_date_(-1),
reports_sent_(0)
{
FILE *fd;
if(OpenCheckpointFile(L"r", &fd) == 0)
{
ReadCheckpoint(fd);
fclose(fd);
}
}
ReportResult CrashReportSender::SendCrashReport(const wstring &url,
const map<wstring, wstring> ¶meters,
const wstring &dump_file_name, wstring *report_code)
{
int today = GetCurrentDate();
if(today == last_sent_date_ && max_reports_per_day_ != -1 && reports_sent_ >= max_reports_per_day_)
return RESULT_THROTTLED;
// 上传文件部分代码,省略
}
调整每日上传崩溃的最大数量的函数是set_max_reports_per_day。
需要注意的是:在上传dump文件的时候,crash_report_sender并不会对dump文件进行分析,而是直接上传整个dump文件,如果你需要上传的dump文件非常大的话,可以考虑把崩溃分析处理的逻辑放入前台,通过去重或者直接上传分析结果,减少上传的文件大小。
⑥ breakpad存在的问题
进程外生成dump有很多好处,其中最大的好处就是不会被崩溃进程影响,这样dump的过程就不容易出错,但是这样也有一定的弊端。
-
部分崩溃无法抓取
在一些极端的崩溃,如堆栈溢出之类的崩溃,进程外抓取dump有时候会失败。 -
无法抓取死锁或者其他原因导致的进程僵死
breakpad现在没有检测进程死锁的代码,也没有在服务端控制客户端请求dump的代码,所以现在breakpad无法抓取死锁等进程僵死的问题。不过因为breakpad的定位是处理崩溃,如果有这种需要的童鞋,可以自行修改breakpad的代码,添加这些功能。 -
对服务端有依赖
如果指定了在使用进程外抓取dump,breakpad对服务端就有依赖。主要体现在抓取dump时,如果服务端不存在,客户端将无法正常抓取dump,甚至有时会出现阻塞。
当然对于这些问题,随着breakpad的发展肯定会越来越完善。如果,你遇到了了这些问题,而又绕过不了,那就改代码,并且提交给breakpad吧,开源项目就是这么发展的。
最后
以上就是害怕翅膀为你收集整理的Google Breakpad的使用0. Google Breakpad介绍1.进程内抓取Dump文件2. 进程外抓取Dump文件3. 注意事项4. breakpad介绍5. breakpad中C/S结构的全部内容,希望文章能够帮你解决Google Breakpad的使用0. Google Breakpad介绍1.进程内抓取Dump文件2. 进程外抓取Dump文件3. 注意事项4. breakpad介绍5. breakpad中C/S结构所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复