diff --git a/interface/src/CrashReporter.cpp b/interface/src/CrashReporter.cpp new file mode 100644 index 0000000000..022866ce4b --- /dev/null +++ b/interface/src/CrashReporter.cpp @@ -0,0 +1,137 @@ +// +// CrashReporter.cpp +// interface/src +// +// Created by Ryan Huffman on 11 April 2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifdef HAS_BUGSPLAT +#include "CrashReporter.h" +#include +#include +#include +#include + +// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information +// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li +// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so +// that the applicaiton can handle it itself. +// The CAPIHook class referenced in the above article is not openly available, but a similar implementation +// can be found here: http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/ +// The below has been adapted to work with different library and funcitons. +BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn) +{ + HMODULE lib = LoadLibrary(library); + if (lib == NULL) return FALSE; + void *pOrgEntry = GetProcAddress(lib, function); + if (pOrgEntry == NULL) return FALSE; + + DWORD dwOldProtect = 0; + SIZE_T jmpSize = 5; +#ifdef _M_X64 + jmpSize = 13; +#endif + BOOL bProt = VirtualProtect(pOrgEntry, jmpSize, + PAGE_EXECUTE_READWRITE, &dwOldProtect); + BYTE newJump[20]; + void *pNewFunc = fn; +#ifdef _M_IX86 + DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; + dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32 + DWORD dwNewEntryAddr = (DWORD)pNewFunc; + DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; + // JMP rel32: Jump near, relative, displacement relative to next instruction. + newJump[0] = 0xE9; // JMP rel32 + memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc)); +#elif _M_X64 + // We must use R10 or R11, because these are "scratch" registers + // which need not to be preserved accross function calls + // For more info see: Register Usage for x64 64-Bit + // http://msdn.microsoft.com/en-us/library/ms794547.aspx + // Thanks to Matthew Smith!!! + newJump[0] = 0x49; // MOV R11, ... + newJump[1] = 0xBB; // ... + memcpy(&newJump[2], &pNewFunc, sizeof(pNewFunc)); + //pCur += sizeof (ULONG_PTR); + newJump[10] = 0x41; // JMP R11, ... + newJump[11] = 0xFF; // ... + newJump[12] = 0xE3; // ... +#endif + SIZE_T bytesWritten; + BOOL bRet = WriteProcessMemory(GetCurrentProcess(), + pOrgEntry, newJump, jmpSize, &bytesWritten); + + if (bProt != FALSE) + { + DWORD dwBuf; + VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf); + } + return bRet; +} + + +void handleSignal(int signal) { + // Throw so BugSplat can handle + throw(signal); +} + +void handlePureVirtualCall() { + // Throw so BugSplat can handle + throw("ERROR: Pure virtual call"); +} + +void handleInvalidParameter(const wchar_t * expression, const wchar_t * function, const wchar_t * file, + unsigned int line, uintptr_t pReserved ) { + // Throw so BugSplat can handle + throw("ERROR: Invalid parameter"); +} + +int handleNewError(size_t size) { + // Throw so BugSplat can handle + throw("ERROR: Errors calling new"); +} + +LPTOP_LEVEL_EXCEPTION_FILTER WINAPI +noop_SetUnhandledExceptionFilter( + LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) +{ + return nullptr; +} + +_purecall_handler __cdecl +noop_set_purecall_handler(_purecall_handler pNew) +{ + return nullptr; +} + +static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY; + +CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version) + : mpSender(qPrintable(bugSplatDatabase), qPrintable(bugSplatApplicationName), qPrintable(version), nullptr, BUG_SPLAT_FLAGS) +{ + signal(SIGSEGV, handleSignal); + signal(SIGABRT, handleSignal); + _set_purecall_handler(handlePureVirtualCall); + _set_invalid_parameter_handler(handleInvalidParameter); + _set_new_mode(1); + _set_new_handler(handleNewError); + + // Disable WER popup +// SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); +// _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + // QtWebEngineCore internally sets its own purecall handler, overriding our own error handling. This disables that. + if (!redirectLibraryFunctionToFunction("msvcr120.dll", "_set_purecall_handler", &noop_set_purecall_handler)) { + qWarning() << "Failed to patch _set_purecall_handler"; + } + // Patch SetUnhandledExceptionFilter to keep the CRT from overriding our own error handling. + if (!redirectLibraryFunctionToFunction("kernel32.dll", "SetUnhandledExceptionFilter", &noop_SetUnhandledExceptionFilter)) { + qWarning() << "Failed to patch setUnhandledExceptionFilter"; + } +} +#endif diff --git a/interface/src/CrashReporter.h b/interface/src/CrashReporter.h new file mode 100644 index 0000000000..5f02066a74 --- /dev/null +++ b/interface/src/CrashReporter.h @@ -0,0 +1,32 @@ +// +// CrashReporter.h +// interface/src +// +// Created by Ryan Huffman on 11 April 2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#ifndef hifi_CrashReporter_h +#define hifi_CrashReporter_h + +#include + +#ifdef HAS_BUGSPLAT + +#include + +class CrashReporter { +public: + CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version); + + MiniDmpSender mpSender; +}; + +#endif + +#endif // hifi_CrashReporter_h \ No newline at end of file diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 9c07912b99..d2d346072d 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -25,25 +25,101 @@ #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" +#include #ifdef HAS_BUGSPLAT #include #include +#include #endif +#include + +namespace crash { + + void pureVirtualCall() { + struct B { + B() { + qDebug() << "Pure Virtual Function Call crash!"; + Bar(); + } + + virtual void Foo() = 0; + + void Bar() { + Foo(); + } + }; + + struct D : public B { + void Foo() { + qDebug() << "D:Foo()"; + } + }; + + B* b = new D; + qDebug() << "About to make a pure virtual call"; + b->Foo(); + } + + void doubleFree() { + qDebug() << "About to double delete memory"; + int* blah = new int(200); + delete blah; + delete blah; + } + + void nullDeref() { + qDebug() << "About to dereference a null pointer"; + int* p = nullptr; + *p = 1; + } + + void doAbort() { + qDebug() << "About to abort"; + abort(); + } + + void outOfBoundsVectorCrash() { + qDebug() << "std::vector out of bounds crash!"; + std::vector v; + v[0] = 5; + } + + void newFault() { + qDebug() << "About to crash inside new fault"; + // Force crash with large allocation + int *pi = new int[std::numeric_limits::max()]; + } +} +// +//LONG WINAPI TopLevelExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { +// qDebug() << "Got exception"; +// +// mpSender.unhandledExceptionHandler(pExceptionInfo); +// +// return EXCEPTION_CONTINUE_SEARCH; +//} -int main(int argc, const char* argv[]) { - disableQtBearerPoll(); // Fixes wifi ping spikes #if HAS_BUGSPLAT // Prevent other threads from hijacking the Exception filter, and allocate 4MB up-front that may be useful in // low-memory scenarios. - static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY; - static const char* BUG_SPLAT_DATABASE = "interface_alpha"; - static const char* BUG_SPLAT_APPLICATION_NAME = "Interface"; - MiniDmpSender mpSender { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, qPrintable(BuildInfo::VERSION), - nullptr, BUG_SPLAT_FLAGS }; +// static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;// | MDSF_CUSTOMEXCEPTIONFILTER; +// static const char* BUG_SPLAT_DATABASE = "interface_alpha"; +// static const char* BUG_SPLAT_APPLICATION_NAME = "Interface"; +// static MiniDmpSender mpSender { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, qPrintable(BuildInfo::VERSION), +// nullptr, BUG_SPLAT_FLAGS }; #endif + +int main(int argc, const char* argv[]) { +#if HAS_BUGSPLAT + static QString BUG_SPLAT_DATABASE = "interface_alpha"; + static QString BUG_SPLAT_APPLICATION_NAME = "Interface"; + CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION }; +#endif + + disableQtBearerPoll(); // Fixes wifi ping spikes QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME"); @@ -160,6 +236,19 @@ int main(int argc, const char* argv[]) { } } +// crash::doAbort(); // works +// crash::doubleFree(); + + std::thread([]() { +// crash::pureVirtualCall(); // works + crash::newFault(); // works + }); + +// QThread thread; +// QObject::connect(&thread, &QThread::started, &app, []() { +// }, Qt::DirectConnection); +// thread.start(); + // Setup local server QLocalServer server { &app }; @@ -167,18 +256,18 @@ int main(int argc, const char* argv[]) { server.removeServer(applicationName); server.listen(applicationName); - QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection); + QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection); #ifdef HAS_BUGSPLAT AccountManager& accountManager = AccountManager::getInstance(); - mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername())); - QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&mpSender](const QString& newUsername) { - mpSender.setDefaultUserName(qPrintable(newUsername)); + crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername())); + QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { + crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername)); }); // BugSplat WILL NOT work with file paths that do not use OS native separators. auto logPath = QDir::toNativeSeparators(app.getLogger()->getFilename()); - mpSender.sendAdditionalFile(qPrintable(logPath)); + crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath)); #endif printSystemInformation();