Windows平台崩溃转储系统crashrpt的使用

概述

CrashRpt 是一个免费的、轻量级的开源错误报告库开源库,旨在拦截C++程序中的异常,收集有关崩溃的技术信息并通过互联网向软件供应商发送错误报告,用于在 Microsoft Visual Studio IDE 中创建并在 Windows 中运行C++应用程序。(不跨平台)

官网为:CrashRpt - A crash reporting system for Windows applications (sourceforge.net)

官网介绍它支持 Visual C++ 2005、2008、2010、2012 和 Visual C++ Express ,笔者使用的编译器Visual Studio 2022也支持。 可以针对 32 位和 64 位平台进行编译。截止2022年10月,适用于 Windows XP以上的操作系统。

1、它可以处理主线程和用户模式程序的所有工作线程中的异常:SEH 异常(Structured Exception Handling 结构化异常处理)、未处理C++类型异常、信号和 CRT (C运行时)错误。在 CrashRpt 可以处理的错误类型中,有:NULL 指针分配、访问冲突、无限递归、堆栈溢出、内存耗尽等。

2、它能生成错误报告,包括小型崩溃转储minidump文件、可扩展的崩溃描述 XML文件、应用程序自定义的文件(如程序日志文件)、桌面屏幕截图和屏幕捕获视频。

3、它提供允许用户查看崩溃报告的 UI以及支持隐私政策定义。

4、它可以使用不同的语言显示其UI,这使得它更适合多语言应用程序。

5、 它在用户同意后在后台发送错误报告。

6、可以通过HTTP(或 HTTPS)、SMTP 和Simple MAPI 的方式从用户端上传崩溃分析文件。

7、崩溃时自动重新启动应用程序(如果用户同意)。

8、使用命令行工具在开发人员端自动处理错误报告。当您从软件用户那里收到大量错误报告时,此选项将非常有用。

9、提供用于API函数接口访问错误报告 。

10、CrashRpt库是轻量级的,这意味着你应该只分发大约1.9 Mb的附加文件与你的软件(dbghelp.dll,CrashRpt.dll,CrashSender.exe)。

体系结构概述

CrashRpt 包含两种功能:错误报告功能错误报告分析功能

1、错误报告功能随客户端软件一起分发,负责处理客户端软件中的异常并将错误报告传递给开发人员。

2、错误报告分析功能旨在帮助开发人员提取错误报告中包含的数据并简化错误报告分析。它通常位于开发人员的一侧,不与客户端软件一起发布。

错误报告功能

错误报告功能由两个核心模块组成:CrashRpt.dll和CrashSender.exe。

CrashRpt.dll 包含用于处理客户端软件中的异常的功能。

CrashSender.exe包含向软件支持团队发送错误报告的功能。

注意:CrashRpt有多个版本,这里的写法是简写,真正编译出来的dll和exe名称类似于(CrashRpt1400.dllCrashSender1400.exe

通常,可执行文件在进程启动时将 CrashRpt.dll 加载到其地址空间中。CrashRpt 的这个实例在加载到进程地址空间的所有 DLL 模块之间共享。客户端应用程序使用 CrashRpt 函数在进程中设置异常处理程序一次,通常在其 main()(或 WinMain()) 函数中。在多线程程序中,客户端在每个工作线程的线程过程的开头额外设置异常处理程序。

错误报告分析功能

错误报告功能也由两个核心模块组成:CrashRptProbe.dll和crprober.exe。

CrashRptProbe.dll实现了可用于访问错误报告数据的API。

crprober.exe 是一个控制台工具,有助于提取错误报告数据并以人类可读的形式编写摘要。

当您从软件用户那里收到许多错误报告时,您将不得不花费大量时间来打开每个报告,分析其内容并编写有关报告的一些摘要。可能有许多报告与同一问题相关,因此打开此类报告不会提供任何新内容,错误报告分析功能旨在解决这些问题。

功能介绍

“错误报告”对话框

当客户端应用程序中发生崩溃时,将显示“错误报告”对话框(请参见下图)。

 “错误报告详细信息”对话框

上面界面的此报告包含哪些内容?链接打开“错误报告详细信息”对话框。

错误报告包含什么?

错误报告是旨在帮助开发人员诊断崩溃原因的文件集合:崩溃小型转储文件、崩溃描述 XML 文件和其他可选文件。

故障小型转储文件 (crashdump.dmp) 包含每个执行线程的操作系统版本、处理器类型、CPU 寄存器状态、局部变量和调用堆栈。

可以在开发人员端使用小型转储文件来确定崩溃的原因并尝试解决问题。(用Windbg工具再结合pdb符号文件来找出bug所在)

minidump文件的创建方式:

1、父进程的所有线程都将挂起,并记录进程的“快照”。

2、快照包括加载到进程中的所有 DLL 模块的名称和版本,以及在进程中工作的线程列表。

3、对于其中每个线程,都会记录调用堆栈映像。

4、此外,有关操作系统版本、CPU 数量及其品牌名称的信息将写入小型转储文件。

5、 小型转储通常是在 DbgHelp DLL 的 MiniDumpWriteDump()函数的帮助下创建的。

6、有关 MiniDumpWriteDump()函数的其他信息,请参阅 MSDN。

 CrashRpt 生成一个 XML 崩溃描述文件 (crashrpt.xml) 来补充小型转储。

XML文件包含各种信息,如应用程序名称和版本,发件人的电子邮件地址和地理位置,用户提供的问题描述,异常类型和地址等。

自定义文件,例如应用程序日志文件,桌面屏幕截图和屏幕捕获视频,可以通过CrashRpt提供的API功能包含在崩溃报告中。

编译CrashRpt

默认情况下,CrashRpt 存档的 bin 目录包含在 Visual Studio 2010 中编译的二进制文件。这些文件仅用于演示目的。强烈建议您使用Visual Studio版本自行编译CrashRpt。这是确保 CrashRpt 使用与应用程序相同版本的 C 运行时库 (CRT) 所必需的。

CrashRpt 发行版的顶级目录包含 Visual Studio 2010 (CrashRpt_vs2010.sln) 的解决方案文件。 打开此解决方案文件以在 Visual C++ 2010 或 Visual C++ Express 2010 中编译 CrashRpt。

不是以上版本的编译器则需进行转换,笔者的编译器是Visual Studio 2022需要使用CMake工具进行转换,步骤如下:

1、下载 CMake(版本 2.8 或更高版本)并安装它。

2、照图填好路径(你下载的CrashRpt文件夹位置),进行操作:

Configure配置: 

完成后你就可以会看到生成的 CrashRpt.sln 文件,您可以使用该文件编译 CrashRpt(在 Visual Studio 中打开它)。 

解决方案结构

打开项目文件后我们可以看到以下的工程:

ConsoleDemo是一个控制台应用程序,有助于测试CrashRpt如何与控制台应用程序一起工作。

CrashRpt 项目包含 API 实现,并提供拦截异常的功能。

CrashRptProbe项目包含处理错误报告的功能。

CrashSender项目包含用于显示GUI,发送错误报告和显示错误报告发送进度的功能。

crprober是用于错误报告处理的控制台工具。

jpeg项目包含 JPEG 文件管理功能。

libogg项目包含OGG视频容器管理功能(与libtheora一起使用)。

libpng项目包含 PNG 文件管理功能。

libtheora项目包含 OGG Theora 视频编解码器功能。

MFCDemo是一个 GUI 应用程序,可帮助测试 CrashRpt API 函数是否与基于 MFC 的应用程序按预期工作。

minizip项目包含ZIP文件管理功能。

Tests包含自动测试功能。

tinyxml项目包含XML文件管理功能。

WTLDemo是一个GUI应用程序,有助于测试CrashRpt API函数是否与基于WTL的应用程序一起按预期工作。

Zlib项目包含文件压缩功能。

直接重新编译整个项目即可,编译完成后,可以在 bin 目录中找到 CrashRpt 可执行文件,可以在 lib 目录中找到库文件。

以下文件是最重要的:

bin\CrashRptXXXX.dll - 这是 CrashRpt 崩溃处理程序模块;

bin\CrashSenderXXXX.exe - 这是崩溃报告发送器模块;

lib\CrashRptXXXX.lib - 这是 CrashRpt 导入库。

上面,XXXX 占位符是 CrashRpt 的版本号。

Debug, Release and Release LIB编译配置

一般情况下以Release方式编译 CrashRpt,在此配置中,生成可在生产环境中使用的二进制文件。

Debug方式编译 CrashRpt,用于 CrashRpt 调试,不应用于生产模式,在此配置中,生成生成 d 后缀文件,例如 CrashRptXXXXd.dll、CrashSenderXXXXd.exe。

Release LIB 配置是对Debug, Release生成配置的补充。通常,您应该以Release方式编译 CrashRpt,但如果使用静态 CRT 链接,则需要以Release LIB 编译。在Release LIB配置中,编译生成CrashSenderXXXX.exe可执行文件和CrashRptLIB.lib静态库。 在此配置中,CrashSenderXXXX.exe 和 CrashRptLIB.lib 使用静态链接到 CRT。

一个使用CrashRpt的例子

 以下示例演示如何使用 CrashRpt API 函数和结构在控制台C++应用程序中启用崩溃报告支持。

项目配置

一般我们需要拷贝CrashRpt中的include目录和bin目录、lib目录中生成的文件到自己的项目中.

我的例子目录是这样组织的:

include目录配置:

lib目录配置:

lib文件配置:

注意这里的是导入库(为了更方便的使用dll)不是静态链接库.

增加一个 编译生成后事件,来自动拷贝bin目录和config目录下的文件:

一些其他项目配置:

请务必将Release配置中使用 CRT 作为多线程 DLL (/MD),这是 MSDN 中推荐的方法,也是项目默认配置。

对所有应用模块使用相同的 CRT 版本

 确保应用程序中存在的所有模块都使用相同的 CRT 版本,如果某些依赖模块是使用较旧版本的 CRT 编译的,则应重新编译它们以确保使用单个版本或 CRT DLL。例如,假设您使用 Visual Studio 2008 和 CRT 9.0 作为 DLL 链接,但应用程序中的某些依赖模块是在 Visual Studio 2005 中编译的,并使用链接为 DLL 的 CRT 8.0。在这种情况下,依赖模块中的 CRT 错误不会被 CrashRpt 截获,因为错误处理程序仅适用于 CRT 9.0。

Release配置启用程序数据库

为了能够从崩溃小型转储中恢复堆栈跟踪,调试器需要应用程序的调试符号(PDB 文件)。若要启用 PDB 文件的生成,请执行以下操作:

 

 应对解决方案中所有工程都进行以上设置

禁用省略帧指针优化

我们建议在发布版本配置中关闭省略帧指针 (FPO) 优化,因为此优化并没有真正带来明显的收益,但会使转储分析变得非常复杂。/Oy编译器选项使使用调试器更加困难,因为编译器禁止显示帧指针信息 。此外,在Visual Studio 2010中,默认情况下禁用此优化。

示例源代码

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <stdarg.h>
#include "CrashRpt.h"

// 应用程序日志文件句柄
FILE* g_hLog = NULL;

// 程序崩溃时的回调函数
int CALLBACK CrashCallback(CR_CRASH_CALLBACK_INFO* pInfo)
{
	// 应用程序崩溃了!

	// 关闭日志文件,避免日志文件一直被客户程序占用
	// 确保CrashRpt能够有Read权限把他包含到错误报告中
	if (g_hLog != NULL)
	{
		// 关闭句柄
		fclose(g_hLog);
		g_hLog = NULL;
	}

	// 返回CR_CB_DODEFAULT以生成错误报告
	return CR_CB_DODEFAULT;
}

// 写日志
void log_write(int num_args, LPCTSTR szFormat, ...)
{
	if (g_hLog == NULL)
		return;

	va_list args;
	va_start(args, num_args);
	_vftprintf_s(g_hLog, szFormat, args);
	fflush(g_hLog);
}

// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	crInstallToCurrentThread2(0);

	log_write(1, _T("Entering the thread proc\n"));

	while (true)
	{
		int* p = NULL;
		*p = 13;         // 这里会引发非法访问异常(Access Violation)
	}

	log_write(1, _T("Leaving the thread proc\n"));

	crUninstallFromCurrentThread();

	return 0;
}

// 安装CrashRpt
bool InstallCrashRpt()
{
	// 定义CrashRpt配置参数
	CR_INSTALL_INFO info;
	memset(&info, 0, sizeof(CR_INSTALL_INFO));
	info.cb = sizeof(CR_INSTALL_INFO);

	// 应用程序名称
	info.pszAppName = _T("MyApp");

	// 应用程序版本
	info.pszAppVersion = _T("1.0.0");

	// 邮件主题
	info.pszEmailSubject = _T("MyApp 1.0.0 Error Report");

	// 崩溃日志收件地址
	info.pszEmailTo = _T("[email protected]");

	// 崩溃日志应该上传的服务器
	info.pszUrl = _T("http://myapp.com/tools/crashrpt.php");

	info.uPriorities[CR_HTTP] = 3;  // 首先使用HTTP发送错误报告
	info.uPriorities[CR_SMTP] = 2;  // 其次使用SMTP发送错误报告
	info.uPriorities[CR_SMAPI] = 1; // 然后使用Simple MAPI发送错误报告

	// 对所有可能出现的异常进行处理
	info.dwFlags |= CR_INST_ALL_POSSIBLE_HANDLERS;

	// 崩溃时重启程序
	info.dwFlags |= CR_INST_APP_RESTART;

	// 对以前未发送成功的错误报告,CrashRpt应该进行发送操作
	info.dwFlags |= CR_INST_SEND_QUEUED_REPORTS;

	// 程序重启命令行
	info.pszRestartCmdLine = _T("/restart");

	// 定义隐私策略网址
	info.pszPrivacyPolicyURL = _T("http://myapp.com/privacypolicy.html");

	// 安装错误报告
	int nResult = crInstall(&info);
	if (nResult != 0)
	{
		// 出错了,获取错误信息
		TCHAR szErrorMsg[512] = _T("");
		crGetLastErrorMsg(szErrorMsg, 512);
		_tprintf_s(_T("%s\n"), szErrorMsg);
		return false;
	}

	return true;
}

// 添加CrashRpt功能
void AddCrashRptFunction()
{
	crSetCrashCallback(CrashCallback, NULL);

	// 增加日志文件到错误报告中
	crAddFile2(_T("log.txt"), NULL, _T("Log File"), CR_AF_MAKE_FILE_COPY);

	// 增加整个桌面的截图到崩溃报告文件中
	crAddScreenshot2(CR_AS_VIRTUAL_SCREEN, 0);

	// 将命名属性添加到崩溃说明 XML 文件中
	crAddProperty(_T("VideoCard"), _T("nVidia GeForce 8600 GTS"));
}

// 卸载CrashRpt
void UninstallCrashRpt()
{
	// 在主线程退出前卸载CrashRpt
	crUninstall();
}

int _tmain(int argc, _TCHAR* argv[])
{
	// 安装CrashRpt
	if (!InstallCrashRpt())
		// 安装失败
		return 1;

	// 添加CrashRpt功能
	AddCrashRptFunction();

	// 打开日志文件
	errno_t err = _tfopen_s(&g_hLog, _T("log.txt"), _T("wt"));
	if (err != 0 || g_hLog == NULL)
	{
		// 不能打开日志文件
		_tprintf_s(_T("Error opening log.txt\n"));
		return 1;
	}

	log_write(1, _T("Started successfully\n"));

	// 创建工作线程
	HANDLE hWorkingThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)NULL, 0, NULL);

	log_write(1, _T("Created working thread\n"));

	// 这里会引发无效参数异常
	TCHAR* szFormatString = NULL;
	_tprintf_s(szFormatString);

	// 等待工作线程退出
	WaitForSingleObject(hWorkingThread, INFINITE);

	log_write(1, _T("Working thread has exited\n"));

	// 关闭日志文件
	if (g_hLog != NULL)
	{
		// 关闭日志文件句柄
		fclose(g_hLog);
		g_hLog = NULL;
	}

	// 退出时卸载CrashRpt
	UninstallCrashRpt();
	return 0;
}

修改CrashRpt界面语言

默认的CrashRpt是英语界面的,我们可以将CrashRpt根目录下的lang_files目录下的crashrpt_lang_ZH-CN.ini重命名为crashrpt_lang.ini,替换我们项目里的同名文件即可,就可以让界面显示为中文了。

猜你喜欢

转载自blog.csdn.net/u013404885/article/details/127592243