最近项目中新作成了一个动态链接库,长时间运行后,偶尔会崩溃。根据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
dump生成原理:
生成dump文件的原理是在未处理异常发生时,系统调用设置的未处理异常过滤器回调函数,并将异常信息传递给这个回调函数。回调函数可以利用这些异常信息,如异常指针和线程ID,来创建一个包含应用程序状态快照的dump文件。这个快照包括了应用程序的内存、寄存器状态、堆栈信息等,可以帮助开发人员在应用程序崩溃时进行调试和分析。
SetUnhandledExceptionFilter函数是Windows平台上用于设置未处理异常过滤器的函数。
MiniDumpWriteDump函数是Windows平台上用于输出dump文件的函数。
代码实例
dump文件名和输出路径设置
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);
// 程序逻辑
//......
}
try-catch模块场合
如果异常发生在try快中,且被catch捕捉到,系统就不会生成dump文件了。如果仍希望在try-catch块中捕获异常的同时生成dump文件,就必须在catch块中手动调用生成dump文件的函数。
catch
{
// 其他处理......
UnhandledExceptionFilterCallback(nullptr);
}
注意事项
在生成dump文件时,有一些注意事项需要考虑:
- 文件大小:生成的dump文件可能会很大,特别是在应用程序的内存占用较大时。确保生成dump文件的目标位置有足够的磁盘空间。
- 调试符号:为了更好地分析dump文件,通常需要应用程序的调试符号(PDB文件)。确保在生成dump文件时,同时保存了相关的调试符号信息。
- 版本一致性:在分析dump文件时,确保使用与生成dump文件时相同版本的调试符号和源代码。否则,可能会导致分析结果不准确。