Implement backtraces on Windows.

Much !!FUN!! was had
This commit is contained in:
Andrew 2014-05-10 14:16:27 -05:00
parent 9e80ddb040
commit 5099964c67
5 changed files with 281 additions and 19 deletions

View File

@ -81,12 +81,18 @@ set(CRASH_HANDLER_IMPL "")
message(STATUS "Crash dumps are ${MultiMC_HANDLE_SEGV}") message(STATUS "Crash dumps are ${MultiMC_HANDLE_SEGV}")
if (MultiMC_HANDLE_SEGV) if (MultiMC_HANDLE_SEGV)
add_definitions(-DHANDLE_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 () 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) if (MultiMC_TEST_SEGV)
# TODO: Make this a unit test instead. # 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) add_definitions(-DTEST_SEGV)
endif () endif ()
@ -268,6 +274,8 @@ SET(MULTIMC_SOURCES
# Crash handling # Crash handling
HandleCrash.h HandleCrash.h
HandleCrash.cpp HandleCrash.cpp
${MultiMC_CRASH_HANDLER_EXTRA_H} # Extra platform specific stuff
${MultiMC_CRASH_HANDLER_EXTRA_CPP}
# Logging # Logging
logger/QsDebugOutput.cpp logger/QsDebugOutput.cpp

View File

@ -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. // This is the Unix implementation of MultiMC's crash handling system.
#include <stdio.h> #include <stdio.h>
#include <iostream> #include <iostream>
@ -9,13 +24,17 @@
#include <errno.h> #include <errno.h>
#include <sys/stat.h> #include <sys/stat.h>
#ifdef Q_OS_UNIX #include <MultiMC.h>
#if defined Q_OS_UNIX
#include <sys/utsname.h> #include <sys/utsname.h>
#include <execinfo.h> #include <execinfo.h>
#elif defined Q_OS_WIN32
#include <windows.h>
#include <dbghelp.h>
#include <WinBacktrace.h>
#endif #endif
#include <MultiMC.h>
#include "BuildConfig.h" #include "BuildConfig.h"
#include "HandleCrash.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. // 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 #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 // {{{ Handling
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@ -43,7 +107,7 @@ void dprintf(int fd, const char* fmt...)
char buffer[10240]; char buffer[10240];
// Just sprintf to a really long string and hope it works... // 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. // 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); printf(buffer, fmt, args);
write(fd, buffer, len); write(fd, buffer, len);
va_end(args); va_end(args);
@ -53,6 +117,19 @@ void dprintf(int fd, const char* fmt...)
void getVsnType(char* out); void getVsnType(char* out);
void readFromTo(int from, int to); 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) void dumpMiscInfo(int dumpFile)
{ {
char vsnType[42]; // The version type. If it's more than 42 chars, the universe might implode... char vsnType[42]; // The version type. If it's more than 42 chars, the universe might implode...
@ -67,7 +144,7 @@ void dumpMiscInfo(int dumpFile)
dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType); dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType);
} }
void dumpBacktrace(int dumpFile) void dumpBacktrace(int dumpFile, CrashData crash)
{ {
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
// Variables for storing crash info. // Variables for storing crash info.
@ -83,7 +160,47 @@ void dumpBacktrace(int dumpFile)
dprintf(dumpFile, "---- END BACKTRACE ----\n"); dprintf(dumpFile, "---- END BACKTRACE ----\n");
#elif defined Q_OS_WIN32 #elif defined Q_OS_WIN32
dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n"); 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"); dprintf(dumpFile, "---- END BACKTRACE ----\n");
#endif #endif
} }
@ -127,9 +244,12 @@ 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. // 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. time_t unixTime = 0; // Unix timestamp. Used to give our crash dumps "unique" names.
@ -162,7 +282,7 @@ void handler(int sig)
// Dump misc info // Dump misc info
dprintf(dumpFile, "Unix Time: %d\n", unixTime); dprintf(dumpFile, "Unix Time: %d\n", unixTime);
dprintf(dumpFile, "Signal: %d\n", sig); dumpErrorInfo(dumpFile, crash);
dumpMiscInfo(dumpFile); dumpMiscInfo(dumpFile);
dprintf(dumpFile, "\n"); dprintf(dumpFile, "\n");
@ -171,7 +291,7 @@ void handler(int sig)
dprintf(dumpFile, "\n"); dprintf(dumpFile, "\n");
dumpBacktrace(dumpFile); dumpBacktrace(dumpFile, crash);
dprintf(dumpFile, "\n"); dprintf(dumpFile, "\n");
@ -238,9 +358,14 @@ void testCrash()
// Initializes the Unix crash handler. // Initializes the Unix crash handler.
void initBlackMagic() void initBlackMagic()
{ {
#ifdef Q_OS_UNIX
// Register the handler. // Register the handler.
signal(SIGSEGV, handler); signal(SIGSEGV, handler);
signal(SIGABRT, handler); signal(SIGABRT, handler);
#elif defined Q_OS_WIN32
// I hate Windows
SetUnhandledExceptionFilter(ExceptionFilter);
#endif
#ifdef TEST_SEGV #ifdef TEST_SEGV
testCrash(); testCrash();

80
WinBacktrace.cpp Normal file
View File

@ -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 <windows.h>
#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;
}

44
WinBacktrace.h Normal file
View File

@ -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 <windows.h>
#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);

View File

@ -782,6 +782,11 @@ void MainWindow::setCatBackground(bool enabled)
void MainWindow::on_actionAddInstance_triggered() void MainWindow::on_actionAddInstance_triggered()
{ {
#ifdef TEST_SEGV
// For further testing stuff.
int v = *((int*)-1);
#endif
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask && if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask &&
m_versionLoadTask->isRunning()) m_versionLoadTask->isRunning())
{ {