CrashHandler: Add "Attach and Debug" button.

This will launch a new instance of Qt Creator attaching to the crashed
instance.

The 'Restart' functionality is now represented as a check box since it
would be confusing to have one button that restarts the app and quits
the crash handler and another one that starts a debugger but keeps the
crash handler open (which is necessary, otherwise the crashed app will
quit).

Change-Id: Id88f418ff73ab7bc72b05753ce2b61bbef8f30cf
Reviewed-by: Christian Kandeler <christian.kandeler@digia.com>
This commit is contained in:
Nikolai Kosjar
2012-09-24 16:05:43 +02:00
parent 0f27043fe6
commit e186f9b257
9 changed files with 240 additions and 76 deletions

View File

@@ -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*/) void BacktraceCollector::onDebuggerFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/)
{ {
if (d->errorOccurred) { if (d->errorOccurred) {

View File

@@ -43,6 +43,8 @@ public:
~BacktraceCollector(); ~BacktraceCollector();
void run(Q_PID pid); void run(Q_PID pid);
bool isRunning() const;
void kill();
signals: signals:
void error(const QString &errorMessage); void error(const QString &errorMessage);

View File

@@ -33,13 +33,17 @@
#include "backtracecollector.h" #include "backtracecollector.h"
#include "utils.h" #include "utils.h"
#include <utils/environment.h>
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <QDesktopServices> #include <QDesktopServices>
#include <QDir>
#include <QFile> #include <QFile>
#include <QRegExp> #include <QRegExp>
#include <QTextStream> #include <QTextStream>
#include <QUrl> #include <QUrl>
#include <QVector>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -48,9 +52,11 @@
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h>
static const char FileDistroInformation[] = "/etc/lsb-release"; static const char FileDistroInformation[] = "/etc/lsb-release";
static const char FileKernelVersion[] = "/proc/version"; static const char FileKernelVersion[] = "/proc/version";
static const char QtCreatorExecutable[] = "qtcreator";
static QString collectLinuxDistributionInfo() static QString collectLinuxDistributionInfo()
{ {
@@ -62,33 +68,40 @@ static QString collectKernelVersionInfo()
return QString::fromLatin1(fileContents(QLatin1String(FileKernelVersion))); return QString::fromLatin1(fileContents(QLatin1String(FileKernelVersion)));
} }
// Convience class for interacting with exec() family of functions.
class CExecList : public QVector<char *>
{
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 class CrashHandlerPrivate
{ {
public: public:
CrashHandlerPrivate(pid_t pid, CrashHandler *crashHandler) 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() const pid_t pid;
{ const QString creatorInPath; // Backup debugger.
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);
}
pid_t pid;
BacktraceCollector backtraceCollector; BacktraceCollector backtraceCollector;
CrashHandlerDialog dialog; CrashHandlerDialog dialog;
// For restarting the process. QStringList restartAppCommandLine;
char **argv; QStringList restartAppEnvironment;
char **envp;
}; };
CrashHandler::CrashHandler(pid_t pid, QObject *parent) 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(collectKernelVersionInfo());
d->dialog.appendDebugInfo(collectLinuxDistributionInfo()); d->dialog.appendDebugInfo(collectLinuxDistributionInfo());
d->dialog.show();
if (!collectRestartAppData()) // If we can't restart the app properly, ... if (!collectRestartAppData()) {
d->dialog.disableRestartAppButton(); d->dialog.disableRestartAppCheckBox();
if (d->creatorInPath.isEmpty())
d->dialog.disableDebugAppButton();
}
d->dialog.show();
} }
CrashHandler::~CrashHandler() CrashHandler::~CrashHandler()
@@ -130,8 +147,7 @@ void CrashHandler::onError(const QString &errorMessage)
void CrashHandler::onBacktraceChunk(const QString &chunk) void CrashHandler::onBacktraceChunk(const QString &chunk)
{ {
d->dialog.appendDebugInfo(chunk); d->dialog.appendDebugInfo(chunk);
QTextStream out(stdout); QTextStream(stdout) << chunk;
out << chunk;
} }
void CrashHandler::onBacktraceFinished(const QString &backtrace) void CrashHandler::onBacktraceFinished(const QString &backtrace)
@@ -164,54 +180,40 @@ bool CrashHandler::collectRestartAppData()
{ {
const QString procDir = QString::fromLatin1("/proc/%1").arg(d->pid); const QString procDir = QString::fromLatin1("/proc/%1").arg(d->pid);
// Construct d->argv. // Get command line.
// man 5 proc: /proc/[pid]/cmdline // man 5 proc: /proc/[pid]/cmdline
// The command-line arguments appear in this file as a set of strings separated by // 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. // null bytes ('\0'), with a further null byte after the last string.
const QString procCmdFileName = procDir + QLatin1String("/cmdline"); const QString procCmdFileName = procDir + QLatin1String("/cmdline");
QList<QByteArray> cmdEntries = fileContents(procCmdFileName).split('\0'); QList<QByteArray> commandLine = fileContents(procCmdFileName).split('\0');
if (cmdEntries.size() < 2) { if (commandLine.size() < 2) {
qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procCmdFileName)); qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procCmdFileName));
return false; return false;
} }
cmdEntries.removeLast(); commandLine.removeLast();
char * const executable = qstrdup(qPrintable(cmdEntries.takeFirst())); foreach (const QByteArray &item, commandLine)
d->argv = (char **) malloc(sizeof(char*) * (cmdEntries.size() + 2)); d->restartAppCommandLine.append(QString::fromLatin1(item));
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;
// Construct d->envp. // Get environment.
// man 5 proc: /proc/[pid]/environ // 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"); const QString procEnvFileName = procDir + QLatin1String("/environ");
QList<QByteArray> envEntries = fileContents(procEnvFileName).split('\0'); QList<QByteArray> environment = fileContents(procEnvFileName).split('\0');
if (envEntries.isEmpty()) { if (environment.isEmpty()) {
qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procEnvFileName)); qWarning("%s: Unexpected format in file '%s'.\n", Q_FUNC_INFO, qPrintable(procEnvFileName));
return false; return false;
} }
if (envEntries.last().isEmpty()) if (environment.last().isEmpty())
envEntries.removeLast(); environment.removeLast();
d->envp = (char **) malloc(sizeof(char*) * (envEntries.size() + 1)); foreach (const QByteArray &item, environment)
if (d->envp == 0) d->restartAppEnvironment.append(QString::fromLatin1(item));
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;
return true; return true;
} }
void CrashHandler::restartApplication() void CrashHandler::runCommand(QStringList commandLine, QStringList environment, WaitMode waitMode)
{ {
// TODO: If QTBUG-2284 is resolved, use QProcess::startDetached() here. // 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 // We can't use QProcess::startDetached because of bug
// //
// QTBUG-2284 // QTBUG-2284
@@ -224,15 +226,19 @@ void CrashHandler::restartApplication()
case -1: // error case -1: // error
qFatal("%s: fork() failed.", Q_FUNC_INFO); qFatal("%s: fork() failed.", Q_FUNC_INFO);
break; break;
case 0: // child case 0: { // child
qDebug("Restarting Qt Creator with\n"); CExecList argv(commandLine);
for (int i = 0; d->argv[i]; ++i) CExecList envp(environment);
qDebug(" %s", d->argv[i]); qDebug("Running\n");
qDebug("\nand environment\n"); for (int i = 0; argv[i]; ++i)
for (int i = 0; d->envp[i]; ++i) qDebug(" %s", argv[i]);
qDebug(" %s", d->envp[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. // receive a SIGPIPE as soon as these are used.
if (freopen("/dev/null", "r", stdin) == 0) if (freopen("/dev/null", "r", stdin) == 0)
qFatal("%s: freopen() failed for stdin: %s.\n", Q_FUNC_INFO, strerror(errno)); 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) if (freopen("/dev/null", "w", stderr) == 0)
qFatal("%s: freopen() failed for stderr: %s.\n.", Q_FUNC_INFO, strerror(errno)); 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); _exit(EXIT_FAILURE);
default: // parent } default: // parent
qApp->quit(); 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; 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();
}

View File

@@ -55,10 +55,14 @@ public slots:
void openBugTracker(); void openBugTracker();
void restartApplication(); void restartApplication();
void debugApplication();
private: private:
bool collectRestartAppData(); bool collectRestartAppData();
enum WaitMode { WaitForExit, DontWaitForExit };
static void runCommand(QStringList commandLine, QStringList environment, WaitMode waitMode);
CrashHandlerPrivate *d; CrashHandlerPrivate *d;
}; };

View File

@@ -34,9 +34,15 @@
#include "utils.h" #include "utils.h"
#include <app/app_version.h> #include <app/app_version.h>
#include <utils/checkablemessagebox.h>
#include <QClipboard> #include <QClipboard>
#include <QIcon> #include <QIcon>
#include <QSettings>
static const char SettingsApplication[] = "QtCreator";
static const char SettingsKeySkipWarningAbortingBacktrace[]
= "CrashHandler/SkipWarningAbortingBacktrace";
CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) : CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) :
QDialog(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->copyToClipBoardButton, SIGNAL(clicked()), this, SLOT(copyToClipboardClicked()));
connect(m_ui->reportBugButton, SIGNAL(clicked()), m_crashHandler, SLOT(openBugTracker())); connect(m_ui->reportBugButton, SIGNAL(clicked()), m_crashHandler, SLOT(openBugTracker()));
connect(m_ui->restartAppButton, SIGNAL(clicked()), m_crashHandler, SLOT(restartApplication())); connect(m_ui->debugAppButton, SIGNAL(clicked()), m_crashHandler, SLOT(debugApplication()));
connect(m_ui->closeButton, SIGNAL(clicked()), qApp, SLOT(quit())); connect(m_ui->closeButton, SIGNAL(clicked()), this, SLOT(close()));
setApplicationInfo(); setApplicationInfo();
} }
@@ -70,6 +76,34 @@ CrashHandlerDialog::~CrashHandlerDialog()
delete m_ui; 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(
"<html><head/><body>"
"<p><b>Run the debugger and abort collecting backtrace?</b></p>"
"<p>You have requested to run the debugger while collecting the backtrace was not "
"finished.</p>"
"</body></html>");
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() void CrashHandlerDialog::setToFinalState()
{ {
m_ui->progressBar->hide(); m_ui->progressBar->hide();
@@ -77,9 +111,14 @@ void CrashHandlerDialog::setToFinalState()
m_ui->reportBugButton->setEnabled(true); 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() void CrashHandlerDialog::setApplicationInfo()
@@ -130,3 +169,10 @@ void CrashHandlerDialog::copyToClipboardClicked()
{ {
QApplication::clipboard()->setText(m_ui->debugInfoEdit->toPlainText()); QApplication::clipboard()->setText(m_ui->debugInfoEdit->toPlainText());
} }
void CrashHandlerDialog::close()
{
if (m_ui->restartAppCheckBox->isEnabled() && m_ui->restartAppCheckBox->isChecked())
m_crashHandler->restartApplication();
qApp->quit();
}

View File

@@ -50,14 +50,17 @@ public:
~CrashHandlerDialog(); ~CrashHandlerDialog();
public: public:
void disableRestartAppButton();
void setApplicationInfo(); void setApplicationInfo();
void setToFinalState();
void appendDebugInfo(const QString &chunk); void appendDebugInfo(const QString &chunk);
void selectLineWithContents(const QString &text); void selectLineWithContents(const QString &text);
void setToFinalState();
void disableRestartAppCheckBox();
void disableDebugAppButton();
bool runDebuggerWhileBacktraceNotFinished();
private slots: private slots:
void copyToClipboardClicked(); void copyToClipboardClicked();
void close();
private: private:
CrashHandler *m_crashHandler; CrashHandler *m_crashHandler;

View File

@@ -12,7 +12,7 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>400</width> <width>500</width>
<height>300</height> <height>300</height>
</size> </size>
</property> </property>
@@ -64,6 +64,16 @@
<item> <item>
<widget class="QTextEdit" name="debugInfoEdit"/> <widget class="QTextEdit" name="debugInfoEdit"/>
</item> </item>
<item alignment="Qt::AlignRight">
<widget class="QCheckBox" name="restartAppCheckBox">
<property name="text">
<string>&amp;Restart Qt Creator on close</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@@ -71,6 +81,9 @@
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="toolTip">
<string>Copy the whole contents to clipboard.</string>
</property>
<property name="text"> <property name="text">
<string>C&amp;opy to clipboard</string> <string>C&amp;opy to clipboard</string>
</property> </property>
@@ -81,6 +94,9 @@
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="toolTip">
<string>Open the bug tracker web site.</string>
</property>
<property name="text"> <property name="text">
<string>Report this &amp;bug</string> <string>Report this &amp;bug</string>
</property> </property>
@@ -100,14 +116,23 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QPushButton" name="restartAppButton"> <widget class="QPushButton" name="debugAppButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Debug the application with a new instance of Qt Creator. During debugging the crash handler will be hidden.</string>
</property>
<property name="text"> <property name="text">
<string>&amp;Restart Qt Creator</string> <string>Attach and &amp;Debug</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="closeButton"> <widget class="QPushButton" name="closeButton">
<property name="toolTip">
<string>Quit the handler and the crashed application.</string>
</property>
<property name="text"> <property name="text">
<string>&amp;Close</string> <string>&amp;Close</string>
</property> </property>

View File

@@ -11,13 +11,18 @@ SOURCES += \
backtracecollector.cpp \ backtracecollector.cpp \
crashhandlerdialog.cpp \ crashhandlerdialog.cpp \
crashhandler.cpp \ crashhandler.cpp \
utils.cpp utils.cpp \
../../libs/utils/checkablemessagebox.cpp \
../../libs/utils/environment.cpp
HEADERS += \ HEADERS += \
backtracecollector.h \ backtracecollector.h \
crashhandlerdialog.h \ crashhandlerdialog.h \
crashhandler.h \ crashhandler.h \
utils.h utils.h \
../../libs/utils/checkablemessagebox.h \
../../libs/utils/environment.h
FORMS += \ FORMS += \
crashhandlerdialog.ui crashhandlerdialog.ui

View File

@@ -6,7 +6,8 @@ QtcTool {
condition: qbs.targetOS == "linux" && qbs.buildVariant == "debug" condition: qbs.targetOS == "linux" && qbs.buildVariant == "debug"
cpp.includePaths: [ cpp.includePaths: [
buildDirectory buildDirectory,
"../../libs"
] ]
Depends { name: "cpp" } Depends { name: "cpp" }
@@ -14,6 +15,10 @@ QtcTool {
Depends { name: "app_version_header" } Depends { name: "app_version_header" }
files: [ files: [
"../../libs/utils/checkablemessagebox.cpp",
"../../libs/utils/checkablemessagebox.h",
"../../libs/utils/environment.cpp",
"../../libs/utils/environment.h",
"backtracecollector.cpp", "backtracecollector.cpp",
"backtracecollector.h", "backtracecollector.h",
"crashhandler.cpp", "crashhandler.cpp",
@@ -23,6 +28,6 @@ QtcTool {
"crashhandlerdialog.ui", "crashhandlerdialog.ui",
"main.cpp", "main.cpp",
"utils.cpp", "utils.cpp",
"utils.h", "utils.h"
] ]
} }