CrashHandler: Generalize for other clients

* Put the binary into the libexec path
* Check for process origin more explicitly
* Allow providing the application name
* Allow disabling the restart action

Change-Id: Ide7132215d7c251ce486daab60ac680de3c9f9a6
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Nikolai Kosjar
2016-10-25 12:29:52 +02:00
parent a1b0302014
commit 69555ec851
12 changed files with 103 additions and 35 deletions

View File

@@ -8,6 +8,8 @@ DESTDIR = $$IDE_APP_PATH
VERSION = $$QTCREATOR_VERSION VERSION = $$QTCREATOR_VERSION
QT -= testlib QT -= testlib
DEFINES += IDE_LIBEXEC_PATH=\\\"$$IDE_LIBEXEC_PATH\\\"
HEADERS += ../tools/qtcreatorcrashhandler/crashhandlersetup.h HEADERS += ../tools/qtcreatorcrashhandler/crashhandlersetup.h
SOURCES += main.cpp ../tools/qtcreatorcrashhandler/crashhandlersetup.cpp SOURCES += main.cpp ../tools/qtcreatorcrashhandler/crashhandlersetup.cpp

View File

@@ -23,6 +23,7 @@ QtcProduct {
installDir: qtc.ide_bin_path installDir: qtc.ide_bin_path
property bool qtcRunnable: true property bool qtcRunnable: true
cpp.defines: base.concat(['IDE_LIBEXEC_PATH="' + qtc.ide_libexec_path + '"'])
cpp.rpaths: qbs.targetOS.contains("macos") ? ["@executable_path/../Frameworks"] cpp.rpaths: qbs.targetOS.contains("macos") ? ["@executable_path/../Frameworks"]
: ["$ORIGIN/../" + qtc.libDirName + "/qtcreator"] : ["$ORIGIN/../" + qtc.libDirName + "/qtcreator"]
cpp.includePaths: [ cpp.includePaths: [

View File

@@ -327,7 +327,8 @@ int main(int argc, char **argv)
const int threadCount = QThreadPool::globalInstance()->maxThreadCount(); const int threadCount = QThreadPool::globalInstance()->maxThreadCount();
QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount)); QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount));
CrashHandlerSetup setupCrashHandler; // Display a backtrace once a serious signal is delivered. // Display a backtrace once a serious signal is delivered.
CrashHandlerSetup setupCrashHandler(IDE_LIBEXEC_PATH);
#ifdef ENABLE_QT_BREAKPAD #ifdef ENABLE_QT_BREAKPAD
QtSystemExceptionHandler systemExceptionHandler; QtSystemExceptionHandler systemExceptionHandler;

View File

@@ -85,10 +85,13 @@ public:
class CrashHandlerPrivate class CrashHandlerPrivate
{ {
public: public:
CrashHandlerPrivate(pid_t pid, const QString &signalName, CrashHandler *crashHandler) CrashHandlerPrivate(pid_t pid,
const QString &signalName,
const QString &appName,
CrashHandler *crashHandler)
: pid(pid), : pid(pid),
creatorInPath(Utils::Environment::systemEnvironment().searchInPath(QLatin1String(QtCreatorExecutable))), creatorInPath(Utils::Environment::systemEnvironment().searchInPath(QLatin1String(QtCreatorExecutable))),
dialog(crashHandler, signalName) {} dialog(crashHandler, signalName, appName) {}
const pid_t pid; const pid_t pid;
const Utils::FileName creatorInPath; // Backup debugger. const Utils::FileName creatorInPath; // Backup debugger.
@@ -100,8 +103,12 @@ public:
QStringList restartAppEnvironment; QStringList restartAppEnvironment;
}; };
CrashHandler::CrashHandler(pid_t pid, const QString &signalName, QObject *parent) CrashHandler::CrashHandler(pid_t pid,
: QObject(parent), d(new CrashHandlerPrivate(pid, signalName, this)) const QString &signalName,
const QString &appName,
RestartCapability restartCap,
QObject *parent)
: QObject(parent), d(new CrashHandlerPrivate(pid, signalName, appName, this))
{ {
connect(&d->backtraceCollector, &BacktraceCollector::error, this, &CrashHandler::onError); connect(&d->backtraceCollector, &BacktraceCollector::error, this, &CrashHandler::onError);
connect(&d->backtraceCollector, &BacktraceCollector::backtraceChunk, connect(&d->backtraceCollector, &BacktraceCollector::backtraceChunk,
@@ -112,7 +119,7 @@ CrashHandler::CrashHandler(pid_t pid, const QString &signalName, QObject *parent
d->dialog.appendDebugInfo(collectKernelVersionInfo()); d->dialog.appendDebugInfo(collectKernelVersionInfo());
d->dialog.appendDebugInfo(collectLinuxDistributionInfo()); d->dialog.appendDebugInfo(collectLinuxDistributionInfo());
if (!collectRestartAppData()) { if (restartCap == DisableRestart || !collectRestartAppData()) {
d->dialog.disableRestartAppCheckBox(); d->dialog.disableRestartAppCheckBox();
if (d->creatorInPath.isEmpty()) if (d->creatorInPath.isEmpty())
d->dialog.disableDebugAppButton(); d->dialog.disableDebugAppButton();

View File

@@ -38,7 +38,13 @@ class CrashHandler : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit CrashHandler(pid_t pid, const QString &signalName, QObject *parent = 0); enum RestartCapability { EnableRestart, DisableRestart };
explicit CrashHandler(pid_t pid,
const QString &signalName,
const QString &appName,
RestartCapability restartCap = EnableRestart,
QObject *parent = 0);
~CrashHandler(); ~CrashHandler();
void run(); void run();

View File

@@ -39,7 +39,9 @@ static const char SettingsApplication[] = "QtCreator";
static const char SettingsKeySkipWarningAbortingBacktrace[] static const char SettingsKeySkipWarningAbortingBacktrace[]
= "CrashHandler/SkipWarningAbortingBacktrace"; = "CrashHandler/SkipWarningAbortingBacktrace";
CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, const QString &signalName, CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler,
const QString &signalName,
const QString &appName,
QWidget *parent) : QWidget *parent) :
QDialog(parent), QDialog(parent),
m_crashHandler(handler), m_crashHandler(handler),
@@ -51,6 +53,7 @@ CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, const QString &sig
m_ui->debugInfoEdit->setReadOnly(true); m_ui->debugInfoEdit->setReadOnly(true);
m_ui->progressBar->setMinimum(0); m_ui->progressBar->setMinimum(0);
m_ui->progressBar->setMaximum(0); m_ui->progressBar->setMaximum(0);
m_ui->restartAppCheckBox->setText(tr("&Restart %1 on close").arg(appName));
const QStyle * const style = QApplication::style(); const QStyle * const style = QApplication::style();
m_ui->closeButton->setIcon(style->standardIcon(QStyle::SP_DialogCloseButton)); m_ui->closeButton->setIcon(style->standardIcon(QStyle::SP_DialogCloseButton));
@@ -67,7 +70,7 @@ CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, const QString &sig
m_crashHandler, &CrashHandler::debugApplication); m_crashHandler, &CrashHandler::debugApplication);
connect(m_ui->closeButton, &QAbstractButton::clicked, this, &CrashHandlerDialog::close); connect(m_ui->closeButton, &QAbstractButton::clicked, this, &CrashHandlerDialog::close);
setApplicationInfo(signalName); setApplicationInfo(signalName, appName);
} }
CrashHandlerDialog::~CrashHandlerDialog() CrashHandlerDialog::~CrashHandlerDialog()
@@ -120,10 +123,9 @@ void CrashHandlerDialog::disableDebugAppButton()
m_ui->debugAppButton->setDisabled(true); m_ui->debugAppButton->setDisabled(true);
} }
void CrashHandlerDialog::setApplicationInfo(const QString &signalName) void CrashHandlerDialog::setApplicationInfo(const QString &signalName, const QString &appName)
{ {
const QString ideName = QLatin1String("Qt Creator"); const QString title = tr("%1 has closed unexpectedly (Signal \"%2\")").arg(appName, signalName);
const QString title = tr("%1 has closed unexpectedly (Signal \"%2\")").arg(ideName, signalName);
const QString introLabelContents = tr( const QString introLabelContents = tr(
"<p><b>%1.</b></p>" "<p><b>%1.</b></p>"
"<p>Please file a <a href='%2'>bug report</a> with the debug information provided below.</p>") "<p>Please file a <a href='%2'>bug report</a> with the debug information provided below.</p>")
@@ -137,7 +139,7 @@ void CrashHandlerDialog::setApplicationInfo(const QString &signalName)
#endif #endif
const QString versionInformation = tr( const QString versionInformation = tr(
"%1 %2%3, based on Qt %4 (%5 bit)\n") "%1 %2%3, based on Qt %4 (%5 bit)\n")
.arg(ideName, QLatin1String(Core::Constants::IDE_VERSION_LONG), revision, .arg(appName, QLatin1String(Core::Constants::IDE_VERSION_LONG), revision,
QLatin1String(QT_VERSION_STR), QLatin1String(QT_VERSION_STR),
QString::number(QSysInfo::WordSize)); QString::number(QSysInfo::WordSize));
m_ui->debugInfoEdit->append(versionInformation); m_ui->debugInfoEdit->append(versionInformation);

View File

@@ -39,12 +39,14 @@ class CrashHandlerDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit CrashHandlerDialog(CrashHandler *handler, const QString &signalName, explicit CrashHandlerDialog(CrashHandler *handler,
const QString &signalName,
const QString &appName,
QWidget *parent = 0); QWidget *parent = 0);
~CrashHandlerDialog(); ~CrashHandlerDialog();
public: public:
void setApplicationInfo(const QString &signalName); void setApplicationInfo(const QString &signalName, const QString &appName);
void appendDebugInfo(const QString &chunk); void appendDebugInfo(const QString &chunk);
void selectLineWithContents(const QString &text); void selectLineWithContents(const QString &text);
void setToFinalState(); void setToFinalState();

View File

@@ -57,8 +57,10 @@
#include <X11/Xlib.h> #include <X11/Xlib.h>
#endif #endif
static const char *crashHandlerPathC; static const char *appNameC = nullptr;
static void *signalHandlerStack; static const char *disableRestartOptionC = nullptr;
static const char *crashHandlerPathC = nullptr;
static void *signalHandlerStack = nullptr;
extern "C" void signalHandler(int signal) extern "C" void signalHandler(int signal)
{ {
@@ -72,7 +74,12 @@ extern "C" void signalHandler(int signal)
case -1: // error case -1: // error
break; break;
case 0: // child case 0: // child
execl(crashHandlerPathC, crashHandlerPathC, strsignal(signal), (char *) 0); if (disableRestartOptionC) {
execl(crashHandlerPathC, crashHandlerPathC, strsignal(signal), appNameC,
disableRestartOptionC, (char *) 0);
} else {
execl(crashHandlerPathC, crashHandlerPathC, strsignal(signal), appNameC, (char *) 0);
}
_exit(EXIT_FAILURE); _exit(EXIT_FAILURE);
default: // parent default: // parent
prctl(PR_SET_PTRACER, pid, 0, 0, 0); prctl(PR_SET_PTRACER, pid, 0, 0, 0);
@@ -83,14 +90,23 @@ extern "C" void signalHandler(int signal)
} }
#endif // BUILD_CRASH_HANDLER #endif // BUILD_CRASH_HANDLER
CrashHandlerSetup::CrashHandlerSetup() CrashHandlerSetup::CrashHandlerSetup(const QString &appName,
RestartCapability restartCap,
const QString &executableDirPath)
{ {
#ifdef BUILD_CRASH_HANDLER #ifdef BUILD_CRASH_HANDLER
if (qgetenv("QTC_USE_CRASH_HANDLER").isEmpty()) if (qgetenv("QTC_USE_CRASH_HANDLER").isEmpty())
return; return;
const QString crashHandlerPath = qApp->applicationDirPath() appNameC = qstrdup(qPrintable(appName));
+ QLatin1String("/qtcreator_crash_handler");
if (restartCap == DisableRestart)
disableRestartOptionC = "--disable-restart";
const QString execDirPath = executableDirPath.isEmpty()
? qApp->applicationDirPath()
: executableDirPath;
const QString crashHandlerPath = execDirPath + QLatin1String("/qtcreator_crash_handler");
crashHandlerPathC = qstrdup(qPrintable(crashHandlerPath)); crashHandlerPathC = qstrdup(qPrintable(crashHandlerPath));
// Setup an alternative stack for the signal handler. This way we are able to handle SIGSEGV // Setup an alternative stack for the signal handler. This way we are able to handle SIGSEGV
@@ -137,6 +153,7 @@ CrashHandlerSetup::~CrashHandlerSetup()
{ {
#ifdef BUILD_CRASH_HANDLER #ifdef BUILD_CRASH_HANDLER
delete[] crashHandlerPathC; delete[] crashHandlerPathC;
delete[] appNameC;
free(signalHandlerStack); free(signalHandlerStack);
#endif #endif
} }

View File

@@ -25,9 +25,15 @@
#pragma once #pragma once
#include <QString>
class CrashHandlerSetup class CrashHandlerSetup
{ {
public: public:
CrashHandlerSetup(); enum RestartCapability { EnableRestart, DisableRestart };
CrashHandlerSetup(const QString &appName,
RestartCapability restartCap = EnableRestart,
const QString &executableDirPath = QString());
~CrashHandlerSetup(); ~CrashHandlerSetup();
}; };

View File

@@ -27,6 +27,7 @@
#include "utils.h" #include "utils.h"
#include <QApplication> #include <QApplication>
#include <QCommandLineParser>
#include <QFile> #include <QFile>
#include <QProcess> #include <QProcess>
#include <QString> #include <QString>
@@ -37,27 +38,52 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
// Called by signal handler of qtcreator. static void printErrorAndExit()
// Usage: $0 <name of signal causing the crash> {
QTextStream err(stderr);
err << QString::fromLatin1("This crash handler will be called by Qt Creator itself. "
"Do not call this manually.\n");
exit(EXIT_FAILURE);
}
static bool hasProcessOriginFromQtCreatorBuildDir(Q_PID pid)
{
const QString executable = QFile::symLinkTarget(QString::fromLatin1("/proc/%1/exe")
.arg(QString::number(pid)));
return executable.contains("qtcreator");
}
// Called by signal handler of crashing application.
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QApplication app(argc, argv); QApplication app(argc, argv);
app.setApplicationName(QLatin1String(APPLICATION_NAME)); app.setApplicationName(QLatin1String(APPLICATION_NAME));
app.setWindowIcon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical)); app.setWindowIcon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical));
// Parse arguments.
QCommandLineParser parser;
parser.addPositionalArgument("signal-name", QString());
parser.addPositionalArgument("app-name", QString());
const QCommandLineOption disableRestartOption("disable-restart");
parser.addOption(disableRestartOption);
parser.process(app);
// Check usage. // Check usage.
const QStringList positionalArguments = parser.positionalArguments();
if (positionalArguments.size() != 2)
printErrorAndExit();
Q_PID parentPid = getppid(); Q_PID parentPid = getppid();
QString parentExecutable = QFile::symLinkTarget(QString::fromLatin1("/proc/%1/exe") if (!hasProcessOriginFromQtCreatorBuildDir(parentPid))
.arg(QString::number(parentPid))); printErrorAndExit();
if (argc > 2 || !parentExecutable.contains(QLatin1String("qtcreator"))) {
QTextStream err(stderr);
err << QString::fromLatin1("This crash handler will be called by Qt Creator itself. "
"Do not call this manually.\n");
return EXIT_FAILURE;
}
// Run. // Run.
CrashHandler *crashHandler = new CrashHandler(parentPid, app.arguments().at(1)); const QString signalName = parser.positionalArguments().at(0);
const QString appName = parser.positionalArguments().at(1);
CrashHandler::RestartCapability restartCap = CrashHandler::EnableRestart;
if (parser.isSet(disableRestartOption))
restartCap = CrashHandler::DisableRestart;
CrashHandler *crashHandler = new CrashHandler(parentPid, signalName, appName, restartCap);
crashHandler->run(); crashHandler->run();
return app.exec(); return app.exec();

View File

@@ -2,7 +2,6 @@ QTC_LIB_DEPENDS += utils
include(../../qtcreatortool.pri) include(../../qtcreatortool.pri)
DESTDIR = $$IDE_BIN_PATH
TARGET = qtcreator_crash_handler TARGET = qtcreator_crash_handler
SOURCES += \ SOURCES += \

View File

@@ -3,7 +3,6 @@ import qbs 1.0
QtcTool { QtcTool {
name: "qtcreator_crash_handler" name: "qtcreator_crash_handler"
condition: qbs.targetOS.contains("linux") && qbs.buildVariant == "debug" condition: qbs.targetOS.contains("linux") && qbs.buildVariant == "debug"
installDir: qtc.ide_bin_path
Depends { name: "Utils" } Depends { name: "Utils" }
Depends { name: "Qt.widgets" } Depends { name: "Qt.widgets" }