diff --git a/Modules/BreakpadCrashReporting/CMakeLists.txt b/Modules/BreakpadCrashReporting/CMakeLists.txt index 3acecde186..100a53c992 100644 --- a/Modules/BreakpadCrashReporting/CMakeLists.txt +++ b/Modules/BreakpadCrashReporting/CMakeLists.txt @@ -1,36 +1,34 @@ # TODOs # LATER nicer separation of Linux/Window/.. code # 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 # OK use the same library structure in "our" cmake script as google uses in their build # OK otherwise we cannot switch between custom-built versions of breakpad and our superbuild version # [optional] add install step to our Breakpad build if(MITK_USE_Breakpad) # from top-level CMakeLists.txt find_package(Breakpad) if (NOT Breakpad_FOUND) message(FATAL_ERROR "MITK_USE_Breakpad was set but Breakpad build cannot be found. Plaese check CMake cache variables regarding Breakpad") endif() MITK_CREATE_MODULE(BreakpadCrashReporting INTERNAL_INCLUDE_DIRS ${Breakpad_INCLUDE_DIR} ADDITIONAL_LIBS ${Breakpad_LIBRARIES} + PACKAGE_DEPENDS QT FORCE_STATIC ) if(CMAKE_SYSTEM MATCHES "Windows") - add_executable(CrashReportingServer mitkCrashReportingServer.cpp) - target_link_libraries(CrashReportingServer ${ALL_LIBRARIES} BreakpadCrashReporting) - MITK_INSTALL(TARGETS CrashReportingServer) + add_executable(CrashReportingServer WIN32 mitkCrashReportingServer.cpp) + # TODO MITK_INSTALL(CrashReportingServer ..) + target_link_libraries(CrashReportingServer ${ALL_LIBRARIES} ${QT_QTMAIN_LIBRARY} BreakpadCrashReporting) endif() - if(CMAKE_SYSTEM MATCHES "Linux") - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ProduceBreakpadSymbols.sh.in ${MITK_CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ProduceBreakpadSymbols.sh) - endif() -add_subdirectory(Testing) +# add_subdirectory(Testing) endif(MITK_USE_Breakpad) diff --git a/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt b/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt index 42a24eb824..34ebc06565 100644 --- a/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt +++ b/Modules/BreakpadCrashReporting/Testing/CMakeLists.txt @@ -1,13 +1,12 @@ if(BUILD_TESTING) # currently breakpad testing is only available for windows # ToDo: currently a problem can occure during crash dump generation on linux. please have a look at the documentation of mitkBreakpadCrashReportingDumpTest #set_tests_properties(mitkBreakpadCrashReportingDumpTest PROPERTIES TIMEOUT 5) # the test should not need 5 seconds if(CMAKE_SYSTEM MATCHES "Windows") MITK_CREATE_MODULE_TESTS() - add_executable(BreakpadCrashReportingDumpTestApplication mitkBreakpadCrashReportingDumpTestApplication.cpp) - target_link_libraries(BreakpadCrashReportingDumpTestApplication ${ALL_LIBRARIES} BreakpadCrashReporting) -endif() + 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 endif() 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 656e352f52..e8ae6e542b 100644 --- a/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTest.cpp +++ b/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTest.cpp @@ -1,156 +1,86 @@ /*=================================================================== 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 -#include -#include -#include -#include -#include -#include -#include - -std::string CreateEmptyTestFolder() -{ - std::string modulePath = mitk::BreakpadCrashReporting::GetModulePath(); // get path of executable - char dateTime[15]; - time_t now = time(0); - tm* time = localtime( &now ); - strftime( dateTime, 15, "%Y%m%d%H%M%S", time ); +#include +#include +#include - std::string dirName = modulePath + "/breakpadtestdump/" + "mitkBreakpadCrashReportingDumpTest-" + dateTime; +QString CreateEmptyTestFolder() +{ + QString dirname = QString("mitkBreakpadCrashReportingDumpTest-%1").arg( + QDateTime::currentDateTime().toString( Qt::ISODate ).remove(':').remove('-').remove('+') ); - if( itksys::SystemTools::MakeDirectory(dirName.c_str()) ) + if ( QDir::temp().mkdir( dirname ) ) + { + return QDir::tempPath() + QDir::separator() + dirname; + } + else { - return dirName; + return QString::null; } - return ""; -}; +} /** \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. Then we check the actual existence of the crash dump and if it has any content. If the dump generation was not successful the dump file will be empty or not existent. In order to get the output of ctest correctly we provoke the crash in a different process, else the test itself would crash. Linux: The test is currently deactivated for Linux. ToDo: Currently there seems to be a problem in linux with crash dump generation. In this test we provoke a segfault. Sometimes the crash dump generation gets in a state where it waits for the child process (the process which crashed) to change the process state, but the child process is allready attach via ptrace to the parent process and therefore has allready the state stopped and does not change until it gets a signal from the parent process. The parent process itself waits for the child process to change the state. */ int mitkBreakpadCrashReportingDumpTest(int argc, char** const argv) { // always start with this! MITK_TEST_BEGIN("mitkBreakpadCrashReportingDumpTest") - std::string emptyTempFolder = CreateEmptyTestFolder(); + QCoreApplication qtApplication(argc,argv); + + QString emptyTempFolder = CreateEmptyTestFolder(); - MITK_TEST_OUTPUT( << "Dumping files to " << emptyTempFolder ); -#ifdef WIN32 - mitk::BreakpadCrashReporting crashReporting( emptyTempFolder ); + 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 -> call external application which will crash - std::string commandline = mitk::BreakpadCrashReporting::GetModulePath() + "/BreakpadCrashReportingDumpTestApplication" + " " + emptyTempFolder; - system( commandline.c_str() ); -#elif __gnu_linux__ - pid_t child_pid = fork(); - - if ( child_pid != 0) - { - waitpid(child_pid, NULL, __WALL); - /* - if( waitpid(child_pid, NULL, __WALL) != child_pid ) { - // the child process does not complete within 30 seconds - kill(child_pid, SIGTERM); - } - */ - } - else - { - //alarm(5); - mitk::BreakpadCrashReporting crashReporting( emptyTempFolder ); - - // start out-of-process crash dump server - crashReporting.StartCrashServer(true); - - // in-process reporting client (minimal code to tell other process to dump information) - crashReporting.InitializeClientHandler(true); - - // provoke a seg-fault to make test crash - crashReporting.CrashAppForTestPurpose(); - } -#endif - - // check dump creation -#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) - Sleep( 3000 ); -#endif - itk::Directory::Pointer crashDumpDirectory = itk::Directory::New(); - MITK_TEST_CONDITION_REQUIRED( crashDumpDirectory->Load( emptyTempFolder.c_str() ), "Crash dump folder exists." ); - - // since the folder is only used for this testing purposes, there should only be crash dump folders here - // so we only have to check if the crash dump directory contains anything - MITK_TEST_CONDITION_REQUIRED( crashDumpDirectory->GetNumberOfFiles() > 0, "Found at least one file in crash dump directory."); - bool dmpFileFound = false; - for ( unsigned int i = 0; i < crashDumpDirectory->GetNumberOfFiles(); ++i ) - { - std::string filename = emptyTempFolder + "/" + crashDumpDirectory->GetFile( i ); - - std::string extension = itksys::SystemTools::GetFilenameExtension(filename.c_str()); - - if(extension.compare(".dmp")==0) - { - dmpFileFound = true; - std::ifstream in(filename.c_str(), std::ifstream::in | std::ifstream::binary); - in.seekg(0, std::ifstream::end); - MITK_TEST_CONDITION( in.tellg() > 0, "The dump file is not empty." ); - } - remove( filename.c_str() ); - } - MITK_TEST_CONDITION( dmpFileFound, "Dump file was created." ); - rmdir( emptyTempFolder.c_str() ); + // provoke a seg-fault to make test crash + crashReporting.CrashAppForTestPurpose(); // always end with this! MITK_TEST_END() } diff --git a/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTestApplication.cpp b/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTestApplication.cpp deleted file mode 100644 index 34c7812d6c..0000000000 --- a/Modules/BreakpadCrashReporting/Testing/mitkBreakpadCrashReportingDumpTestApplication.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/*=================================================================== - -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 - -int main(int argc, char* argv[]) -{ - std::string folderForCrashDumps =""; - - if (argc != 2) - { - MITK_ERROR << "Exactly 2 argument expected, got " << argc << " instead!"; - exit(2); - } - else - { - folderForCrashDumps = argv[1]; - } - - mitk::BreakpadCrashReporting crashReporting( folderForCrashDumps ); - - // start out-of-process crash dump server - crashReporting.StartCrashServer(true); - - // in-process reporting client (minimal code to tell other process to dump information) - crashReporting.InitializeClientHandler(true); - - // provoke a seg-fault to make test crash - crashReporting.CrashAppForTestPurpose(); -} diff --git a/Modules/BreakpadCrashReporting/files.cmake b/Modules/BreakpadCrashReporting/files.cmake index f3c9800d44..de72da8274 100644 --- a/Modules/BreakpadCrashReporting/files.cmake +++ b/Modules/BreakpadCrashReporting/files.cmake @@ -1,4 +1,4 @@ set(CPP_FILES mitkBreakpadCrashReporting.cpp -# mitkCrashReportingServer.cpp + mitkCrashReportingServer.cpp ) diff --git a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp index 9ae7698e67..9c9aef1332 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.cpp @@ -1,463 +1,446 @@ /*=================================================================== 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 WIN32 +#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 __APPLE__ +#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 -// Get application path: there is no cross-plattform standard c++ method which can get the executable path -// (other toolkits offer it, e.g. Qt). -// Currently only for Windows and Linux implemented: -std::string mitk::BreakpadCrashReporting::GetModulePath() { -#ifdef WIN32 - TCHAR path[MAX_PATH]; - if( GetModuleFileName( NULL, path, MAX_PATH ) ) - { - std::string pathString = path; - pathString.erase( pathString.find_last_of("\\") ); - return pathString; - } -#elif __gnu_linux__ - char buff[1024]; - ssize_t len = ::readlink("/proc/self/exe", buff, sizeof(buff)-1); - if (len != -1) { - buff[len] = '\0'; - std::string path = buff; - path.erase( path.find_last_of("/") ); - return path; - } else { - /* handle error condition */ - } -#endif - return ""; -} - -mitk::BreakpadCrashReporting::BreakpadCrashReporting( const std::string& dumpPath ) +mitk::BreakpadCrashReporting::BreakpadCrashReporting( const QString& dumpPath ) : m_CrashServer(NULL) , m_ExceptionHandler(NULL) , m_CrashDumpPath( dumpPath ) // Linux connection parameters , server_fd(-1) , client_fd(-1) // Windows connection parameters , m_NamedPipeString("\\\\.\\pipe\\MitkCrashServices\\MitkBasedApplication") -, m_CrashReportingServerExecutable( GetModulePath() + "/CrashReportingServer.exe" ) +, m_CrashReportingServerExecutable( QDir(QCoreApplication::instance()->applicationDirPath()).absolutePath().append("/CrashReportingServer.exe") ) , m_NumberOfConnectionAttempts(3) , m_ReconnectDelay(300) { - if ( m_CrashDumpPath.empty() ) + if ( m_CrashDumpPath.isEmpty() ) { - m_CrashDumpPath = GetModulePath() + "/CrashDumps"; // ToDo: what happens if GetModulePath returns emtpy string + 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( m_CrashDumpPath.begin(), m_CrashDumpPath.end() ); + std::wstring dump_path = std::wstring((const wchar_t *)m_CrashDumpPath.utf16()); #else - std::string dump_path = m_CrashDumpPath; + 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.c_str(); - MITK_INFO << "Initializing Breakpad Crash Handler, connecting to named pipe: " << m_NamedPipeString.c_str() << "\n"; + 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); MITK_INFO << "Wait for observed breakpad child to finish/crash..."; int status; do { pid_t w = waitpid(child_pid, &status, WUNTRACED | WCONTINUED); if (w == -1) { perror("waitpid"); exit(EXIT_FAILURE); } if (WIFEXITED(status)) { printf("exited, status=%d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("killed by signal %d\n", WTERMSIG(status)); } else if (WIFSTOPPED(status)) { printf("stopped by signal %d\n", WSTOPSIG(status)); } else if (WIFCONTINUED(status)) { printf("continued\n"); } } while (!WIFEXITED(status) && !WIFSIGNALED(status)); MITK_INFO << "Breakpad child terminated, so I also terminate..."; exit(EXIT_SUCCESS); } else { // child process return true; // assume we are fine since we got here.. } -#elif WIN32 +#endif if(lauchOutOfProcessExecutable) { // spawn process and launch CrashReportingServer executable - std::string commandline = m_CrashReportingServerExecutable + " \"" + m_NamedPipeString + "\" " + m_CrashDumpPath; - int success = system( commandline.c_str() ); - - return ( success != -1 ); + 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(); } - -#endif } bool mitk::BreakpadCrashReporting::InitializeServer( int listen_fd ) { - itksys::SystemTools::MakeDirectory(m_CrashDumpPath.c_str()); // Make sure directory is created. + 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(m_CrashDumpPath.begin(), m_CrashDumpPath.end() ); - std::wstring pipe_name( m_NamedPipeString.begin(), m_NamedPipeString.end() ); + 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; + 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 std::string& name) +void mitk::BreakpadCrashReporting::SetNamedPipeName(const QString& name) { m_NamedPipeString = name; } -std::string mitk::BreakpadCrashReporting::GetNamedPipeName() const +QString mitk::BreakpadCrashReporting::GetNamedPipeName() const { return m_NamedPipeString; } -void mitk::BreakpadCrashReporting::SetCrashDumpPath(const std::string& path) +void mitk::BreakpadCrashReporting::SetCrashDumpPath(const QString& path) { m_CrashDumpPath = path; } -std::string mitk::BreakpadCrashReporting::GetCrashDumpPath() const +QString mitk::BreakpadCrashReporting::GetCrashDumpPath() const { return m_CrashDumpPath; } -void mitk::BreakpadCrashReporting::SetCrashReportingServerExecutable(const std::string& exe) +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 7ca1437e58..1f04f4fdd5 100644 --- a/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h +++ b/Modules/BreakpadCrashReporting/mitkBreakpadCrashReporting.h @@ -1,167 +1,174 @@ /*=================================================================== 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 +#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 is already + * (either due to shutdown or due to crash). Also, the sample server will shutdown automatically, if there isalready * one server instance running. * * Note 4: LINUX * Currently there seems to be a problem on linux with crash dump generation. For example in case of a segfault, * the crash dump generation sometimes (seems to be random) gets in a state where it waits for * the child process (the process which crashed) to change the process state, but the child * process is allready attached via ptrace to the parent process (crash server) and therefore has allready the state * "stopped" and does not change until it gets a signal from the parent process. The parent process * itself waits for the child process to change the state. If this happens all processes are in a waiting state and * no crash dump will be generated and only an empty dump file exists. * This is currently only a assumption! The problem seems to lay in the google breakpad class * linux_ptrace_dumper.cc in the method SuspendThread(pid_t pid). * */ class BreakpadCrashReporting { public: - BreakpadCrashReporting( const std::string& dumpPath = "" ); + 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 "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 std::string& name); - std::string GetNamedPipeName() const; + void SetNamedPipeName(const QString& name); + QString GetNamedPipeName() const; // Directory path to save crash dumps. - void SetCrashDumpPath(const std::string& path); - std::string GetCrashDumpPath() const; + 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; - // Get the path of the breakpad module - static std::string GetModulePath(); protected: bool InitializeServer(int listen_fd = -1); // External out-of-process (OOP) Crash Reporting Server file path - if OOP is used. - void SetCrashReportingServerExecutable(const std::string& exe); + void SetCrashReportingServerExecutable(QString exe); bool DumpCallbackPlatformIndependent(); google_breakpad::CrashGenerationServer* m_CrashServer; google_breakpad::ExceptionHandler* m_ExceptionHandler; - std::string m_CrashDumpPath; + QString m_CrashDumpPath; // Linux connection parameters int server_fd; int client_fd; // Windows connection parameters - std::string m_NamedPipeString; - std::string m_CrashReportingServerExecutable; + QString m_NamedPipeString; + QString m_CrashReportingServerExecutable; int m_NumberOfConnectionAttempts; int m_ReconnectDelay; }; } // namespace mitk #endif /* _H_HEADER_INCLUDED_ */ diff --git a/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp b/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp index 9dff2da4ca..3dbdb000dd 100644 --- a/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp +++ b/Modules/BreakpadCrashReporting/mitkCrashReportingServer.cpp @@ -1,129 +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 -#ifdef WIN32 - #include -#elif __gnu_linux__ - #include -#endif +#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[]) { - std::string folderForCrashDumps =""; - std::string namedPipeName = ""; + 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("/"); - time_t now = time(0); - tm* time = localtime( &now ); - char dateTime[20]; - - strftime( dateTime, 20, "%Y-%m-%d_%H-%M-%S", time ); - if(folderForCrashDumps.empty()) - folderForCrashDumps = mitk::BreakpadCrashReporting::GetModulePath(); - - std::stringstream pid; - pid << getpid(); + QDateTime date(QDateTime::currentDateTime()); + QString dateString(date.toString("yyyy-MM-dd_hh-mm-ss")); + logfile.append(dateString); + logfile.append("-CrashReportingServer-"); - std::string logfile = folderForCrashDumps + "/" + dateTime + "-CrashReportingServer-" + pid.str() + ".log"; + QString pidString( QString::number(QCoreApplication::applicationPid()) ); + logfile.append(pidString); + QString logfile2 = logfile + QString(".log"); - MITK_INFO << "** Logging to " << logfile << std::endl; - mitk::LoggingBackend::SetLogFile( logfile.c_str() ); + MITK_INFO << "** Logging to " << logfile2.toStdString() << std::endl; + mitk::LoggingBackend::SetLogFile( logfile2.toLocal8Bit().constData() ); // init breakpad server myBreakpad = new mitk::BreakpadCrashReporting(); - if(!namedPipeName.empty()) + if(!namedPipeName.isEmpty()) { - MITK_INFO << "Using arg[1] as named pipe name" << namedPipeName; + MITK_INFO << "Using arg[1] as named pipe name"; myBreakpad->SetNamedPipeName(namedPipeName); } - if(!folderForCrashDumps.empty()) + if(!folderForCrashDumps.isEmpty()) { - MITK_INFO << "Using arg[2] as crash dump path" << folderForCrashDumps; + MITK_INFO << "Using arg[2] as crash dump path"; myBreakpad->SetCrashDumpPath(folderForCrashDumps); } - MITK_INFO << "NamedPipeName: " << myBreakpad->GetNamedPipeName() << "\n"; - MITK_INFO << "FolderForCrashDumps: " << myBreakpad->GetCrashDumpPath() << "\n"; + MITK_INFO << "NamedPipeName: " << myBreakpad->GetNamedPipeName().toStdString().c_str() << "\n"; + MITK_INFO << "FolderForCrashDumps: " << myBreakpad->GetCrashDumpPath().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(); + qtapplication.exec(); } catch(...) { MITK_WARN << "mitk Crash Reporting Server exception caught, shutdown."; exit(2); } + + MITK_INFO << "mitk Crash Reporting Server shuting down."; + }