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()) {