mirror of
https://github.com/epasveer/seer.git
synced 2026-08-29 16:40:42 +08:00
202 lines
7.5 KiB
C++
202 lines
7.5 KiB
C++
|
|
#pragma once
|
||
|
|
|
||
|
|
/*
|
||
|
|
CrashCatch - A simple cross-platform crash handler
|
||
|
|
Version 1.2.0
|
||
|
|
Created by Keith Pottratz
|
||
|
|
Email: keithpotz@gmail.com
|
||
|
|
License: MIT
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string>
|
||
|
|
#include <sstream>
|
||
|
|
#include <fstream>
|
||
|
|
#include <iostream>
|
||
|
|
#include <iomanip>
|
||
|
|
#include <chrono>
|
||
|
|
#include <filesystem>
|
||
|
|
#include <functional>
|
||
|
|
|
||
|
|
#if defined(_WIN32)
|
||
|
|
#define CRASHCATCH_PLATFORM_WINDOWS
|
||
|
|
#include <Windows.h>
|
||
|
|
#include <DbgHelp.h>
|
||
|
|
#pragma comment(lib, "DbgHelp.lib") //Auto-link debugging support library
|
||
|
|
#elif defined(__linux__)
|
||
|
|
#define CRASHCATCH_PLATFORM_LINUX
|
||
|
|
#include <signal.h>
|
||
|
|
#include <execinfo.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <limits.h>
|
||
|
|
#include <cxxabi.h>
|
||
|
|
#include <string.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
namespace CrashCatch {
|
||
|
|
// Config structure to customize CrashCatch behaviour
|
||
|
|
struct Config {
|
||
|
|
std::string dumpFolder = "./crash_dumps/"; // Default folder for crash dumps
|
||
|
|
std::string dumpFileName = "crash"; // Default file name for crash dumps
|
||
|
|
bool enableTextLog = true; // Enable text log for crash dumps
|
||
|
|
bool autoTimestamp = true; // Automatically append timestamp to dump file name
|
||
|
|
bool showCrashDialog = false; // Windows only
|
||
|
|
std::function<void()> onCrash = nullptr; // Callback function to execute on crash
|
||
|
|
std::string appVersion = "unknown"; // Application version
|
||
|
|
std::string buildConfig =
|
||
|
|
#ifdef _DEBUG
|
||
|
|
"Debug";
|
||
|
|
#else
|
||
|
|
"Release";
|
||
|
|
#endif
|
||
|
|
std::string additionalNotes = ""; // Additional notes to include in the crash log
|
||
|
|
};
|
||
|
|
|
||
|
|
inline Config globalConfig; // Global configuration object used internally
|
||
|
|
// Utility: Generate a timestamp string
|
||
|
|
inline std::string getTimestamp() {
|
||
|
|
auto now = std::chrono::system_clock::now();
|
||
|
|
auto time = std::chrono::system_clock::to_time_t(now);
|
||
|
|
std::stringstream ss;
|
||
|
|
ss << std::put_time(std::localtime(&time), "%Y-%m-%d_%H-%M-%S");
|
||
|
|
return ss.str();
|
||
|
|
}
|
||
|
|
|
||
|
|
inline std::string getExecutablePath() {
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_WINDOWS
|
||
|
|
char buffer[MAX_PATH];
|
||
|
|
GetModuleFileNameA(nullptr, buffer, MAX_PATH);
|
||
|
|
return std::string(buffer);
|
||
|
|
#elif defined(CRASHCATCH_PLATFORM_LINUX)
|
||
|
|
char path[PATH_MAX];
|
||
|
|
ssize_t len = readlink("/proc/self/exe", path, sizeof(path) - 1);
|
||
|
|
if (len != -1) {
|
||
|
|
path[len] = '\0';
|
||
|
|
return std::string(path);
|
||
|
|
}
|
||
|
|
return "(unknown)";
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_LINUX
|
||
|
|
inline std::string demangle(const char* symbol) {
|
||
|
|
size_t size;
|
||
|
|
int status;
|
||
|
|
char* demangled = abi::__cxa_demangle(symbol, nullptr, &size, &status);
|
||
|
|
std::string result = (status == 0) ? demangled : symbol;
|
||
|
|
free(demangled);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
// Collect Diagnositcs for crash log (version, build config, platform, executable path)
|
||
|
|
inline std::string getDiagnosticsInfo() {
|
||
|
|
std::stringstream ss;
|
||
|
|
ss << "App Version: " << globalConfig.appVersion << "\n";
|
||
|
|
ss << "Build Config: " << globalConfig.buildConfig << "\n";
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_WINDOWS
|
||
|
|
ss << "Platform: Windows\n";
|
||
|
|
#elif defined(CRASHCATCH_PLATFORM_LINUX)
|
||
|
|
ss << "Platform: Linux\n";
|
||
|
|
#endif
|
||
|
|
ss << "Executable: " << getExecutablePath() << "\n";
|
||
|
|
if (!globalConfig.additionalNotes.empty()) {
|
||
|
|
ss << "Notes: " << globalConfig.additionalNotes << "\n";
|
||
|
|
}
|
||
|
|
return ss.str();
|
||
|
|
}
|
||
|
|
// Write Crash summary .txt file
|
||
|
|
inline void writeCrashLog(const std::string& logPath, const std::string& timestamp, int signal = 0) {
|
||
|
|
std::ofstream log(logPath);
|
||
|
|
if (!log.is_open()) return;
|
||
|
|
|
||
|
|
log << "Crash Report\n============\n";
|
||
|
|
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_LINUX
|
||
|
|
// Stack trace for Linux
|
||
|
|
log << "Signal: " << strsignal(signal) << " (" << signal << ")\n";
|
||
|
|
#endif
|
||
|
|
log << "Timestamp: " << (timestamp.empty() ? "N/A" : timestamp) << "\n\n";
|
||
|
|
log << "Environment Info:\n" << getDiagnosticsInfo() << "\n";
|
||
|
|
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_LINUX
|
||
|
|
void* callstack[128];
|
||
|
|
int frames = backtrace(callstack, 128);
|
||
|
|
char** symbols = backtrace_symbols(callstack, frames);
|
||
|
|
log << "\nStack Trace:\n";
|
||
|
|
for (int i = 0; i < frames; ++i) {
|
||
|
|
std::string raw = symbols[i];
|
||
|
|
std::string demangled = demangle(raw.c_str());
|
||
|
|
log << " [" << i << "]: " << demangled << "\n";
|
||
|
|
}
|
||
|
|
free(symbols);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
log.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_WINDOWS
|
||
|
|
// Windows-specific crash handler
|
||
|
|
inline LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* ep) {
|
||
|
|
if (globalConfig.onCrash) globalConfig.onCrash();
|
||
|
|
std::filesystem::create_directories(globalConfig.dumpFolder);
|
||
|
|
std::string timestamp = globalConfig.autoTimestamp ? getTimestamp() : "";
|
||
|
|
std::string base = globalConfig.dumpFileName + (timestamp.empty() ? "" : ("_" + timestamp));
|
||
|
|
std::string dumpPath = globalConfig.dumpFolder + base + ".dmp";
|
||
|
|
|
||
|
|
HANDLE hFile = CreateFileA(dumpPath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
||
|
|
MINIDUMP_EXCEPTION_INFORMATION dumpInfo = { GetCurrentThreadId(), ep, FALSE };
|
||
|
|
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpWithDataSegs, &dumpInfo, nullptr, nullptr);
|
||
|
|
CloseHandle(hFile);
|
||
|
|
|
||
|
|
if (globalConfig.enableTextLog) {
|
||
|
|
std::string logPath = globalConfig.dumpFolder + base + ".txt";
|
||
|
|
writeCrashLog(logPath, timestamp);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (globalConfig.showCrashDialog) {
|
||
|
|
std::string msg = "Crash occurred. Dump written to:\n" + dumpPath;
|
||
|
|
MessageBoxA(nullptr, msg.c_str(), "Crash Detected", MB_OK | MB_ICONERROR);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_LINUX
|
||
|
|
//Linux signal handler: creates .txt crash log with stack trace
|
||
|
|
inline void linuxSignalHandler(int signum) {
|
||
|
|
if (globalConfig.onCrash) globalConfig.onCrash();
|
||
|
|
std::filesystem::create_directories(globalConfig.dumpFolder);
|
||
|
|
std::string timestamp = globalConfig.autoTimestamp ? getTimestamp() : "";
|
||
|
|
std::string base = globalConfig.dumpFileName + (timestamp.empty() ? "" : ("_" + timestamp));
|
||
|
|
std::string logPath = globalConfig.dumpFolder + base + ".txt";
|
||
|
|
writeCrashLog(logPath, timestamp, signum);
|
||
|
|
_exit(1); // Exit immediately after handling signal
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
//Main initialization function: sets up the crash handler
|
||
|
|
inline bool initialize(const Config& config = Config()) {
|
||
|
|
globalConfig = config;
|
||
|
|
#ifdef CRASHCATCH_PLATFORM_WINDOWS
|
||
|
|
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
|
||
|
|
#elif defined(CRASHCATCH_PLATFORM_LINUX)
|
||
|
|
signal(SIGSEGV, linuxSignalHandler); // Segmentation fault
|
||
|
|
signal(SIGABRT, linuxSignalHandler); // Abort signal
|
||
|
|
signal(SIGFPE, linuxSignalHandler); // Floating point exception
|
||
|
|
signal(SIGILL, linuxSignalHandler); // Illegal instruction
|
||
|
|
signal(SIGBUS, linuxSignalHandler); // Misaligned memory address
|
||
|
|
#endif
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
//One liner to enable crash handling with default config
|
||
|
|
inline bool enable() { return initialize(Config{}); }
|
||
|
|
|
||
|
|
//Optional Auto enable crash handler
|
||
|
|
#ifdef CRASHCATCH_AUTO_INIT
|
||
|
|
namespace {
|
||
|
|
const bool _autoInit = CrashCatch::enable();
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
} // namespace CrashCatch
|