Windows C++程序运行过程中生成dump文件

文章目录

  • 前言
  • 一、如何实现
    • 1、捕获全局异常
    • 2、基于异常生成dump
    • 3、任意时刻生成dump
  • 二、完整代码
  • 三、使用示例
    • 1、全局异常捕获
    • 2、生成内存快照
  • 总结


前言

开发Windows C++程序跟踪异常是比较重要的功能,一般情况都需要进行全局异常捕获,并且生成dump文件。而且C++没有运行时直接获取堆栈信息的方法,返回错误或者异常处理时无法记录到堆栈信息,如果这个时候能够生成dump,对于bug分析是非常有利的。


一、如何实现

1、捕获全局异常

我们使用SetUnhandledExceptionFilter方法注册回调来捕获全局异常。

#include 
//全局异常捕获回调方法
LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
	printf("发生全局异常\n"));
	return EXCEPTION_EXECUTE_HANDLER;
}
void main()
{
  //设置全局异常捕获回调
  SetUnhandledExceptionFilter(ExceptionFilter);
}

2、基于异常生成dump

我们使用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;
}

3、任意时刻生成dump

在任意时刻生成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)) {}
	}
}

三、使用示例

1、全局异常捕获

#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文件了
Windows C++程序运行过程中生成dump文件_第1张图片
使用vs打开dump并使用本机进行调试就能看到异常代码和堆栈了。(需要注意pdb与exe以及代码相匹配)
Windows C++程序运行过程中生成dump文件_第2张图片

2、生成内存快照

调用Snapshot生成当前内存快照。生成快照会触发异常并捕获,不会导致程序奔溃,程序依然可以按照正常流程执行。

#include"DumpHelper.h"
int main(int argc, char** argv) {
	AC::DumpHelper::Snapshot("current.dmp");
	return 0;
}

程序执行后就会产生dump文件了
在这里插入图片描述
使用vs调试dump,异常点在我们生成dump的代码中。

Windows C++程序运行过程中生成dump文件_第3张图片
通过堆栈找到上一层代码
Windows C++程序运行过程中生成dump文件_第4张图片


总结

以上就是今天要讲的内容,生成dump其实是比较基本的Windows开发所需具备的技能,当然通常都会用于全局异常捕获,但有时使用了try-catch也能在catch生成dump而不让程序奔溃。总的来说,dump文件还是很有用的当然也是有着一些限制比如要有相应pdb以及和代码和可执行程序版本必须对应。

你可能感兴趣的:(c++,Windows,c++,windows,异常处理,堆栈,内存)