diff --git a/Modules/BreakpadCrashReporting/CMakeLists.txt b/Modules/BreakpadCrashReporting/CMakeLists.txt index c4fda751f8..7dd424479c 100644 --- a/Modules/BreakpadCrashReporting/CMakeLists.txt +++ b/Modules/BreakpadCrashReporting/CMakeLists.txt @@ -1,29 +1,44 @@ option(MITK_USE_BREAKPAD_CRASH_REPORTING "Enable support for Google Breakpad Crash Reporting" ON) if(MITK_USE_BREAKPAD_CRASH_REPORTING) if(CMAKE_SYSTEM MATCHES "Windows") find_path(MITK_BREAKPAD_SRC_DIR breakpad_googletest_includes.h ${MITK_BREAKPAD_SRC_DIR} DOC "Directory breakpad/src/") + set(INCLUDE_DIRS_INTERNAL ${INCLUDE_DIRS_INTERNAL} ${MITK_BREAKPAD_SRC_DIR}) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${MITK_BREAKPAD_SRC_DIR}/client/windows/Release/lib/common.lib) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${MITK_BREAKPAD_SRC_DIR}/client/windows/Release/lib/crash_generation_client.lib) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${MITK_BREAKPAD_SRC_DIR}/client/windows/Release/lib/crash_generation_server.lib) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${MITK_BREAKPAD_SRC_DIR}/client/windows/Release/lib/exception_handler.lib) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${MITK_BREAKPAD_SRC_DIR}/client/windows/Release/lib/crash_report_sender.lib) message(INFORMATION "Building BreakpadCrashReporting module.") MITK_CREATE_MODULE(BreakpadCrashReporting INCLUDE_DIRS ${MITK_BIN_DIR} INTERNAL_INCLUDE_DIRS ${INCLUDE_DIRS_INTERNAL} DEPENDS Mitk Qmitk EXPORT_DEFINE MITK_BREAKPAD_EXPORT ADDITIONAL_LIBS ${ADDITIONAL_LIBS} QT_MODULE ) + + if(WIN32) # crash report server without console window + + # how to create main.moc ?? + #QT4_WRAP_CPP(SERVER_MOC_CPP mitkCrashReportingServer.cpp) + + #use this for windows console: + # add_executable(CrashReportingServer mitkCrashReportingServer.cpp ${SERVER_MOC_CPP}) + add_executable(CrashReportingServer WIN32 mitkCrashReportingServer.cpp ${SERVER_MOC_CPP}) + + target_link_libraries(CrashReportingServer ${ALL_LIBRARIES} ${QT_QTMAIN_LIBRARY} BreakpadCrashReporting) + + endif() + endif() endif(MITK_USE_BREAKPAD_CRASH_REPORTING) diff --git a/Modules/BreakpadCrashReporting/files.cmake b/Modules/BreakpadCrashReporting/files.cmake index 291ff72be0..de72da8274 100644 --- a/Modules/BreakpadCrashReporting/files.cmake +++ b/Modules/BreakpadCrashReporting/files.cmake @@ -1,3 +1,4 @@ set(CPP_FILES mitkBreakpadCrashReporting.cpp + mitkCrashReportingServer.cpp ) diff --git a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp index d175386cf2..1c157714de 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp @@ -1,170 +1,294 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkBreakpadCrashReporting.h" #include "mitkLogMacros.h" + +#if defined(Q_OS_WIN) #include #include - #include "client/windows/crash_generation/client_info.h" #include "client/windows/crash_generation/crash_generation_server.h" #include "client/windows/handler/exception_handler.h" #include "client/windows/common/ipc_protocol.h" +#elif defined(Q_OS_MAC) +#include +#include +#include +#elif defined(Q_OS_LINUX) +#include +#include +#include +#endif + #include #include +#include +#include + +#include + +static bool breakpadOnceConnected = false; // indicates a server having had at least one client connection +static int breakpadNumberOfConnections = 0; // current number of connected clients +static int numberOfConnectionAttemptsPerformed = 1; // number of performed re-connect attempts of a crash client mitk::BreakpadCrashReporting::BreakpadCrashReporting() { m_CrashServer = NULL; m_ExceptionHandler = NULL; - m_NamedPipeString = "\\\\.\\pipe\\BreakpadCrashServices\\TestServer"; - m_CrashDumpPath = QDir(QApplication::instance()->applicationDirPath()).filePath("CrashDumps"); + m_NamedPipeString = "\\\\.\\pipe\\MitkCrashServices\\MitkBasedApplication"; + m_CrashDumpPath = QDir(QApplication::instance()->applicationDirPath()).absolutePath(); + m_CrashDumpPath.append("/CrashDumps/"); + + m_NumberOfConnectionAttempts = 3; + m_ReconnectDelay = 300; + + m_CrashReportingServerExecutable = QDir(QApplication::instance()->applicationDirPath()).absolutePath().append("/CrashReportingServer.exe"); } mitk::BreakpadCrashReporting::~BreakpadCrashReporting() { if (m_ExceptionHandler) { delete m_ExceptionHandler; } - if (m_CrashServer) { delete m_CrashServer; } - } - +//This function gets called in the event of a crash. bool ShowDumpResults(const wchar_t* dump_path, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded) { - /*if(succeeded) - MITK_INFO << "Dump generation request result succeeded."; - else - MITK_INFO << "Dump generation request result failed."; - return succeeded;*/ + /* + NO STACK USE, NO HEAP USE IN THIS FUNCTION + Creating QString's, using qDebug, etc. - everything is crash-unfriendly. + */ + //QMessageBox::information( NULL, "Application problem", "The application encountered an error. \n\n A detailed error report may have been written - contact support.", QMessageBox::Ok ); + return succeeded; } -void mitk::BreakpadCrashReporting::Initialize() +void mitk::BreakpadCrashReporting::InitializeClientHandler(bool connectToCrashGenerationServer) { - //std::wstring dumpPath = this->m_CrashDumpPath.toStdWString(); - google_breakpad::CustomClientInfo custom_info; - StartCrashServer(); - /* This is needed for CRT to not show dialog for invalid param failures and instead let the code handle it.*/ _CrtSetReportMode(_CRT_ASSERT, 0); - const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer"; - m_ExceptionHandler = new google_breakpad::ExceptionHandler(L"C:\\dumps\\", + const wchar_t* pipe; + if(connectToCrashGenerationServer) + { + pipe = (const wchar_t*)m_NamedPipeString.utf16(); + MITK_INFO << "Initializing Breakpad Crash Handler, connecting to named pipe: " << m_NamedPipeString.toStdString().c_str() << "\n"; + } + else + { + pipe = (const wchar_t*) L""; + MITK_INFO << "Initializing Breakpad Crash Handler, connecting to named pipe: "; + } + +#ifdef _MSC_VER // http://stackoverflow.com/questions/5625884/conversion-of-stdwstring-to-qstring-throws-linker-error + std::wstring dump_path = std::wstring((const wchar_t *)m_CrashDumpPath.utf16()); +#else + std::wstring dump_path = m_CrashDumpPath.toStdWString(); +#endif + + + + m_ExceptionHandler = new google_breakpad::ExceptionHandler( +#if defined(Q_OS_WIN) + dump_path, +#else + m_CrashDumpPath.toStdString(), +#endif NULL, ShowDumpResults, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL, MiniDumpNormal, //see DbgHelp.h - kPipeName, + pipe, &custom_info); + if(connectToCrashGenerationServer) + { + if(!m_ExceptionHandler->IsOutOfProcess()) + { // we want to connect to a server but connection handler did not connect to OOP server. + + MITK_INFO << "Initializing Breakpad Crash Handler: connection attempt to crash report server failed. Server started?"; + + if(numberOfConnectionAttemptsPerformed < this->m_NumberOfConnectionAttempts) + { + itksys::SystemTools::Delay(m_ReconnectDelay); //sleep a little + numberOfConnectionAttemptsPerformed++; + InitializeClientHandler(connectToCrashGenerationServer); + } + else + { + MITK_INFO << "Initializing Breakpad Crash Handler: connection attempt to crash report server failed - will proceed with in process handler."; + } + } + } } static void _cdecl ShowClientConnected(void* context, const google_breakpad::ClientInfo* client_info) -{ +{ // callback of the crash generation server on client connect MITK_INFO << "Breakpad Client connected: " << client_info->pid(); + + breakpadOnceConnected = true; // static variables indicate server shutdown after usage + breakpadNumberOfConnections++; } static void _cdecl ShowClientCrashed(void* context, const google_breakpad::ClientInfo* client_info, const std::wstring* dump_path) -{ +{ // callback of the crash generation server on client crash MITK_INFO << "Breakpad Client request dump: " << client_info->pid(); + // we may add some log info here along the dump file google_breakpad::CustomClientInfo custom_info = client_info->GetCustomInfo(); - if (custom_info.count <= 0) - { - return; - } } static void _cdecl ShowClientExited(void* context, const google_breakpad::ClientInfo* client_info) -{ +{ // callback of the crash generation server on client exit MITK_INFO << "Breakpad Client exited :" << client_info->pid(); + + // we'd like to shut down server if there is no further client connected, + // but no access to private server members in this callback + breakpadNumberOfConnections--; + if(breakpadNumberOfConnections == 0 && breakpadOnceConnected) + { + MITK_INFO << "Breakpad Server: no more client connections. Shuting down..."; + exit(0); + } } -void mitk::BreakpadCrashReporting::StartCrashServer() +bool mitk::BreakpadCrashReporting::StartCrashServer(bool lauchOutOfProcessExecutable) { - // Do not create another instance of the server. + if (m_CrashServer) - { - return; + { // Do not create another instance of the server. + MITK_INFO << "Crash Server object already generated."; + return true; + } + + if(lauchOutOfProcessExecutable) + { // spawn process and launch CrashReportingServer executable + QString tmpPipeString = m_NamedPipeString; + QString tmpCrashDumpPathString = m_CrashDumpPath; + QStringList arguments; + arguments << tmpPipeString.prepend('"').append('"'); + arguments << tmpCrashDumpPathString.prepend('"').append('"'); + bool success = QProcess::startDetached( m_CrashReportingServerExecutable, arguments); + + return success; + } + else + { // directly open up server instance in this thread + return InitializeServer(); } +} + +bool mitk::BreakpadCrashReporting::InitializeServer() +{ + QDir myDir; + myDir.mkpath(m_CrashDumpPath); // Assure directory is created. - std::wstring dump_path = L"C:\\Dumps\\"; - const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer"; - m_CrashServer = new google_breakpad::CrashGenerationServer(kPipeName, +#ifdef _MSC_VER // http://stackoverflow.com/questions/5625884/conversion-of-stdwstring-to-qstring-throws-linker-error + std::wstring dump_path = std::wstring((const wchar_t *)m_CrashDumpPath.utf16()); +#else + std::wstring dump_path = m_CrashDumpPath.toStdWString(); +#endif + + m_CrashServer = new google_breakpad::CrashGenerationServer((const wchar_t*)m_NamedPipeString.utf16(), NULL, - ShowClientConnected, + ShowClientConnected, // connect callback NULL, - ShowClientCrashed, + ShowClientCrashed, // dump callback NULL, - ShowClientExited, + ShowClientExited, // exit callback NULL, NULL, NULL, true, &dump_path); if (!m_CrashServer->Start()) { MITK_ERROR << "Unable to start google breakpad server."; delete m_CrashServer; m_CrashServer = NULL; + return false; } else { MITK_INFO << "Google breakpad server started."; + return true; } } +bool mitk::BreakpadCrashReporting::RequestDump() +{ + if(this->m_ExceptionHandler != NULL) + { + if(m_ExceptionHandler->WriteMinidump()) + { + MITK_INFO << "Breakpad Crash Reporting: Successfully requested a minidump."; + return true; + } + else + { + MITK_INFO << "Breakpad Crash Reporting: Requested of minidump failed."; + return false; + } + } + return false; +} + void mitk::BreakpadCrashReporting::StopCrashServer() { delete m_CrashServer; m_CrashServer = NULL; } void mitk::BreakpadCrashReporting::CrashAppForTestPurpose() { //printf(NULL); //derived derived; int* x = 0; *x = 1; } + +int mitk::BreakpadCrashReporting::GetNumberOfConnections() +{ + return breakpadNumberOfConnections; +} diff --git a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h index 4d9e0deaf6..9c51a716c4 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h @@ -1,66 +1,150 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITK_BREAKPAD_CRASH_REPORTING_H #define MITK_BREAKPAD_CRASH_REPORTING_H #include "BreakpadCrashReportingExports.h" #include -#include namespace google_breakpad { class ExceptionHandler; class CrashGenerationServer; } namespace mitk { /** - * \brief Prototype for using Google's Breakpad Project in MITK. + * \brief Integration of Google's Breakpad Project in MITK. + * + * Breakpad is a library and tool suite that allows you to distribute an application to users with compiler-provided + * debugging information removed, record crashes in compact "minidump" files, send them back to your server, and + * produce C and C++ stack traces from these minidumps. Breakpad can also write minidumps on request for programs that + * have not crashed (from http://code.google.com/p/google-breakpad/wiki/GettingStartedWithBreakpad). + * + * + * Usage: + * + * In-process usage: + * Instantiate this class, and initialize an event handler for 'unhandled' exceptions by calling + * InitializeClientHandler(false). In the event of a crash your application will try to generate a dump file. + * + * Out-of-process (OOP) usage: + * However,your application crashed - therefore using your application's process for dealing with unhandled exceptions is + * not safe. Heap and stack may be corrupted. Having a separated process that invoces the generation of a minidump of your process is best, + * this is called out-of-process exception handling. + * + * Sample code for simple OOP usage: + * mitk::BreakpadCrashReporting myBreakpad; + * myBreakpad.StartCrashServer(true); + * [... some code ...] + * myBreakpad.InitializeClientHandler(true); + * + * Note 1: The start of a detached process takes some time. Either you call InitializeClientHandler(true) a while after calling + * StartCrashServer(true), or it may take some time and a few connection attempts (configurable, see re-connect handling). + * + * Note 2: If there is no connection to the server possible, there is an automatic fallback to in-process usage. + * Client and server output will indicate the operating mode. + * + * Note 3: The crash reporting server process will automatically shutdown, if there was a client connected and exits + * (either due to shutdown or due to crash). Also, the sample server will shutdown automatically, if there isalready + * one server instance running. + * */ class MITK_BREAKPAD_EXPORT BreakpadCrashReporting { public: BreakpadCrashReporting(); ~BreakpadCrashReporting(); - void Initialize(); - // named pipe string to communicate with OutOfProcessCrashReporter. + /** Initializes an event handler for 'unhandled exceptions' that will dump a so-called 'minidump' to a defined folder. + * For usage as "in-process" exception handler set connectToCrashGenerationServer = false. + * For usage as "out-of-process" (OOP) exception handler, set connectToCrashGenerationServer = true. + * + * Related params: + * Are defined by means of SetNamedPipeName() and SetCrashDumpPath(). + * + * OOP Usage: + * In OOP use case, the handler uses a crash generation client that connects to a crash generation server via named pipes. + * Such a crash generation server should be started then on beforehand by means of the function StartCrashServer() below. + * + * If the connection attempt to a server fails, reconnects attempt may be scheduled by SetNumberOfConnectionAttempts() + * and SetReconnectDelayInMilliSeconds(). Note that during re-connect attempts, your application will be blocked. + * + * + * + */ + void InitializeClientHandler(bool connectToCrashGenerationServer); + + /** Starts a crash generation server for "out-of-process" exception handling. + * + * For usage outside of your main application (i.e. already in a separate process), set launchOutOfProcessExecutable = false. + * For usage inside of your main application, set launchOutOfProcessExecutable = true. + * + * In the latter case, StartCrashServer() will spawn a detached process launching the crash generation server. + * This server process will automatically shutdown again, if a once connected client exits due to client shutdown or crash. + * + * By default, an instance of the sample crash reporting server, mitk::CrashReportingServer will be used. Alternatively, + * you may define a process to be started by SetCrashReportingServerExecutable(). + * + * Related params are defined by means of SetNamedPipeName() and SetCrashDumpPath(). + * + */ + bool StartCrashServer(bool launchOutOfProcessExecutable); + + // Named pipe string to communicate with OutOfProcessCrashReporter. QString m_NamedPipeString; + void SetNamedPipeName(QString name){m_NamedPipeString = name;} - // QString directory path to save crash dumps. + // Directory path to save crash dumps. QString m_CrashDumpPath; + void SetCrashDumpPath(QString path){m_CrashDumpPath = path;} - mutable QMutex m_CriticalSection; + // External out-of-process (OOP) Crash Reporting Server file path - if OOP is used. + QString m_CrashReportingServerExecutable; + void SetCrashReportingServerExecutable(QString exe){m_CrashReportingServerExecutable = exe;} + // Re-connect handling in case a crash server cannot be reached. + void SetNumberOfConnectionAttempts(int no){m_NumberOfConnectionAttempts = no;} + void SetReconnectDelayInMilliSeconds(int ms) {m_ReconnectDelay = ms;} + int m_NumberOfConnectionAttempts; + int m_ReconnectDelay; + + // This may be a security issue. google_breakpad::ExceptionHandler* m_ExceptionHandler; google_breakpad::CrashGenerationServer* m_CrashServer; + // Do not call this without purpose :-) void CrashAppForTestPurpose(); - void RequestDump(); - protected: - void StartCrashServer(); - void StopCrashServer(); + // Writes a minidump immediately. This can be used to capture the + // execution state independently of a crash. Returns true on success. + bool RequestDump(); + // returns the number of currently connected clients + int GetNumberOfConnections(); + + protected: + bool InitializeServer(); + void StopCrashServer(); }; } // namespace mitk #endif /* _H_HEADER_INCLUDED_ */ diff --git a/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp b/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp new file mode 100644 index 0000000000..eb90715dd7 --- /dev/null +++ b/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp @@ -0,0 +1,135 @@ +/*=================================================================== + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center, +Division of Medical and Biological Informatics. +All rights reserved. + +This software is distributed WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. + +See LICENSE.txt or http://www.mitk.org for details. + +===================================================================*/ + +#include "mitkBreakpadCrashReporting.h" + +#include +#include "mitkLogMacros.h" + +#include +#include +#include +#include +#include +#include + +// Simple server process for out-of-process crash reporting. By default this server will log to the executables directory, or +// to the crash dump path if provided properly. +// Arguments: +// NamedPipeName: Quotated string of the named pipe to communicate with the client. +// FolderForCrashDumps: Quotated string of the folder to save minidump files in the event of a crash. + +static mitk::BreakpadCrashReporting* myBreakpad; + +/*class Timer : public QTimer { + Q_OBJECT +public: + explicit Timer(QObject *parent = 0) : QTimer(parent) { + connect(this, SIGNAL(timeout()), this, SLOT(CheckForServerShutdown())); + } +private slots: + void CheckForServerShutdown() + { + MITK_INFO << "mitk Crash Reporting Server check for server shutdown."; + MITK_INFO << "number of active connections: " << QString(myBreakpad->GetNumberOfConnections()).toStdString().c_str(); + } +}; + +#include "main.moc"*/ + +int main(int argc, char* argv[]) +{ + QApplication qtapplication( argc, argv ); + + QString folderForCrashDumps =""; + QString namedPipeName = ""; + + if (argc != 3) + { + MITK_WARN << "mitk Crash Reporting Server using default arguments"; + } + else + { + namedPipeName = argv[1]; + folderForCrashDumps = argv[2]; + namedPipeName.remove('"'); + folderForCrashDumps.remove('"'); + } + try + { + // set up logging to file + mitk::LoggingBackend::Register(); + QString logfile; + if(folderForCrashDumps.isEmpty()) + logfile = QCoreApplication::applicationDirPath().append("/"); + else + logfile = folderForCrashDumps.append("/"); + + QDateTime date(QDateTime::currentDateTime()); + QString dateString(date.toString("yyyy-MM-dd_hh-mm-ss")); + logfile.append(dateString); + logfile.append("-CrashReportingServer-"); + + QString pidString( QString::number(QCoreApplication::applicationPid()) ); + logfile.append(pidString); + QString logfile2 = logfile + QString(".log"); + + MITK_INFO << "** Logging to " << logfile2.toStdString() << std::endl; + mitk::LoggingBackend::SetLogFile( logfile2.toLocal8Bit().constData() ); + + // init breakpad server + myBreakpad = new mitk::BreakpadCrashReporting(); + + if(!namedPipeName.isEmpty()) + { + MITK_INFO << "Using arg[1] as named pipe name"; + myBreakpad->SetNamedPipeName(namedPipeName); + } + if(!folderForCrashDumps.isEmpty()) + { + MITK_INFO << "Using arg[2] as crash dump path"; + myBreakpad->SetCrashDumpPath(folderForCrashDumps); + } + + MITK_INFO << "NamedPipeName: " << myBreakpad->m_NamedPipeString.toStdString().c_str() << "\n"; + MITK_INFO << "FolderForCrashDumps: " << myBreakpad->m_CrashDumpPath.toStdString().c_str() << "\n"; + + if(myBreakpad->StartCrashServer(false)) // false = we are already in a separate process. + { + MITK_INFO << "mitk Crash Reporting Server successfully started."; + } + else + { + MITK_WARN << "Error during start of mitk Crash Reporting Server. Shutting down."; + exit(2); // a server might be already running. + } + + //Timer* shutdownTimer = new Timer(); + // shutdownTimer->start(3000); + + qtapplication.exec(); + } + catch(...) + { + MITK_WARN << "mitk Crash Reporting Server exception caught, shutdown."; + exit(2); + } + + + + MITK_INFO << "mitk Crash Reporting Server shuting down."; + +}