最近项目中新作成了一个动态链接库,长时间运行后,偶尔会崩溃。根据log分析,被调用的动态库函数最外层catch到了这个异常,但是不能定位哪里出了问题。另外虽然上层exe是有dump文件输出处理的,但是在C++中,如果异常被捕获并处理的情况下,系统就不会生成dump文件了。如果仍希望在try-catch块中捕获异常的同时生成dump文件,就必须在catch块中手动调用生成dump文件的函数。这样可以在异常被捕获后仍然生成dump文件以供后续分析。本文详细介绍下怎么生成dump文件。


链接库

Windows平台上用Windows调试帮助库中的函数MiniDumpWriteDump来生成dump文件。

dbghelp.h头文件

#include <dbghelp.h>

dbghelp.lib库

项目【属性】→【链接器】→【输入】→【附加依赖项】→ 添加dbghelp.lib
C++程序中dump文件生成方法详解-LMLPHP


dump生成原理:

生成dump文件的原理是在未处理异常发生时,系统调用设置的未处理异常过滤器回调函数,并将异常信息传递给这个回调函数。回调函数可以利用这些异常信息,如异常指针和线程ID,来创建一个包含应用程序状态快照的dump文件。这个快照包括了应用程序的内存、寄存器状态、堆栈信息等,可以帮助开发人员在应用程序崩溃时进行调试和分析。


代码实例

dump文件名和输出路径设置

dump文件名和输出路径没有什么硬性要求,根据各自程序的需求来即可。

// 获取当前模块的路径
std::string GetCurrentModuleFilePath()
{
	char buffer[MAX_PATH];
	GetModuleFileName(nullptr, buffer, MAX_PATH);
	return std::string(buffer);
}

// 获取当前模块的名字(不含后缀)
std::string GetModuleName(const std::string& filePath)
{
	std::string fileName = "";

	size_t lastSlash = filePath.find_last_of("\\");
	size_t lastDot = filePath.find_last_of(".");
	if (lastSlash != std::string::npos 
		&& lastDot != std::string::npos
		&& lastDot > lastSlash)
	{
		fileName = filePath.substr(lastSlash + 1, lastDot - lastSlash - 1);
	}

	return fileName;
}

// 获取当前时间(2023-11-23_18-42-345形式)
std::string GetCurrentTimeWithFormat() 
{
	SYSTEMTIME st;
	GetLocalTime(&st);

	std::string fileName;
	fileName = std::to_string(st.wYear) + "-" + std::to_string(st.wMonth) + "-" + std::to_string(st.wDay) + "_" + std::to_string(st.wHour) + "-" + std::to_string(st.wMinute) + "-" + std::to_string(st.wSecond) + "-" + std::to_string(st.wMilliseconds);

	return fileName;
}

未处理异常过滤器回调函数

// 未处理异常过滤器回调函数																																		
LONG WINAPI UnhandledExceptionFilterCallback(EXCEPTION_POINTERS *pExceptionPointers)
{
	std::string moduleFilePath = GetCurrentModuleFilePath();
	std::string moduleFileName = GetModuleName(moduleFilePath);
	std::string currentTime = GetCurrentTimeWithFormat();

	// 获取dump文件名字
	std::string dumpFileName = moduleFileName + "_" + currentTime + ".dmp";

	// 获取dump文件输出路径
	std::string dumpFilePath = "";
	size_t pos = moduleFilePath.find_last_of("\\");
	if (pos != std::string::npos)
	{
		dumpFilePath = moduleFilePath.substr(0, pos + 1) + dumpFileName;
	}
	else
	{
		dumpFilePath = dumpFileName;
	}

	// 创建dump文件
	HANDLE hDumpFile = CreateFile(dumpFilePath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hDumpFile != INVALID_HANDLE_VALUE) 
	{
		MINIDUMP_EXCEPTION_INFORMATION miniDumpExceptionInfo;
		miniDumpExceptionInfo.ThreadId = GetCurrentThreadId();           // 表示引发异常的线程的线程标识符
		miniDumpExceptionInfo.ExceptionPointers = pExceptionPointers;    // 指向包含有关异常上下文信息的指针的指针
		miniDumpExceptionInfo.ClientPointers = FALSE;                    // 指示是否包含有关客户端指针的信息。如果设置为 TRUE,则会包含客户端指针的信息;如果设置为 FALSE,则不会包含客户端指针的信息。
		
		/*
		Note
		在 Windows 编程中,客户端指针通常指的是指向客户端应用程序内存中的数据结构或对象的指针。
		当生成 dump 文件时,包含客户端指针的信息可能会暴露应用程序的内部结构和数据,可能包含敏感信息,因此在某些情况下可能需要禁用客户端指针的信息以保护隐私和安全。
		*/

		// 根据自己的需要指定dump文件的类型,一般MiniDumpNormal就可
		MINIDUMP_TYPE miniDumpType = (MINIDUMP_TYPE)(MiniDumpNormal
			| MiniDumpWithHandleData
			| MiniDumpScanMemory
			| MiniDumpWithProcessThreadData
			| MiniDumpWithThreadInfo);

		// 写入dump文件
		BOOL bMiniDumpWriteSuccessful = MiniDumpWriteDump(
			GetCurrentProcess(),     // 获取进程句柄
			GetCurrentProcessId(),   // 获取进程ID
			hDumpFile,               // 要写入的dump文件句柄
			miniDumpType,            // 指定dump文件的类型
			&miniDumpExceptionInfo,  // 指向包含异常信息的结构体指针
			NULL,                    // 指向用户自定义数据的结构体指针
			NULL                     // 指向回调函数的结构体指针
		);                      
		CloseHandle(hDumpFile);
	}

	return EXCEPTION_EXECUTE_HANDLER;
}

未处理异常过滤器函数的调用

未处理异常过滤器函数的调用应该在main函数(MFC程序的话是InitInstance函数)入口或者dll函数的入口。

int main(){
	// 设置未处理异常过滤器
	SetUnhandledExceptionFilter(UnhandledExceptionFilterCallback);
	
    // 程序逻辑
    //......
}

注意事项

11-23 20:18