Implement crash handling on Linux
This will allow us to generate crash dumps and have users report crashes.
This commit is contained in:
parent
cf616efb5d
commit
93ae21abfc
@ -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@";
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
18
HandleCrash.h
Normal file
18
HandleCrash.h
Normal file
@ -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();
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
210
UnixCrash.cpp
Normal file
210
UnixCrash.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
// This is the Unix implementation of MultiMC's crash handling system.
|
||||
#include <stdio.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include <MultiMC.h>
|
||||
|
||||
#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-<unixtime>.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
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
11
main.cpp
11
main.cpp
@ -1,6 +1,12 @@
|
||||
#include "MultiMC.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
// Crash handling
|
||||
#ifdef HANDLE_SEGV
|
||||
#include <HandleCrash.h>
|
||||
#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:
|
||||
|
Loading…
Reference in New Issue
Block a user