From 93ae21abfcce1c6b36e5cd68d23045f2b9ac438d Mon Sep 17 00:00:00 2001 From: Forkk Date: Fri, 9 May 2014 17:33:32 -0500 Subject: [PATCH 1/8] 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: From e3b9b3030240b0cf1454fdf1dea1705ae7e3865b Mon Sep 17 00:00:00 2001 From: Forkk Date: Fri, 9 May 2014 17:36:53 -0500 Subject: [PATCH 2/8] Remove some unnecessary dummy functions. --- UnixCrash.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/UnixCrash.cpp b/UnixCrash.cpp index a28e4b09..8b5a9868 100644 --- a/UnixCrash.cpp +++ b/UnixCrash.cpp @@ -185,13 +185,7 @@ void testCrash() // 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. @@ -202,7 +196,7 @@ void initBlackMagic() signal(SIGABRT, handler); #ifdef TEST_SEGV - bar(); + testCrash(); #endif } From 489cb4dbf597818ea2dc463961cc86e7c1261412 Mon Sep 17 00:00:00 2001 From: Forkk Date: Fri, 9 May 2014 18:21:15 -0500 Subject: [PATCH 3/8] Change dump file extension --- UnixCrash.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UnixCrash.cpp b/UnixCrash.cpp index 8b5a9868..151c0ef3 100644 --- a/UnixCrash.cpp +++ b/UnixCrash.cpp @@ -20,10 +20,10 @@ #define BT_SIZE 20 -#define DUMPF_NAME_FMT "mmc-crash-%X.dump\0" -// 1234567890 123456 +#define DUMPF_NAME_FMT "mmc-crash-%X.bm\0" // Black magic? Bowel movement? Dump? +// 1234567890 1234 // The maximum number of digits in a unix timestamp when encoded in hexadecimal is about 17. -// Our format string is ~16 characters long. +// Our format string is ~14 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 From 9e80ddb0405ecd5d45d65e5385d0fbd1f3e734e3 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 9 May 2014 20:08:07 -0500 Subject: [PATCH 4/8] Implement crash report system on Windows. --- CMakeLists.txt | 10 +- UnixCrash.cpp => HandleCrash.cpp | 173 ++++++++++++++++++++----------- 2 files changed, 111 insertions(+), 72 deletions(-) rename UnixCrash.cpp => HandleCrash.cpp (65%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 86881241..19b4dc2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,14 +81,6 @@ 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) @@ -275,7 +267,7 @@ SET(MULTIMC_SOURCES # Crash handling HandleCrash.h - ${CRASH_HANDLER_IMPL} + HandleCrash.cpp # Logging logger/QsDebugOutput.cpp diff --git a/UnixCrash.cpp b/HandleCrash.cpp similarity index 65% rename from UnixCrash.cpp rename to HandleCrash.cpp index 151c0ef3..3d6a425b 100644 --- a/UnixCrash.cpp +++ b/HandleCrash.cpp @@ -1,6 +1,6 @@ // This is the Unix implementation of MultiMC's crash handling system. #include -#include +#include #include #include #include @@ -8,7 +8,11 @@ #include #include #include + +#ifdef Q_OS_UNIX #include +#include +#endif #include @@ -20,7 +24,7 @@ #define BT_SIZE 20 -#define DUMPF_NAME_FMT "mmc-crash-%X.bm\0" // Black magic? Bowel movement? Dump? +#define DUMPF_NAME_FMT "mmc-crash-%X.bm" // Black magic? Bowel movement? Dump? // 1234567890 1234 // The maximum number of digits in a unix timestamp when encoded in hexadecimal is about 17. // Our format string is ~14 characters long. @@ -29,43 +33,108 @@ // {{{ Handling +#ifdef Q_OS_WIN32 +// #ThanksMicrosoft +// Blame Microsoft for this atrocity. +void dprintf(int fd, const char* fmt...) +{ + va_list args; + va_start(args, fmt); + char buffer[10240]; + // Just sprintf to a really long string and hope it works... + // This is a hack, but I can't think of a better way to do it easily. + int len = vsprintf(buffer, fmt, args); + printf(buffer, fmt, args); + write(fd, buffer, len); + va_end(args); +} +#endif + 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) +void dumpMiscInfo(int dumpFile) { - // 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); // Get MMC info. getVsnType(vsnType); + dprintf(dumpFile, "MultiMC Version: %s\n", BuildConfig.VERSION_CSTR); + dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType); +} + +void dumpBacktrace(int dumpFile) +{ +#ifdef Q_OS_UNIX + // Variables for storing crash info. + void* trace[BT_SIZE]; // Backtrace frames + size_t size; // The backtrace size + + // Get the backtrace. + size = backtrace(trace, BT_SIZE); + + // Dump the backtrace + dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n"); + backtrace_symbols_fd(trace, size, dumpFile); + dprintf(dumpFile, "---- END BACKTRACE ----\n"); +#elif defined Q_OS_WIN32 + dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n"); + dprintf(dumpFile, "Not yet implemented on this platform.\n"); + dprintf(dumpFile, "---- END BACKTRACE ----\n"); +#endif +} + +void dumpSysInfo(int dumpFile) +{ +#ifdef Q_OS_UNIX + bool gotSysInfo = false; // True if system info check succeeded + utsname sysinfo; // System information + + // Dump system info + if (uname(&sysinfo) >= 0) + { + 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"); + } +#else + // TODO: Get more information here. + dprintf(dumpFile, "OS System: Windows"); +#endif +} + +void dumpLogs(int dumpFile) +{ + int otherFile; + + // 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"); +} + +// 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) +{ + fprintf(stderr, "Fatal error! Received signal %d\n", sig); + + 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. // Determine what our dump file should be called. // We'll just call it "mmc-crash-.dump" @@ -83,7 +152,8 @@ void handler(int sig) // 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. @@ -92,53 +162,30 @@ void handler(int sig) // 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"); - } + dumpMiscInfo(dumpFile); + + dprintf(dumpFile, "\n"); + + dumpSysInfo(dumpFile); dprintf(dumpFile, "\n"); - - // Dump the backtrace - dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n"); - backtrace_symbols_fd(trace, size, dumpFile); - dprintf(dumpFile, "---- END BACKTRACE ----\n"); + + dumpBacktrace(dumpFile); 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); + fprintf(stderr, "Failed to open dump file %s to write crash info (ERRNO: %d)\n", dumpFileName, errno); exit(2); } } + // Reads data from the file descriptor on the first argument into the second argument. void readFromTo(int from, int to) { From 5099964c673cd45e16d443a3b02f51bade303eee Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 10 May 2014 14:16:27 -0500 Subject: [PATCH 5/8] Implement backtraces on Windows. Much !!FUN!! was had --- CMakeLists.txt | 12 +++- HandleCrash.cpp | 159 ++++++++++++++++++++++++++++++++++++++++----- WinBacktrace.cpp | 80 +++++++++++++++++++++++ WinBacktrace.h | 44 +++++++++++++ gui/MainWindow.cpp | 5 ++ 5 files changed, 281 insertions(+), 19 deletions(-) create mode 100644 WinBacktrace.cpp create mode 100644 WinBacktrace.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 19b4dc2c..2d25fc6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,12 +81,18 @@ set(CRASH_HANDLER_IMPL "") message(STATUS "Crash dumps are ${MultiMC_HANDLE_SEGV}") if (MultiMC_HANDLE_SEGV) add_definitions(-DHANDLE_SEGV) + if (WIN32) + find_package(DbgHelp) + set(MultiMC_LINK_ADDITIONAL_LIBS "${MultiMC_LINK_ADDITIONAL_LIBS}dbghelp") + set(MultiMC_CRASH_HANDLER_EXTRA_H "WinBacktrace.h") + set(MultiMC_CRASH_HANDLER_EXTRA_CPP "WinBacktrace.cpp") + endif () endif () -option(MultiMC_TEST_SEGV "Intentionally segfault on startup to test crash handling." OFF) +option(MultiMC_TEST_SEGV "Intentionally segfault sometimes 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.") + message(WARNING "You have enabled crash handler testing. MULTIMC WILL INTENTIONALLY CRASH ITSELF. Crashes should occur on exit and when the new instance button is pressed.") add_definitions(-DTEST_SEGV) endif () @@ -268,6 +274,8 @@ SET(MULTIMC_SOURCES # Crash handling HandleCrash.h HandleCrash.cpp + ${MultiMC_CRASH_HANDLER_EXTRA_H} # Extra platform specific stuff + ${MultiMC_CRASH_HANDLER_EXTRA_CPP} # Logging logger/QsDebugOutput.cpp diff --git a/HandleCrash.cpp b/HandleCrash.cpp index 3d6a425b..91a5f2a1 100644 --- a/HandleCrash.cpp +++ b/HandleCrash.cpp @@ -1,3 +1,18 @@ +/* Copyright 2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + // This is the Unix implementation of MultiMC's crash handling system. #include #include @@ -9,13 +24,17 @@ #include #include -#ifdef Q_OS_UNIX +#include + +#if defined Q_OS_UNIX #include #include +#elif defined Q_OS_WIN32 +#include +#include +#include #endif -#include - #include "BuildConfig.h" #include "HandleCrash.h" @@ -31,6 +50,51 @@ // 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 +// {{{ Platform hackery + +#if defined Q_OS_UNIX + +struct CrashData +{ + int signal = 0; +}; + +// This has to be declared here, after the CrashData struct, but before the function that uses it. +void handleCrash(CrashData); + +void handle(int sig) +{ + CrashData cData; + cData.signal = sig; + handleCrash(cData); +} + +#elif defined Q_OS_WIN32 + +// Struct for storing platform specific crash information. +// This gets passed into the generic handler, which will use +// it to access platform specific information. +struct CrashData +{ + EXCEPTION_RECORD* exceptionInfo; + CONTEXT* context; +}; + +void handleCrash(CrashData); + +LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* eInfo) +{ + CrashData cData; + cData.exceptionInfo = eInfo->ExceptionRecord; + cData.context = eInfo->ContextRecord; + handleCrash(cData); + return EXCEPTION_EXECUTE_HANDLER; +} + +#endif + +// }}} + // {{{ Handling #ifdef Q_OS_WIN32 @@ -43,7 +107,7 @@ void dprintf(int fd, const char* fmt...) char buffer[10240]; // Just sprintf to a really long string and hope it works... // This is a hack, but I can't think of a better way to do it easily. - int len = vsprintf(buffer, fmt, args); + int len = vsnprintf(buffer, 10240, fmt, args); printf(buffer, fmt, args); write(fd, buffer, len); va_end(args); @@ -53,10 +117,23 @@ void dprintf(int fd, const char* fmt...) void getVsnType(char* out); void readFromTo(int from, int to); +void dumpErrorInfo(int dumpFile, CrashData crash) +{ +#ifdef Q_OS_UNIX + // TODO: Moar unix + dprintf(dumpFile, "Signal: %d\n", crash.signal); +#elif defined Q_OS_WIN32 + EXCEPTION_RECORD* excInfo = crash.exceptionInfo; + + dprintf(dumpFile, "Exception Code: %d\n", excInfo->ExceptionCode); + dprintf(dumpFile, "Exception Address: 0x%0X\n", excInfo->ExceptionAddress); +#endif +} + void dumpMiscInfo(int dumpFile) { char vsnType[42]; // The version type. If it's more than 42 chars, the universe might implode... - + // Get MMC info. getVsnType(vsnType); @@ -67,7 +144,7 @@ void dumpMiscInfo(int dumpFile) dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType); } -void dumpBacktrace(int dumpFile) +void dumpBacktrace(int dumpFile, CrashData crash) { #ifdef Q_OS_UNIX // Variables for storing crash info. @@ -83,7 +160,47 @@ void dumpBacktrace(int dumpFile) dprintf(dumpFile, "---- END BACKTRACE ----\n"); #elif defined Q_OS_WIN32 dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n"); - dprintf(dumpFile, "Not yet implemented on this platform.\n"); + + StackFrame stack[BT_SIZE]; + size_t size; + + SYMBOL_INFO *symbol; + HANDLE process; + + size = getBacktrace(stack, BT_SIZE, *crash.context); + + // FIXME: Accessing heap memory is supposedly "dangerous", + // but I can't find another way of doing this. + + // Initialize + process = GetCurrentProcess(); + if (!SymInitialize(process, NULL, true)) + { + dprintf(dumpFile, "Failed to initialize symbol handler. Can't print stack trace.\n"); + dprintf(dumpFile, "Here's a list of addresses in the call stack instead:\n"); + for(int i = 0; i < size; i++) + { + dprintf(dumpFile, "0x%0X\n", (DWORD64)stack[i].address); + } + } else { + // Allocate memory... ._. + symbol = (SYMBOL_INFO *) calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + + // Dump stacktrace + for(int i = 0; i < size; i++) + { + DWORD64 addr = (DWORD64)stack[i].address; + if (!SymFromAddr(process, (DWORD64)(addr), 0, symbol)) + dprintf(dumpFile, "?? - 0x%0X\n", addr); + else + dprintf(dumpFile, "%s - 0x%0X\n", symbol->Name, symbol->Address); + } + + free(symbol); + } + dprintf(dumpFile, "---- END BACKTRACE ----\n"); #endif } @@ -93,7 +210,7 @@ void dumpSysInfo(int dumpFile) #ifdef Q_OS_UNIX bool gotSysInfo = false; // True if system info check succeeded utsname sysinfo; // System information - + // Dump system info if (uname(&sysinfo) >= 0) { @@ -127,10 +244,13 @@ void dumpLogs(int dumpFile) } // 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) +// This is the generic part of the code that will be called after platform specific handling is finished. +void handleCrash(CrashData crash) { - fprintf(stderr, "Fatal error! Received signal %d\n", sig); - +#ifdef Q_OS_UNIX + fprintf(stderr, "Fatal error! Received signal %d\n", crash.signal); +#endif + 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. @@ -140,7 +260,7 @@ void handler(int sig) // 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 @@ -162,16 +282,16 @@ void handler(int sig) // Dump misc info dprintf(dumpFile, "Unix Time: %d\n", unixTime); - dprintf(dumpFile, "Signal: %d\n", sig); + dumpErrorInfo(dumpFile, crash); dumpMiscInfo(dumpFile); - + dprintf(dumpFile, "\n"); - + dumpSysInfo(dumpFile); dprintf(dumpFile, "\n"); - - dumpBacktrace(dumpFile); + + dumpBacktrace(dumpFile, crash); dprintf(dumpFile, "\n"); @@ -238,9 +358,14 @@ void testCrash() // Initializes the Unix crash handler. void initBlackMagic() { +#ifdef Q_OS_UNIX // Register the handler. signal(SIGSEGV, handler); signal(SIGABRT, handler); +#elif defined Q_OS_WIN32 + // I hate Windows + SetUnhandledExceptionFilter(ExceptionFilter); +#endif #ifdef TEST_SEGV testCrash(); diff --git a/WinBacktrace.cpp b/WinBacktrace.cpp new file mode 100644 index 00000000..f111cd6f --- /dev/null +++ b/WinBacktrace.cpp @@ -0,0 +1,80 @@ +/* Copyright 2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// CAUTION: +// This file contains all manner of hackery and insanity. +// I will not be responsible for any loss of sanity due to reading this code. +// Here be dragons! + +#include "WinBacktrace.h" + +#include + +#ifndef __i386__ +#error WinBacktrace is only supported on x86 architectures. +#endif + + +void dumpInfo(StackFrame* frame, const BYTE* caller, HINSTANCE hInst); + +// We need to do some crazy shit to walk through the stack. +// Windows unwinds the stack when an exception is thrown, so we +// need to examine the EXCEPTION_POINTERS's CONTEXT. +size_t getBacktrace(StackFrame *stack, size_t size, CONTEXT ctx) +{ + // Written using information and a bit of pseudocode from + // http://www.eptacom.net/pubblicazioni/pub_eng/except.html + // This is probably one of the most horrifying things I've ever written. + + // This tracks whether the current EBP is valid. + // When an invalid EBP is encountered, we stop walking the stack. + bool validEBP = true; + DWORD ebp = ctx.Ebp; // The current EBP (Extended Base Pointer) + DWORD eip = ctx.Eip; + int i; + for (i = 0; i < size; i++) + { + if (ebp & 3) + validEBP = false; + // FIXME: This function is obsolete, according to MSDN. + else if (IsBadReadPtr((void*) ebp, 8)) + validEBP = false; + + if (!validEBP) break; + + // Find the caller. + // On the first iteration, the caller is whatever EIP points to. + // On successive iterations, the caller is the byte after EBP. + BYTE* caller = !i ? (BYTE*)eip : *((BYTE**) ebp + 1); + // The first ebp is the EBP from the CONTEXT. + // On successive iterations, the EBP is the DWORD that the previous EBP points to. + ebp = !i ? ebp : *(DWORD*)ebp; + + // Find the caller's module. + // We'll use VirtualQuery to get information about the caller's address. + MEMORY_BASIC_INFORMATION mbi; + VirtualQuery(caller, &mbi, sizeof(mbi)); + + // We can get the instance handle from the allocation base. + HINSTANCE hInst = (HINSTANCE)mbi.AllocationBase; + + // If the handle is 0, then the EBP is invalid. + if (hInst == 0) validEBP = false; + // Otherwise, dump info about the caller. + else stack[i].address = (void*)caller; + } + + return i; +} diff --git a/WinBacktrace.h b/WinBacktrace.h new file mode 100644 index 00000000..d1e6301a --- /dev/null +++ b/WinBacktrace.h @@ -0,0 +1,44 @@ +/* Copyright 2014 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#ifndef SF_STR_LEN +// The max length of all strings in the StackFrame struct. +// Because it must be stack allocated, this must be known at compile time. +// Stuff longer than this will be truncated. +// Defaults to 4096 (4kB) +#define SF_STR_LEN 4096 +#endif + +// Data structure for holding information about a stack frame. +// There's some more hackery in here so it can be allocated on the stack. +struct StackFrame +{ + // The address of this stack frame. + void* address; + + // The name of the function at this address. + char funcName[SF_STR_LEN]; +}; + +// This function walks through the given CONTEXT structure, extracting a +// backtrace from it. +// The backtrace will be put into the array given by the `stack` argument +// with a maximum length of `size`. +// This function returns the size of the backtrace retrieved. +size_t getBacktrace(StackFrame* stack, size_t size, CONTEXT ctx); diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 58491a7e..f437f8a3 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -782,6 +782,11 @@ void MainWindow::setCatBackground(bool enabled) void MainWindow::on_actionAddInstance_triggered() { +#ifdef TEST_SEGV + // For further testing stuff. + int v = *((int*)-1); +#endif + if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && m_versionLoadTask->isRunning()) { From 4f6cd65c13261bd3562a3adf5891ed4ee4c1cb59 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 10 May 2014 14:19:13 -0500 Subject: [PATCH 6/8] Remove unused function --- WinBacktrace.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/WinBacktrace.cpp b/WinBacktrace.cpp index f111cd6f..2c9a3960 100644 --- a/WinBacktrace.cpp +++ b/WinBacktrace.cpp @@ -26,9 +26,6 @@ #error WinBacktrace is only supported on x86 architectures. #endif - -void dumpInfo(StackFrame* frame, const BYTE* caller, HINSTANCE hInst); - // We need to do some crazy shit to walk through the stack. // Windows unwinds the stack when an exception is thrown, so we // need to examine the EXCEPTION_POINTERS's CONTEXT. From aefa73ad111578d5c118bd05ee68bff1f2f2119b Mon Sep 17 00:00:00 2001 From: Forkk Date: Sat, 10 May 2014 14:56:44 -0500 Subject: [PATCH 7/8] Fix stupid tabs. Thanks, QtCreator... ._. --- WinBacktrace.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WinBacktrace.cpp b/WinBacktrace.cpp index 2c9a3960..1ea079bf 100644 --- a/WinBacktrace.cpp +++ b/WinBacktrace.cpp @@ -38,7 +38,7 @@ size_t getBacktrace(StackFrame *stack, size_t size, CONTEXT ctx) // This tracks whether the current EBP is valid. // When an invalid EBP is encountered, we stop walking the stack. bool validEBP = true; - DWORD ebp = ctx.Ebp; // The current EBP (Extended Base Pointer) + DWORD ebp = ctx.Ebp; // The current EBP (Extended Base Pointer) DWORD eip = ctx.Eip; int i; for (i = 0; i < size; i++) @@ -57,7 +57,7 @@ size_t getBacktrace(StackFrame *stack, size_t size, CONTEXT ctx) BYTE* caller = !i ? (BYTE*)eip : *((BYTE**) ebp + 1); // The first ebp is the EBP from the CONTEXT. // On successive iterations, the EBP is the DWORD that the previous EBP points to. - ebp = !i ? ebp : *(DWORD*)ebp; + ebp = !i ? ebp : *(DWORD*)ebp; // Find the caller's module. // We'll use VirtualQuery to get information about the caller's address. From e6ca58a89e12bb2c7636dac91b6a1a609c6780b3 Mon Sep 17 00:00:00 2001 From: Forkk Date: Sat, 10 May 2014 15:10:24 -0500 Subject: [PATCH 8/8] Add a missing letter --- HandleCrash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HandleCrash.cpp b/HandleCrash.cpp index 91a5f2a1..ed319cf1 100644 --- a/HandleCrash.cpp +++ b/HandleCrash.cpp @@ -62,7 +62,7 @@ struct CrashData // This has to be declared here, after the CrashData struct, but before the function that uses it. void handleCrash(CrashData); -void handle(int sig) +void handler(int sig) { CrashData cData; cData.signal = sig;