前言
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 <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 |
运行一段时间后崩溃 | 内存泄漏或数据竞争 | 启用 Valgrind 或 ThreadSanitizer |
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 <程序> <core> ,或 eu-stack -p core | gdb |
macOS | ~/Library/Logs/DiagnosticReports/ | ✅ 需要 ulimit -c unlimited | .crash | signal(SIGSEGV, handler) 生成 .crash | 进程崩溃(SIGSEGV 等)自动生成 或 lldb -> process save-core 生成 | lldb -c <crash文件> ,atos -o <可执行文件> -p <进程ID> | lldb, atos |