Bootstrap

跨平台 C++ 程序崩溃调试与 Dump 文件分析

前言

C++ 程序在运行时可能会由于 空指针访问、数组越界、非法内存访问、栈溢出 等原因崩溃。为了分析崩溃原因,我们通常会生成 Dump 文件(Windows 的 .dmp,Linux 的 core,macOS 的 .crash),然后用调试工具分析。


1. Windows: MiniDump 生成 .dmp 文件

Windows 提供了 MiniDumpWriteDump() API 来生成 MiniDump 文件(.dmp),它可以记录程序崩溃时的内存、线程、异常信息等。

1.1 MiniDumpWriteDump() 介绍

BOOL MiniDumpWriteDump(
  HANDLE hProcess,             							// 进程句柄
  DWORD ProcessId,             							// 进程 ID
  HANDLE hFile,                							// Dump 文件句柄
  MINIDUMP_TYPE DumpType,     							// Dump 类型
  PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, 		// 异常信息 (可选)
  PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, 	// 额外数据 (可选)
  PMINIDUMP_CALLBACK_INFORMATION CallbackParam      	// 回调函数 (可选)
);

1.2 参数解析

参数作用
hProcess进程句柄(用 GetCurrentProcess() 获取)
ProcessId进程 ID(用 GetCurrentProcessId() 获取)
hFile目标 Dump 文件的句柄(用 CreateFile() 创建)
DumpTypeDump 类型(控制记录多少信息)
ExceptionParam异常信息(用于捕获崩溃时的状态,可选)
UserStreamParam额外数据(可为空)
CallbackParam回调函数(可为空)

1.3 Dump 类型(MINIDUMP_TYPE)

类型值(十六进制)描述
MiniDumpNormal0x00000000默认值,仅包含基本信息(进程、线程、模块)
MiniDumpWithDataSegs0x00000001记录全局变量数据段
MiniDumpWithFullMemory0x00000002完整 Dump,包含所有进程内存(文件较大)
MiniDumpWithHandleData0x00000004记录所有句柄信息
MiniDumpFilterMemory0x00000008过滤一些私有的内存区域以减少 Dump 大小
MiniDumpScanMemory0x00000010扫描进程内存以获取更多信息
MiniDumpWithUnloadedModules0x00000020记录已卸载的模块
MiniDumpWithIndirectlyReferencedMemory0x00000040记录被指针间接引用的内存内容
MiniDumpFilterModulePaths0x00000080仅记录模块路径,不包含完整的模块数据
MiniDumpWithProcessThreadData0x00000100记录进程/线程额外信息
MiniDumpWithPrivateReadWriteMemory0x00000200记录进程私有的读写内存
MiniDumpWithoutOptionalData0x00000400不包含可选数据,减小 Dump 大小
MiniDumpWithFullMemoryInfo0x00000800记录完整的内存信息
MiniDumpWithThreadInfo0x00001000记录所有线程的详细信息
MiniDumpWithCodeSegs0x00002000记录代码段信息
MiniDumpWithoutAuxiliaryState0x00004000不包含辅助状态信息
MiniDumpWithFullAuxiliaryState0x00008000记录完整的辅助状态信息
MiniDumpWithPrivateWriteCopyMemory0x00010000记录写时复制(Copy-on-Write)的私有内存
MiniDumpIgnoreInaccessibleMemory0x00020000忽略无法访问的内存区域
MiniDumpWithTokenInformation0x00040000记录进程的 Token 信息(用于权限分析)
MiniDumpWithModuleHeaders0x00080000记录模块的 PE 头信息
MiniDumpFilterTriage0x00100000仅记录用于故障诊断的最小数据集
MiniDumpValidTypeFlags0x001FFFFF所有可用的 Dump 类型标志位(用于验证 Dump 类型)

推荐使用 MiniDumpWithDataSegs | MiniDumpWithThreadInfo,可以更全面地分析崩溃原因。

1.4 自定义 Dump 文件存储路径

在 Windows 上,我们可以自定义 Dump 文件的存储路径,例如存放到 C:\Dumps\ 目录下:

std::string dumpPath = "C:\\Dumps\\crash_dump.dmp";  
HANDLE hFile = CreateFileA(dumpPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

确保 C:\Dumps\ 目录存在,否则 CreateFileA() 可能会失败。

1.5 Windows 代码示例

#include <windows.h>
#include <dbghelp.h>
#include <iostream>

#pragma comment(lib, "dbghelp.lib")

void CreateDump(const std::string& dumpPath, EXCEPTION_POINTERS* pExceptionInfo) {
    HANDLE hFile = CreateFileA(dumpPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE) {
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ExceptionPointers = pExceptionInfo;
        dumpInfo.ClientPointers = TRUE;
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpWithDataSegs | MiniDumpWithThreadInfo, &dumpInfo, NULL, NULL);
        CloseHandle(hFile);
    }
}

LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* pExceptionInfo) {
    std::string dumpPath = "C:\\Dumps\\crash_dump.dmp";
    CreateDump(dumpPath, pExceptionInfo);
    return EXCEPTION_EXECUTE_HANDLER;
}

int main() {
    SetUnhandledExceptionFilter(ExceptionHandler);
    
    // 触发崩溃
    int* p = nullptr;
    *p = 10;
    return 0;
}

1.6 Dump 分析

可以用 Visual Studio 或 WinDbg 打开 .dmp 文件,查看调用栈、崩溃地址等信息。


2. Linux: Core Dump 生成

Linux 默认会生成 core 文件,记录程序崩溃时的内存状态。

2.1 启用 Core Dump(需要命令启动)

ulimit -c unlimited   # 允许生成 core dump 文件

2.2 自定义 Core Dump 存储路径

可以修改 /proc/sys/kernel/core_pattern 来改变 Core Dump 存放路径,例如:

echo "/tmp/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern

这样崩溃后,Core Dump 文件将存放到 /tmp/ 目录,并包含程序名和进程 ID。

2.3 代码示例

#include <iostream>
#include <csignal>
#include <sys/resource.h>

void EnableCoreDump() {
    struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
    setrlimit(RLIMIT_CORE, &core_limit);
}

void Crash() {
    int* p = nullptr;
    *p = 42;
}

int main() {
    EnableCoreDump();
    Crash();
    return 0;
}

2.4 Dump 生成路径

  • 默认在当前目录 ./core

  • 或 /var/lib/systemd/coredump/

  • 可修改 /proc/sys/kernel/core_pattern

2.5 Dump 分析

gdb ./your_program core
bt  # 查看调用栈

3. macOS: Crash Report(需要命令启动)

3.1 启用 Core Dump(需要命令启动)

macOS 也可能默认 禁用了 Core Dump,需要运行以下命令启用:

ulimit -c unlimited

3.2 自定义 Core Dump 存储路径

macOS 默认将 .crash 文件存放在 ~/Library/Logs/DiagnosticReports/,但可以使用 ulimit -c unlimited 后修改:

echo "/tmp/crash.%e.%p" | sudo tee /proc/sys/kernel/core_pattern

这样崩溃后,Crash Report 文件会存放到 /tmp/ 目录。

3.3 代码示例

#include <iostream>
#include <csignal>

void Crash() {
    int* p = nullptr;
    *p = 42;
}

int main() {
    signal(SIGSEGV, [](int signum) {
        std::cerr << "Segmentation fault caught!" << std::endl;
        exit(1);
    });
    Crash();
    return 0;
}

3.4 Dump 分析

lldb -c ~/Library/Logs/DiagnosticReports/YourApp.crash
bt  # 查看调用栈

结论

参考表:如何判断崩溃原因

现象可能原因解决方法
崩溃在 nullptr 访问访问空指针assert(p != nullptr);
崩溃在数组访问数组越界开启 ASan,检查索引
delete 崩溃释放了已释放的内存nullptr 赋值后再 delete
运行一段时间后崩溃内存泄漏或数据竞争启用 ValgrindThreadSanitizer
GUI 无响应死锁或 UI 线程阻塞检查 std::mutex 是否死锁

对比总结

平台默认 Dump 目录是否需要命令启动Dump 文件格式代码生成 Dump触发 Dump 的方式如何分析 Dump调试工具
Windows%LOCALAPPDATA%\CrashDumps\C:\Users\<用户名>\AppData\Local\CrashDumps\❌ 无需命令.dmpMiniDumpWriteDump() 生成 .dmp进程崩溃自动生成 或 手动调用 MiniDumpWriteDump()WinDbg (!analyze -v),Visual Studio 直接打开 .dmpWinDbg, Visual Studio
Linux./core/var/lib/systemd/coredump/✅ 需要 ulimit -c unlimitedcoresetrlimit(RLIMIT_CORE, &core_limit) 生成 core进程崩溃(SIGSEGV 等)自动生成 或 gcore 手动触发gdb <程序> <core>,或 eu-stack -p coregdb
macOS~/Library/Logs/DiagnosticReports/✅ 需要 ulimit -c unlimited.crashsignal(SIGSEGV, handler) 生成 .crash进程崩溃(SIGSEGV 等)自动生成 或 lldb -> process save-core 生成lldb -c <crash文件>atos -o <可执行文件> -p <进程ID>lldb, atos
;