Implement backtraces on Windows.
Much !!FUN!! was had
This commit is contained in:
parent
9e80ddb040
commit
5099964c67
@ -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
|
||||||
|
159
HandleCrash.cpp
159
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.
|
// 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,10 +117,23 @@ 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...
|
||||||
|
|
||||||
// Get MMC info.
|
// Get MMC info.
|
||||||
getVsnType(vsnType);
|
getVsnType(vsnType);
|
||||||
|
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -93,7 +210,7 @@ void dumpSysInfo(int dumpFile)
|
|||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
bool gotSysInfo = false; // True if system info check succeeded
|
bool gotSysInfo = false; // True if system info check succeeded
|
||||||
utsname sysinfo; // System information
|
utsname sysinfo; // System information
|
||||||
|
|
||||||
// Dump system info
|
// Dump system info
|
||||||
if (uname(&sysinfo) >= 0)
|
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.
|
// 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.
|
||||||
|
|
||||||
char dumpFileName[DUMPF_NAME_LEN]; // The name of the file we're dumping to.
|
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-<unixtime>.dump"
|
// We'll just call it "mmc-crash-<unixtime>.dump"
|
||||||
// First, check the time.
|
// First, check the time.
|
||||||
time(&unixTime);
|
time(&unixTime);
|
||||||
|
|
||||||
// Now we get to do some !!FUN!! hackery to ensure we don't use the stack when we convert
|
// 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
|
// 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
|
// 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
|
// 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");
|
||||||
|
|
||||||
dumpSysInfo(dumpFile);
|
dumpSysInfo(dumpFile);
|
||||||
|
|
||||||
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
80
WinBacktrace.cpp
Normal 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
44
WinBacktrace.h
Normal 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);
|
@ -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())
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user