377 lines
9.3 KiB
C++
377 lines
9.3 KiB
C++
/* 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 <stdio.h>
|
|
#include <iostream>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <MultiMC.h>
|
|
|
|
#if defined Q_OS_UNIX
|
|
#include <sys/utsname.h>
|
|
#include <execinfo.h>
|
|
#elif defined Q_OS_WIN32
|
|
#include <windows.h>
|
|
#include <dbghelp.h>
|
|
#include <WinBacktrace.h>
|
|
#endif
|
|
|
|
#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.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.
|
|
// 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 handler(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
|
|
// #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 = vsnprintf(buffer, 10240, fmt, args);
|
|
printf(buffer, fmt, args);
|
|
write(fd, buffer, len);
|
|
va_end(args);
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
|
|
// 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, CrashData crash)
|
|
{
|
|
#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");
|
|
|
|
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
|
|
}
|
|
|
|
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.
|
|
// This is the generic part of the code that will be called after platform specific handling is finished.
|
|
void handleCrash(CrashData crash)
|
|
{
|
|
#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.
|
|
int dumpFile; // File descriptor for our dump file.
|
|
|
|
// 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);
|
|
dumpErrorInfo(dumpFile, crash);
|
|
dumpMiscInfo(dumpFile);
|
|
|
|
dprintf(dumpFile, "\n");
|
|
|
|
dumpSysInfo(dumpFile);
|
|
|
|
dprintf(dumpFile, "\n");
|
|
|
|
dumpBacktrace(dumpFile, crash);
|
|
|
|
dprintf(dumpFile, "\n");
|
|
|
|
// DIE DIE DIE!
|
|
exit(1);
|
|
}
|
|
else
|
|
{
|
|
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)
|
|
{
|
|
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';
|
|
}
|
|
#endif
|
|
|
|
// 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();
|
|
#endif
|
|
}
|
|
|
|
// }}}
|
|
|