diff --git a/interface/src/CrashReporter.cpp b/interface/src/CrashReporter.cpp new file mode 100644 index 0000000000..1fd534ffac --- /dev/null +++ b/interface/src/CrashReporter.cpp @@ -0,0 +1,135 @@ +// +// 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 functions. +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 be56ad12b1..09369dc25a 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -25,26 +25,23 @@ #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" +#include #ifdef HAS_BUGSPLAT #include #include +#include #endif - 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 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"); bool instanceMightBeRunning = true; @@ -168,27 +165,27 @@ 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 logger = app.getLogger(); auto logPath = QDir::toNativeSeparators(logger->getFilename()); - mpSender.sendAdditionalFile(qPrintable(logPath)); + crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath)); QMetaObject::Connection connection; - connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&mpSender, &connection](QString newFilename) { + connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&crashReporter, &connection](QString newFilename) { // We only want to add the first rolled log file (the "beginning" of the log) to BugSplat to ensure we don't exceed the 2MB // zipped limit, so we disconnect here. QObject::disconnect(connection); auto rolledLogPath = QDir::toNativeSeparators(newFilename); - mpSender.sendAdditionalFile(qPrintable(rolledLogPath)); + crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath)); }); #endif