diff --git a/Modules/BreakpadCrashReporting/CMakeLists.txt b/Modules/BreakpadCrashReporting/CMakeLists.txt index 1c3f0dfdd9..f53321d397 100644 --- a/Modules/BreakpadCrashReporting/CMakeLists.txt +++ b/Modules/BreakpadCrashReporting/CMakeLists.txt @@ -1,161 +1,161 @@ # TODOs # LATER nicer separation of Linux/Window/.. code -# - test should check existence of dump file +# OK test should check existence of dump file # - find nice script to use linux symbol writer tool dump_.. on all relevant libraries # - update documentation # OK check buildtype (O2/-g/..) in cmake # - use the same library structure in "our" cmake script as google uses in their build # - otherwise we cannot switch between custom-built versions of breakpad and our superbuild version function(checkWindowsCompilerFlags) # TODO simplify this with foreach and some string functions set (WINDOWS_CXX_FLAGS_OK_ZI 0) if ( CMAKE_CXX_FLAGS_DEBUG MATCHES .*/Zi.* ) set(WINDOWS_CXX_FLAGS_OK_ZI 1) endif() if ( CMAKE_CXX_FLAGS_RELEASE MATCHES .*/Zi.* ) set(WINDOWS_CXX_FLAGS_OK_ZI 1) endif() if ( CMAKE_CXX_FLAGS MATCHES .*/Zi.* ) set(WINDOWS_CXX_FLAGS_OK_ZI 1) endif() set (WINDOWS_C_FLAGS_OK_ZI 0) if ( CMAKE_C_FLAGS_DEBUG MATCHES .*/Zi.* ) set(WINDOWS_C_FLAGS_OK_ZI 1) endif() if ( CMAKE_C_FLAGS_RELEASE MATCHES .*/Zi.* ) set(WINDOWS_C_FLAGS_OK_ZI 1) endif() if ( CMAKE_C_FLAGS MATCHES .*/Zi.* ) set(WINDOWS_C_FLAGS_OK_ZI 1) endif() set (WINDOWS_CXX_FLAGS_OK_DEBUG 0) if ( CMAKE_CXX_FLAGS_DEBUG MATCHES .*/debug.* ) set(WINDOWS_CXX_FLAGS_OK_DEBUG 1) endif() if ( CMAKE_CXX_FLAGS_RELEASE MATCHES .*/debug.* ) set(WINDOWS_CXX_FLAGS_OK_DEBUG 1) endif() if ( CMAKE_CXX_FLAGS MATCHES .*/debug.* ) set(WINDOWS_CXX_FLAGS_OK_DEBUG 1) endif() set (WINDOWS_C_FLAGS_OK_DEBUG 0) if ( CMAKE_C_FLAGS_DEBUG MATCHES .*/debug.* ) set(WINDOWS_C_FLAGS_OK_DEBUG 1) endif() if ( CMAKE_C_FLAGS_RELEASE MATCHES .*/debug.* ) set(WINDOWS_C_FLAGS_OK_DEBUG 1) endif() if ( CMAKE_C_FLAGS MATCHES .*/debug.* ) set(WINDOWS_C_FLAGS_OK_DEBUG 1) endif() if (NOT WINDOWS_CXX_FLAGS_OK_ZI) message(WARNING "When using the breakpad crash reporting module, you should use the /Zi flag in CMAKE_CXX_FLAGS (_RELEASE)") endif() if (NOT WINDOWS_C_FLAGS_OK_ZI) message(WARNING "When using the breakpad crash reporting module, you should use the /Zi flag in CMAKE_C_FLAGS (_RELEASE)") endif() if (NOT WINDOWS_CXX_FLAGS_OK_DEBUG) message(WARNING "When using the breakpad crash reporting module, you should use the /debug flag in CMAKE_CXX_FLAGS (_RELEASE)") endif() if (NOT WINDOWS_C_FLAGS_OK_DEBUG) message(WARNING "When using the breakpad crash reporting module, you should use the /debug flag in CMAKE_C_FLAGS (_RELEASE)") endif() endfunction() function(checkLinuxCompilerFlags) # TODO simplify this with foreach and some string functions set (LINUX_CXX_FLAGS_OK_G 0) if ( CMAKE_CXX_FLAGS_DEBUG MATCHES .*-g.* ) set(LINUX_CXX_FLAGS_OK_G 1) endif() if ( CMAKE_CXX_FLAGS_RELEASE MATCHES .*-g.* ) set(LINUX_CXX_FLAGS_OK_G 1) endif() if ( CMAKE_CXX_FLAGS MATCHES .*-g.* ) set(LINUX_CXX_FLAGS_OK_G 1) endif() set (LINUX_C_FLAGS_OK_G 0) if ( CMAKE_C_FLAGS_DEBUG MATCHES .*-g.* ) set(LINUX_C_FLAGS_OK_G 1) endif() if ( CMAKE_C_FLAGS_RELEASE MATCHES .*-g.* ) set(LINUX_C_FLAGS_OK_G 1) endif() if ( CMAKE_C_FLAGS MATCHES .*-g.* ) set(LINUX_C_FLAGS_OK_G 1) endif() if (NOT LINUX_CXX_FLAGS_OK_G) message(WARNING "When using the breakpad crash reporting module, you should use the -g flag in CMAKE_CXX_FLAGS (_RELEASE)") endif() if (NOT LINUX_C_FLAGS_OK_G) message(WARNING "When using the breakpad crash reporting module, you should use the -g flag in CMAKE_C_FLAGS (_RELEASE)") endif() endfunction() if(MITK_USE_BREAKPAD) message(STATUS "Get directory hint for Breakpad:") message(STATUS ${Breakpad_DIR}) message(STATUS ${Breakpad_SRC}) # TODO this should be properly moved to some BreakpadConfig.cmake find_path(MITK_BREAKPAD_SRC_DIR breakpad_googletest_includes.h DOC "Directory breakpad/src/" PATHS ${Breakpad_SRC}/src ${Breakpad_DIR}) message(STATUS "Found Breakpad at:") message(STATUS ${MITK_BREAKPAD_SRC_DIR}) if(CMAKE_SYSTEM MATCHES "Windows") checkWindowsCompilerFlags() set(INCLUDE_DIRS_INTERNAL ${INCLUDE_DIRS_INTERNAL} ${MITK_BREAKPAD_SRC_DIR}) set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${Breakpad_DIR}/Debug/breakpad_client.lib # ${Breakpad_DIR}/Release/common.lib # ?? # ${Breakpad_DIR}/Release/crash_generation_client.lib # ??? # ${Breakpad_DIR}/Release/crash_generation_server.lib # out-of-process dump generator # ${Breakpad_DIR}/Release/exception_handler.lib # dump creator (ExceptionHandler) # ${Breakpad_DIR}/Release/crash_report_sender.lib ) # ??? unused, I guess set(EXECUTABLE_PROPERTY WIN32) elseif(CMAKE_SYSTEM MATCHES "Linux") checkLinuxCompilerFlags() set(INCLUDE_DIRS_INTERNAL ${INCLUDE_DIRS_INTERNAL} ${MITK_BREAKPAD_SRC_DIR}) # TODO why the repetition, is INCLUDE_DIRS_INTERNAL not empty before?? set(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} ${Breakpad_DIR}/libbreakpad_client.a ) else() message(FATAL_ERROR "Unsupported platform for Breakpad crash reporting: ${CMAKE_SYSTEM}") endif() message(STATUS "Building BreakpadCrashReporting module.") message(STATUS " Including ${INCLUDE_DIRS_INTERNAL}") message(STATUS " Linking ${ADDITIONAL_LIBS}") 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(CMAKE_SYSTEM MATCHES "Windows") add_executable(CrashReportingServer ${EXECUTABLE_PROPERTY} mitkCrashReportingServer.cpp ${SERVER_MOC_CPP}) target_link_libraries(CrashReportingServer ${ALL_LIBRARIES} ${QT_QTMAIN_LIBRARY} BreakpadCrashReporting) endif() add_subdirectory(Testing) endif(MITK_USE_BREAKPAD) diff --git a/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt b/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt index 153cd81e2e..de2fdbfb2d 100644 --- a/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt +++ b/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt @@ -1 +1,4 @@ MITK_CREATE_MODULE_TESTS() + +set_property(TEST mitkBreakpadCrashReportingDumpTest PROPERTY WILL_FAIL TRUE) # this one SHOULD crash +set_property(TEST mitkBreakpadCrashReportingDumpCheckTest PROPERTY DEPENDS mitkBreakpadCrashReportingDumpTest) # this one checks if a dump was created diff --git a/Modules/BreakpadCrashReporting/Testing/files.cmake b/Modules/BreakpadCrashReporting/Testing/files.cmake index 1335fd0458..28c9935fb5 100644 --- a/Modules/BreakpadCrashReporting/Testing/files.cmake +++ b/Modules/BreakpadCrashReporting/Testing/files.cmake @@ -1,3 +1,4 @@ set(MODULE_TESTS mitkBreakpadCrashReportingDumpTest.cpp + mitkBreakpadCrashReportingDumpCheckTest.cpp ) diff --git a/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpCheckTest.cpp b/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpCheckTest.cpp new file mode 100644 index 0000000000..9a8beed6b7 --- /dev/null +++ b/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpCheckTest.cpp @@ -0,0 +1,80 @@ +/*=================================================================== + +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 "mitkTestingMacros.h" + +#include +#include +#include + +/** + \brief Checks result of crash test mitkBreakpadCrashReportingDumpTest. + + Checks for the most recent directory that would have been created by + a crashing test mitkBreakpadCrashReportingDumpTest (identified by common + string in both tests). + + The directory must contain exactly one file, which is expected to be + the cash dump. +*/ +int mitkBreakpadCrashReportingDumpCheckTest(int, char** const) +{ + // always start with this! + MITK_TEST_BEGIN("mitkBreakpadCrashReportingDumpCheckTest"); + +#ifdef WIN32 + // Waiting a bit to let the crash reporting server do its work + // This is not neccessary on Linux because we run the SERVER as + // the unittest, so when it is done, the crash dump must exist. + // (On Windows, we run the client and this in turn starts a server) + QTime dieTime = QTime::currentTime().addSecs(3); + while( QTime::currentTime() < dieTime ) + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); +#endif + + QString dirnamePattern = QString("mitkBreakpadCrashReportingDumpTest-*"); + + QDir tempDir = QDir::temp(); + QStringList nameFilters; + nameFilters << dirnamePattern; + tempDir.setNameFilters( nameFilters ); + tempDir.setSorting( QDir::Time ); + + QStringList dumpFolders = tempDir.entryList(); + + MITK_TEST_CONDITION_REQUIRED( !dumpFolders.empty(), "Found at least one folder matching mitkBreakpadCrashReportingDumpTest-*") + + QString foldernameToCheck = dumpFolders.first(); // most recent + + QDir folderToCheck( QDir::tempPath() + QDir::separator() + foldernameToCheck ); + MITK_TEST_CONDITION( folderToCheck.exists(), qPrintable(foldernameToCheck) << " exists") + QStringList dumpFiles = folderToCheck.entryList( QDir::Files ); + MITK_TEST_CONDITION( dumpFiles.size() == 1, qPrintable(foldernameToCheck) << " has one entry (the DUMP)") + + MITK_TEST_OUTPUT( << "----------------------------------------" ) + MITK_TEST_OUTPUT( << "Files in " << qPrintable(folderToCheck.path()) ) + foreach(QString f, dumpFiles) + { + MITK_TEST_OUTPUT( << " .. entry " << qPrintable(f) ) + folderToCheck.remove(f); + } + MITK_TEST_OUTPUT( << "--- End of file list--------------------" ) + + MITK_TEST_CONDITION( tempDir.rmdir( foldernameToCheck ), "Clean up created folders, i.e. remove " << qPrintable(foldernameToCheck) ) + + // always end with this! + MITK_TEST_END() +} diff --git a/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTest.cpp b/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTest.cpp index b7cdcda200..794e4e5308 100644 --- a/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTest.cpp +++ b/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTest.cpp @@ -1,47 +1,76 @@ /*=================================================================== 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 "mitkTestingMacros.h" #include "mitkBreakpadCrashReporting.h" #include +#include +#include +QString CreateEmptyTestFolder() +{ + QString dirname = QString("mitkBreakpadCrashReportingDumpTest-%1").arg( + QDateTime::currentDateTime().toString( Qt::ISODate ).remove(':').remove('-').remove('+') ); + + if ( QDir::temp().mkdir( dirname ) ) + { + return QDir::tempPath() + QDir::separator() + dirname; + } + else + { + return QString::null; + } +} + +/** + \brief Start crash reporting and crash (expectedly). + + This method is excpected to setup BreakpadCrashReporting, + then provoke a crash, thus creating a crash dump in a configured + folder. + + CMake is configured to expect failure of this test. + In addition we check the actual existence of a crash dump + in mitkBreakpadCrashReportingDumpCheckTest. +*/ int mitkBreakpadCrashReportingDumpTest(int argc, char** const argv) { // always start with this! MITK_TEST_BEGIN("mitkBreakpadCrashReportingDumpTest") QCoreApplication qtApplication(argc,argv); - mitk::BreakpadCrashReporting crashReporting; + QString emptyTempFolder = CreateEmptyTestFolder(); + + MITK_TEST_OUTPUT( << "Dumping files to " << qPrintable( emptyTempFolder ) ); + mitk::BreakpadCrashReporting crashReporting(emptyTempFolder); // start out-of-process crash dump server MITK_TEST_CONDITION_REQUIRED( crashReporting.StartCrashServer(true) == true, "Start out-of-process crash reporting server"); // in-process reporting client (minimal code to tell other process to dump information) crashReporting.InitializeClientHandler(true); MITK_TEST_CONDITION_REQUIRED( true, "Start crash reporting client (in crashing process)"); // provoke a seg-fault to make test crash crashReporting.CrashAppForTestPurpose(); - MITK_TEST_CONDITION_REQUIRED( false, "Test failed, did not crash...)"); - // always end with this! MITK_TEST_END() } diff --git a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp index 239ea7e6db..2c6f0c6d7d 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp @@ -1,434 +1,438 @@ /*=================================================================== 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() +mitk::BreakpadCrashReporting::BreakpadCrashReporting( const QString& dumpPath ) : m_CrashServer(NULL) , m_ExceptionHandler(NULL) -, m_CrashDumpPath( QDir(QCoreApplication::instance()->applicationDirPath()).absolutePath() + "/CrashDumps" ) +, m_CrashDumpPath( dumpPath ) // 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) { + if ( m_CrashDumpPath.isEmpty() ) + { + m_CrashDumpPath = QDir(QCoreApplication::instance()->applicationDirPath()).absolutePath() + "/CrashDumps"; + } } mitk::BreakpadCrashReporting::~BreakpadCrashReporting() { if (m_ExceptionHandler) { delete m_ExceptionHandler; } if (m_CrashServer) { delete m_CrashServer; } } #ifdef WIN32 //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; } #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::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 0ce35a6f5a..b450f62cdc 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h @@ -1,163 +1,162 @@ /*=================================================================== 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( const QString& dumpPath = QString::null ); ~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); // External out-of-process (OOP) Crash Reporting Server file path - if OOP is used. void SetCrashReportingServerExecutable(QString exe); bool DumpCallbackPlatformIndependent(); 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_ */