跨平台 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() 创建)
DumpType Dump 类型(控制记录多少信息)
ExceptionParam 异常信息(用于捕获崩溃时的状态,可选)
UserStreamParam 额外数据(可为空)
CallbackParam 回调函数(可为空)

1.3 Dump 类型(MINIDUMP_TYPE)

类型 值(十六进制) 描述
MiniDumpNormal 0x00000000 默认值,仅包含基本信息(进程、线程、模块)
MiniDumpWithDataSegs 0x00000001 记录全局变量数据段
MiniDumpWithFullMemory 0x00000002 完整 Dump,包含所有进程内存(文件较大)
MiniDumpWithHandleData 0x00000004 记录所有句柄信息
MiniDumpFilterMemory 0x00000008 过滤一些私有的内存区域以减少 Dump 大小
MiniDumpScanMemory 0x00000010 扫描进程内存以获取更多信息
MiniDumpWithUnloadedModules 0x00000020 记录已卸载的模块
MiniDumpWithIndirectlyReferencedMemory 0x00000040 记录被指针间接引用的内存内容
MiniDumpFilterModulePaths 0x00000080 仅记录模块路径,不包含完整的模块数据
MiniDumpWithProcessThreadData 0x00000100 记录进程/线程额外信息
MiniDumpWithPrivateReadWriteMemory 0x00000200 记录进程私有的读写内存
MiniDumpWithoutOptionalData 0x00000400 不包含可选数据,减小 Dump 大小
MiniDumpWithFullMemoryInfo 0x00000800 记录完整的内存信息
MiniDumpWithThreadInfo 0x00001000 记录所有线程的详细信息
MiniDumpWithCodeSegs 0x00002000 记录代码段信息
MiniDumpWithoutAuxiliaryState 0x00004000 不包含辅助状态信息
MiniDumpWithFullAuxiliaryState 0x00008000 记录完整的辅助状态信息
MiniDumpWithPrivateWriteCopyMemory 0x00010000 记录写时复制(Copy-on-Write)的私有内存
MiniDumpIgnoreInaccessibleMemory 0x00020000 忽略无法访问的内存区域
MiniDumpWithTokenInformation 0x00040000 记录进程的 Token 信息(用于权限分析)
MiniDumpWithModuleHeaders 0x00080000 记录模块的 PE 头信息
MiniDumpFilterTriage 0x00100000 仅记录用于故障诊断的最小数据集
MiniDumpValidTypeFlags 0x001FFFFF 所有可用的 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 
#include 
#include 

#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 
#include 
#include 

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 
#include 

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\ ❌ 无需命令 .dmp MiniDumpWriteDump() 生成 .dmp 进程崩溃自动生成 或 手动调用 MiniDumpWriteDump() WinDbg (!analyze -v),Visual Studio 直接打开 .dmp WinDbg, Visual Studio
Linux ./core/var/lib/systemd/coredump/ ✅ 需要 ulimit -c unlimited core setrlimit(RLIMIT_CORE, &core_limit) 生成 core 进程崩溃(SIGSEGV 等)自动生成 或 gcore 手动触发 gdb <程序> ,或 eu-stack -p core gdb
macOS ~/Library/Logs/DiagnosticReports/ ✅ 需要 ulimit -c unlimited .crash signal(SIGSEGV, handler) 生成 .crash 进程崩溃(SIGSEGV 等)自动生成 或 lldb -> process save-core 生成 lldb -c atos -o <可执行文件> -p <进程ID> lldb, atos

你可能感兴趣的:(个人经验,c++,开发语言)