开发Windows C++程序跟踪异常是比较重要的功能,一般情况都需要进行全局异常捕获,并且生成dump文件。而且C++没有运行时直接获取堆栈信息的方法,返回错误或者异常处理时无法记录到堆栈信息,如果这个时候能够生成dump,对于bug分析是非常有利的。
我们使用SetUnhandledExceptionFilter方法注册回调来捕获全局异常。
#include
//全局异常捕获回调方法
LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
printf("发生全局异常\n"));
return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
//设置全局异常捕获回调
SetUnhandledExceptionFilter(ExceptionFilter);
}
我们使用MiniDumpWriteDump方法生成dump文件,此方法依赖于EXCEPTION_POINTERS对象,通常在全局异常捕获回调中可以获取。
#include
#include
#pragma comment(lib,"Dbghelp.lib")
/
/// 生成dmp文件
///
/// 异常信息
/// 文件路径(包括文件名)
///
static int GenerateDump(EXCEPTION_POINTERS* exceptionPointers, const std::string& path)
{
HANDLE hFile = ::CreateFileA(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
MINIDUMP_EXCEPTION_INFORMATION minidumpExceptionInformation;
minidumpExceptionInformation.ThreadId = GetCurrentThreadId();
minidumpExceptionInformation.ExceptionPointers = exceptionPointers;
minidumpExceptionInformation.ClientPointers = TRUE;
bool isMiniDumpGenerated = MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MINIDUMP_TYPE::MiniDumpNormal,
&minidumpExceptionInformation,
nullptr,
nullptr);
CloseHandle(hFile);
if (!isMiniDumpGenerated)
{
printf("MiniDumpWriteDump failed\n");
}
}
else
{
printf("Failed to create dump file\n");
}
return EXCEPTION_EXECUTE_HANDLER;
}
在任意时刻生成dump,有一种方法是主动触发异常在异常捕获中获取EXCEPTION_POINTERS对象,继而调用MiniDumpWriteDump生成dump。我们使用RaiseException触发一个异常,并使用msvc的__try、__except进行异常捕获。
#include
///
/// 获取内存快照转储,可以任意时刻记录(包括非崩溃情况下)。
///
/// 保存文件名
void Snapshot(const std::string& path)
{
__try
{
//通过触发异常获取堆栈
RaiseException(0xE0000001, 0, 0, 0);
}
__except (GenerateDump(GetExceptionInformation(), path)) {}
}
将上述实现封装在一个对象中。
DumpHelper.h
#pragma once
#include
//
// Created by xin on 2022/9/12.
//
namespace AC {
class DumpHelper
{
public:
///
/// 设置是否记录崩溃转储
/// 默认否
///
/// 是否记录崩溃转储
static void SetIsDumpCrash(bool value);
///
/// 设置崩溃转储路径
/// 默认为".\\"
///
/// 崩溃转储路径
static void SetDumpDirectory(const std::string& directory);
///
/// 崩溃转储文件数量
/// 超过数量自动删除旧文件
///
/// 最大文件数量,默认500
static void SetDumpMaxFileCount(int count);
///
/// 获取内存快照转储,可以任意时刻记录(包括非崩溃情况下)。
///
/// 保存文件名
static void Snapshot(const std::string& path);
};
}
DumpHelper.cpp
#include "DumpHelper.h"
#include
#include "time.h"
#include
#include
#pragma comment(lib,"Dbghelp.lib")
#ifdef _WIN32
#include
#include
#else
#include
#include
#endif
#include
#include
#define MAX_PATH_LEN 256
#ifdef _WIN32
#define ACCESS(fileName,accessMode) _access(fileName,accessMode)
#define MKDIR(path) _mkdir(path)
#else
#define ACCESS(fileName,accessMode) access(fileName,accessMode)
#define MKDIR(path) mkdir(path,S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
#endif
namespace AC {
static std::string _directory = ".\\";
static int _fileCount = 500;
//获取文件夹下的所有文件名
static void GetFolderFiles(const std::string& path, std::vector<std::string>& files)
{
//文件句柄
intptr_t hFile = 0;
//文件信息
struct _finddata_t fileinfo;
std::string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
// "\\*"是指读取文件夹下的所有类型的文件,若想读取特定类型的文件,以png为例,则用“\\*.png”
{
do
{
//如果是目录,迭代之
//如果不是,加入列表
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
GetFolderFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(path + "\\" + fileinfo.name);
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
//生成多级目录,中间目录不存在则自动创建
static bool CreateMultiLevelDirectory(const std::string& directoryPath) {
uint32_t dirPathLen = directoryPath.length();
if (dirPathLen > MAX_PATH_LEN)
{
return false;
}
char tmpDirPath[MAX_PATH_LEN] = { 0 };
for (uint32_t i = 0; i < dirPathLen; ++i)
{
tmpDirPath[i] = directoryPath[i];
if (tmpDirPath[i] == '\\' || tmpDirPath[i] == '/')
{
if (ACCESS(tmpDirPath, 0) != 0)
{
int32_t ret = MKDIR(tmpDirPath);
if (ret != 0)
{
return false;
}
}
}
}
return true;
}
///
/// 生成dmp文件
///
/// 异常信息
/// 文件路径(包括文件名)
///
static int GenerateDump(EXCEPTION_POINTERS* exceptionPointers, const std::string& path)
{
HANDLE hFile = ::CreateFileA(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
MINIDUMP_EXCEPTION_INFORMATION minidumpExceptionInformation;
minidumpExceptionInformation.ThreadId = GetCurrentThreadId();
minidumpExceptionInformation.ExceptionPointers = exceptionPointers;
minidumpExceptionInformation.ClientPointers = TRUE;
bool isMiniDumpGenerated = MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
MINIDUMP_TYPE::MiniDumpNormal,
&minidumpExceptionInformation,
nullptr,
nullptr);
CloseHandle(hFile);
if (!isMiniDumpGenerated)
{
printf("MiniDumpWriteDump failed\n");
}
}
else
{
printf("Failed to create dump file\n");
}
return EXCEPTION_EXECUTE_HANDLER;
}
//全局异常捕获回调
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
time_t t;
struct tm* local;
char path[1024] = { 0 };
char ext[MAX_PATH] = { 0 };
t = time(NULL);
local = localtime(&t);
//创建目录
if (!CreateMultiLevelDirectory(_directory))
{
printf("Failed to create directory %s\n", _directory.c_str());
return EXCEPTION_EXECUTE_HANDLER;
}
//清除多出的文件
std::vector<std::string> files;
std::vector<std::string> dmpFiles;
GetFolderFiles(_directory, files);
for (auto i : files)
{
_splitpath(i.c_str(), NULL, NULL, NULL, ext);
if (strcmp(ext, "dmp")==0)
{
dmpFiles.push_back(i);
}
}
if (dmpFiles.size() >= _fileCount)
{
if (strcmp(ext, "dmp") == 0 && !DeleteFileA(dmpFiles.front().c_str()))
{
printf("Failed to delete old file %s\n", dmpFiles.front().c_str());
}
}
//生成文件名
sprintf(path, "%s%d%02d%02d%02d%02d%02d.dmp", _directory.c_str(), 1900 + local->tm_year, local->tm_mon + 1, local->tm_mday, local->tm_hour, local->tm_min, local->tm_sec);
if (strlen(path) > MAX_PATH)
{
printf("File path was too long! %s\n",path);
return EXCEPTION_EXECUTE_HANDLER;
}
//生成dmp文件
return GenerateDump(lpExceptionInfo, path);
}
void DumpHelper::SetIsDumpCrash(bool value)
{
if (value)
SetUnhandledExceptionFilter(ExceptionFilter);
else
{
SetUnhandledExceptionFilter(NULL);
}
}
void DumpHelper::SetDumpDirectory(const std::string& directory)
{
_directory = directory;
if (_directory.size() < 1)
{
_directory = ".\\";
}
else if (_directory.back() != '\\')
{
_directory.push_back('\\');
}
}
void DumpHelper::SetDumpMaxFileCount(int count)
{
_fileCount = count;
}
void DumpHelper::Snapshot(const std::string& path)
{
__try
{
//通过触发异常获取堆栈
RaiseException(0xE0000001, 0, 0, 0);
}
__except (GenerateDump(GetExceptionInformation(), path)) {}
}
}
#include"DumpHelper.h"
int main(int argc, char** argv) {
//启动全局异常捕获生成dump
AC::DumpHelper::SetIsDumpCrash(true);
//设置dump文件保存目录
AC::DumpHelper::SetDumpDirectory("dmp");
//设置最大dump文件数,超过会自动删除最旧的文件
AC::DumpHelper::SetDumpMaxFileCount(30);
//触发一个异常
int a = 0;
int b = 5 / a;
return 0;
}
运行之后就会产生一个dump文件了
使用vs打开dump并使用本机进行调试就能看到异常代码和堆栈了。(需要注意pdb与exe以及代码相匹配)
调用Snapshot生成当前内存快照。生成快照会触发异常并捕获,不会导致程序奔溃,程序依然可以按照正常流程执行。
#include"DumpHelper.h"
int main(int argc, char** argv) {
AC::DumpHelper::Snapshot("current.dmp");
return 0;
}
程序执行后就会产生dump文件了
使用vs调试dump,异常点在我们生成dump的代码中。
以上就是今天要讲的内容,生成dump其实是比较基本的Windows开发所需具备的技能,当然通常都会用于全局异常捕获,但有时使用了try-catch也能在catch生成dump而不让程序奔溃。总的来说,dump文件还是很有用的当然也是有着一些限制比如要有相应pdb以及和代码和可执行程序版本必须对应。