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



    前言

    开发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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    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)) {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    二、完整代码

    将上述实现封装在一个对象中。
    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);
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194

    三、使用示例

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行之后就会产生一个dump文件了
    在这里插入图片描述
    使用vs打开dump并使用本机进行调试就能看到异常代码和堆栈了。(需要注意pdb与exe以及代码相匹配)
    在这里插入图片描述

    2、生成内存快照

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

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

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

    在这里插入图片描述
    通过堆栈找到上一层代码
    在这里插入图片描述


    总结

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

  • 相关阅读:
    [附源码]java毕业设计氧气罐管理系统
    leetcode 2602. 使数组元素全部相等的最少操作次数
    Sonatype Nexus: Recommended file descriptor limit is 65536 but count is 4096
    vue3 事件 emit 使用
    C语言实现控制台简易计算器(VC6.0可用)
    【indexedDB】indexedDB知识梳理
    Vue-cli全局配置整理
    [附源码]计算机毕业设计基于springboot的低碳生活记录网站
    接口测试|超详细面试题【附答案】
    Linux部署Kafka2.8.1
  • 原文地址:https://blog.csdn.net/u013113678/article/details/127738968