由于 C++ 没有标准的崩溃报告系统,而且未处理的异常都会导致程序崩溃。最后操作系统会弹出丑丑的“已停止工作”对话框,也不能存下什么有用的信息。为了解决这个问题,我们一般都会自写代码处理崩溃信息,或像我一样采取第三方库或平台。
通常在程序崩溃时,我们要保存崩溃时程序的运行信息,如导出包含运行栈的 Mini Dump、保存最后一瞬间的截图等,最后如果可以的话将收集的信息传输到自己的服务器上。如果自写代码实现这些功能,工作量会比较大;对于第三方,根据我搜索的资料,实现了类似功能的第三方平台有 BugSplat (付费服务),第三方库有 CrashRpt 和 BugTrap。
这几个第三方平台各有优缺点,对我而言因为我暂时用在小项目上,因此在对比了之后暂时使用的是 BugTrap,名意为臭虫陷阱。在这里简单记录下使用方法和使用心得。
为什么是 BugTrap?
Windows 内置有一个用于未处理错误的处理程序,但这个对话框到底讲了啥?如果我们不希望把数据发送到 Microsoft,我们基本上获取不到有用的信息。
而 BugTrap 有以下优点:
- 简单易用,可快速添加到现有项目而不用更改太多代码。
- 支持 C++ x86 / x64 及 .Net。
- 友好的提示界面,元素均可自定义。
- 崩溃时自动收集崩溃数据, 包含 Mini Dump、软件版本、屏幕截图等。
- 在用户同意后可通过网络或邮件传输,并可附加自己的日志文件。
使用 BugTrap 后我们可以很简单的把崩溃提示换成如下图所示的样式(敏感信息已隐藏),是不是很棒!
在代码中增加支持
原生 C++
BugTrap 以动态链接库的形式供使用,使用这种形式是因为崩溃后需要独立句柄才能做到发送数据。链接库同时提供了 Unicode 和 ANSI 两种形式,不过现在的应用程序默认都是编译 Unicode 了吧。
以 MFC 应用举例, 下面是从 BugTrap 官方例程中提取并修改的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// stdafx.h // #endif // _AFX_NO_AFXCMN_SUPPORT 后 #include "BugTrap/BugTrap.h" // Link with one of BugTrap libraries. #if defined _M_IX86 #if defined _DEBUG #pragma comment(lib, "BugTrapUD.lib") #else #pragma comment(lib, "BugTrapU.lib") #endif #elif defined _M_X64 #if defined _DEBUG #pragma comment(lib, "BugTrapUD-x64.lib") #else #pragma comment(lib, "BugTrapU-x64.lib") #endif #else #error CPU architecture is not supported. #endif // #ifdef _UNICODE 前 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// MFC 应用主类 CBugTrapTestApp::CBugTrapTestApp() { // 设置异常处理程序,以下信息均显示在错误报告对话框中 BT_SetAppName(_T("BugTrap Test")); BT_SetFlags(BTF_DETAILEDMODE | BTF_EDITMAIL | BTF_ATTACHREPORT| BTF_SCREENCAPTURE); BT_SetSupportURL(_T("http://www.intellesoft.net")); // = BugTrapServer 报告上传服务器 ======================= //BT_SetSupportServer(_T("localhost"), 9999); // 非 VS6 均需要 BT_InstallSehFilter(); } |
记得把 .lib 文件放在项目目录下,原生 C++ 也是差不多的思路。
MFC 框架
如果使用的是 MFC,框架产生的错误都会被 CException
拦截,但我们也可以使用 BugTrap 代替它。而方法也很简单,几乎不用对代码进行更改,只需要从BTWindow
类派生窗口类即可。
BTWindow
类是一个模板, 以基本窗口类的名称为参数。我们只需要对窗口类使用 BTWindow
模板即可,如:
1 |
class CMainFrame : public BTWindow <CFrameWnd> |
其他使用上的问题,请参阅使用手册。
测试使用
在做完上面的设置之后,已经可以正常使用 BugTrap 了。 我们可以 在设置异常处理程序后,使用断点语句 _asm int 3
来测试 BugTrap 是否正常工作。注意不要直接在 Visual Studio 中运行调试,因为 VS 的调试器会在 BugTrap 前捕捉所有错误,导致 BugTrap 根本不起作用(被坑了一小时时间的人语重心长地说道)。
在错误报告中显示符号信息需要 PDB 或 MAP 文件,在需要分发的情况下推荐生成一份 MAP 文件备用。原因是由于 MAP 文件只包含了符号的链接信息,会比 PDB 文件小很多。
自定义捕捉
在 BugTrap 可以正常使用之后,我们想要对捕捉流程做些自定义也很简单。
崩溃预处理函数
如果我们想在程序崩溃前对程序做些处理,比如说增加几条日志提示发生了严重错误,则我们可以使用 BT_SetPreErrHandler
函数设置预处理器。
1 2 3 4 5 6 7 8 9 10 11 |
void CALLBACK logging_err_handler(INT_PTR nErrHandlerParam) { BOOST_LOG_TRIVIAL(fatal) << "---------------------------------------------------------"; BOOST_LOG_TRIVIAL(fatal) << "Fatal Error! Please check the dump files."; } Automaton::Automaton() { ... BT_SetPreErrHandler(logging_err_handler, 0); } |
其中第二个参数可以传递一个指针,以便在处理函数中使用。
自定义错误报告对话框
错误报告对话框在 BugTrap 中是高度可定制的。举例来说,默认对话框的第一句话是 “A Crash has been detected by BugTrap”。如果我们不想显示 BugTrap 的信息,可以使用以下代码来覆盖:
1 |
BT_SetDialogMessage(BTDM_INTRO1, L"A Crash has been detected"); |
我在上面截图中的程序中便使用了这行代码,其中的 BTDM_INTRO1
表示设定的是介绍文本第一行,其他值需要参考头文件里的描述。
增加自定义日志文件
有时候,除了某些 BUG,部分逻辑问题也会导致程序崩溃。在这种情况下,标准错误报告可能无济于事。而自定义日志记录可能是最有效的错误预防机制。BugTrap 可以在详细模式下(设定了标志 BTF_DETAILEDMODE
),将任意数量的其他文件附加到报告中。
1 |
BT_AddLogFile(log_file_name.c_str()); |
如果要从 Windows 注册表中导出某些项并将其附加到报告中,则可以利用内置的 BugTrap 函数:
1 2 |
BT_AddRegFile(_T("Settings.reg"), _T("HKEY_CURRENT_USER\\Software\\My Company\\My Application\\Settings")); |
BugTrap 还另外提供了一套内置的日志系统,由于我使用的是 boost::log,没有深入对其进行了解。需要的可以自行阅读使用手册相关的内容。
查看报告
不论是服务器上传或是邮件传输,在收到错误报告后我们都可以通过 BugTrap 附带的 CrashExplorer 小程序来阅览报告。若是收到的报告中只有堆栈地址而没有符号信息,前面我们生成的 MAP 文件就派上了用场。只需要在“Map/Pdb Folder”中设定我们相应的文件就可以显示详细的数据啦,精确到哪一行代码,是不是很方便。
小结
BugTrap 正如其名,是一个方便易用的错误报告处理器。以前有挺多游戏及程序采用了这个模块来收集错误报告,如 CF、LoL 等。这次也只是做了简单的应用,使用时也请多翻阅使用手册。
基于我个人的使用需求,部分高级功能如服务器上传等都没有研究,请大家多多包含。能帮到你们更是再好不过了,若有更好的错误报告处理模块,欢迎大家告诉我,谢谢!
参考资料
- Catch All Bugs with BugTrap! - https://www.codeproject.com/Articles/14618/Catch-All-Bugs-with-BugTrap