From 93ae21abfcce1c6b36e5cd68d23045f2b9ac438d Mon Sep 17 00:00:00 2001 From: Forkk Date: Fri, 9 May 2014 17:33:32 -0500 Subject: [PATCH] Implement crash handling on Linux This will allow us to generate crash dumps and have users report crashes. --- BuildConfig.cpp.in | 2 + BuildConfig.h | 4 + CMakeLists.txt | 29 ++++++- HandleCrash.h | 18 ++++ MultiMC.cpp | 3 + MultiMC.h | 3 + UnixCrash.cpp | 210 +++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 11 +++ 8 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 HandleCrash.h create mode 100644 UnixCrash.cpp diff --git a/BuildConfig.cpp.in b/BuildConfig.cpp.in index 1ea07374..76196b7e 100644 --- a/BuildConfig.cpp.in +++ b/BuildConfig.cpp.in @@ -29,7 +29,9 @@ Config::Config() UPDATER_FORCE_LOCAL = @MultiMC_UPDATER_FORCE_LOCAL_value@; GIT_COMMIT = "@MultiMC_GIT_COMMIT@"; + GIT_COMMIT_CSTR = "@MultiMC_GIT_COMMIT@"; VERSION_STR = "@MultiMC_VERSION_STRING@"; + VERSION_CSTR = "@MultiMC_VERSION_STRING@"; NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@"; } diff --git a/BuildConfig.h b/BuildConfig.h index baf2ad6d..5e10beb0 100644 --- a/BuildConfig.h +++ b/BuildConfig.h @@ -61,10 +61,14 @@ public: /// The commit hash of this build QString GIT_COMMIT; + const char* GIT_COMMIT_CSTR; /// This is printed on start to standard output QString VERSION_STR; + /// Version string as a char string. Used by the crash handling system to avoid touching heap memory. + const char* VERSION_CSTR; + /** * This is used to fetch the news RSS feed. * It defaults in CMakeLists.txt to "http://multimc.org/rss.xml" diff --git a/CMakeLists.txt b/CMakeLists.txt index 85ac4b57..86881241 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,8 +75,31 @@ if(${BIGENDIAN}) endif(${BIGENDIAN}) -######## Set URLs ######## +######## Dark magic crash reports ######## +option(MultiMC_HANDLE_SEGV "Handle fatal crashes and generate crash reports." OFF) +set(CRASH_HANDLER_IMPL "") +message(STATUS "Crash dumps are ${MultiMC_HANDLE_SEGV}") +if (MultiMC_HANDLE_SEGV) + add_definitions(-DHANDLE_SEGV) + if (UNIX) + set(CRASH_HANDLER_IMPL "UnixCrash.cpp") + elseif (WIN32) + message(WARNING "The crash dump system is not currently implemented on Windows") + #set(CRASH_HANDLER_IMPL "WinCrash.cpp") + else () + message(WARNING "The crash dump system is not supported on this platform.") + endif () +endif () +option(MultiMC_TEST_SEGV "Intentionally segfault on startup to test crash handling." OFF) +if (MultiMC_TEST_SEGV) + # TODO: Make this a unit test instead. + message(WARNING "You have enabled crash handler testing. MULTIMC WILL INTENTIONALLY CRASH ITSELF ON STARTUP.") + add_definitions(-DTEST_SEGV) +endif () + + +######## Set URLs ######## set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.") @@ -250,6 +273,10 @@ SET(MULTIMC_SOURCES BuildConfig.h ${PROJECT_BINARY_DIR}/BuildConfig.cpp + # Crash handling + HandleCrash.h + ${CRASH_HANDLER_IMPL} + # Logging logger/QsDebugOutput.cpp logger/QsDebugOutput.h diff --git a/HandleCrash.h b/HandleCrash.h new file mode 100644 index 00000000..3b2e7d00 --- /dev/null +++ b/HandleCrash.h @@ -0,0 +1,18 @@ +// This is a simple header file for the crash handling system. It exposes only one method, +// initBlackMagic, which initializes the system, registering signal handlers, or doing +// whatever stupid things need to be done on Windows. +// The platform specific implementations for this system are in UnixCrash.cpp and +// WinCrash.cpp. + +#if defined Q_OS_WIN +#warning Crash handling is not yet implemented on Windows. +#elif defined Q_OS_UNIX +#else +#warning Crash handling is not supported on this platform. +#endif + +/** + * Initializes the crash handling system. + */ +void initBlackMagic(); + diff --git a/MultiMC.cpp b/MultiMC.cpp index a0ff8b66..dc878a32 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -327,8 +327,11 @@ void MultiMC::initLogger() logger.addDestination(m_debugDestination.get()); // log all the things logger.setLoggingLevel(QsLogging::TraceLevel); + loggerInitialized = true; } +bool loggerInitialized = false; + void MultiMC::initGlobalSettings() { m_settings.reset(new INISettingsObject("multimc.cfg", this)); diff --git a/MultiMC.h b/MultiMC.h index cb37b1e6..2455cbb4 100644 --- a/MultiMC.h +++ b/MultiMC.h @@ -48,6 +48,9 @@ enum UpdateFlag Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag); Q_DECLARE_OPERATORS_FOR_FLAGS(UpdateFlags); +// Global var used by the crash handling system to determine if a log file should be included in a crash report. +extern bool loggerInitialized; + class MultiMC : public QApplication { Q_OBJECT diff --git a/UnixCrash.cpp b/UnixCrash.cpp new file mode 100644 index 00000000..a28e4b09 --- /dev/null +++ b/UnixCrash.cpp @@ -0,0 +1,210 @@ +// This is the Unix implementation of MultiMC's crash handling system. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "BuildConfig.h" + +#include "HandleCrash.h" + +// The maximum number of frames to include in the backtrace. +#define BT_SIZE 20 + + +#define DUMPF_NAME_FMT "mmc-crash-%X.dump\0" +// 1234567890 123456 +// The maximum number of digits in a unix timestamp when encoded in hexadecimal is about 17. +// Our format string is ~16 characters long. +// The maximum length of the dump file's filename should be well over both of these. 42 is a good number. +#define DUMPF_NAME_LEN 42 + +// {{{ Handling + +void getVsnType(char* out); +void readFromTo(int from, int to); + +// The signal handler. If this function is called, it means shit has probably collided with some sort of device one might use to keep oneself cool. +void handler(int sig) +{ + // Variables for storing crash info. + void* trace[BT_SIZE]; // Backtrace frames + size_t size; // The backtrace size + + bool gotSysInfo = false; // True if system info check succeeded + utsname sysinfo; // System information + + time_t unixTime = 0; // Unix timestamp. Used to give our crash dumps "unique" names. + + char dumpFileName[DUMPF_NAME_LEN]; // The name of the file we're dumping to. + int dumpFile; // File descriptor for our dump file. + + char vsnType[42]; // The version type. If it's more than 42 chars, the universe might implode... + + int otherFile; // File descriptor for other things. + + + fprintf(stderr, "Fatal error! Received signal %d\n", sig); + + + // Get the backtrace. + size = backtrace(trace, BT_SIZE); + + + // Get system info. + gotSysInfo = uname(&sysinfo) >= 0; + + + // Get MMC info. + getVsnType(vsnType); + + + // Determine what our dump file should be called. + // We'll just call it "mmc-crash-.dump" + // First, check the time. + time(&unixTime); + + // Now we get to do some !!FUN!! hackery to ensure we don't use the stack when we convert + // the timestamp from an int to a string. To do this, we just allocate a fixed size array + // of chars on the stack, and sprintf into it. We know the timestamp won't ever be longer + // than a certain number of digits, so this should work just fine. + // sprintf doesn't support writing signed values as hex, so this breaks on negative timestamps. + // It really shouldn't matter, though... + sprintf(dumpFileName, DUMPF_NAME_FMT, unixTime); + + // Now, we need to open the file. + // Fail if it already exists. This should never happen. + dumpFile = open(dumpFileName, O_WRONLY | O_CREAT | O_TRUNC, 0644); + + if (dumpFile >= 0) + { + // If we opened the dump file successfully. + // Dump everything we can and GTFO. + fprintf(stderr, "Dumping crash report to %s\n", dumpFileName); + + // Dump misc info + dprintf(dumpFile, "Unix Time: %d\n", unixTime); + dprintf(dumpFile, "MultiMC Version: %s\n", BuildConfig.VERSION_CSTR); + dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType); + dprintf(dumpFile, "Signal: %d\n", sig); + + // Dump system info + if (gotSysInfo) + { + dprintf(dumpFile, "OS System: %s\n", sysinfo.sysname); + dprintf(dumpFile, "OS Machine: %s\n", sysinfo.machine); + dprintf(dumpFile, "OS Release: %s\n", sysinfo.release); + dprintf(dumpFile, "OS Version: %s\n", sysinfo.version); + } else { + dprintf(dumpFile, "OS System: Unknown Unix"); + } + + dprintf(dumpFile, "\n"); + + // Dump the backtrace + dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n"); + backtrace_symbols_fd(trace, size, dumpFile); + dprintf(dumpFile, "---- END BACKTRACE ----\n"); + + dprintf(dumpFile, "\n"); + + // Attempt to attach the log file if the logger was initialized. + dprintf(dumpFile, "---- BEGIN LOGS ----\n"); + if (loggerInitialized) + { + otherFile = open("MultiMC-0.log", O_RDONLY); + readFromTo(otherFile, dumpFile); + } else { + dprintf(dumpFile, "Logger not initialized.\n"); + } + dprintf(dumpFile, "---- END LOGS ----\n"); + + // DIE DIE DIE! + exit(1); + } + else + { + fprintf(stderr, "Failed to open dump file %s to write crash info (%d). Here's a backtrace on stderr instead.\n", dumpFileName, errno); + // If we can't dump to the file, dump a backtrace to stderr and give up. + backtrace_symbols_fd(trace, size, STDERR_FILENO); + exit(2); + } +} + +// Reads data from the file descriptor on the first argument into the second argument. +void readFromTo(int from, int to) +{ + char buffer[1024]; + size_t lastread = 1; + while (lastread > 0) + { + lastread = read(from, buffer, 1024); + if (lastread > 0) write(to, buffer, lastread); + } +} + +// Writes the current version type to the given char buffer. +void getVsnType(char* out) +{ + switch (BuildConfig.versionTypeEnum) + { + case Config::Release: + sprintf(out, "Release"); + break; + case Config::ReleaseCandidate: + sprintf(out, "ReleaseCandidate"); + break; + case Config::Development: + sprintf(out, "Development"); + break; + default: + sprintf(out, "Unknown"); + break; + } +} + +// }}} + +// {{{ Misc + +#if defined TEST_SEGV +// Causes a crash. For testing. +void testCrash() +{ + char* lol = (char*)MMC->settings().get(); + lol -= 8; + + // Throw shit at the fan. + for (int i = 0; i < 8; i++) + lol[i] = 'f'; + + //delete lol; +} + +// Some dummy functions to make the crash more interesting. +void foo() { testCrash(); } +void bar() { foo(); } +#endif + +// Initializes the Unix crash handler. +void initBlackMagic() +{ + // Register the handler. + signal(SIGSEGV, handler); + signal(SIGABRT, handler); + +#ifdef TEST_SEGV + bar(); +#endif +} + +// }}} + diff --git a/main.cpp b/main.cpp index 181d7299..a79cc014 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,12 @@ #include "MultiMC.h" #include "gui/MainWindow.h" +// Crash handling +#ifdef HANDLE_SEGV +#include +#endif + + int main_gui(MultiMC &app) { // show main window @@ -23,6 +29,11 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(backgrounds); +#ifdef HANDLE_SEGV + // Register signal handler for generating crash reports. + initBlackMagic(); +#endif + switch (app.status()) { case MultiMC::Initialized: