diff --git a/cmake/externals/crashpad/CMakeLists.txt b/cmake/externals/crashpad/CMakeLists.txt new file mode 100644 index 0000000000..c464dcbc1b --- /dev/null +++ b/cmake/externals/crashpad/CMakeLists.txt @@ -0,0 +1,35 @@ +include(ExternalProject) +set(EXTERNAL_NAME crashpad) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +if (WIN32) + ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://backtrace.io/download/crashpad_062317.zip + URL_MD5 65817e564b3628492abfc1dbd2a1e98b + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 + ) + + ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "List of Crashpad include directories") + + set(LIB_EXT "lib") + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad release library") + set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base release library") + set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util release library") + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad debug library") + set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base debug library") + set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util debug library") + + set(CRASHPAD_HANDLER_EXE_PATH ${SOURCE_DIR}/out/Release_x64/crashpad_handler.exe CACHE FILEPATH "Path to the Crashpad handler executable") +endif () + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") diff --git a/cmake/macros/AddBugSplat.cmake b/cmake/macros/AddBugSplat.cmake deleted file mode 100644 index 979dcfe817..0000000000 --- a/cmake/macros/AddBugSplat.cmake +++ /dev/null @@ -1,34 +0,0 @@ -# -# AddBugSplat.cmake -# cmake/macros -# -# Created by Ryan Huffman on 02/09/16. -# 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 -# - -macro(add_bugsplat) - get_property(BUGSPLAT_CHECKED GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE) - - if (NOT BUGSPLAT_CHECKED) - find_package(BugSplat) - set_property(GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE TRUE) - endif () - - if (BUGSPLAT_FOUND) - add_definitions(-DHAS_BUGSPLAT) - - target_include_directories(${TARGET_NAME} PRIVATE ${BUGSPLAT_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${BUGSPLAT_LIBRARIES}) - add_paths_to_fixup_libs(${BUGSPLAT_DLL_PATH}) - - add_custom_command(TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_RC_DLL_PATH} "$/") - add_custom_command(TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_EXE_PATH} "$/") - endif () -endmacro() diff --git a/cmake/macros/AddCrashpad.cmake b/cmake/macros/AddCrashpad.cmake new file mode 100644 index 0000000000..bf59418f37 --- /dev/null +++ b/cmake/macros/AddCrashpad.cmake @@ -0,0 +1,58 @@ +# +# AddCrashpad.cmake +# cmake/macros +# +# Created by Clement Brisset on 01/19/18. +# Copyright 2018 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 +# + +macro(add_crashpad) + set (USE_CRASHPAD TRUE) + if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "") + set(USE_CRASHPAD FALSE) + else() + set(CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL}) + endif() + + if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "") + set(USE_CRASHPAD FALSE) + else() + set(CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN}) + endif() + + if (WIN32 AND USE_CRASHPAD) + get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE) + if (NOT CRASHPAD_CHECKED) + + add_dependency_external_projects(crashpad) + find_package(crashpad REQUIRED) + + set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE) + endif() + + add_definitions(-DHAS_CRASHPAD) + add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\") + add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\") + + target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY}) + + if (WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099") + endif() + + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$/" + ) + install( + PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH} + DESTINATION ${CLIENT_COMPONENT} + COMPONENT ${INTERFACE_INSTALL_DIR} + ) + endif () +endmacro() diff --git a/cmake/modules/FindBugSplat.cmake b/cmake/modules/FindBugSplat.cmake deleted file mode 100644 index 8bea1cb1e1..0000000000 --- a/cmake/modules/FindBugSplat.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# -# FindBugSplat.cmake -# cmake/modules -# -# Created by Ryan Huffman on 02/09/16. -# 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 -# - -if (WIN32) - include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") - hifi_library_search_hints("BugSplat") - - find_path(BUGSPLAT_INCLUDE_DIRS NAMES BugSplat.h PATH_SUFFIXES inc HINTS ${BUGSPLAT_SEARCH_DIRS}) - - find_library(BUGSPLAT_LIBRARY_RELEASE "BugSplat64.lib" PATH_SUFFIXES "lib64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - find_path(BUGSPLAT_DLL_PATH NAMES "BugSplat64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - find_file(BUGSPLAT_RC_DLL_PATH NAMES "BugSplatRc64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - find_file(BUGSPLAT_EXE_PATH NAMES "BsSndRpt64.exe" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS}) - - include(SelectLibraryConfigurations) - select_library_configurations(BUGSPLAT) - - set(BUGSPLAT_LIBRARIES ${BUGSPLAT_LIBRARY_RELEASE}) -endif () - -set(BUGSPLAT_REQUIREMENTS BUGSPLAT_INCLUDE_DIRS BUGSPLAT_LIBRARIES BUGSPLAT_DLL_PATH BUGSPLAT_RC_DLL_PATH BUGSPLAT_EXE_PATH) -find_package_handle_standard_args(BugSplat DEFAULT_MSG ${BUGSPLAT_REQUIREMENTS}) diff --git a/cmake/modules/FindCrashpad.cmake b/cmake/modules/FindCrashpad.cmake new file mode 100644 index 0000000000..283058336d --- /dev/null +++ b/cmake/modules/FindCrashpad.cmake @@ -0,0 +1,41 @@ +# +# FindCrashpad.cmake +# +# Try to find Crashpad libraries and include path. +# Once done this will define +# +# CRASHPAD_FOUND +# CRASHPAD_INCLUDE_DIRS +# CRASHPAD_LIBRARY +# CRASHPAD_BASE_LIBRARY +# CRASHPAD_UTIL_LIBRARY +# +# Created on 01/19/2018 by Clement Brisset +# Copyright 2018 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 +# + +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("crashpad") + +find_path(CRASHPAD_INCLUDE_DIRS base/macros.h PATH_SUFFIXES include HINTS ${CRASHPAD_SEARCH_DIRS}) + +find_library(CRASHPAD_LIBRARY_RELEASE crashpad PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_BASE_LIBRARY_RELEASE base PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_UTIL_LIBRARY_RELEASE util PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) + +find_library(CRASHPAD_LIBRARY_DEBUG crashpad PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_BASE_LIBRARY_DEBUG base PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) +find_library(CRASHPAD_UTIL_LIBRARY_DEBUG util PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS}) + +find_file(CRASHPAD_HANDLER_EXE_PATH NAME "crashpad_handler.exe" PATH_SUFFIXES "Release_x64" HINTS ${CRASHPAD_SEARCH_DIRS}) + +include(SelectLibraryConfigurations) +select_library_configurations(CRASHPAD) +select_library_configurations(CRASHPAD_BASE) +select_library_configurations(CRASHPAD_UTIL) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CRASHPAD DEFAULT_MSG CRASHPAD_INCLUDE_DIRS CRASHPAD_LIBRARY CRASHPAD_BASE_LIBRARY CRASHPAD_UTIL_LIBRARY) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 290f4a7f53..22273ae85f 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -157,7 +157,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : DependencyManager::set(); LogUtils::init(); - Setting::init(); qDebug() << "Setting up domain-server"; qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 725a04ec46..dc3ee54fe7 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -29,6 +29,8 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 21225756b4..db1ea6df9a 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -216,6 +216,7 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries target_bullet() target_opengl() +add_crashpad() # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) @@ -347,8 +348,6 @@ if (SCRIPTS_INSTALL_DIR) ) endif() -add_bugsplat() - if (WIN32) set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 326575c6cc..0932eaf396 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -144,6 +145,7 @@ #include "avatar/AvatarManager.h" #include "avatar/MyHead.h" #include "CrashHandler.h" +#include "Crashpad.h" #include "devices/DdeFaceTracker.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" @@ -612,8 +614,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, cacheDir); } - Setting::init(); - // Tell the plugin manager about our statically linked plugins auto pluginManager = PluginManager::getInstance(); pluginManager->setInputPluginProvider([] { return getInputPlugins(); }); @@ -861,6 +861,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _logger->setSessionID(accountManager->getSessionID()); + setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString()); + if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); } diff --git a/interface/src/CrashReporter.cpp b/interface/src/CrashReporter.cpp deleted file mode 100644 index 596c34ca92..0000000000 --- a/interface/src/CrashReporter.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// -// 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 -// - - -#include "Application.h" -#include "CrashReporter.h" - -#ifdef _WIN32 -#include -#include -#include - -#include -#include - - -#pragma comment(lib, "Dbghelp.lib") - -// 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 printStackTrace(ULONG framesToSkip = 1) { - HANDLE process = GetCurrentProcess(); - SymInitialize(process, NULL, TRUE); - void* stack[100]; - uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL); - SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); - symbol->MaxNameLen = 255; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - - for (uint16_t i = 0; i < frames; ++i) { - SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); - qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16)); - } - - free(symbol); - - // Try to force the log to sync to the filesystem - auto app = qApp; - if (app && app->getLogger()) { - app->getLogger()->sync(); - } -} - -void handleSignal(int signal) { - // Throw so BugSplat can handle - throw(signal); -} - -void __cdecl handlePureVirtualCall() { - qWarning() << "Pure virtual function call detected"; - printStackTrace(2); - // 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; -} - -#ifdef HAS_BUGSPLAT - -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 -#endif diff --git a/interface/src/CrashReporter.h b/interface/src/CrashReporter.h deleted file mode 100644 index 5f02066a74..0000000000 --- a/interface/src/CrashReporter.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// 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/Crashpad.cpp b/interface/src/Crashpad.cpp new file mode 100644 index 0000000000..8ed3fc23bd --- /dev/null +++ b/interface/src/Crashpad.cpp @@ -0,0 +1,101 @@ +// +// Crashpad.cpp +// interface/src +// +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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 +// + +#include "Crashpad.h" + +#include + +#if HAS_CRASHPAD + +#include +#include + +#include + +#include +#include +#include +// #include +// #include + +using namespace crashpad; + +static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; +static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; + +extern QString qAppFileName(); + +// crashpad::AnnotationList* crashpadAnnotations { nullptr }; + +bool startCrashHandler() { + if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { + return false; + } + + CrashpadClient client; + std::vector arguments; + + std::map annotations; + annotations["token"] = BACKTRACE_TOKEN; + annotations["format"] = "minidump"; + annotations["version"] = BuildInfo::VERSION.toStdString(); + + arguments.push_back("--no-rate-limit"); + + // Setup Crashpad DB directory + const auto crashpadDbName = "crashpad-db"; + const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists + const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName; + + // Locate Crashpad handler + const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe"; + + // Setup different file paths + base::FilePath::StringType dbPath; + base::FilePath::StringType handlerPath; + dbPath.assign(crashpadDbPath.cbegin(), crashpadDbPath.cend()); + handlerPath.assign(CRASHPAD_HANDLER_PATH.cbegin(), CRASHPAD_HANDLER_PATH.cend()); + + base::FilePath db(dbPath); + base::FilePath handler(handlerPath); + + auto database = crashpad::CrashReportDatabase::Initialize(db); + if (database == nullptr || database->GetSettings() == nullptr) { + return false; + } + + // Enable automated uploads. + database->GetSettings()->SetUploadsEnabled(true); + + return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); +} + +void setCrashAnnotation(std::string name, std::string value) { + // if (!crashpadAnnotations) { + // crashpadAnnotations = new crashpad::AnnotationList(); // don't free this, let it leak + // crashpad::CrashpadInfo* crashpad_info = crashpad::GetCrashpadInfo(); + // crashpad_info->set_simple_annotations(crashpadAnnotations); + // } + // crashpadAnnotations->SetKeyValue(name, value); +} + +#else + +bool startCrashHandler() { + qDebug() << "No crash handler available."; + return false; +} + +void setCrashAnnotation(std::string name, std::string value) { +} + +#endif diff --git a/interface/src/Crashpad.h b/interface/src/Crashpad.h new file mode 100644 index 0000000000..a815ed701a --- /dev/null +++ b/interface/src/Crashpad.h @@ -0,0 +1,20 @@ +// +// Crashpad.h +// interface/src +// +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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 +// + +#ifndef hifi_Crashpad_h +#define hifi_Crashpad_h + +#include + +bool startCrashHandler(); +void setCrashAnnotation(std::string name, std::string value); + +#endif // hifi_Crashpad_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 5c07bebc23..fdc4f091f0 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -27,15 +27,11 @@ #include "AddressManager.h" #include "Application.h" +#include "Crashpad.h" #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" -#ifdef HAS_BUGSPLAT -#include -#include -#endif - #ifdef Q_OS_WIN extern "C" { typedef int(__stdcall * CHECKMINSPECPROC) (); @@ -43,11 +39,6 @@ extern "C" { #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 #ifdef Q_OS_LINUX QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); @@ -64,6 +55,17 @@ int main(int argc, const char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + + // Instance UserActivityLogger now that the settings are loaded + auto& ual = UserActivityLogger::getInstance(); + qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); + + if (ual.isEnabled()) { + auto crashHandlerStarted = startCrashHandler(); + qDebug() << "Crash handler started:" << crashHandlerStarted; + } + QStringList arguments; for (int i = 0; i < argc; ++i) { arguments << argv[i]; @@ -252,7 +254,6 @@ int main(int argc, const char* argv[]) { } } #endif - // Setup local server QLocalServer server { &app }; @@ -261,29 +262,8 @@ int main(int argc, const char* argv[]) { server.removeServer(applicationName); server.listen(applicationName); - QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection); - -#ifdef HAS_BUGSPLAT - auto accountManager = DependencyManager::get(); - crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername())); - QObject::connect(accountManager.data(), &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()); - crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath)); - - QMetaObject::Connection connection; - 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); - crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath)); - }); -#endif + QObject::connect(&server, &QLocalServer::newConnection, + &app, &Application::handleLocalServerConnection, Qt::DirectConnection); printSystemInformation(); diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index f9b835df5c..da345d1970 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -8,4 +8,4 @@ if (WIN32) endif() target_zlib() -target_nsight() \ No newline at end of file +target_nsight() diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 01b9f3884f..327668574e 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -20,6 +20,7 @@ #include "SettingHelpers.h" #include "SettingManager.h" #include "SharedLogging.h" +#include "SharedUtil.h" namespace Setting { static QSharedPointer globalManager; @@ -32,14 +33,34 @@ namespace Setting { // tell the private instance to clean itself up on its thread DependencyManager::destroy(); - // globalManager.reset(); - + // quit the settings manager thread and wait on it to make sure it's gone settingsManagerThread->quit(); settingsManagerThread->wait(); } - + + void setupPrivateInstance() { + // Ensure Setting::init has already ran and qApp exists + if (qApp && globalManager) { + // Let's set up the settings Private instance on its own thread + QThread* thread = new QThread(); + Q_CHECK_PTR(thread); + thread->setObjectName("Settings Thread"); + + QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); + QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); + globalManager->moveToThread(thread); + thread->start(); + qCDebug(shared) << "Settings thread started."; + + // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. + qAddPostRoutine(cleanupPrivateInstance); + } + } + FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance) + // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, void init() { // Set settings format @@ -59,23 +80,9 @@ namespace Setting { qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; } - - // Let's set up the settings Private instance on its own thread - QThread* thread = new QThread(); - Q_CHECK_PTR(thread); - thread->setObjectName("Settings Thread"); - globalManager = DependencyManager::set(); - QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); - QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); - globalManager->moveToThread(thread); - thread->start(); - qCDebug(shared) << "Settings thread started."; - - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); + setupPrivateInstance(); } void Interface::init() { diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 2d2ec7c28f..8e5c30711c 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -62,6 +63,43 @@ extern "C" FILE * __cdecl __iob_func(void) { #include "OctalCode.h" #include "SharedLogging.h" +static std::unordered_map stagedGlobalInstances; + + +std::mutex& globalInstancesMutex() { + static std::mutex mutex; + return mutex; +} + +static void commitGlobalInstances() { + std::unique_lock lock(globalInstancesMutex()); + for (const auto& it : stagedGlobalInstances) { + qApp->setProperty(it.first.c_str(), it.second); + } + stagedGlobalInstances.clear(); +} +FIXED_Q_COREAPP_STARTUP_FUNCTION(commitGlobalInstances) + +QVariant getGlobalInstance(const char* propertyName) { + if (qApp) { + return qApp->property(propertyName); + } else { + auto it = stagedGlobalInstances.find(propertyName); + if (it != stagedGlobalInstances.end()) { + return it->second; + } + } + return QVariant(); +} + +void setGlobalInstance(const char* propertyName, const QVariant& variant) { + if (qApp) { + qApp->setProperty(propertyName, variant); + } else { + stagedGlobalInstances[propertyName] = variant; + } +} + static qint64 usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 6cf5a4755d..5a1e48d9c0 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -25,6 +25,22 @@ #include #include +// Workaround for https://bugreports.qt.io/browse/QTBUG-54479 +// Wrap target function inside another function that holds +// a unique string identifier and uses it to ensure it only runs once +// by storing a state within the qApp +// We cannot used std::call_once with a static once_flag because +// this is used in shared libraries that are linked by several DLLs +// (ie. plugins), meaning the static will be useless in that case +#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \ + static void AFUNC ## _fixed() { \ + const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \ + if (!qApp->property(propertyName.c_str()).toBool()) { \ + AFUNC(); \ + qApp->setProperty(propertyName.c_str(), QVariant(true)); \ + } \ + } \ + Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed) // When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows // the value to be reset when the sessionID changes. @@ -52,6 +68,10 @@ bool destroyGlobalInstance() { return false; } +std::mutex& globalInstancesMutex(); +QVariant getGlobalInstance(const char* propertyName); +void setGlobalInstance(const char* propertyName, const QVariant& variant); + // Provides efficient access to a named global type. By storing the value // in the QApplication by name we can implement the singleton pattern and // have the single instance function across DLL boundaries. @@ -60,9 +80,9 @@ T* globalInstance(const char* propertyName, Args&&... args) { static T* resultInstance { nullptr }; static std::mutex mutex; if (!resultInstance) { - std::unique_lock lock(mutex); + std::unique_lock lock(globalInstancesMutex()); if (!resultInstance) { - auto variant = qApp->property(propertyName); + auto variant = getGlobalInstance(propertyName); if (variant.isNull()) { std::unique_ptr& instancePtr = globalInstancePointer(); if (!instancePtr.get()) { @@ -72,7 +92,7 @@ T* globalInstance(const char* propertyName, Args&&... args) { } void* voidInstance = &(*instancePtr); variant = QVariant::fromValue(voidInstance); - qApp->setProperty(propertyName, variant); + setGlobalInstance(propertyName, variant); } void* returnedVoidInstance = variant.value(); resultInstance = static_cast(returnedVoidInstance); diff --git a/tests/qt59/src/main.cpp b/tests/qt59/src/main.cpp index c66a5e6f9a..7b95cabd6c 100644 --- a/tests/qt59/src/main.cpp +++ b/tests/qt59/src/main.cpp @@ -33,8 +33,6 @@ private: Qt59TestApp::Qt59TestApp(int argc, char* argv[]) : QCoreApplication(argc, argv) { - - Setting::init(); DependencyManager::registerInheritance(); DependencyManager::set([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); }); DependencyManager::set(); diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index 88884a4fee..9eadc1dec2 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -97,7 +97,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : _password = pieces[1]; } - Setting::init(); DependencyManager::registerInheritance(); DependencyManager::set([&]{ return QString("Mozilla/5.0 (HighFidelityACClient)"); }); diff --git a/tools/ac-client/src/main.cpp b/tools/ac-client/src/main.cpp index 12c5e6f5f8..c9affde3b5 100644 --- a/tools/ac-client/src/main.cpp +++ b/tools/ac-client/src/main.cpp @@ -25,6 +25,8 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + ACClientApp app(argc, argv); return app.exec(); diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp index 9fd1bf8d4f..0e7f223f28 100644 --- a/tools/atp-client/src/ATPClientApp.cpp +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -135,7 +135,6 @@ ATPClientApp::ATPClientApp(int argc, char* argv[]) : _domainServerAddress = domainURL.toString(); } - Setting::init(); DependencyManager::registerInheritance(); DependencyManager::set(); diff --git a/tools/atp-client/src/main.cpp b/tools/atp-client/src/main.cpp index 88119604cf..830c049bc7 100644 --- a/tools/atp-client/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -25,6 +25,8 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + Setting::init(); + ATPClientApp app(argc, argv); return app.exec(); diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 9de06a35bb..69d2ef84ce 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -14,7 +14,6 @@ #include #include -#include #include "ui/OvenMainWindow.h" #include "Oven.h" @@ -29,12 +28,6 @@ static const QString CLI_TYPE_PARAMETER = "t"; Oven::Oven(int argc, char* argv[]) : QApplication(argc, argv) { - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setApplicationName("Oven"); - - // init the settings interface so we can save and load settings - Setting::init(); - // parse the command line parameters QCommandLineParser parser; diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index 9c778245b5..788470b75e 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -10,7 +10,15 @@ #include "Oven.h" +#include + int main (int argc, char** argv) { + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setApplicationName("Oven"); + + // init the settings interface so we can save and load settings + Setting::init(); + Oven app(argc, argv); return app.exec(); }