/* 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 #include #include #include #include #include #include #include #include #if defined Q_OS_UNIX #include #include #elif defined Q_OS_WIN32 #include #include #include #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 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-.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 } // }}}