forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
|
|
||||||
|
@@ -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: [
|
||||||
|
@@ -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;
|
||||||
|
@@ -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();
|
||||||
|
@@ -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();
|
||||||
|
@@ -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);
|
||||||
|
@@ -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();
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
};
|
};
|
||||||
|
@@ -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();
|
||||||
|
@@ -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 += \
|
||||||
|
@@ -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" }
|
||||||
|
Reference in New Issue
Block a user