From 0b53706a9dd497ee47b3f1a0aa788c7e10d772ab Mon Sep 17 00:00:00 2001 From: Ernie Pasveer Date: Sat, 5 Apr 2025 10:35:18 -0500 Subject: [PATCH] Try out CrashCatch for signal reporting. --- tests/hellosegv/.gitignore | 1 + tests/hellosegv/CrashCatch.hpp | 201 +++++++++++++++++++++++++++++++++ tests/hellosegv/hellosegv.cpp | 4 + 3 files changed, 206 insertions(+) create mode 100644 tests/hellosegv/CrashCatch.hpp diff --git a/tests/hellosegv/.gitignore b/tests/hellosegv/.gitignore index cf1a3d9..4edb208 100644 --- a/tests/hellosegv/.gitignore +++ b/tests/hellosegv/.gitignore @@ -2,3 +2,4 @@ hellosegv *.seer core.* rr/* +crash_dumps/* diff --git a/tests/hellosegv/CrashCatch.hpp b/tests/hellosegv/CrashCatch.hpp new file mode 100644 index 0000000..35c39b5 --- /dev/null +++ b/tests/hellosegv/CrashCatch.hpp @@ -0,0 +1,201 @@ +#pragma once + +/* +CrashCatch - A simple cross-platform crash handler +Version 1.2.0 +Created by Keith Pottratz +Email: keithpotz@gmail.com +License: MIT +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#define CRASHCATCH_PLATFORM_WINDOWS +#include +#include +#pragma comment(lib, "DbgHelp.lib") //Auto-link debugging support library +#elif defined(__linux__) +#define CRASHCATCH_PLATFORM_LINUX +#include +#include +#include +#include +#include +#include +#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 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 diff --git a/tests/hellosegv/hellosegv.cpp b/tests/hellosegv/hellosegv.cpp index 73eab62..93a52c9 100644 --- a/tests/hellosegv/hellosegv.cpp +++ b/tests/hellosegv/hellosegv.cpp @@ -1,9 +1,13 @@ +#include "CrashCatch.hpp" + #include "function1.h" #include #include int main (int argc, char** argv) { + CrashCatch::enable(); + for (int i=0; i