diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/app.pro | 7 | ||||
-rw-r--r-- | src/app/main.cpp | 7 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/backtracecollector.cpp | 119 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/backtracecollector.h | 63 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandler.cpp | 250 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandler.h | 66 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp | 132 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandlerdialog.h | 67 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui | 122 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandlersetup.cpp | 133 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/crashhandlersetup.h | 37 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/main.cpp | 68 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro | 26 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs | 28 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/utils.cpp | 44 | ||||
-rw-r--r-- | src/tools/qtcreatorcrashhandler/utils.h | 42 | ||||
-rw-r--r-- | src/tools/tools.pro | 8 |
17 files changed, 1217 insertions, 2 deletions
diff --git a/src/app/app.pro b/src/app/app.pro index 896b2cc884..85831adb6f 100644 --- a/src/app/app.pro +++ b/src/app/app.pro @@ -6,6 +6,13 @@ TARGET = $$IDE_APP_TARGET DESTDIR = $$IDE_APP_PATH SOURCES += main.cpp +linux-* { + # Build only in debug mode. + debug_and_release|CONFIG(debug, debug|release) { + HEADERS += ../tools/qtcreatorcrashhandler/crashhandlersetup.h + SOURCES += ../tools/qtcreatorcrashhandler/crashhandlersetup.cpp + } +} include(../rpath.pri) diff --git a/src/app/main.cpp b/src/app/main.cpp index 526ca15477..e96692b04f 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -29,6 +29,7 @@ **************************************************************************/ #include "qtsingleapplication.h" +#include "../tools/qtcreatorcrashhandler/crashhandlersetup.h" #include <app/app_version.h> #include <extensionsystem/iplugin.h> @@ -228,6 +229,8 @@ int main(int argc, char **argv) const int threadCount = QThreadPool::globalInstance()->maxThreadCount(); QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount)); + setupCrashHandler(); // Display a backtrace once a serious signal is delivered. + #ifdef ENABLE_QT_BREAKPAD QtSystemExceptionHandler systemExceptionHandler; #endif @@ -432,5 +435,7 @@ int main(int argc, char **argv) QTimer::singleShot(100, &pluginManager, SLOT(startTests())); #endif - return app.exec(); + const int r = app.exec(); + cleanupCrashHandler(); + return r; } diff --git a/src/tools/qtcreatorcrashhandler/backtracecollector.cpp b/src/tools/qtcreatorcrashhandler/backtracecollector.cpp new file mode 100644 index 0000000000..df8500000f --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/backtracecollector.cpp @@ -0,0 +1,119 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#include "backtracecollector.h" + +#include <QDebug> +#include <QScopedPointer> +#include <QTemporaryFile> + +const char GdbBatchCommands[] = + "set height 0\n" + "set width 0\n" + "thread\n" + "thread apply all backtrace full\n"; + +class BacktraceCollectorPrivate +{ +public: + BacktraceCollectorPrivate() : errorOccurred(false) {} + + bool errorOccurred; + QScopedPointer<QTemporaryFile> commandFile; + QProcess debugger; + QString output; +}; + +BacktraceCollector::BacktraceCollector(QObject *parent) : + QObject(parent), d(new BacktraceCollectorPrivate) +{ + connect(&d->debugger, SIGNAL(finished(int,QProcess::ExitStatus)), + SLOT(onDebuggerFinished(int,QProcess::ExitStatus))); + connect(&d->debugger, SIGNAL(error(QProcess::ProcessError)), + SLOT(onDebuggerError(QProcess::ProcessError))); + connect(&d->debugger, SIGNAL(readyRead()), SLOT(onDebuggerOutputAvailable())); + d->debugger.setProcessChannelMode(QProcess::MergedChannels); +} + +BacktraceCollector::~BacktraceCollector() +{ + delete d; +} + +void BacktraceCollector::run(Q_PID pid) +{ + d->debugger.start(QLatin1String("gdb"), QStringList() + << QLatin1String("--nw") // Do not use a window interface. + << QLatin1String("--nx") // Do not read .gdbinit file. + << QLatin1String("--batch") // Exit after processing options. + << QLatin1String("--command") << createTemporaryCommandFile() + << QLatin1String("--pid") << QString::number(pid) + ); +} + +void BacktraceCollector::onDebuggerFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/) +{ + if (d->errorOccurred) { + emit error(QLatin1String("QProcess: ") + d->debugger.errorString()); + return; + } + if (exitCode != 0) { + emit error(QString::fromLatin1("Debugger exited with code %1.").arg(exitCode)); + return; + } + emit backtrace(d->output); +} + +void BacktraceCollector::onDebuggerError(QProcess::ProcessError /*error*/) +{ + d->errorOccurred = true; +} + +QString BacktraceCollector::createTemporaryCommandFile() +{ + d->commandFile.reset(new QTemporaryFile); + if (!d->commandFile->open()) { + emit error(QLatin1String("Error: Could not create temporary command file.")); + return QString(); + } + if (d->commandFile->write(GdbBatchCommands) == -1) { + emit error(QLatin1String("Error: Could not write temporary command file.")); + return QString(); + } + d->commandFile->close(); + return d->commandFile->fileName(); +} + +void BacktraceCollector::onDebuggerOutputAvailable() +{ + const QString newChunk = d->debugger.readAll(); + d->output.append(newChunk); + emit backtraceChunk(newChunk); +} diff --git a/src/tools/qtcreatorcrashhandler/backtracecollector.h b/src/tools/qtcreatorcrashhandler/backtracecollector.h new file mode 100644 index 0000000000..151d7c09d1 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/backtracecollector.h @@ -0,0 +1,63 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#ifndef BACKTRACECOLLECTOR_H +#define BACKTRACECOLLECTOR_H + +#include <QProcess> + +class BacktraceCollectorPrivate; + +class BacktraceCollector : public QObject +{ + Q_OBJECT +public: + explicit BacktraceCollector(QObject *parent = 0); + ~BacktraceCollector(); + + void run(Q_PID pid); + +signals: + void error(const QString &errorMessage); + void backtrace(const QString &backtrace); + void backtraceChunk(const QString &chunk); + +private slots: + void onDebuggerOutputAvailable(); + void onDebuggerFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onDebuggerError(QProcess::ProcessError err); + +private: + QString createTemporaryCommandFile(); + + BacktraceCollectorPrivate *d; +}; + +#endif // BACKTRACECOLLECTOR_H diff --git a/src/tools/qtcreatorcrashhandler/crashhandler.cpp b/src/tools/qtcreatorcrashhandler/crashhandler.cpp new file mode 100644 index 0000000000..dfc5e5012f --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandler.cpp @@ -0,0 +1,250 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#include "crashhandler.h" +#include "crashhandlerdialog.h" +#include "backtracecollector.h" +#include "utils.h" + +#include <QApplication> +#include <QDebug> +#include <QDesktopServices> +#include <QFile> +#include <QRegExp> +#include <QTextStream> +#include <QUrl> + +#include <stdio.h> +#include <stdlib.h> + +#include <errno.h> +#include <unistd.h> + +#include <sys/types.h> + +static const char FileDistroInformation[] = "/etc/lsb-release"; +static const char FileKernelVersion[] = "/proc/version"; + +static QString collectLinuxDistributionInfo() +{ + return QString::fromLatin1(fileContents(QLatin1String(FileDistroInformation))); +} + +static QString collectKernelVersionInfo() +{ + return QString::fromLatin1(fileContents(QLatin1String(FileKernelVersion))); +} + +class CrashHandlerPrivate +{ +public: + CrashHandlerPrivate(pid_t pid, CrashHandler *crashHandler) + : pid(pid), dialog(crashHandler), argv(0), envp(0) {} + + ~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); + } + + pid_t pid; + BacktraceCollector backtraceCollector; + CrashHandlerDialog dialog; + + // For restarting the process. + char **argv; + char **envp; +}; + +CrashHandler::CrashHandler(pid_t pid, QObject *parent) + : QObject(parent), d(new CrashHandlerPrivate(pid, this)) +{ + connect(&d->backtraceCollector, SIGNAL(error(QString)), SLOT(onError(QString))); + connect(&d->backtraceCollector, SIGNAL(backtraceChunk(QString)), SLOT(onBacktraceChunk(QString))); + connect(&d->backtraceCollector, SIGNAL(backtrace(QString)), SLOT(onBacktraceFinished(QString))); + + d->dialog.appendDebugInfo(collectKernelVersionInfo()); + d->dialog.appendDebugInfo(collectLinuxDistributionInfo()); + d->dialog.show(); + + if (!collectRestartAppData()) // If we can't restart the app properly, ... + d->dialog.disableRestartAppButton(); +} + +CrashHandler::~CrashHandler() +{ + delete d; +} + +void CrashHandler::run() +{ + d->backtraceCollector.run(d->pid); +} + +void CrashHandler::onError(const QString &errorMessage) +{ + d->dialog.setToFinalState(); + + QTextStream(stderr) << errorMessage; + const QString text = QLatin1String("There occured a problem providing the backtrace. " + "Please make sure to have the debugger \"gdb\" installed.\n"); + d->dialog.appendDebugInfo(text); + d->dialog.appendDebugInfo(errorMessage); +} + +void CrashHandler::onBacktraceChunk(const QString &chunk) +{ + d->dialog.appendDebugInfo(chunk); + QTextStream out(stdout); + out << chunk; +} + +void CrashHandler::onBacktraceFinished(const QString &backtrace) +{ + d->dialog.setToFinalState(); + + // Select first line of relevant thread. + + // Example debugger output: + // ... + // [Current thread is 1 (Thread 0x7f1c33c79780 (LWP 975))] + // ... + // Thread 1 (Thread 0x7f1c33c79780 (LWP 975)): + // ... + QRegExp rx("\\[Current thread is (\\d+)"); + const int pos = rx.indexIn(backtrace); + if (pos == -1) + return; + const QString threadNumber = rx.cap(1); + const QString textToSelect = QString::fromLatin1("Thread %1").arg(threadNumber); + d->dialog.selectLineWithContents(textToSelect); +} + +void CrashHandler::openBugTracker() +{ + QDesktopServices::openUrl(QUrl(URL_BUGTRACKER)); +} + +bool CrashHandler::collectRestartAppData() +{ + const QString procDir = QString::fromLatin1("/proc/%1").arg(d->pid); + + // Construct d->argv. + // 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) { + 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. + // man 5 proc: /proc/[pid]/environ + // 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()) { + 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; + + return true; +} + +void CrashHandler::restartApplication() +{ + // 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 + // QProcess::startDetached does not support setting an environment for the new process + // + // therefore, we use fork-exec. + + pid_t pid = fork(); + switch (pid) { + 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 + // 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)); + if (freopen("/dev/null", "w", stdout) == 0) + qFatal("%s: freopen() failed for stdout: %s.\n", Q_FUNC_INFO, strerror(errno)); + 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); + _exit(EXIT_FAILURE); + default: // parent + qApp->quit(); + break; + } +} diff --git a/src/tools/qtcreatorcrashhandler/crashhandler.h b/src/tools/qtcreatorcrashhandler/crashhandler.h new file mode 100644 index 0000000000..f834befb75 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandler.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#ifndef CRASHHANDLER_H +#define CRASHHANDLER_H + +#include <QObject> + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +class ApplicationInfo; +class CrashHandlerPrivate; + +class CrashHandler : public QObject +{ + Q_OBJECT +public: + explicit CrashHandler(pid_t pid, QObject *parent = 0); + ~CrashHandler(); + + void run(); + +public slots: + void onError(const QString &errorMessage); + void onBacktraceChunk(const QString &chunk); + void onBacktraceFinished(const QString &backtrace); + + void openBugTracker(); + void restartApplication(); + +private: + bool collectRestartAppData(); + + CrashHandlerPrivate *d; +}; + +#endif // CRASHHANDLER_H diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp new file mode 100644 index 0000000000..d1ff87d270 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.cpp @@ -0,0 +1,132 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#include "crashhandler.h" +#include "crashhandlerdialog.h" +#include "ui_crashhandlerdialog.h" +#include "utils.h" + +#include <app/app_version.h> + +#include <QClipboard> +#include <QIcon> + +CrashHandlerDialog::CrashHandlerDialog(CrashHandler *handler, QWidget *parent) : + QDialog(parent), + m_crashHandler(handler), + m_ui(new Ui::CrashHandlerDialog) +{ + m_ui->setupUi(this); + m_ui->introLabel->setTextFormat(Qt::RichText); + m_ui->introLabel->setOpenExternalLinks(true); + m_ui->debugInfoEdit->setReadOnly(true); + m_ui->progressBar->setMinimum(0); + m_ui->progressBar->setMaximum(0); + + const QStyle * const style = QApplication::style(); + m_ui->closeButton->setIcon(style->standardIcon(QStyle::SP_DialogCloseButton)); + + const int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0); + QIcon icon = style->standardIcon(QStyle::SP_MessageBoxCritical); + m_ui->iconLabel->setPixmap(icon.pixmap(iconSize, iconSize)); + + 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())); + + setApplicationInfo(); +} + +CrashHandlerDialog::~CrashHandlerDialog() +{ + delete m_ui; +} + +void CrashHandlerDialog::setToFinalState() +{ + m_ui->progressBar->hide(); + m_ui->copyToClipBoardButton->setEnabled(true); + m_ui->reportBugButton->setEnabled(true); +} + +void CrashHandlerDialog::disableRestartAppButton() +{ + m_ui->restartAppButton->setDisabled(true); +} + +void CrashHandlerDialog::setApplicationInfo() +{ + const QString ideName = QLatin1String("Qt Creator"); + const QString contents = tr( + "<p><b>%1 has closed unexpectedly.</b></p>" + "<p>Please file a <a href='%2'>bug report</a> with the debug information provided below.</p>") + .arg(ideName, QLatin1String(URL_BUGTRACKER)); + m_ui->introLabel->setText(contents); + + QString revision; +#ifdef IDE_REVISION + revision = tr(" from revision %1").arg(QString::fromLatin1(Core::Constants::IDE_REVISION_STR).left(10)); +#endif + const QString versionInformation = tr( + "%1 %2%3, built on %4 at %5, based on Qt %6 (%7 bit)\n") + .arg(ideName, QLatin1String(Core::Constants::IDE_VERSION_LONG), revision, + QLatin1String(__DATE__), QLatin1String(__TIME__), QLatin1String(QT_VERSION_STR), + QString::number(QSysInfo::WordSize)); + m_ui->debugInfoEdit->append(versionInformation); +} + +void CrashHandlerDialog::appendDebugInfo(const QString &chunk) +{ + m_ui->debugInfoEdit->append(chunk); +} + +void CrashHandlerDialog::selectLineWithContents(const QString &text) +{ + // The selected line will be the first line visible. + + // Go to end. + QTextCursor cursor = m_ui->debugInfoEdit->textCursor(); + cursor.movePosition(QTextCursor::End); + m_ui->debugInfoEdit->setTextCursor(cursor); + + // Find text by searching backwards. + m_ui->debugInfoEdit->find(text, QTextDocument::FindCaseSensitively | QTextDocument::FindBackward); + + // Highlight whole line. + cursor = m_ui->debugInfoEdit->textCursor(); + cursor.select(QTextCursor::LineUnderCursor); + m_ui->debugInfoEdit->setTextCursor(cursor); +} + +void CrashHandlerDialog::copyToClipboardClicked() +{ + QApplication::clipboard()->setText(m_ui->debugInfoEdit->toPlainText()); +} diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h new file mode 100644 index 0000000000..ecee4cffa9 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#ifndef CRASHHANDLERDIALOG_H +#define CRASHHANDLERDIALOG_H + +#include <QDialog> + +QT_BEGIN_NAMESPACE +namespace Ui { +class CrashHandlerDialog; +} +QT_END_NAMESPACE + +class CrashHandler; + +class CrashHandlerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CrashHandlerDialog(CrashHandler *handler, QWidget *parent = 0); + ~CrashHandlerDialog(); + +public: + void disableRestartAppButton(); + void setApplicationInfo(); + void setToFinalState(); + void appendDebugInfo(const QString &chunk); + void selectLineWithContents(const QString &text); + +private slots: + void copyToClipboardClicked(); + +private: + CrashHandler *m_crashHandler; + Ui::CrashHandlerDialog *m_ui; +}; + +#endif // CRASHHANDLERDIALOG_H diff --git a/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui new file mode 100644 index 0000000000..101efd2e48 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandlerdialog.ui @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CrashHandlerDialog</class> + <widget class="QDialog" name="CrashHandlerDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>800</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>400</width> + <height>300</height> + </size> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="QLabel" name="iconLabel"> + <property name="text"> + <string>Icon</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="introLabel"> + <property name="text"> + <string>Some useful information here...</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>24</number> + </property> + </widget> + </item> + <item> + <widget class="QTextEdit" name="debugInfoEdit"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="copyToClipBoardButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>C&opy to clipboard</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="reportBugButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Report this &bug</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="restartAppButton"> + <property name="text"> + <string>&Restart Qt Creator</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="closeButton"> + <property name="text"> + <string>&Close</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/tools/qtcreatorcrashhandler/crashhandlersetup.cpp b/src/tools/qtcreatorcrashhandler/crashhandlersetup.cpp new file mode 100644 index 0000000000..658429aabb --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandlersetup.cpp @@ -0,0 +1,133 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#include "crashhandlersetup.h" + +#include <QtGlobal> + +#if !defined(QT_NO_DEBUG) && defined(Q_OS_LINUX) +#define BUILD_CRASH_HANDLER +#endif + +#ifdef BUILD_CRASH_HANDLER + +#include <QApplication> +#include <QDebug> +#include <QString> + +#include <stdlib.h> + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#ifdef Q_WS_X11 +#include <qx11info_x11.h> +#include <X11/Xlib.h> +#endif + +static const char *crashHandlerPathC; +static void *signalHandlerStack; + +extern "C" void signalHandler(int /*signal*/) +{ +#ifdef Q_WS_X11 + // Kill window since it's frozen anyway. + if (QX11Info::display()) + close(ConnectionNumber(QX11Info::display())); +#endif + pid_t pid = fork(); + switch (pid) { + case -1: // error + break; + case 0: // child + execl(crashHandlerPathC, crashHandlerPathC, (char *) 0); + _exit(EXIT_FAILURE); + default: // parent + waitpid(pid, 0, 0); + _exit(EXIT_FAILURE); + break; + } +} +#endif // BUILD_CRASH_HANDLER + +void setupCrashHandler() +{ +#ifdef BUILD_CRASH_HANDLER + const QString crashHandlerPath = qApp->applicationDirPath() + + QLatin1String("/qtcreator_crash_handler"); + crashHandlerPathC = qstrdup(qPrintable(crashHandlerPath)); + + // Setup an alternative stack for the signal handler. This way we are able to handle SIGSEGV + // even if the normal process stack is exhausted. + stack_t ss; + ss.ss_sp = signalHandlerStack = malloc(SIGSTKSZ); // Usual requirements for alternative signal stack. + if (ss.ss_sp == 0) { + qWarning("Warning: Could not allocate space for alternative signal stack (%s).", Q_FUNC_INFO); + return; + } + ss.ss_size = SIGSTKSZ; + ss.ss_flags = 0; + if (sigaltstack(&ss, 0) == -1) { + qWarning("Warning: Failed to set alternative signal stack (%s).", Q_FUNC_INFO); + return; + } + + // Install signal handler for calling the crash handler. + struct sigaction sa; + if (sigemptyset(&sa.sa_mask) == -1) { + qWarning("Warning: Failed to empty signal set (%s).", Q_FUNC_INFO); + return; + } + sa.sa_handler = &signalHandler; + // SA_RESETHAND - Restore signal action to default after signal handler has been called. + // SA_NODEFER - Don't block the signal after it was triggered (otherwise blocked signals get + // inherited via fork() and execve()). Without this the signal will not be delivered to the + // restarted Qt Creator. + // SA_ONSTACK - Use alternative stack. + sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_ONSTACK; + const int signalsToHandle[] = { SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGPIPE, 0 }; + for (int i = 0; signalsToHandle[i]; ++i) { + if (sigaction(signalsToHandle[i], &sa, 0) == -1 ) + qWarning("Warning: Failed to install signal handler for SIGILL (%s).", Q_FUNC_INFO); + } +#endif // BUILD_CRASH_HANDLER +} + +void cleanupCrashHandler() +{ +#ifdef BUILD_CRASH_HANDLER + delete[] crashHandlerPathC; + free(signalHandlerStack); +#endif +} diff --git a/src/tools/qtcreatorcrashhandler/crashhandlersetup.h b/src/tools/qtcreatorcrashhandler/crashhandlersetup.h new file mode 100644 index 0000000000..698cd5f9a6 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/crashhandlersetup.h @@ -0,0 +1,37 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#ifndef CRASHHANDLERSETUP_H +#define CRASHHANDLERSETUP_H + +void setupCrashHandler(); +void cleanupCrashHandler(); + +#endif // CRASHHANDLERSETUP_H diff --git a/src/tools/qtcreatorcrashhandler/main.cpp b/src/tools/qtcreatorcrashhandler/main.cpp new file mode 100644 index 0000000000..94c21db2db --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/main.cpp @@ -0,0 +1,68 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#include "crashhandler.h" +#include "utils.h" + +#include <QApplication> +#include <QFile> +#include <QProcess> +#include <QString> +#include <QStyle> +#include <QTextStream> + +#include <stdlib.h> + +#include <sys/types.h> +#include <unistd.h> + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + app.setApplicationName(QLatin1String(APPLICATION_NAME)); + app.setWindowIcon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical)); + + // Check usage. + Q_PID parentPid = getppid(); + QString parentExecutable = QFile::symLinkTarget(QString::fromLatin1("/proc/%1/exe") + .arg(QString::number(parentPid))); + if (argc > 1 || !parentExecutable.contains("qtcreator")) { + QTextStream err(stderr); + err << QString::fromLatin1("This crash handler will be called by Qt Creator itself. " + "Don't call this manually.\n"); + return EXIT_FAILURE; + } + + // Run. + CrashHandler *crashHandler = new CrashHandler(parentPid); + crashHandler->run(); + + return app.exec(); +} diff --git a/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro new file mode 100644 index 0000000000..551de3008d --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.pro @@ -0,0 +1,26 @@ +include(../../../qtcreator.pri) + +TARGET = qtcreator_crash_handler +DESTDIR = $$IDE_BIN_PATH + +CONFIG -= app_bundle +TEMPLATE = app + +SOURCES += \ + main.cpp \ + backtracecollector.cpp \ + crashhandlerdialog.cpp \ + crashhandler.cpp \ + utils.cpp + +HEADERS += \ + backtracecollector.h \ + crashhandlerdialog.h \ + crashhandler.h \ + utils.h + +FORMS += \ + crashhandlerdialog.ui + +target.path = /bin +INSTALLS += target diff --git a/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs new file mode 100644 index 0000000000..820f26d68c --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/qtcreatorcrashhandler.qbs @@ -0,0 +1,28 @@ +import qbs.base 1.0 +import "../QtcTool.qbs" as QtcTool + +QtcTool { + name: "qtcreator_crash_handler" + condition: qbs.targetOS == "linux" && qbs.buildVariant == "debug" + + cpp.includePaths: [ + buildDirectory + ] + + Depends { name: "cpp" } + Depends { name: "Qt.widgets" } + Depends { name: "app_version_header" } + + files: [ + "main.cpp", + "backtracecollector.cpp", + "backtracecollector.h", + "crashhandlerdialog.cpp", + "crashhandlerdialog.h", + "crashhandler.cpp", + "crashhandler.h", + "utils.cpp", + "utils.h", + "crashhandlerdialog.ui" + ] +} diff --git a/src/tools/qtcreatorcrashhandler/utils.cpp b/src/tools/qtcreatorcrashhandler/utils.cpp new file mode 100644 index 0000000000..aca361bab7 --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/utils.cpp @@ -0,0 +1,44 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#include "utils.h" + +#include <QDebug> +#include <QFile> + +QByteArray fileContents(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Warning: Could not open '%s'.", qPrintable(filePath)); + return QByteArray(); + } + return file.readAll(); +} diff --git a/src/tools/qtcreatorcrashhandler/utils.h b/src/tools/qtcreatorcrashhandler/utils.h new file mode 100644 index 0000000000..0979d248de --- /dev/null +++ b/src/tools/qtcreatorcrashhandler/utils.h @@ -0,0 +1,42 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: http://www.qt-project.org/ +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +**************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#include <QByteArray> +#include <QString> + +const char APPLICATION_NAME[] = "Qt Creator Crash Handler"; +const char URL_BUGTRACKER[] = "https://bugreports.qt-project.org/"; + +QByteArray fileContents(const QString &filePath); + +#endif // UTILS_H diff --git a/src/tools/tools.pro b/src/tools/tools.pro index dcbb5bb6de..879144711b 100644 --- a/src/tools/tools.pro +++ b/src/tools/tools.pro @@ -21,5 +21,11 @@ win32 { QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH) !isEmpty(QT_BREAKPAD_ROOT_PATH) { SUBDIRS += qtcrashhandler +} else { + linux-* { + # Build only in debug mode. + debug_and_release|CONFIG(debug, debug|release) { + SUBDIRS += qtcreatorcrashhandler + } + } } - |