diff --git a/src/tools/qtcreatorcrashhandler/backtracecollector.cpp b/src/tools/qtcreatorcrashhandler/backtracecollector.cpp index df8500000f1..dbb52c6b12e 100644 --- a/src/tools/qtcreatorcrashhandler/backtracecollector.cpp +++ b/src/tools/qtcreatorcrashhandler/backtracecollector.cpp @@ -78,6 +78,16 @@ void BacktraceCollector::run(Q_PID pid) ); } +bool BacktraceCollector::isRunning() const +{ + return d->debugger.state() == QProcess::Running; +} + +void BacktraceCollector::kill() +{ + d->debugger.kill(); +} + void BacktraceCollector::onDebuggerFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/) { if (d->errorOccurred) { diff --git a/src/tools/qtcreatorcrashhandler/backtracecollector.h b/src/tools/qtcreatorcrashhandler/backtracecollector.h index 151d7c09d1e..a11f49c8986 100644 --- a/src/tools/qtcreatorcrashhandler/backtracecollector.h +++ b/src/tools/qtcreatorcrashhandler/backtracecollector.h @@ -43,6 +43,8 @@ public: ~BacktraceCollector(); void run(Q_PID pid); + bool isRunning() const; + void kill(); signals: void error(const QString &errorMessage); diff --git a/src/tools/qtcreatorcrashhandler/crashhandler.cpp b/src/tools/qtcreatorcrashhandler/crashhandler.cpp index dfc5e5012ff..3646bba5a75 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandler.cpp +++ b/src/tools/qtcreatorcrashhandler/crashhandler.cpp @@ -33,13 +33,17 @@ #include "backtracecollector.h" #include "utils.h" +#include + #include #include #include +#include #include #include #include #include +#include #include #include @@ -48,9 +52,11 @@ #include #include +#include static const char FileDistroInformation[] = "/etc/lsb-release"; static const char FileKernelVersion[] = "/proc/version"; +static const char QtCreatorExecutable[] = "qtcreator"; static QString collectLinuxDistributionInfo() { @@ -62,33 +68,40 @@ static QString collectKernelVersionInfo() return QString::fromLatin1(fileContents(QLatin1String(FileKernelVersion))); } +// Convience class for interacting with exec() family of functions. +class CExecList : public QVector +{ +public: + CExecList(const QStringList &list) + { + foreach (const QString &item, list) + append(qstrdup(item.toLatin1().data())); + append(0); + } + + ~CExecList() + { + for (int i = 0; i < size(); ++i) + delete[] value(i); + } +}; + class CrashHandlerPrivate { public: CrashHandlerPrivate(pid_t pid, CrashHandler *crashHandler) - : pid(pid), dialog(crashHandler), argv(0), envp(0) {} + : pid(pid), + creatorInPath(Utils::Environment::systemEnvironment().searchInPath(QtCreatorExecutable)), + dialog(crashHandler) {} - ~CrashHandlerPrivate() - { - if (argv) { - for (int i = 0; argv[i]; ++i) - delete[] argv[i]; - } - if (envp) { - for (int i = 0; envp[i]; ++i) - delete[] envp[i]; - } - free(argv); - free(envp); - } + const pid_t pid; + const QString creatorInPath; // Backup debugger. - pid_t pid; BacktraceCollector backtraceCollector; CrashHandlerDialog dialog; - // For restarting the process. - char **argv; - char **envp; + QStringList restartAppCommandLine; + QStringList restartAppEnvironment; }; CrashHandler::CrashHandler(pid_t pid, QObject *parent) @@ -100,10 +113,14 @@ CrashHandler::CrashHandler(pid_t pid, QObject *parent) d->dialog.appendDebugInfo(collectKernelVersionInfo()); d->dialog.appendDebugInfo(collectLinuxDistributionInfo()); - d->dialog.show(); - if (!collectRestartAppData()) // If we can't restart the app properly, ... - d->dialog.disableRestartAppButton(); + if (!collectRestartAppData()) { + d->dialog.disableRestartAppCheckBox(); + if (d->creatorInPath.isEmpty()) + d->dialog.disableDebugAppButton(); + } + + d->dialog.show(); } CrashHandler::~CrashHandler() @@ -130,8 +147,7 @@ void CrashHandler::onError(const QString &errorMessage) void CrashHandler::onBacktraceChunk(const QString &chunk) { d->dialog.appendDebugInfo(chunk); - QTextStream out(stdout); - out << chunk; + QTextStream(stdout) << chunk; } void CrashHandler::onBacktraceFinished(const QString &backtrace) @@ -164,54 +180,40 @@ bool CrashHandler::collectRestartAppData() { const QString procDir = QString::fromLatin1("/proc/%1").arg(d->pid); - // Construct d->argv. + // Get command line. // man 5 proc: /proc/[pid]/cmdline // The command-line arguments appear in this file as a set of strings separated by // null bytes ('\0'), with a further null byte after the last string. const QString procCmdFileName = procDir + QLatin1String("/cmdline"); - QList cmdEntries = fileContents(procCmdFileName).split('\0'); - if (cmdEntries.size() < 2) { + QList commandLine = fileContents(procCmdFileName).split('\0'); + if (commandLine.size() < 2) { qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procCmdFileName)); return false; } - cmdEntries.removeLast(); - char * const executable = qstrdup(qPrintable(cmdEntries.takeFirst())); - d->argv = (char **) malloc(sizeof(char*) * (cmdEntries.size() + 2)); - if (d->argv == 0) - qFatal("%s: malloc() failed.\n", Q_FUNC_INFO); - d->argv[0] = executable; - int i; - for (i = 1; i <= cmdEntries.size(); ++i) - d->argv[i] = qstrdup(cmdEntries.at(i-1)); - d->argv[i] = 0; + commandLine.removeLast(); + foreach (const QByteArray &item, commandLine) + d->restartAppCommandLine.append(QString::fromLatin1(item)); - // Construct d->envp. + // Get environment. // man 5 proc: /proc/[pid]/environ - // The entries are separated by null bytes ('\0'), and there may be a null byte at the end. + // The entries are separated by null bytes ('\0'), and there may be a null byte at the end. const QString procEnvFileName = procDir + QLatin1String("/environ"); - QList envEntries = fileContents(procEnvFileName).split('\0'); - if (envEntries.isEmpty()) { + QList environment = fileContents(procEnvFileName).split('\0'); + if (environment.isEmpty()) { qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procEnvFileName)); return false; } - if (envEntries.last().isEmpty()) - envEntries.removeLast(); - d->envp = (char **) malloc(sizeof(char*) * (envEntries.size() + 1)); - if (d->envp == 0) - qFatal("%s: malloc() failed.\n", Q_FUNC_INFO); - for (i = 0; i < envEntries.size(); ++i) - d->envp[i] = qstrdup(envEntries.at(i)); - d->envp[i] = 0; + if (environment.last().isEmpty()) + environment.removeLast(); + foreach (const QByteArray &item, environment) + d->restartAppEnvironment.append(QString::fromLatin1(item)); return true; } -void CrashHandler::restartApplication() +void CrashHandler::runCommand(QStringList commandLine, QStringList environment, WaitMode waitMode) { // TODO: If QTBUG-2284 is resolved, use QProcess::startDetached() here. - // Close the crash handler and start the process again with same environment and - // command line arguments. - // // We can't use QProcess::startDetached because of bug // // QTBUG-2284 @@ -224,15 +226,19 @@ void CrashHandler::restartApplication() case -1: // error qFatal("%s: fork() failed.", Q_FUNC_INFO); break; - case 0: // child - qDebug("Restarting Qt Creator with\n"); - for (int i = 0; d->argv[i]; ++i) - qDebug(" %s", d->argv[i]); - qDebug("\nand environment\n"); - for (int i = 0; d->envp[i]; ++i) - qDebug(" %s", d->envp[i]); + case 0: { // child + CExecList argv(commandLine); + CExecList envp(environment); + qDebug("Running\n"); + for (int i = 0; argv[i]; ++i) + qDebug(" %s", argv[i]); + if (!environment.isEmpty()) { + qDebug("\nwith environment:\n"); + for (int i = 0; envp[i]; ++i) + qDebug(" %s", envp[i]); + } - // The standards pipes must be open, otherwise the restarted Qt Creator will + // The standards pipes must be open, otherwise the application will // receive a SIGPIPE as soon as these are used. if (freopen("/dev/null", "r", stdin) == 0) qFatal("%s: freopen() failed for stdin: %s.\n", Q_FUNC_INFO, strerror(errno)); @@ -241,10 +247,68 @@ void CrashHandler::restartApplication() if (freopen("/dev/null", "w", stderr) == 0) qFatal("%s: freopen() failed for stderr: %s.\n.", Q_FUNC_INFO, strerror(errno)); - execve(d->argv[0], d->argv, d->envp); + if (environment.isEmpty()) + execv(argv[0], argv.data()); + else + execve(argv[0], argv.data(), envp.data()); _exit(EXIT_FAILURE); - default: // parent - qApp->quit(); + } default: // parent + if (waitMode == WaitForExit) { + while (true) { + int status; + if (waitpid(pid, &status, 0) == -1) { + if (errno == EINTR) // Signal handler of QProcess for SIGCHLD was triggered. + continue; + perror("waitpid() failed unexpectedly"); + } + if (WIFEXITED(status)) { + qDebug("Child exited with exit code %d.", WEXITSTATUS(status)); + break; + } else if (WIFSIGNALED(status)) { + qDebug("Child terminated by signal %d.", WTERMSIG(status)); + break; + } + } + } break; } } + +void CrashHandler::restartApplication() +{ + runCommand(d->restartAppCommandLine, d->restartAppEnvironment, DontWaitForExit); +} + +void CrashHandler::debugApplication() +{ + // User requested to debug the app while our debugger is running. + if (d->backtraceCollector.isRunning()) { + if (!d->dialog.runDebuggerWhileBacktraceNotFinished()) + return; + if (d->backtraceCollector.isRunning()) { + d->backtraceCollector.disconnect(); + d->backtraceCollector.kill(); + d->dialog.setToFinalState(); + d->dialog.appendDebugInfo(tr("\n\nCollecting backtrace aborted by user.")); + QCoreApplication::processEvents(); // Show the last appended output immediately. + } + } + + // Prepare command. + QString executable = d->creatorInPath; + if (!d->restartAppCommandLine.isEmpty()) + executable = d->restartAppCommandLine.at(0); + const QStringList commandLine = QStringList() + << executable + << QLatin1String("-debug") + << QString::number(d->pid); + + QStringList environment; + if (!d->restartAppEnvironment.isEmpty()) + environment = d->restartAppEnvironment; + + // The UI is blocked/frozen anyway, so hide the dialog while debugging. + d->dialog.hide(); + runCommand(commandLine, environment, WaitForExit); + d->dialog.show(); +} diff --git a/src/tools/qtcreatorcrashhandler/crashhandler.h b/src/tools/qtcreatorcrashhandler/crashhandler.h index cbd8d3daf61..f0132b2025d 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandler.h +++ b/src/tools/qtcreatorcrashhandler/crashhandler.h @@ -55,10 +55,14 @@ public slots: void openBugTracker(); void restartApplication(); + void debugApplication(); private: bool collectRestartAppData(); + enum WaitMode { WaitForExit, DontWaitForExit }; + static void runCommand(QStringList commandLine, QStringList environment, WaitMode waitMode); + CrashHandlerPrivate *d; }; diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp index d1ff87d2706..25cd72ae5fd 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp @@ -34,9 +34,15 @@ #include "utils.h" #include +#include #include #include +#include + +static const char SettingsApplication[] = "QtCreator"; +static const char SettingsKeySkipWarningAbortingBacktrace[] + = "CrashHandler/SkipWarningAbortingBacktrace"; CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) : QDialog(parent), @@ -59,8 +65,8 @@ CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) : connect(m_ui->copyToClipBoardButton, SIGNAL(clicked()), this, SLOT(copyToClipboardClicked())); connect(m_ui->reportBugButton, SIGNAL(clicked()), m_crashHandler, SLOT(openBugTracker())); - connect(m_ui->restartAppButton, SIGNAL(clicked()), m_crashHandler, SLOT(restartApplication())); - connect(m_ui->closeButton, SIGNAL(clicked()), qApp, SLOT(quit())); + connect(m_ui->debugAppButton, SIGNAL(clicked()), m_crashHandler, SLOT(debugApplication())); + connect(m_ui->closeButton, SIGNAL(clicked()), this, SLOT(close())); setApplicationInfo(); } @@ -70,6 +76,34 @@ CrashHandlerDialog::~CrashHandlerDialog() delete m_ui; } +bool CrashHandlerDialog::runDebuggerWhileBacktraceNotFinished() +{ + // Check settings. + QSettings settings(QSettings::IniFormat, QSettings::UserScope, + QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), + QLatin1String(SettingsApplication)); + if (settings.value(QLatin1String(SettingsKeySkipWarningAbortingBacktrace), false).toBool()) + return true; + + // Ask user. + const QString title = tr("Run Debugger And Abort Collecting Backtrace?"); + const QString message = tr( + "" + "

Run the debugger and abort collecting backtrace?

" + "

You have requested to run the debugger while collecting the backtrace was not " + "finished.

" + ""); + const QString checkBoxText = tr("Do not &ask again."); + bool checkBoxSetting = false; + const QDialogButtonBox::StandardButton button = Utils::CheckableMessageBox::question(this, + title, message, checkBoxText, &checkBoxSetting, + QDialogButtonBox::Yes|QDialogButtonBox::No, QDialogButtonBox::No); + if (checkBoxSetting) + settings.setValue(QLatin1String(SettingsKeySkipWarningAbortingBacktrace), checkBoxSetting); + + return button == QDialogButtonBox::Yes; +} + void CrashHandlerDialog::setToFinalState() { m_ui->progressBar->hide(); @@ -77,9 +111,14 @@ void CrashHandlerDialog::setToFinalState() m_ui->reportBugButton->setEnabled(true); } -void CrashHandlerDialog::disableRestartAppButton() +void CrashHandlerDialog::disableRestartAppCheckBox() { - m_ui->restartAppButton->setDisabled(true); + m_ui->restartAppCheckBox->setDisabled(true); +} + +void CrashHandlerDialog::disableDebugAppButton() +{ + m_ui->debugAppButton->setDisabled(true); } void CrashHandlerDialog::setApplicationInfo() @@ -130,3 +169,10 @@ void CrashHandlerDialog::copyToClipboardClicked() { QApplication::clipboard()->setText(m_ui->debugInfoEdit->toPlainText()); } + +void CrashHandlerDialog::close() +{ + if (m_ui->restartAppCheckBox->isEnabled() && m_ui->restartAppCheckBox->isChecked()) + m_crashHandler->restartApplication(); + qApp->quit(); +} diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h index ecee4cffa9d..20c82ef6773 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h @@ -50,14 +50,17 @@ public: ~CrashHandlerDialog(); public: - void disableRestartAppButton(); void setApplicationInfo(); - void setToFinalState(); void appendDebugInfo(const QString &chunk); void selectLineWithContents(const QString &text); + void setToFinalState(); + void disableRestartAppCheckBox(); + void disableDebugAppButton(); + bool runDebuggerWhileBacktraceNotFinished(); private slots: void copyToClipboardClicked(); + void close(); private: CrashHandler *m_crashHandler; diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui index 101efd2e480..9a7b67a003b 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui @@ -12,7 +12,7 @@ - 400 + 500 300 @@ -64,6 +64,16 @@ + + + + &Restart Qt Creator on close + + + true + + + @@ -71,6 +81,9 @@ false + + Copy the whole contents to clipboard. + C&opy to clipboard @@ -81,6 +94,9 @@ false + + Open the bug tracker web site. + Report this &bug @@ -100,14 +116,23 @@ - + + + true + + + Debug the application with a new instance of Qt Creator. During debugging the crash handler will be hidden. + - &Restart Qt Creator + Attach and &Debug + + Quit the handler and the crashed application. + &Close diff --git a/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro index 551de3008d1..bfcb41e303f 100644 --- a/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro +++ b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro @@ -11,13 +11,18 @@ SOURCES += \ backtracecollector.cpp \ crashhandlerdialog.cpp \ crashhandler.cpp \ - utils.cpp + utils.cpp \ + ../../libs/utils/checkablemessagebox.cpp \ + ../../libs/utils/environment.cpp + HEADERS += \ backtracecollector.h \ crashhandlerdialog.h \ crashhandler.h \ - utils.h + utils.h \ + ../../libs/utils/checkablemessagebox.h \ + ../../libs/utils/environment.h FORMS += \ crashhandlerdialog.ui diff --git a/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs index 67e5d574b8a..35d6187d568 100644 --- a/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs +++ b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs @@ -6,7 +6,8 @@ QtcTool { condition: qbs.targetOS == "linux" && qbs.buildVariant == "debug" cpp.includePaths: [ - buildDirectory + buildDirectory, + "../../libs" ] Depends { name: "cpp" } @@ -14,6 +15,10 @@ QtcTool { Depends { name: "app_version_header" } files: [ + "../../libs/utils/checkablemessagebox.cpp", + "../../libs/utils/checkablemessagebox.h", + "../../libs/utils/environment.cpp", + "../../libs/utils/environment.h", "backtracecollector.cpp", "backtracecollector.h", "crashhandler.cpp", @@ -23,6 +28,6 @@ QtcTool { "crashhandlerdialog.ui", "main.cpp", "utils.cpp", - "utils.h", + "utils.h" ] }