跨平台崩溃捕获库 - 纯C++实现,支持Windows/Mac/Linux
简介
这是一个轻量级的跨平台崩溃捕获库,可以自动捕获程序崩溃并生成包含堆栈信息的转储文件。代码简洁,无第三方依赖,打开即可编译运行。
核心特性:
- 纯C++17实现,无第三方依赖
- Windows平台生成
.dmp文件(支持WinDbg分析) - Linux/Mac平台生成
.txt文件(包含符号化的堆栈跟踪) - 支持崩溃回调通知
- 线程安全
源码
CrashDump.h
#ifndef CRASHDUMP_H_
#define CRASHDUMP_H_
#include <string>
#include <functional>
#ifndef CRASHDUMP_EXPORT
#ifdef _MSC_VER
#ifdef BUILD_CRASHDUMP
#define CRASHDUMP_EXPORT __declspec(dllexport)
#else
#define CRASHDUMP_EXPORT __declspec(dllimport)
#endif
#elif defined(__GNUC__)
#define CRASHDUMP_EXPORT __attribute__((visibility("default")))
#endif
#endif
typedef std::function<int(const std::string&)> DumpCallback;
void CRASHDUMP_EXPORT InitCrashDump(const std::string& dumpFilePath, DumpCallback cb = {});
#endif
CrashDump.cpp
#include "CrashDump.h"
#include <csignal>
#include <csetjmp>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>
#include <atomic>
#include <mutex>
#include <fstream>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib")
#else
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <execinfo.h>
#include <fcntl.h>
#include <ucontext.h>
#include <cxxabi.h>
#include <dlfcn.h>
#endif
namespace {
std::string g_dumpPath;
DumpCallback g_callback;
std::atomic<bool> g_crashed{false};
std::mutex g_mutex;
#ifdef _WIN32
BOOL CALLBACK MiniDumpCallback(
PVOID callbackParam,
PMINIDUMP_CALLBACK_INPUT callbackInput,
PMINIDUMP_CALLBACK_OUTPUT callbackOutput
) {
(void)callbackParam;
(void)callbackOutput;
if (callbackInput->CallbackType == ModuleCallback ||
callbackInput->CallbackType == IncludeModuleCallback ||
callbackInput->CallbackType == IncludeThreadCallback ||
callbackInput->CallbackType == ThreadCallback ||
callbackInput->CallbackType == ThreadExCallback) {
return TRUE;
}
return FALSE;
}
LONG WINAPI ExceptionHandler(LPEXCEPTION_POINTERS exceptionInfo) {
if (g_crashed.exchange(true)) {
return EXCEPTION_CONTINUE_SEARCH;
}
std::lock_guard<std::mutex> lock(g_mutex);
std::string filePath = g_dumpPath;
if (filePath.empty()) {
char tempPath[MAX_PATH];
GetTempPathA(MAX_PATH, tempPath);
filePath = std::string(tempPath) + "crash.dmp";
}
HANDLE hFile = CreateFileA(
filePath.c_str(),
GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
if (hFile == INVALID_HANDLE_VALUE) {
return EXCEPTION_CONTINUE_SEARCH;
}
MINIDUMP_EXCEPTION_INFORMATION exceptInfo;
exceptInfo.ThreadId = GetCurrentThreadId();
exceptInfo.ExceptionPointers = exceptionInfo;
exceptInfo.ClientPointers = FALSE;
MINIDUMP_TYPE dumpType = MiniDumpNormal
| MiniDumpWithIndirectlyReferencedMemory
| MiniDumpWithDataSegs
| MiniDumpWithHandleData
| MiniDumpWithFullMemoryInfo
| MiniDumpWithThreadInfo
| MiniDumpWithUnloadedModules;
MINIDUMP_CALLBACK_INFORMATION callbackInfo;
callbackInfo.CallbackRoutine = MiniDumpCallback;
callbackInfo.CallbackParam = nullptr;
BOOL success = MiniDumpWriteDump(
GetCurrentProcess(),
GetCurrentProcessId(),
hFile,
dumpType,
&exceptInfo,
nullptr,
&callbackInfo
);
CloseHandle(hFile);
if (success && g_callback) {
g_callback(filePath);
}
return EXCEPTION_EXECUTE_HANDLER;
}
#else
struct sigaction g_oldSigHandlers[64];
sigjmp_buf g_sigJumpBuffer;
void PrintStackTrace(std::ofstream& out, int skipFrames) {
void* buffer[128];
int nframes = backtrace(buffer, 128);
char** symbols = backtrace_symbols(buffer, nframes);
out << "\n========== Stack Trace ==========\n";
for (int i = skipFrames; i < nframes; ++i) {
out << "Frame " << (i - skipFrames) << ": " << buffer[i] << "\n";
if (symbols && symbols[i]) {
Dl_info info;
if (dladdr(buffer[i], &info) && info.dli_sname) {
int status = 0;
char* demangled = __cxxabiv1::__cxa_demangle(
info.dli_sname, nullptr, nullptr, &status
);
if (status == 0 && demangled) {
out << " Symbol: " << demangled << "\n";
free(demangled);
} else {
out << " Symbol: " << info.dli_sname << "\n";
}
if (info.dli_fname) {
out << " Module: " << info.dli_fname << "\n";
}
out << " Offset: " << reinterpret_cast<char*>(buffer[i])
<< " - " << reinterpret_cast<char*>(info.dli_saddr) << "\n";
}
}
}
free(symbols);
out << "================================\n";
}
void SignalHandler(int sig, siginfo_t* info, void* ucontext) {
sigset_t mask;
sigfillset(&mask);
sigdelset(&mask, sig);
pthread_sigmask(SIG_SETMASK, &mask, nullptr);
if (sig == SIGABRT) {
abort();
}
std::ofstream out(g_dumpPath, std::ios::app);
if (out.is_open()) {
out << "\n===== Signal: " << strsignal(sig) << " (" << sig << ") =====\n";
if (info) {
out << "Signal Info: addr=" << info->si_addr
<< " code=" << info->si_code << "\n";
}
PrintStackTrace(out, 3);
out.close();
}
if (g_callback) {
g_callback(g_dumpPath);
}
siglongjmp(g_sigJumpBuffer, sig);
}
void InstallSignalHandlers() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = SignalHandler;
const int signals[] = {
SIGSEGV, SIGBUS, SIGFPE, SIGABRT, SIGILL, SIGTRAP, SIGSYS
};
for (int sig : signals) {
struct sigaction old;
if (sigaction(sig, &sa, &old) == 0) {
g_oldSigHandlers[sig] = old;
}
}
sigsetjmp(g_sigJumpBuffer, 1);
}
#endif
bool EnsureDirectoryExists(const std::string& path) {
if (path.empty()) return false;
#ifdef _WIN32
DWORD attrib = GetFileAttributesA(path.c_str());
if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY)) {
return true;
}
size_t pos = path.find_last_of("/\\");
if (pos != std::string::npos) {
return EnsureDirectoryExists(path.substr(0, pos));
}
return CreateDirectoryA(path.c_str(), nullptr) != 0;
#else
struct stat st;
if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
return true;
}
size_t pos = path.find_last_of('/');
if (pos != std::string::npos) {
EnsureDirectoryExists(path.substr(0, pos));
}
return mkdir(path.c_str(), 0755) == 0 || errno == EEXIST;
#endif
}
std::string GetDefaultDumpPath() {
std::string path;
#ifdef _WIN32
char tempPath[MAX_PATH];
if (GetTempPathA(MAX_PATH, tempPath)) {
path = tempPath;
}
#else
path = "/tmp";
#endif
time_t now = time(nullptr);
char timestamp[64];
#ifdef _WIN32
struct tm tmm;
localtime_s(&tmm, &now);
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &tmm);
#else
struct tm* tmm = localtime(&now);
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tmm);
#endif
path += "/crash_";
path += timestamp;
#ifdef _WIN32
path += ".dmp";
#else
path += ".txt";
#endif
return path;
}
}
void InitCrashDump(const std::string& dumpFilePath, DumpCallback cb) {
std::lock_guard<std::mutex> lock(g_mutex);
g_dumpPath = dumpFilePath.empty() ? GetDefaultDumpPath() : dumpFilePath;
g_callback = cb;
#ifdef _WIN32
SetUnhandledExceptionFilter(ExceptionHandler);
#else
if (!dumpFilePath.empty()) {
size_t lastSep = dumpFilePath.find_last_of("/\\");
std::string dir = (lastSep != std::string::npos)
? dumpFilePath.substr(0, lastSep) : ".";
EnsureDirectoryExists(dir);
}
InstallSignalHandlers();
#endif
}
main.cpp(演示代码)
#include <iostream>
#include "CrashDump.h"
void CauseCrash() {
int* p = nullptr;
*p = 42;
}
int main() {
std::cout << "CrashDump Demo\n";
InitCrashDump("./crash_dump.txt", [](const std::string& path) {
std::cout << "\n崩溃已转储: " << path << "\n";
});
std::cout << "准备触发崩溃...\n";
#ifdef _WIN32
Sleep(3000);
#else
sleep(3);
#endif
CauseCrash();
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(CrashDumpDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(CrashDump SHARED CrashDump.h CrashDump.cpp)
target_include_directories(CrashDump PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(CrashDump PRIVATE BUILD_CRASHDUMP)
add_executable(Demo main.cpp)
target_link_libraries(Demo PRIVATE CrashDump)
if(WIN32)
target_link_libraries(CrashDump PRIVATE dbghelp)
endif()
编译运行
mkdir build && cd build
cmake ..
cmake --build .
./Demo
使用方法
#include "CrashDump.h"
int main() {
InitCrashDump("./logs/crash.txt", [](const std::string& path) {
printf("崩溃文件: %s\n", path.c_str());
});
return 0;
}
实现原理
Windows:通过 SetUnhandledExceptionFilter 注册全局异常处理,崩溃时调用 MiniDumpWriteDump 生成完整的进程快照。
Linux/Mac:通过 sigaction 捕获 SIGSEGV、SIGBUS 等信号,使用 backtrace() 获取堆栈,__cxa_demangle 还原C++符号名。
崩溃文件示例
Linux平台生成的 .txt 文件内容:
===== Signal: Segmentation fault (11) =====
Signal Info: addr=0x0 code=128
========== Stack Trace ==========
Frame 0: 0x55a3b4c2a1a7
Symbol: CauseCrash()
Module: ./Demo
Offset: 0x55a3b4c2a1a7 - 0x55a3b4c2a190
Frame 1: 0x55a3b4c2a1f4
Symbol: main
Module: ./Demo
Offset: 0x55a3b4c2a1f4 - 0x55a3b4c2a1c0
================================

388

被折叠的 条评论
为什么被折叠?



