Visual C++下使用Win32 API为Release模式导出崩溃堆栈

上一篇文章讲述了在MSYS2和Linux里,进行崩溃信息导出的方法。对MSVC来说,除了导出出错文本外,还可以用API直接导出可以进行调试的 dmp 文件。相关代码参考Gitcode.

文章目录

  • [1. 头文件](#1. 头文件)
  • [2. 测试程序](#2. 测试程序)
  • [3. MSVC 的Release版本配置](#3. MSVC 的Release版本配置)
  • [4. 发布程序并收集错误](#4. 发布程序并收集错误)
  • [5. 发回本地调试错误](#5. 发回本地调试错误)
  • [6. 注意事项](#6. 注意事项)

1. 头文件

延续上一篇文章的接口,驱动AI编程,竟然直接获得了接口完全一致的Win32 API 程序:

cpp 复制代码
#pragma once

// 必要的系统头文件
#include <Windows.h>
#include <DbgHelp.h>
#include <time.h>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
// 链接DbgHelp库(MSVC专用)
#pragma comment(lib, "Dbghelp.lib")


// 禁用安全警告(localtime_s已替代不安全的localtime)
#pragma warning(disable: 4996)
namespace CRASHDMP_CPP {
	// 异常代码转可读字符串
	inline std::string ExceptionCodeToString(DWORD exceptionCode)
	{
		switch (exceptionCode)
		{
		case EXCEPTION_ACCESS_VIOLATION:    return "EXCEPTION_ACCESS_VIOLATION (非法内存访问)";
		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED (数组越界)";
		case EXCEPTION_BREAKPOINT:          return "EXCEPTION_BREAKPOINT (断点异常)";
		case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT (数据类型未对齐)";
		case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND (浮点非正规操作数)";
		case EXCEPTION_FLT_DIVIDE_BY_ZERO:  return "EXCEPTION_FLT_DIVIDE_BY_ZERO (浮点除零)";
		case EXCEPTION_FLT_INEXACT_RESULT:  return "EXCEPTION_FLT_INEXACT_RESULT (浮点不精确结果)";
		case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION (浮点无效操作)";
		case EXCEPTION_FLT_OVERFLOW:        return "EXCEPTION_FLT_OVERFLOW (浮点溢出)";
		case EXCEPTION_FLT_STACK_CHECK:     return "EXCEPTION_FLT_STACK_CHECK (浮点栈检查)";
		case EXCEPTION_FLT_UNDERFLOW:       return "EXCEPTION_FLT_UNDERFLOW (浮点下溢)";
		case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION (非法指令)";
		case EXCEPTION_IN_PAGE_ERROR:       return "EXCEPTION_IN_PAGE_ERROR (页错误)";
		case EXCEPTION_INT_DIVIDE_BY_ZERO:  return "EXCEPTION_INT_DIVIDE_BY_ZERO (整数除零)";
		case EXCEPTION_INT_OVERFLOW:        return "EXCEPTION_INT_OVERFLOW (整数溢出)";
		case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION (无效处置)";
		case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION (不可继续异常)";
		case EXCEPTION_PRIV_INSTRUCTION:    return "EXCEPTION_PRIV_INSTRUCTION (特权指令)";
		case EXCEPTION_SINGLE_STEP:         return "EXCEPTION_SINGLE_STEP (单步执行)";
		case EXCEPTION_STACK_OVERFLOW:      return "EXCEPTION_STACK_OVERFLOW (栈溢出)";
		default:
			char buf[64] = { 0 };
			sprintf_s(buf, "未知异常代码 (0x%08X)", exceptionCode);
			return buf;
		}
	}

	// 获取系统信息(OS版本、位数等)
	inline std::string GetSystemInfoString()
	{
		std::ostringstream oss;

		// 获取系统位数
		SYSTEM_INFO sysInfo = { 0 };
		GetSystemInfo(&sysInfo);
		oss << "系统位数: " << (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? "x64" : "x86") << "\n";


		// 进程/线程ID
		oss << "进程ID: " << GetCurrentProcessId() << "\n";
		oss << "线程ID: " << GetCurrentThreadId() << "\n";

		return oss.str();
	}

	// 解析调用堆栈(核心新增函数)
	inline std::string GetCallStackString(EXCEPTION_POINTERS* pExceptionPointers)
	{
		if (pExceptionPointers == nullptr) return "调用堆栈:无异常上下文\n";

		std::ostringstream oss;
		HANDLE hProcess = GetCurrentProcess();
		HANDLE hThread = GetCurrentThread();
		CONTEXT* pContext = pExceptionPointers->ContextRecord;
		SYSTEM_INFO sysInfo = { 0 };
		GetSystemInfo(&sysInfo);

		// ========== 1. 初始化符号引擎 ==========
		// 符号路径:优先本地符号,其次系统符号服务器(自动下载)
		// srv*C:\Symbols*https://msdl.microsoft.com/download/symbols 表示:
		// 本地缓存到C:\Symbols,远程从微软符号服务器下载
		const char* szSymbolPath = "srv*C:\\Symbols*https://msdl.microsoft.com/download/symbols";
		if (!SymInitialize(hProcess, szSymbolPath, TRUE))
		{
			oss << "调用堆栈:符号引擎初始化失败 (错误码: " << GetLastError() << ")\n";
			return oss.str();
		}

		// 设置符号选项(提升解析成功率)
		SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES);

		// ========== 2. 初始化堆栈遍历结构 ==========
		STACKFRAME64 stackFrame = { 0 };
		DWORD machineType = 0;

		// 区分x86/x64架构
#ifdef _WIN64
		machineType = IMAGE_FILE_MACHINE_AMD64;
		stackFrame.AddrPC.Offset = pContext->Rip;
		stackFrame.AddrPC.Mode = AddrModeFlat;
		stackFrame.AddrFrame.Offset = pContext->Rbp;
		stackFrame.AddrFrame.Mode = AddrModeFlat;
		stackFrame.AddrStack.Offset = pContext->Rsp;
		stackFrame.AddrStack.Mode = AddrModeFlat;
#else
		machineType = IMAGE_FILE_MACHINE_I386;
		stackFrame.AddrPC.Offset = pContext->Eip;
		stackFrame.AddrPC.Mode = AddrModeFlat;
		stackFrame.AddrFrame.Offset = pContext->Ebp;
		stackFrame.AddrFrame.Mode = AddrModeFlat;
		stackFrame.AddrStack.Offset = pContext->Esp;
		stackFrame.AddrStack.Mode = AddrModeFlat;
#endif

		oss << "调用堆栈(帧序号 | 地址 | 函数名 | 模块名 | 偏移):\n";
		int frameIndex = 0;
		const int MAX_FRAMES = 64; // 最多解析64层堆栈(避免无限循环)

		// ========== 3. 遍历堆栈帧 ==========
		while (StackWalk64(
			machineType,
			hProcess,
			hThread,
			&stackFrame,
			pContext,
			nullptr,
			SymFunctionTableAccess64,
			SymGetModuleBase64,
			nullptr
		) && frameIndex < MAX_FRAMES)
		{
			// 跳过无效的PC地址
			if (stackFrame.AddrPC.Offset == 0) break;

			oss << "  帧" << std::setw(2) << frameIndex << ": ";
			oss << "0x" << std::hex << std::uppercase << stackFrame.AddrPC.Offset << " | ";

			// ========== 4. 解析符号信息 ==========
			DWORD64 dwDisplacement = 0;
			char szFuncName[2048] = "未知函数";
			char szModuleName[MAX_PATH] = "未知模块";
			SYMBOL_INFO* pSymbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO) + 2048);
			if (pSymbol != nullptr)
			{
				memset(pSymbol, 0, sizeof(SYMBOL_INFO) + 2048);
				pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
				pSymbol->MaxNameLen = 2047;

				// 解析地址对应的符号
				if (SymFromAddr(hProcess, stackFrame.AddrPC.Offset, &dwDisplacement, pSymbol))
				{
					strcpy_s(szFuncName, sizeof(szFuncName), pSymbol->Name);
					// 解析模块名
					IMAGEHLP_MODULE64 moduleInfo = { 0 };
					moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
					if (SymGetModuleInfo64(hProcess, stackFrame.AddrPC.Offset, &moduleInfo))
					{
						strcpy_s(szModuleName, sizeof(szModuleName), moduleInfo.ModuleName);
					}
				}
				free(pSymbol);
			}

			// 输出解析结果(函数名 + 模块名 + 偏移)
			oss << szFuncName << " | " << szModuleName << " | +0x" << std::hex << dwDisplacement << "\n";

			frameIndex++;
		}

		if (frameIndex == 0)
		{
			oss << "  无有效堆栈帧\n";
		}

		// ========== 5. 清理符号引擎 ==========
		SymCleanup(hProcess);

		return oss.str();
	}

	class CrashDumper;
	// 全局CrashDumper实例指针(用于静态回调函数访问)
	static CrashDumper* g_pGlobalCrashDumper = nullptr;

	class CrashDumper
	{
	public:
		// 构造函数:接收dump文件前缀(如D:\log\myApp)
		explicit CrashDumper(const std::string& dumpFilePrefix)
			: m_dumpFilePrefix(dumpFilePrefix)
		{
			// 保存原始错误模式并设置静默崩溃(不弹出系统错误框)
			m_originalErrorMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);

			// 保存原始异常过滤器并设置自定义过滤器
			m_originalExceptionFilter = SetUnhandledExceptionFilter(UnhandledExceptionHandler);
		}

		// 析构函数:还原系统原始设置
		~CrashDumper()
		{
			// 还原异常过滤器
			SetUnhandledExceptionFilter(m_originalExceptionFilter);

			// 还原错误模式
			SetErrorMode(m_originalErrorMode);
		}

		// 禁用拷贝构造和赋值(单例语义,避免重复设置过滤器)
		CrashDumper(const CrashDumper&) = delete;
		CrashDumper& operator=(const CrashDumper&) = delete;

	private:
		// MiniDump级别优先级:从最详细到最小(逐一尝试)
		static const std::vector<MINIDUMP_TYPE> s_miniDumpTypes;
		// MiniDump级别名称映射(用于TXT日志)
		static const std::vector<std::string> s_miniDumpTypeNames;
		// 记录最终成功的DMP级别
		std::string m_successfulDumpType;
		// 文件名前缀(如D:\log\myApp)
		std::string m_dumpFilePrefix;

		// 系统原始设置(用于析构还原)
		static LPTOP_LEVEL_EXCEPTION_FILTER m_originalExceptionFilter;
		static UINT m_originalErrorMode;

		// 生成带时间戳的dump文件名
		std::string GenerateDumpFileName() const
		{
			// 获取当前系统时间
			time_t now = time(nullptr);
			tm localTime{};
			localtime_s(&localTime, &now);

			// 格式化时间戳:YYYYMMDD.HHMMSS
			char timeStamp[32] = { 0 };
			strftime(timeStamp, sizeof(timeStamp), "%Y%m%d.%H%M%S", &localTime);

			// 拼接完整文件名:前缀.crashdump.时间戳.dmp
			return m_dumpFilePrefix + ".crash_log_" + timeStamp + ".dmp";
		}

		// 生成对应TXT日志文件名(DMP路径 + .txt)
		std::string GenerateTxtFileName(const std::string& dumpPath) const
		{
			return dumpPath + ".txt";
		}
		// 写入崩溃详情到TXT文件(新增调用堆栈)
		bool WriteCrashInfoToTxt(const std::string& txtPath, EXCEPTION_POINTERS* pExceptionPointers)
		{
			if (pExceptionPointers == nullptr) return false;

			// 创建TXT文件(ANSI编码,避免乱码)
			HANDLE hFile = CreateFileA(
				txtPath.c_str(),
				GENERIC_WRITE,
				FILE_SHARE_READ | FILE_SHARE_WRITE,
				nullptr,
				CREATE_ALWAYS,
				FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
				nullptr
			);

			if (hFile == INVALID_HANDLE_VALUE) return false;

			// 构建日志内容
			std::ostringstream oss;
			oss << "==================== 程序崩溃详情 ====================\n";

			// 1. 崩溃时间(精确到毫秒)
			SYSTEMTIME sysTime = { 0 };
			GetLocalTime(&sysTime);
			oss << "崩溃时间: "
				<< sysTime.wYear << "-" << std::setw(2) << std::setfill('0') << sysTime.wMonth << "-" << std::setw(2) << sysTime.wDay
				<< " " << std::setw(2) << sysTime.wHour << ":" << std::setw(2) << sysTime.wMinute << ":" << std::setw(2) << sysTime.wSecond
				<< "." << std::setw(3) << sysTime.wMilliseconds << "\n";

			// 2. DMP文件信息
			oss << "DMP文件路径: " << GenerateDumpFileName() << "\n";
			oss << "成功生成的DMP级别: " << m_successfulDumpType << "\n\n";

			// 3. 系统信息
			oss << "==================== 系统信息 ====================\n";
			oss << GetSystemInfoString() << "\n";

			// 4. 异常详情
			oss << "==================== 异常详情 ====================\n";
			EXCEPTION_RECORD* pExceptionRecord = pExceptionPointers->ExceptionRecord;
			oss << "异常类型: " << ExceptionCodeToString(pExceptionRecord->ExceptionCode) << "\n";
			oss << "异常代码: 0x" << std::hex << std::uppercase << pExceptionRecord->ExceptionCode << "\n";
			oss << "异常地址: 0x" << std::hex << std::uppercase << (DWORD64)pExceptionRecord->ExceptionAddress << "\n";
			oss << "异常参数数量: " << pExceptionRecord->NumberParameters << "\n";
			for (DWORD i = 0; i < pExceptionRecord->NumberParameters; ++i)
			{
				oss << "异常参数" << i + 1 << ": 0x" << std::hex << std::uppercase << pExceptionRecord->ExceptionInformation[i] << "\n";
			}

			// 5. 线程上下文(简化版,仅关键寄存器)
			oss << "\n==================== 线程上下文(关键寄存器) ====================\n";
			CONTEXT* pContext = pExceptionPointers->ContextRecord;
#ifdef _WIN64
			oss << "RAX: 0x" << std::hex << std::uppercase << pContext->Rax << "\n";
			oss << "RBX: 0x" << std::hex << std::uppercase << pContext->Rbx << "\n";
			oss << "RCX: 0x" << std::hex << std::uppercase << pContext->Rcx << "\n";
			oss << "RDX: 0x" << std::hex << std::uppercase << pContext->Rdx << "\n";
			oss << "RIP: 0x" << std::hex << std::uppercase << pContext->Rip << "\n";
			oss << "RSP: 0x" << std::hex << std::uppercase << pContext->Rsp << "\n";
#else
			oss << "EAX: 0x" << std::hex << std::uppercase << pContext->Eax << "\n";
			oss << "EBX: 0x" << std::hex << std::uppercase << pContext->Ebx << "\n";
			oss << "ECX: 0x" << std::hex << std::uppercase << pContext->Ecx << "\n";
			oss << "EDX: 0x" << std::hex << std::uppercase << pContext->Edx << "\n";
			oss << "EIP: 0x" << std::hex << std::uppercase << pContext->Eip << "\n";
			oss << "ESP: 0x" << std::hex << std::uppercase << pContext->Esp << "\n";
#endif

			// 6. 调用堆栈(核心新增)
			oss << "\n==================== 调用堆栈 ====================\n";
			oss << GetCallStackString(pExceptionPointers) << "\n";

			// 写入文件
			std::string logContent = oss.str();
			DWORD bytesWritten = 0;
			BOOL writeResult = WriteFile(
				hFile,
				logContent.c_str(),
				(DWORD)logContent.length(),
				&bytesWritten,
				nullptr
			);

			// 关闭文件句柄
			CloseHandle(hFile);

			// 验证写入长度
			return writeResult && (bytesWritten == logContent.length());
		}

		// 尝试写入MiniDump(按级别逐一尝试)
		bool TryWriteMiniDump(const std::string& dumpPath, EXCEPTION_POINTERS* pExceptionPointers)
		{
			// 构造MiniDump异常信息结构体(关键修正点)
			MINIDUMP_EXCEPTION_INFORMATION exceptionInfo = { 0 };
			exceptionInfo.ThreadId = GetCurrentThreadId();          // 当前崩溃线程ID
			exceptionInfo.ExceptionPointers = pExceptionPointers;  // 异常指针
			exceptionInfo.ClientPointers = TRUE;                   // 客户端指针标识

			// 遍历所有MiniDump级别(从详细到简单)
			for (size_t i = 0; i < s_miniDumpTypes.size(); ++i)
			{
				MINIDUMP_TYPE dumpType = s_miniDumpTypes[i];
				const std::string& dumpTypeName = s_miniDumpTypeNames[i];
				// 创建dump文件(处理权限问题,允许读写共享)
				HANDLE hFile = CreateFileA(
					dumpPath.c_str(),
					GENERIC_WRITE,
					FILE_SHARE_READ | FILE_SHARE_WRITE,
					nullptr,
					CREATE_ALWAYS,
					FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
					nullptr
				);

				// 文件创建失败,直接退出
				if (hFile == INVALID_HANDLE_VALUE)
				{
					return false;
				}

				// 尝试写入MiniDump(修正参数5为异常信息结构体)
				BOOL dumpResult = MiniDumpWriteDump(
					GetCurrentProcess(),
					GetCurrentProcessId(),
					hFile,
					dumpType,
					pExceptionPointers ? &exceptionInfo : nullptr,  // 正确传入结构体指针
					nullptr,
					nullptr
				);

				// 关闭文件句柄
				CloseHandle(hFile);

				// 写入成功则返回
				if (dumpResult)
				{
					m_successfulDumpType = dumpTypeName;

					// 生成TXT文件(失败不影响主流程)
					WriteCrashInfoToTxt(GenerateTxtFileName(dumpPath), pExceptionPointers);

					return true;
				}
			}

			// 所有级别都失败,清理0字节文件
			CleanupEmptyFile(dumpPath);
			return false;
		}

		// 清理0字节的dump文件
		void CleanupEmptyFile(const std::string& filePath)
		{
			// 获取文件大小
			HANDLE hFile = CreateFileA(
				filePath.c_str(),
				GENERIC_READ,
				FILE_SHARE_READ,
				nullptr,
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL,
				nullptr
			);

			if (hFile != INVALID_HANDLE_VALUE)
			{
				DWORD fileSize = GetFileSize(hFile, nullptr);
				CloseHandle(hFile);

				// 如果文件大小为0则删除
				if (fileSize == 0)
				{
					DeleteFileA(filePath.c_str());
				}
			}
		}

		// 未处理异常的回调函数(静态成员)
		static LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* pExceptionPointers)
		{
			// 获取CrashDumper实例(注:需确保全局唯一实例,此处通过全局变量实现)
			//extern CrashDumper* CRASHDMP_MSVC::g_pGlobalCrashDumper;
			if (g_pGlobalCrashDumper == nullptr)
			{
				ExitProcess(EXIT_FAILURE);
			}

			// 生成dump文件名
			std::string dumpPath = g_pGlobalCrashDumper->GenerateDumpFileName();

			// 尝试写入dump文件
			g_pGlobalCrashDumper->TryWriteMiniDump(dumpPath, pExceptionPointers);

			// 静默退出程序
			ExitProcess(EXIT_FAILURE);

			// 返回值无实际意义(已调用ExitProcess)
			return EXCEPTION_EXECUTE_HANDLER;
		}
	};

	// 静态成员初始化
	LPTOP_LEVEL_EXCEPTION_FILTER CrashDumper::m_originalExceptionFilter = nullptr;
	UINT CrashDumper::m_originalErrorMode = 0;

	// MiniDump级别定义(从最详细到最小)
	const std::vector<MINIDUMP_TYPE> CrashDumper::s_miniDumpTypes = {
		MiniDumpWithFullMemory,          // 最详细:包含完整内存
		MiniDumpWithFullMemoryInfo,      // 完整内存信息
		MiniDumpWithHandleData,          // 包含句柄数据
		MiniDumpWithThreadInfo,          // 包含线程信息
		MiniDumpWithUnloadedModules,     // 包含已卸载模块信息
		MiniDumpNormal                   // 最小级别:仅基本信息
	};

	// MiniDump级别名称映射(与上面顺序一致)
	const std::vector<std::string> CrashDumper::s_miniDumpTypeNames = {
		"MiniDumpWithFullMemory (完整内存)",
		"MiniDumpWithFullMemoryInfo (完整内存信息)",
		"MiniDumpWithHandleData (句柄数据)",
		"MiniDumpWithThreadInfo (线程信息)",
		"MiniDumpWithUnloadedModules (已卸载模块)",
		"MiniDumpNormal (基础信息)"
	};

	// 便捷初始化函数(建议在程序入口处调用)
	inline void init_crash_handler(const std::string& dumpFilePrefix)
	{
		static CrashDumper crashDumper(dumpFilePrefix);
		g_pGlobalCrashDumper = &crashDumper;
	}
}

2. 测试程序

测试程序依旧采用上一篇的例子:

cpp 复制代码
#include <stdio.h>
#include "crash_dump_msvc.h"

void func_c(int value) {
	int* null_ptr = NULL;
	null_ptr[value] = value; // 崩溃点(第 113 行)
}

void func_b(int value) {
	func_c(value * 2); // 调用 func_c(第 117 行)
}

void func_a(int value) {
	func_b(value + 3); // 调用 func_b(第 121 行)
}

int main(int /*argc*/, char * argv[]) {
	// 初始化崩溃处理器(必须在程序启动初期调用)
	CRASHDMP_CPP::init_crash_handler(argv[0]);

	puts("Program started...\n");
	puts("Target= crash_log_xxx.txt\n");

	// 触发崩溃(函数调用链:main -> func_a -> func_b -> func_c)
	func_a(10);

	return 0;
}

3. MSVC 的Release版本配置

要注意,在项目里设置正确的编译开关。要确认的位置有两处,一个是编译的C++选项常规选项卡,调试选项选取"程序数据库":

另一个,是链接器选取生成调试信息。

这两个选项只会增加EXE的大小,产生pdb文件,并不会显著拖慢运行速率。

4. 发布程序并收集错误

把编译好的程序拷贝到生产环境,一旦崩溃,就会产生两个文件:

其中,TXT文件如下:

txt 复制代码
==================== 程序崩溃详情 ====================
崩溃时间: 2025-12-06 11:08:31.856
DMP文件路径: D:\crash_dump.exe.crash_log_20251206.110831.dmp
成功生成的DMP级别: MiniDumpWithFullMemory (完整内存)

==================== 系统信息 ====================
系统位数: x64
进程ID: 15172
线程ID: 8156

==================== 异常详情 ====================
异常类型: EXCEPTION_ACCESS_VIOLATION (非法内存访问)
异常代码: 0xC0000005
异常地址: 0x7FF70B773EF7
异常参数数量: 2
异常参数1: 0x1
异常参数2: 0x68

==================== 线程上下文(关键寄存器) ====================
RAX: 0x0
RBX: 0x151D8CEC070
RCX: 0x39D15AFC80
RDX: 0x151D8AB0000
RIP: 0x7FF70B773EF7
RSP: 0x39D15AFCE0

==================== 调用堆栈 ====================
调用堆栈(帧序号 | 地址 | 函数名 | 模块名 | 偏移):
  帧 0: 0x7FF70B773EF7 | main | crash_dump | +0x127
  帧 1: 0x7FF70B775514 | __scrt_common_main_seh | crash_dump | +0x10C
  帧 2: 0x7FF98E0AE8D7 | BaseThreadInitThunk | KERNEL32 | +0x17
  帧 3: 0x7FF98F70C53C | RtlUserThreadStart | ntdll | +0x2C

另一个文件是 dmp文件,要通过邮件传回开发者。

5. 发回本地调试错误

开发者从邮件拿到dmp文件后,直接用Visual Studio打开:

而后选择"仅使用本机调试"

即可定位到错误

6. 注意事项

如果当前的代码已经重新修改编译了,或者换了绝对路径,那拿回来的dmp就无法关联到GUI的报错位置(上图)了。因此,一般发布包建议把代码拷贝到一个专门的发布代码镜像文件夹,编译发布。发布后,文件夹就冻结了。所有的修改都在原有文件夹修改。

这也和使用Git管理代码很像,一个Tag建立后,比如 Release V 1.2.0,则Tag不再改动了。

相关推荐
真上帝的左手2 小时前
18. 操作系统-Windows-命令提示符
windows
小小晓.3 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS3 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
steins_甲乙3 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
煤球王子3 小时前
学而时习之:C++中的异常处理2
c++
仰泳的熊猫4 小时前
1084 Broken Keyboard
数据结构·c++·算法·pat考试
我不会插花弄玉4 小时前
C++的内存管理【由浅入深-C++】
c++
CSDN_RTKLIB4 小时前
代码指令与属性配置
开发语言·c++
上不如老下不如小4 小时前
2025年第七届全国高校计算机能力挑战赛 决赛 C++组 编程题汇总
开发语言·c++
雍凉明月夜4 小时前
c++ 精学笔记记录Ⅱ
开发语言·c++·笔记·vscode