Commit e186f9b2 authored by Nikolai Kosjar's avatar Nikolai Kosjar
Browse files

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: default avatarChristian Kandeler <christian.kandeler@digia.com>
parent 0f27043f
......@@ -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) {
......
......@@ -43,6 +43,8 @@ public:
~BacktraceCollector();
void run(Q_PID pid);
bool isRunning() const;
void kill();
signals:
void error(const QString &errorMessage);
......
......@@ -33,13 +33,17 @@
#include "backtracecollector.h"
#include "utils.h"
#include <utils/environment.h>
#include <QApplication>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QRegExp>
#include <QTextStream>
#include <QUrl>
#include <QVector>
#include <stdio.h>
#include <stdlib.h>
......@@ -48,9 +52,11 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
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)));
}
class CrashHandlerPrivate
// Convience class for interacting with exec() family of functions.
class CExecList : public QVector<char *>
{
public:
CrashHandlerPrivate(pid_t pid, CrashHandler *crashHandler)
: pid(pid), dialog(crashHandler), argv(0), envp(0) {}
CExecList(const QStringList &list)
{
foreach (const QString &item, list)
append(qstrdup(item.toLatin1().data()));
append(0);
}
~CrashHandlerPrivate()
~CExecList()
{
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);
for (int i = 0; i < size(); ++i)
delete[] value(i);
}
};
class CrashHandlerPrivate
{
public:
CrashHandlerPrivate(pid_t pid, CrashHandler *crashHandler)
: pid(pid),
creatorInPath(Utils::Environment::systemEnvironment().searchInPath(QtCreatorExecutable)),
dialog(crashHandler) {}
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<QByteArray> cmdEntries = fileContents(procCmdFileName).split('\0');
if (cmdEntries.size() < 2) {
QList<QByteArray> 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;
// Construct d->envp.
commandLine.removeLast();
foreach (const QByteArray &item, commandLine)
d->restartAppCommandLine.append(QString::fromLatin1(item));
// 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<QByteArray> envEntries = fileContents(procEnvFileName).split('\0');
if (envEntries.isEmpty()) {
QList<QByteArray> 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]);
// The standards pipes must be open, otherwise the restarted Qt Creator will
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 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();
}
......@@ -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;
};
......
......@@ -34,9 +34,15 @@
#include "utils.h"
#include <app/app_version.h>
#include <utils/checkablemessagebox.h>
#include <QClipboard>
#include <QIcon>
#include <QSettings>
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(
"<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()
{
m_ui->progressBar->hide();
......@@ -77,9 +111,14 @@ void CrashHandlerDialog::setToFinalState()
m_ui->reportBugButton->setEnabled(true);
}
void CrashHandlerDialog::disableRestartAppButton()
void CrashHandlerDialog::disableRestartAppCheckBox()
{
m_ui->restartAppCheckBox->setDisabled(true);
}
void CrashHandlerDialog::disableDebugAppButton()
{
m_ui->restartAppButton->setDisabled(true);
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();
}
......@@ -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;
......
......@@ -12,7 +12,7 @@
</property>
<property name="minimumSize">
<size>
<width>400</width>
<width>500</width>
<height>300</height>
</size>
</property>
......@@ -64,6 +64,16 @@
<item>
<widget class="QTextEdit" name="debugInfoEdit"/>
</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>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
......@@ -71,6 +81,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Copy the whole contents to clipboard.</string>
</property>
<property name="text">
<string>C&amp;opy to clipboard</string>
</property>
......@@ -81,6 +94,9 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Open the bug tracker web site.</string>
</property>
<property name="text">
<string>Report this &amp;bug</string>
</property>
......@@ -100,14 +116,23 @@
</spacer>
</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">
<string>&amp;Restart Qt Creator</string>
<string>Attach and &amp;Debug</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="toolTip">
<string>Quit the handler and the crashed application.</string>
</property>
<property name="text">
<string>&amp;Close</string>
</property>
......
......@@ -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
......
......@@ -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"
]
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment