我是靠谱客的博主 害怕翅膀,最近开发中收集的这篇文章主要介绍Google Breakpad的使用0. Google Breakpad介绍1.进程内抓取Dump文件2. 进程外抓取Dump文件3. 注意事项4. breakpad介绍5. breakpad中C/S结构,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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> &parameters, 
    		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的过程就不容易出错,但是这样也有一定的弊端。

  1. 部分崩溃无法抓取
    在一些极端的崩溃,如堆栈溢出之类的崩溃,进程外抓取dump有时候会失败。

  2. 无法抓取死锁或者其他原因导致的进程僵死
    breakpad现在没有检测进程死锁的代码,也没有在服务端控制客户端请求dump的代码,所以现在breakpad无法抓取死锁等进程僵死的问题。不过因为breakpad的定位是处理崩溃,如果有这种需要的童鞋,可以自行修改breakpad的代码,添加这些功能。

  3. 对服务端有依赖
    如果指定了在使用进程外抓取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结构所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部