diff --git a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp index 140a27db56..5513976e32 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp @@ -1,461 +1,449 @@ /*=================================================================== 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" #ifdef 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 Q_OS_MAC #include #include #include #elif __gnu_linux__ #include #include #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 #ifdef WIN32 static int numberOfConnectionAttemptsPerformed = 1; // number of performed re-connect attempts of a crash client #endif mitk::BreakpadCrashReporting::BreakpadCrashReporting() -:server_fd(-1) -,client_fd(-1) +: m_CrashServer(NULL) +, m_ExceptionHandler(NULL) +, m_CrashDumpPath( QDir(QCoreApplication::instance()->applicationDirPath()).absolutePath() + "/CrashDumps" ) + // Linux connection parameters +, server_fd(-1) +, client_fd(-1) + // Windows connection parameters +, m_NamedPipeString("\\\\.\\pipe\\MitkCrashServices\\MitkBasedApplication") +, m_CrashReportingServerExecutable( QDir(QCoreApplication::instance()->applicationDirPath()).absolutePath().append("/CrashReportingServer.exe") ) +, m_NumberOfConnectionAttempts(3) +, m_ReconnectDelay(300) { - m_CrashServer = NULL; - m_ExceptionHandler = NULL; - - m_NamedPipeString = "\\\\.\\pipe\\MitkCrashServices\\MitkBasedApplication"; - m_CrashDumpPath = QDir(QApplication::instance()->applicationDirPath()).absolutePath(); - m_CrashDumpPath.append("/CrashDumps/"); // is created if it does not exist - - m_NumberOfConnectionAttempts = 3; - m_ReconnectDelay = 300; - // TODO platform specific -#ifdef WIN32 - m_CrashReportingServerExecutable = QDir(QApplication::instance()->applicationDirPath()).absolutePath().append("/CrashReportingServer.exe"); -#else - m_CrashReportingServerExecutable = QDir(QApplication::instance()->applicationDirPath()).absolutePath().append("/CrashReportingServer"); -#endif } mitk::BreakpadCrashReporting::~BreakpadCrashReporting() { if (m_ExceptionHandler) { delete m_ExceptionHandler; } if (m_CrashServer) { delete m_CrashServer; } } #ifdef WIN32 // TODO platform specific //This function gets called in the event of a crash. bool BreakpadCrashReportingDumpCallbackWindows(const wchar_t* dump_path, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool 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; } #elif __gnu_linux__ bool BreakpadCrashReportingDumpCallbackLinux( const google_breakpad::MinidumpDescriptor& /*descriptor*/, void* /*context*/, bool succeeded) { return succeeded; } #endif bool mitk::BreakpadCrashReporting::DumpCallbackPlatformIndependent() { return true; } void mitk::BreakpadCrashReporting::InitializeClientHandler(bool connectToCrashGenerationServer) { #ifdef WIN32 // 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::string dump_path = m_CrashDumpPath.toStdString(); #endif #ifdef WIN32 /* 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* 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: "; } m_ExceptionHandler = new google_breakpad::ExceptionHandler( dump_path, NULL, BreakpadCrashReportingDumpCallbackWindows, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL, MiniDumpNormal, //see DbgHelp.h pipe, NULL); // custom client info (unused) 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."; } } } #elif __gnu_linux__ google_breakpad::MinidumpDescriptor dumpDescriptor( dump_path ); if (client_fd == -1) { MITK_WARN << "In-process crash dump handling, the unsafer method"; } m_ExceptionHandler = new google_breakpad::ExceptionHandler( dumpDescriptor, // descriptor (where to dump) NULL, // filter (we don't filter) BreakpadCrashReportingDumpCallbackLinux, // our callback in cases of crashes NULL, // callback_context (no idea.. custom data probably) true, // install_handler (yes, write dumps with each crash, not only on request) client_fd ); // should be initialized in StopCrashServer() by ealier call #endif } #ifdef WIN32 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++; } #endif #ifdef WIN32 static void _cdecl ShowClientCrashed(void* /*context*/, const google_breakpad::ClientInfo* client_info, const std::wstring* /*dump_path*/) #elif __gnu_linux__ static void ShowClientCrashed(void* context, const google_breakpad::ClientInfo* /*client_info*/, const std::string* /*dump_path*/) #endif { // callback of the crash generation server on client crash #ifdef WIN32 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(); #else MITK_INFO << "Breakpad Client request dump: TODO proc-info"; #endif } static void #ifdef WIN32 _cdecl #endif ShowClientExited(void* /*context*/, const google_breakpad::ClientInfo* client_info) { // callback of the crash generation server on client exit #ifdef WIN32 MITK_INFO << "Breakpad Client exited :" << client_info->pid(); #else MITK_INFO << "Breakpad Client exited : TODO proc-info"; #endif // 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); } } bool mitk::BreakpadCrashReporting::StartCrashServer(bool lauchOutOfProcessExecutable) { if (m_CrashServer) { // Do not create another instance of the server. MITK_INFO << "Crash Server object already generated."; return true; } /* Idea here: - application-under-observation starts out-of-process dump generation executable - dump generation executable creates google CrashGenerationServer - requires fd to listen to DONC - CreateReportChannel - fork - in parent: InitializeServer, exit with return value of qtapplication.exec(), i.e. run forever. TODO stop with crashed child - in parent: continue */ #ifdef __gnu_linux__ google_breakpad::CrashGenerationServer::CreateReportChannel(&server_fd, &client_fd); // both OUT parameters pid_t child_pid = fork(); if ( child_pid != 0) { // server process InitializeServer(server_fd); if (qApp) { MITK_INFO << "Wait for observed breakpad child to finish/crash..."; int status; waitpid( child_pid, &status, WEXITED ); MITK_INFO << "Breakpad child terminated, so I also terminate..."; exit(EXIT_SUCCESS); } else { MITK_ERROR << "You MUST initialize the qApp instance before calling StartCrashServer. You did not. Exiting..."; exit(EXIT_FAILURE); } } else { // child process return true; // assume we are fine since we got here.. } #endif 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( int listen_fd ) { QDir myDir; myDir.mkpath(m_CrashDumpPath); // Assure directory is created. google_breakpad::CrashGenerationServer::OnClientDumpRequestCallback dump_callback = &ShowClientCrashed; #ifdef WIN32 google_breakpad::CrashGenerationServer::OnClientExitedCallback exit_callback = &ShowClientExited; // this... #elif __gnu_linux__ google_breakpad::CrashGenerationServer::OnClientExitingCallback exit_callback = &ShowClientExited; // and that.. tell much about cross-platform.. #endif void* dump_context = NULL; void* exit_context = NULL; #ifdef WIN32 // 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()); std::wstring pipe_name = std::wstring((const wchar_t *)m_NamedPipeString.utf16()); m_CrashServer = new google_breakpad::CrashGenerationServer(pipe_name, NULL, ShowClientConnected, // connect callback NULL, dump_callback, dump_context, exit_callback, // exit callback exit_context, NULL, NULL, true, &dump_path); #elif __gnu_linux__ std::string dump_path = m_CrashDumpPath.toStdString(); MITK_INFO << "Start Breakpad crash dump generation server with file descriptor " << listen_fd; m_CrashServer = new google_breakpad::CrashGenerationServer(listen_fd, dump_callback, dump_context, exit_callback, exit_context, true, // generate_dumps &dump_path); #endif if (!m_CrashServer->Start()) { MITK_ERROR << "Unable to start Breakpad crash dump generation server."; delete m_CrashServer; m_CrashServer = NULL; return false; } else { MITK_INFO << "Breakpad crash dump generation server started."; return true; } return false; } 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() { int* x = 0; *x = 1; } int mitk::BreakpadCrashReporting::GetNumberOfConnections() const { return breakpadNumberOfConnections; } void mitk::BreakpadCrashReporting::SetNamedPipeName(const QString& name) { m_NamedPipeString = name; } QString mitk::BreakpadCrashReporting::GetNamedPipeName() const { return m_NamedPipeString; } void mitk::BreakpadCrashReporting::SetCrashDumpPath(const QString& path) { m_CrashDumpPath = path; } QString mitk::BreakpadCrashReporting::GetCrashDumpPath() const { return m_CrashDumpPath; } void mitk::BreakpadCrashReporting::SetCrashReportingServerExecutable(QString exe) { m_CrashReportingServerExecutable = exe; } void mitk::BreakpadCrashReporting::SetNumberOfConnectionAttempts(int no) { m_NumberOfConnectionAttempts = no; } void mitk::BreakpadCrashReporting::SetReconnectDelayInMilliSeconds(int ms) { m_ReconnectDelay = ms; } \ No newline at end of file diff --git a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h index b5e6bf932b..0ce35a6f5a 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h @@ -1,161 +1,163 @@ /*=================================================================== 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 namespace google_breakpad { class ExceptionHandler; class CrashGenerationServer; } namespace 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(/*TODO add path here*/); ~BreakpadCrashReporting(); /** 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. void SetNamedPipeName(const QString& name); QString GetNamedPipeName() const; // Directory path to save crash dumps. void SetCrashDumpPath(const QString& path); QString GetCrashDumpPath() const; // Re-connect handling in case a crash server cannot be reached. void SetNumberOfConnectionAttempts(int no); void SetReconnectDelayInMilliSeconds(int ms); // Do not call this without purpose :-) void CrashAppForTestPurpose(); // 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() const; protected: bool InitializeServer(int listen_fd = -1); - void StopCrashServer(); // External out-of-process (OOP) Crash Reporting Server file path - if OOP is used. void SetCrashReportingServerExecutable(QString exe); bool DumpCallbackPlatformIndependent(); - QString m_NamedPipeString; - QString m_CrashDumpPath; - QString m_CrashReportingServerExecutable; - int m_NumberOfConnectionAttempts; - int m_ReconnectDelay; - google_breakpad::ExceptionHandler* m_ExceptionHandler; google_breakpad::CrashGenerationServer* m_CrashServer; + google_breakpad::ExceptionHandler* m_ExceptionHandler; + QString m_CrashDumpPath; + // Linux connection parameters int server_fd; int client_fd; + // Windows connection parameters + QString m_NamedPipeString; + QString m_CrashReportingServerExecutable; + int m_NumberOfConnectionAttempts; + int m_ReconnectDelay; + }; } // namespace mitk #endif /* _H_HEADER_INCLUDED_ */