Windows上位机软件生成dump文件
一、为什么要生成dump文件在日常开发Windows上位机软件时最让人头疼的问题莫过于程序在客户环境中崩溃而本地却无法复现。dump文件就像是程序的“黑匣子”记录了程序崩溃瞬间的完整状态——包括内存数据、线程调用栈、寄存器信息等是事后分析问题的重要依据。适合使用dump文件进行事后分析的场景包括程序在客户机器上崩溃但无法直接在客户环境调试偶发性Bug运行数天才出现一次服务器程序故障后需要尽快恢复服务无法长时间挂起进程等待调试二、Dump文件的类型Windows下dump文件主要分为类型大小包含内容适用场景MiniDump小型转储几十KB~几MB线程栈、异常信息、加载模块快速崩溃分析最常用Full Dump完整转储进程内存大小完整的进程内存数据内存泄漏、复杂状态分析自定义转储按需开发者自行选择包含内容灵活定制三、生成Dump文件的几种方式3.1 任务管理器手动生成最简单直接的方式。当程序崩溃后尚未退出时打开任务管理器找到对应进程右键选择“创建转储文件”即可。局限仅适用于程序崩溃后没有立即退出的情况。3.2 注册表配置自动生成可以通过配置注册表让操作系统在程序崩溃时自动生成dump文件。在以下路径配置textHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\WindowsErrorReporting\LocalDumps配置项说明配置项类型说明DumpFolderREG_EXPAND_SZdump文件保存路径DumpTypeREG_DWORD0-自定义1-MiniDump2-FullDumpDumpCountREG_DWORD最多保留的dump文件数量也可使用批处理脚本快速配置SET DMPPATHE:\CrashDumps REG ADD HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe /f REG ADD HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe /t REG_SZ /v DumpFolder /d %DMPPATH% /f REG ADD HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe /t REG_DWORD /v DumpCount /d 5 /f REG ADD HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\MyApp.exe /t REG_DWORD /v DumpType /d 1 /f3.3 程序中集成Dump生成代码推荐对于正式发布的上位机软件推荐在程序中集成dump生成功能当程序发生未处理异常时自动生成dump文件。核心原理是通过SetUnhandledExceptionFilter注册一个异常处理回调函数当程序发生未被捕获的异常时系统会调用这个函数在函数中使用MiniDumpWriteDumpAPI生成dump文件。四、完整源码实现以下是完整的dump生成代码可直接集成到C/C项目中#include stdafx.h #include windows.h #include tchar.h #include string #include DbgHelp.h namespace { // 避免异常处理器递归调用写 dump 过程中再次崩溃等 volatile LONG g_inHandler 0; PVOID g_vectoredHandle nullptr; std::wstring GetExeDirW() { wchar_t exePath[MAX_PATH] { 0 }; const DWORD len GetModuleFileNameW(nullptr, exePath, MAX_PATH); if (len 0 || len MAX_PATH) return L; std::wstring p(exePath, len); const size_t pos p.find_last_of(L\\/); if (pos ! std::wstring::npos) p.erase(pos); return p; } bool EnsureDirectoryW(const std::wstring dir) { if (dir.empty()) return false; const DWORD attr GetFileAttributesW(dir.c_str()); if (attr ! INVALID_FILE_ATTRIBUTES (attr FILE_ATTRIBUTE_DIRECTORY)) return true; if (CreateDirectoryW(dir.c_str(), nullptr) ! 0) return true; // 已存在则视为成功 return GetLastError() ERROR_ALREADY_EXISTS; } std::wstring BuildDumpFilePathW() { const std::wstring exeDir GetExeDirW(); std::wstring dumpDir exeDir L\\Dump; // 如目录不存在则创建 EnsureDirectoryW(dumpDir); SYSTEMTIME st; GetLocalTime(st); const DWORD pid GetCurrentProcessId(); wchar_t fileName[MAX_PATH] { 0 }; swprintf_s( fileName, LDelphiScriber_%04u%02u%02u_%02u%02u%02u_pid%u.dmp, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, static_castunsigned(pid)); return dumpDir L\\ fileName; } // VEH 在 first chance 即被调用仅对白名单内的“硬错误”写 dump避免断点、C throw、栈探测等误触发。 // 不在 unwind 阶段重复写同一次崩溃的第二个 dump。 bool ShouldWriteMiniDumpForException(const EXCEPTION_RECORD* er) { if (!er) return false; if (er-ExceptionFlags (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) return false; const DWORD code er-ExceptionCode; switch (code) { case 0xC0000005u: // STATUS_ACCESS_VIOLATION case 0xC0000006u: // STATUS_IN_PAGE_ERROR case 0xC000008Cu: // STATUS_ARRAY_BOUNDS_EXCEEDED case 0xC000008Eu: // STATUS_FLOAT_DIVIDE_BY_ZERO case 0xC0000094u: // STATUS_INTEGER_DIVIDE_BY_ZERO case 0xC0000095u: // STATUS_INTEGER_OVERFLOW case 0xC0000096u: // STATUS_PRIVILEGED_INSTRUCTION case 0xC000001Du: // STATUS_ILLEGAL_INSTRUCTION case 0xC00000FDu: // STATUS_STACK_OVERFLOW case 0xC0000409u: // STATUS_STACK_BUFFER_OVERRUN / 部分安全 cookie 失败 case 0xC0000374u: // STATUS_HEAP_CORRUPTION return true; default: return false; } } LONG CALLBACK VectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo) { if (!exceptionInfo || !exceptionInfo-ExceptionRecord) return EXCEPTION_CONTINUE_SEARCH; if (!ShouldWriteMiniDumpForException(exceptionInfo-ExceptionRecord)) return EXCEPTION_CONTINUE_SEARCH; if (InterlockedExchange(g_inHandler, 1) ! 0) return EXCEPTION_CONTINUE_SEARCH; { const std::wstring dumpFilePath BuildDumpFilePathW(); if (!dumpFilePath.empty()) { HANDLE hFile CreateFileW( dumpFilePath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile ! INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION mdei; mdei.ThreadId GetCurrentThreadId(); mdei.ExceptionPointers exceptionInfo; mdei.ClientPointers FALSE; // mini dump //const MINIDUMP_TYPE dumpType static_castMINIDUMP_TYPE( // MiniDumpNormal | // MiniDumpWithThreadInfo | // MiniDumpWithUnloadedModules); // 全部信息 const MINIDUMP_TYPE dumpType static_castMINIDUMP_TYPE( MiniDumpWithFullMemory | // 包含完整的进程内存 MiniDumpWithFullMemoryInfo | // 包含完整的内存信息 MiniDumpWithHandleData | // 包含句柄信息 MiniDumpWithThreadInfo | // 包含线程信息 MiniDumpWithUnloadedModules | // 包含已卸载的模块信息 MiniDumpWithProcessThreadData | // 包含进程和线程数据 MiniDumpWithPrivateReadWriteMemory); // 包含私有读写内存 (void)MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, dumpType, mdei, nullptr, nullptr); CloseHandle(hFile); } } } InterlockedExchange(g_inHandler, 0); return EXCEPTION_CONTINUE_SEARCH; } } namespace CrashDumpHelper { // 使用向量化异常处理较 SetUnhandledExceptionFilter 更早利于消息线程/工作线程上的硬件异常 void InstallCrashDumpHandler() { if (g_vectoredHandle ! nullptr) return; g_vectoredHandle AddVectoredExceptionHandler(1, VectoredExceptionHandler); } }