diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp index 3e8304cc..a92091fa 100644 --- a/src/backtracewidget.cpp +++ b/src/backtracewidget.cpp @@ -1,409 +1,415 @@ /******************************************************************* * backtracewidget.cpp * Copyright 2009,2010 Dario Andres Rodriguez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "backtracewidget.h" #include #include #include #include #include #include "drkonqi.h" #include "backtraceratingwidget.h" #include "crashedapplication.h" #include "backtracegenerator.h" #include "parser/backtraceparser.h" #include "drkonqi_globals.h" #include "debuggermanager.h" #include "gdbhighlighter.h" static const char extraDetailsLabelMargin[] = " margin: 5px; "; BacktraceWidget::BacktraceWidget(BacktraceGenerator *generator, QWidget *parent, bool showToggleBacktrace) : QWidget(parent), m_btGenerator(generator), m_highlighter(nullptr) { ui.setupUi(this); //Debug package installer m_debugPackageInstaller = new DebugPackageInstaller(this); connect(m_debugPackageInstaller, &DebugPackageInstaller::error, this, &BacktraceWidget::debugPackageError); connect(m_debugPackageInstaller, &DebugPackageInstaller::packagesInstalled, this, &BacktraceWidget::regenerateBacktrace); connect(m_debugPackageInstaller, &DebugPackageInstaller::canceled, this, &BacktraceWidget::debugPackageCanceled); connect(m_btGenerator, &BacktraceGenerator::starting, this, &BacktraceWidget::setAsLoading); connect(m_btGenerator, &BacktraceGenerator::done, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::someError, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::failedToStart, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::newLine, this, &BacktraceWidget::backtraceNewLine); connect(ui.m_extraDetailsLabel, &QLabel::linkActivated, this, &BacktraceWidget::extraDetailsLinkActivated); ui.m_extraDetailsLabel->setVisible(false); ui.m_extraDetailsLabel->setStyleSheet(QLatin1String(extraDetailsLabelMargin)); //Setup the buttons KGuiItem::assign(ui.m_reloadBacktraceButton, KGuiItem2(i18nc("@action:button", "&Reload"), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to " "reload the crash information (backtrace). This is useful when you have " "installed the proper debug symbol packages and you want to obtain " "a better backtrace."))); connect(ui.m_reloadBacktraceButton, &QPushButton::clicked, this, &BacktraceWidget::regenerateBacktrace); KGuiItem::assign(ui.m_installDebugButton, KGuiItem2(i18nc("@action:button", "&Install Debug Symbols"), QIcon::fromTheme(QStringLiteral("system-software-update")), i18nc("@info:tooltip", "Use this button to " "install the missing debug symbols packages."))); ui.m_installDebugButton->setVisible(false); connect(ui.m_installDebugButton, &QPushButton::clicked, this, &BacktraceWidget::installDebugPackages); + if (DrKonqi::crashedApplication()->hasDeletedFiles()) { + ui.m_installDebugButton->setEnabled(false); + ui.m_installDebugButton->setToolTip(i18nc("@info:tooltip", + "Symbol installation is unavailable because the application " + "was updated or uninstalled after it had been started.")); + } KGuiItem::assign(ui.m_copyButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@info:tooltip", "Use this button to copy the " "crash information (backtrace) to the clipboard."))); connect(ui.m_copyButton, &QPushButton::clicked, this, &BacktraceWidget::copyClicked); ui.m_copyButton->setEnabled(false); KGuiItem::assign(ui.m_saveButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@info:tooltip", "Use this button to save the " "crash information (backtrace) to a file. This is useful " "if you want to take a look at it or to report the bug " "later."))); connect(ui.m_saveButton, &QPushButton::clicked, this, &BacktraceWidget::saveClicked); ui.m_saveButton->setEnabled(false); //Create the rating widget m_backtraceRatingWidget = new BacktraceRatingWidget(ui.m_statusWidget); ui.m_statusWidget->addCustomStatusWidget(m_backtraceRatingWidget); ui.m_statusWidget->setIdle(QString()); //Do we need the "Show backtrace" toggle action ? if (!showToggleBacktrace) { ui.mainLayout->removeWidget(ui.m_toggleBacktraceCheckBox); ui.m_toggleBacktraceCheckBox->setVisible(false); toggleBacktrace(true); } else { //Generate help widget ui.m_backtraceHelpLabel->setText( i18n("

What is a \"backtrace\" ?

A backtrace basically describes what was " "happening inside the application when it crashed, so the developers may track " "down where the mess started. They may look meaningless to you, but they might " "actually contain a wealth of useful information.
Backtraces are commonly " "used during interactive and post-mortem debugging.

")); ui.m_backtraceHelpIcon->setPixmap(QIcon::fromTheme(QStringLiteral("help-hint")).pixmap(48,48)); connect(ui.m_toggleBacktraceCheckBox, &QCheckBox::toggled, this, &BacktraceWidget::toggleBacktrace); toggleBacktrace(false); } ui.m_backtraceEdit->setFont( QFontDatabase::systemFont(QFontDatabase::FixedFont) ); } void BacktraceWidget::setAsLoading() { //remove the syntax highlighter delete m_highlighter; m_highlighter = nullptr; //Set the widget as loading and disable all the action buttons ui.m_backtraceEdit->setText(i18nc("@info:status", "Loading...")); ui.m_backtraceEdit->setEnabled(false); ui.m_statusWidget->setBusy(i18nc("@info:status", "Generating backtrace... (this may take some time)")); m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); m_backtraceRatingWidget->setState(BacktraceGenerator::Loading); ui.m_extraDetailsLabel->setVisible(false); ui.m_extraDetailsLabel->clear(); ui.m_installDebugButton->setVisible(false); ui.m_reloadBacktraceButton->setEnabled(false); ui.m_copyButton->setEnabled(false); ui.m_saveButton->setEnabled(false); } //Force backtrace generation void BacktraceWidget::regenerateBacktrace() { setAsLoading(); if (!DrKonqi::debuggerManager()->debuggerIsRunning()) { m_btGenerator->start(); } else { anotherDebuggerRunning(); } emit stateChanged(); } void BacktraceWidget::generateBacktrace() { if (m_btGenerator->state() == BacktraceGenerator::NotLoaded) { //First backtrace generation regenerateBacktrace(); } else if (m_btGenerator->state() == BacktraceGenerator::Loading) { //Set in loading state, the widget will catch the backtrace events anyway setAsLoading(); emit stateChanged(); } else { //*Finished* states setAsLoading(); emit stateChanged(); //Load already generated information loadData(); } } void BacktraceWidget::anotherDebuggerRunning() { //As another debugger is running, we should disable the actions and notify the user ui.m_backtraceEdit->setEnabled(false); ui.m_backtraceEdit->setText(i18nc("@info", "Another debugger is currently debugging the " "same application. The crash information could not be fetched.")); m_backtraceRatingWidget->setState(BacktraceGenerator::Failed); m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The crash information could not be fetched.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "Another debugging process is attached to " "the crashed application. Therefore, the DrKonqi debugger cannot " "fetch the backtrace. Please close the other debugger and " "click Reload.")); ui.m_installDebugButton->setVisible(false); ui.m_reloadBacktraceButton->setEnabled(true); } void BacktraceWidget::loadData() { //Load the backtrace data from the generator m_backtraceRatingWidget->setState(m_btGenerator->state()); if (m_btGenerator->state() == BacktraceGenerator::Loaded) { ui.m_backtraceEdit->setEnabled(true); ui.m_backtraceEdit->setPlainText(m_btGenerator->backtrace()); // scroll to crash QTextCursor crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("[KCrash Handler]")); if (crashCursor.isNull()) { crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("KCrash::defaultCrashHandler")); } if (!crashCursor.isNull()) { crashCursor.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor); ui.m_backtraceEdit->verticalScrollBar()->setValue(ui.m_backtraceEdit->cursorRect(crashCursor).top()); } // highlight if possible if (m_btGenerator->debugger().codeName() == QLatin1String("gdb")) { m_highlighter = new GdbHighlighter(ui.m_backtraceEdit->document(), m_btGenerator->parser()->parsedBacktraceLines()); } BacktraceParser * btParser = m_btGenerator->parser(); m_backtraceRatingWidget->setUsefulness(btParser->backtraceUsefulness()); //Generate the text to put in the status widget (backtrace usefulness) QString usefulnessText; switch (btParser->backtraceUsefulness()) { case BacktraceParser::ReallyUseful: usefulnessText = i18nc("@info", "The generated crash information is useful"); break; case BacktraceParser::MayBeUseful: usefulnessText = i18nc("@info", "The generated crash information may be useful"); break; case BacktraceParser::ProbablyUseless: usefulnessText = i18nc("@info", "The generated crash information is probably not useful"); break; case BacktraceParser::Useless: usefulnessText = i18nc("@info", "The generated crash information is not useful"); break; default: //let's hope nobody will ever see this... ;) usefulnessText = i18nc("@info", "The rating of this crash information is invalid. " "This is a bug in DrKonqi itself."); break; } ui.m_statusWidget->setIdle(usefulnessText); if (btParser->backtraceUsefulness() != BacktraceParser::ReallyUseful) { //Not a perfect bactrace, ask the user to try to improve it ui.m_extraDetailsLabel->setVisible(true); if (canInstallDebugPackages()) { //The script to install the debug packages is available ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You can click the " "Install Debug Symbols button in order to automatically " "install the missing debugging information packages. If this method " "does not work: please read How to " "create useful crash reports to learn how to get a useful " "backtrace; install the needed packages (" "list of files) and click the " "Reload button.", QLatin1String(TECHBASE_HOWTO_DOC), QLatin1String("#missingDebugPackages"))); ui.m_installDebugButton->setVisible(true); //Retrieve the libraries with missing debug info QStringList missingLibraries = btParser->librariesWithMissingDebugSymbols().toList(); m_debugPackageInstaller->setMissingLibraries(missingLibraries); } else { //No automated method to install the missing debug info //Tell the user to read the howto and reload ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "Please read How to " "create useful crash reports to learn how to get a useful " "backtrace; install the needed packages (" "list of files) and click the " "Reload button.", QLatin1String(TECHBASE_HOWTO_DOC), QLatin1String("#missingDebugPackages"))); } } ui.m_copyButton->setEnabled(true); ui.m_saveButton->setEnabled(true); } else if (m_btGenerator->state() == BacktraceGenerator::Failed) { //The backtrace could not be generated because the debugger had an error m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger has quit unexpectedly.")); ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You could try to regenerate the " "backtrace by clicking the Reload" " button.")); } else if (m_btGenerator->state() == BacktraceGenerator::FailedToStart) { //The backtrace could not be generated because the debugger could not start (missing) //Tell the user to install it. m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger application is missing or " "could not be launched.")); ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You need to first install the debugger " "application (%1) then click the Reload" " button.", m_btGenerator->debugger().displayName())); } ui.m_reloadBacktraceButton->setEnabled(true); emit stateChanged(); } void BacktraceWidget::backtraceNewLine(const QString & line) { // We absolutely must not have a highlighter attached. The highlighter has // a static list of lines to highlight from. When we are loading lines // this static list does not match reality breaking text length expectations // and resulting in segfaults. Q_ASSERT(!m_highlighter); //While loading the backtrace (unparsed) a new line was sent from the debugger, append it ui.m_backtraceEdit->append(line.trimmed()); } void BacktraceWidget::copyClicked() { ui.m_backtraceEdit->selectAll(); ui.m_backtraceEdit->copy(); } void BacktraceWidget::saveClicked() { DrKonqi::saveReport(m_btGenerator->backtrace(), this); } void BacktraceWidget::hilightExtraDetailsLabel(bool hilight) { const QString stylesheet = ((!hilight) ? QLatin1String("border-width: 0px;") : QLatin1String("border-width: 2px; " "border-style: solid; " "border-color: red;")) + QLatin1String(extraDetailsLabelMargin); ui.m_extraDetailsLabel->setStyleSheet(stylesheet); } void BacktraceWidget::focusImproveBacktraceButton() { ui.m_installDebugButton->setFocus(); } void BacktraceWidget::installDebugPackages() { ui.m_installDebugButton->setVisible(false); m_debugPackageInstaller->installDebugPackages(); } void BacktraceWidget::debugPackageError(const QString & errorMessage) { ui.m_installDebugButton->setVisible(true); KMessageBox::error(this, errorMessage, i18nc("@title:window", "Error during the installation of" " debug symbols")); } void BacktraceWidget::debugPackageCanceled() { ui.m_installDebugButton->setVisible(true); } bool BacktraceWidget::canInstallDebugPackages() const { return m_debugPackageInstaller->canInstallDebugPackages(); } void BacktraceWidget::toggleBacktrace(bool show) { ui.m_backtraceStack->setCurrentWidget(show ? ui.backtracePage : ui.backtraceHelpPage); } void BacktraceWidget::extraDetailsLinkActivated(QString link) { if (link.startsWith(QLatin1String("http"))) { //Open externally QDesktopServices::openUrl(QUrl(link)); } else if (link == QLatin1String("#missingDebugPackages")) { BacktraceParser * btParser = m_btGenerator->parser(); QStringList missingDbgForFiles = btParser->librariesWithMissingDebugSymbols().toList(); missingDbgForFiles.insert(0, DrKonqi::crashedApplication()->executable().absoluteFilePath()); //HTML message QString message = QStringLiteral("") + i18n("The packages containing debug information for the following application and libraries are missing:") + QStringLiteral("
    "); Q_FOREACH(const QString & string, missingDbgForFiles) { message += QLatin1String("
  • ") + string + QLatin1String("
  • "); } message += QStringLiteral("
"); KMessageBox::information(this, message, i18nc("messagebox title","Missing debug information packages")); } } diff --git a/src/crashedapplication.cpp b/src/crashedapplication.cpp index 2d6a3619..3b59549b 100644 --- a/src/crashedapplication.cpp +++ b/src/crashedapplication.cpp @@ -1,179 +1,184 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "crashedapplication.h" #if HAVE_STRSIGNAL && defined(Q_OS_UNIX) # include # include # include #else # if defined(Q_OS_UNIX) # include # else # include # endif #endif #include CrashedApplication::CrashedApplication(QObject *parent) : QObject(parent), m_restarted(false) { } CrashedApplication::~CrashedApplication() { } QString CrashedApplication::name() const { return m_name.isEmpty() ? fakeExecutableBaseName() : m_name; } QFileInfo CrashedApplication::executable() const { return m_executable; } QString CrashedApplication::fakeExecutableBaseName() const { if (!m_fakeBaseName.isEmpty()) { return m_fakeBaseName; } else { return m_executable.baseName(); } } QString CrashedApplication::version() const { return m_version; } BugReportAddress CrashedApplication::bugReportAddress() const { return m_reportAddress; } int CrashedApplication::pid() const { return m_pid; } int CrashedApplication::signalNumber() const { return m_signalNumber; } QString CrashedApplication::signalName() const { #if HAVE_STRSIGNAL && defined(Q_OS_UNIX) const char * oldLocale = std::setlocale(LC_MESSAGES, nullptr); char * savedLocale; if (oldLocale) { savedLocale = strdup(oldLocale); } else { savedLocale = nullptr; } std::setlocale(LC_MESSAGES, "C"); const char *name = strsignal(m_signalNumber); std::setlocale(LC_MESSAGES, savedLocale); std::free(savedLocale); return QString::fromLocal8Bit(name ? name : "Unknown"); #else switch (m_signalNumber) { # if defined(Q_OS_UNIX) case SIGILL: return QLatin1String("SIGILL"); case SIGABRT: return QLatin1String("SIGABRT"); case SIGFPE: return QLatin1String("SIGFPE"); case SIGSEGV: return QLatin1String("SIGSEGV"); case SIGBUS: return QLatin1String("SIGBUS"); # else case EXCEPTION_ACCESS_VIOLATION: return QLatin1String("EXCEPTION_ACCESS_VIOLATION"); case EXCEPTION_DATATYPE_MISALIGNMENT: return QLatin1String("EXCEPTION_DATATYPE_MISALIGNMENT"); case EXCEPTION_BREAKPOINT: return QLatin1String("EXCEPTION_BREAKPOINT"); case EXCEPTION_SINGLE_STEP: return QLatin1String("EXCEPTION_SINGLE_STEP"); case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return QLatin1String("EXCEPTION_ARRAY_BOUNDS_EXCEEDED"); case EXCEPTION_FLT_DENORMAL_OPERAND: return QLatin1String("EXCEPTION_FLT_DENORMAL_OPERAND"); case EXCEPTION_FLT_DIVIDE_BY_ZERO: return QLatin1String("EXCEPTION_FLT_DIVIDE_BY_ZERO"); case EXCEPTION_FLT_INEXACT_RESULT: return QLatin1String("EXCEPTION_FLT_INEXACT_RESULT"); case EXCEPTION_FLT_INVALID_OPERATION: return QLatin1String("EXCEPTION_FLT_INVALID_OPERATION"); case EXCEPTION_FLT_OVERFLOW: return QLatin1String("EXCEPTION_FLT_OVERFLOW"); case EXCEPTION_FLT_STACK_CHECK: return QLatin1String("EXCEPTION_FLT_STACK_CHECK"); case EXCEPTION_FLT_UNDERFLOW: return QLatin1String("EXCEPTION_FLT_UNDERFLOW"); case EXCEPTION_INT_DIVIDE_BY_ZERO: return QLatin1String("EXCEPTION_INT_DIVIDE_BY_ZERO"); case EXCEPTION_INT_OVERFLOW: return QLatin1String("EXCEPTION_INT_OVERFLOW"); case EXCEPTION_PRIV_INSTRUCTION: return QLatin1String("EXCEPTION_PRIV_INSTRUCTION"); case EXCEPTION_IN_PAGE_ERROR: return QLatin1String("EXCEPTION_IN_PAGE_ERROR"); case EXCEPTION_ILLEGAL_INSTRUCTION: return QLatin1String("EXCEPTION_ILLEGAL_INSTRUCTION"); case EXCEPTION_NONCONTINUABLE_EXCEPTION: return QLatin1String("EXCEPTION_NONCONTINUABLE_EXCEPTION"); case EXCEPTION_STACK_OVERFLOW: return QLatin1String("EXCEPTION_STACK_OVERFLOW"); case EXCEPTION_INVALID_DISPOSITION: return QLatin1String("EXCEPTION_INVALID_DISPOSITION"); # endif default: return QLatin1String("Unknown"); } #endif } bool CrashedApplication::hasBeenRestarted() const { return m_restarted; } int CrashedApplication::thread() const { return m_thread; } const QDateTime& CrashedApplication::datetime() const { return m_datetime; } +bool CrashedApplication::hasDeletedFiles() const +{ + return m_hasDeletedFiles; +} + void CrashedApplication::restart() { if (m_restarted) { return; } //start the application via kdeinit, as it needs to have a pristine environment and //KProcess::startDetached() can't start a new process with custom environment variables. // if m_fakeBaseName is set, this means m_executable is the path to kdeinit4 // so we need to use the fakeBaseName to restart the app const int ret = KToolInvocation::kdeinitExec(!m_fakeBaseName.isEmpty() ? m_fakeBaseName : m_executable.absoluteFilePath()); const bool success = (ret == 0); m_restarted = success; emit restarted(success); } QString getSuggestedKCrashFilename(const CrashedApplication* app) { QString filename = app->fakeExecutableBaseName() + QLatin1Char('-') + app->datetime().toString(QStringLiteral("yyyyMMdd-hhmmss")) + QStringLiteral(".kcrash.txt"); if (filename.contains(QLatin1Char('/'))) { filename = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1); } return filename; } diff --git a/src/crashedapplication.h b/src/crashedapplication.h index 1c3d0fb2..b61c63f0 100644 --- a/src/crashedapplication.h +++ b/src/crashedapplication.h @@ -1,93 +1,97 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef CRASHEDAPPLICATION_H #define CRASHEDAPPLICATION_H #include #include #include #include "bugreportaddress.h" class KCrashBackend; class CrashedApplication : public QObject { Q_OBJECT public: ~CrashedApplication() override; /** Returns the crashed program's name, possibly translated (ex. "The KDE Crash Handler") */ QString name() const; /** Returns a QFileInfo with information about the executable that crashed */ QFileInfo executable() const; /** When an application is run via kdeinit, the executable() method returns kdeinit4, but * we still need a way to know which is the application that was loaded by kdeinit. So, * this method returns the base name of the executable that would have been launched if * the app had not been loaded by kdeinit (ex. "plasma-desktop"). If the application was * not launched via kdeinit, this method returns executable().baseName(); */ QString fakeExecutableBaseName() const; /** Returns the version of the crashed program */ QString version() const; /** Returns the address where the bug report for this application should go */ BugReportAddress bugReportAddress() const; /** Returns the pid of the crashed program */ int pid() const; /** Returns the signal number that the crashed program received */ int signalNumber() const; /** Returns the name of the signal (ex. SIGSEGV) */ QString signalName() const; bool hasBeenRestarted() const; int thread() const; const QDateTime& datetime() const; + /** @returns whether mmap'd files have been deleted, e.g. updated since start of app */ + bool hasDeletedFiles() const; + public Q_SLOTS: void restart(); Q_SIGNALS: void restarted(bool success); protected: friend class KCrashBackend; CrashedApplication(QObject *parent = nullptr); int m_pid; int m_signalNumber; QString m_name; QFileInfo m_executable; QString m_fakeBaseName; QString m_version; BugReportAddress m_reportAddress; bool m_restarted; int m_thread; QDateTime m_datetime; + bool m_hasDeletedFiles; }; QString getSuggestedKCrashFilename(const CrashedApplication* app); #endif // CRASHEDAPPLICATION_H diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp index 801abfbf..5e83b333 100644 --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -1,241 +1,278 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "drkonqibackends.h" #include #include #include #include #include #include +#include #include #include #include #include #include "drkonqi_debug.h" #include "crashedapplication.h" #include "debugger.h" #include "debuggermanager.h" #include "backtracegenerator.h" #include "drkonqi.h" #ifdef Q_OS_MACOS #include #endif AbstractDrKonqiBackend::~AbstractDrKonqiBackend() { } bool AbstractDrKonqiBackend::init() { m_crashedApplication = constructCrashedApplication(); m_debuggerManager = constructDebuggerManager(); return true; } KCrashBackend::KCrashBackend() : QObject(), AbstractDrKonqiBackend(), m_state(ProcessRunning) { } KCrashBackend::~KCrashBackend() { continueAttachedProcess(); } bool KCrashBackend::init() { AbstractDrKonqiBackend::init(); //check whether the attached process exists and whether we have permissions to inspect it if (crashedApplication()->pid() <= 0) { qCWarning(DRKONQI_LOG) << "Invalid pid specified"; return false; } #if !defined(Q_OS_WIN32) if (::kill(crashedApplication()->pid(), 0) < 0) { switch (errno) { case EPERM: qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process"; break; case ESRCH: qCWarning(DRKONQI_LOG) << "The specified process does not exist."; break; default: break; } return false; } //--keeprunning means: generate backtrace instantly and let the process continue execution if(DrKonqi::isKeepRunning()) { stopAttachedProcess(); debuggerManager()->backtraceGenerator()->start(); connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess); } else { connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting); connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished); //stop the process to avoid high cpu usage by other threads (bug 175362). //if the process was started by kdeinit, we need to wait a bit for KCrash //to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else //if we stop it before this call, pending alarm signals will kill the //process when we try to continue it. QTimer::singleShot(2000, this, &KCrashBackend::stopAttachedProcess); } #endif //Handle drkonqi crashes s_pid = crashedApplication()->pid(); //copy pid for use by the crash handler, so that it is safer KCrash::setEmergencySaveFunction(emergencySaveFunction); return true; } CrashedApplication *KCrashBackend::constructCrashedApplication() { CrashedApplication *a = new CrashedApplication(this); a->m_datetime = QDateTime::currentDateTime(); a->m_name = DrKonqi::programName(); a->m_version = DrKonqi::appVersion(); a->m_reportAddress = BugReportAddress(DrKonqi::bugAddress()); a->m_pid = DrKonqi::pid(); a->m_signalNumber = DrKonqi::signal(); a->m_restarted = DrKonqi::isRestarted(); a->m_thread = DrKonqi::thread(); //try to determine the executable that crashed - if ( QFileInfo(QStringLiteral("/proc/%1/exe").arg(a->m_pid)).exists() ) { + const QString procPath(QStringLiteral("/proc/%1").arg(a->m_pid)); + const QString exeProcPath(procPath + QStringLiteral("/exe")); + if (QFileInfo(exeProcPath).exists()) { //on linux, the fastest and most reliable way is to get the path from /proc qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path"; - a->m_executable.setFile(QFile::symLinkTarget(QStringLiteral("/proc/%1/exe").arg(a->m_pid))); + const QString exePath = QFile::symLinkTarget(exeProcPath); + a->m_executable.setFile(exePath); if (DrKonqi::isKdeinit() || a->m_executable.fileName().startsWith(QLatin1String("python")) ) { - a->m_fakeBaseName = DrKonqi::appName(); } + + QDir mapFilesDir(procPath + QStringLiteral("/map_files")); + mapFilesDir.setFilter(mapFilesDir.filter() | QDir::System); // proc is system! + + // "/bin/foo (deleted)" is how the kernel tells us that a file has been deleted since + // it was mmap'd. + QRegularExpression expression(QStringLiteral("(?.+) \\(deleted\\)$")); + // For the map_files we filter only .so files to ensure that + // we don't trip over cache files or the like, as a result we + // manually need to check if the main exe was deleted and add + // it. + // NB: includes .so* and .py* since we also implicitly support snakes to + // a degree + QRegularExpression soExpression(QStringLiteral("(?.+\\.(so|py)([^/]*)) \\(deleted\\)$")); + + bool hasDeletedFiles = false; + + const auto exeMatch = expression.match(exePath); + if (exeMatch.isValid() && exeMatch.hasMatch()) { + hasDeletedFiles = true; + } + + const auto list = mapFilesDir.entryInfoList(); + for (auto it = list.constBegin(); !hasDeletedFiles && it != list.constEnd(); ++it) { + const auto match = soExpression.match(it->symLinkTarget()); + if (!match.isValid() || !match.hasMatch()) { + continue; + } + hasDeletedFiles = true; + } + + a->m_hasDeletedFiles = hasDeletedFiles; + + qCDebug(DRKONQI_LOG) << "exe" << exePath << "has deleted files:" << hasDeletedFiles; } else { if ( DrKonqi::isKdeinit() ) { a->m_executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5"))); a->m_fakeBaseName = DrKonqi::appName(); } else { QFileInfo execPath(DrKonqi::appName()); if ( execPath.isAbsolute() ) { a->m_executable = execPath; } else if ( !DrKonqi::appPath().isEmpty() ) { QDir execDir(DrKonqi::appPath()); a->m_executable = execDir.absoluteFilePath(execPath.fileName()); } else { a->m_executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName())); } } } qCDebug(DRKONQI_LOG) << "Executable is:" << a->m_executable.absoluteFilePath(); qCDebug(DRKONQI_LOG) << "Executable exists:" << a->m_executable.exists(); return a; } DebuggerManager *KCrashBackend::constructDebuggerManager() { QList internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash")); KConfigGroup config(KSharedConfig::openConfig(), "DrKonqi"); #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED > 1070 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("lldb")); #elif !defined(Q_OS_WIN) QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb")); #else QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("cdb")); #endif Debugger firstKnownGoodDebugger, preferredDebugger; foreach (const Debugger & debugger, internalDebuggers) { qCDebug(DRKONQI_LOG) << "Check debugger if" << debugger.displayName() << "[" << debugger.codeName() << "]" << "is installed:" << debugger.isInstalled(); if (!firstKnownGoodDebugger.isValid() && debugger.isInstalled()) { firstKnownGoodDebugger = debugger; } if (debugger.codeName() == defaultDebuggerName) { preferredDebugger = debugger; } if (firstKnownGoodDebugger.isValid() && preferredDebugger.isValid()) { break; } } if (!preferredDebugger.isInstalled()) { if (firstKnownGoodDebugger.isValid()) { preferredDebugger = firstKnownGoodDebugger; } else { qCWarning(DRKONQI_LOG) << "Unable to find an internal debugger that can work with the KCrash backend"; } } qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName(); return new DebuggerManager(preferredDebugger, Debugger::availableExternalDebuggers(QStringLiteral("KCrash")), this); } void KCrashBackend::stopAttachedProcess() { if (m_state == ProcessRunning) { qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process"; ::kill(crashedApplication()->pid(), SIGSTOP); m_state = ProcessStopped; } } void KCrashBackend::continueAttachedProcess() { if (m_state == ProcessStopped) { qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process"; ::kill(crashedApplication()->pid(), SIGCONT); m_state = ProcessRunning; } } void KCrashBackend::onDebuggerStarting() { continueAttachedProcess(); m_state = DebuggerRunning; } void KCrashBackend::onDebuggerFinished() { m_state = ProcessRunning; stopAttachedProcess(); } //static qint64 KCrashBackend::s_pid = 0; //static void KCrashBackend::emergencySaveFunction(int signal) { // In case drkonqi itself crashes, we need to get rid of the process being debugged, // so we kill it, no matter what its state was. Q_UNUSED(signal); ::kill(s_pid, SIGKILL); } diff --git a/src/drkonqidialog.cpp b/src/drkonqidialog.cpp index 7c2c96ba..6f943504 100644 --- a/src/drkonqidialog.cpp +++ b/src/drkonqidialog.cpp @@ -1,320 +1,328 @@ /******************************************************************* * drkonqidialog.cpp * Copyright 2009 Dario Andres Rodriguez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "drkonqidialog.h" #include #include #include #include #include #include "drkonqi_debug.h" #include #include #include #include "drkonqi.h" #include "backtracewidget.h" #include "aboutbugreportingdialog.h" #include "crashedapplication.h" #include "debuggermanager.h" #include "debuggerlaunchers.h" #include "drkonqi_globals.h" #include "config-drkonqi.h" #include "bugzillaintegration/reportassistantdialog.h" static const char ABOUT_BUG_REPORTING_URL[] = "#aboutbugreporting"; static QString DRKONQI_REPORT_BUG_URL = KDE_BUGZILLA_URL + QStringLiteral("enter_bug.cgi?product=drkonqi&format=guided"); DrKonqiDialog::DrKonqiDialog(QWidget * parent) : QDialog(parent), m_aboutBugReportingDialog(nullptr), m_backtraceWidget(nullptr) { setAttribute(Qt::WA_DeleteOnClose, true); //Setting dialog title and icon setWindowTitle(DrKonqi::crashedApplication()->name()); setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); QVBoxLayout* l = new QVBoxLayout(this); m_tabWidget = new QTabWidget(this); l->addWidget(m_tabWidget); m_buttonBox = new QDialogButtonBox(this); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accepted); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::rejected); l->addWidget(m_buttonBox); connect(m_tabWidget, &QTabWidget::currentChanged, this, &DrKonqiDialog::tabIndexChanged); KConfigGroup config(KSharedConfig::openConfig(), "General"); if (!config.readEntry(QStringLiteral("ShowOnlyBacktrace"), false)) { buildIntroWidget(); m_tabWidget->addTab(m_introWidget, i18nc("@title:tab general information", "&General")); } m_backtraceWidget = new BacktraceWidget(DrKonqi::debuggerManager()->backtraceGenerator(), this); m_tabWidget->addTab(m_backtraceWidget, i18nc("@title:tab", "&Developer Information")); m_tabWidget->tabBar()->setVisible(m_tabWidget->count() > 1); buildDialogButtons(); KWindowConfig::restoreWindowSize(windowHandle(), config); setLayout(l); // Force a 16:9 ratio for nice appearance by default. QSize aspect(16, 9); aspect.scale(sizeHint(), Qt::KeepAspectRatioByExpanding); resize(aspect); } DrKonqiDialog::~DrKonqiDialog() { KConfigGroup config(KSharedConfig::openConfig(), "General"); KWindowConfig::saveWindowSize(windowHandle(), config); } void DrKonqiDialog::tabIndexChanged(int index) { if (index == m_tabWidget->indexOf(m_backtraceWidget)) { m_backtraceWidget->generateBacktrace(); } } void DrKonqiDialog::buildIntroWidget() { const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); m_introWidget = new QWidget(this); ui.setupUi(m_introWidget); ui.titleLabel->setText(xi18nc("@info", "We are sorry, %1 " "closed unexpectedly.", crashedApp->name())); QString reportMessage; if (!crashedApp->bugReportAddress().isEmpty()) { if (crashedApp->fakeExecutableBaseName() == QLatin1String("drkonqi")) { //Handle own crashes reportMessage = xi18nc("@info", "As the Crash Handler itself has failed, the " "automatic reporting process is disabled to reduce the " "risks of failing again." "Please, manually report this error " "to the KDE bug tracking system. Do not forget to include " "the backtrace from the Developer Information " "tab.", DRKONQI_REPORT_BUG_URL); } else if (DrKonqi::isSafer()) { reportMessage = xi18nc("@info", "The reporting assistant is disabled because " "the crash handler dialog was started in safe mode." "You can manually report this bug to %1 " "(including the backtrace from the " "Developer Information " "tab.)", crashedApp->bugReportAddress()); + } else if (crashedApp->hasDeletedFiles()) { + reportMessage = xi18nc("@info", "The reporting assistant is disabled because " + "the crashed application appears to have been updated or " + "uninstalled since it had been started. This prevents accurate " + "crash reporting and can also be the cause of this crash." + "After updating it is always a good idea to log out and back " + "in to make sure the update is fully applied and will not cause " + "any side effects."); } else { reportMessage = xi18nc("@info", "You can help us improve KDE Software by reporting " "this error.Learn " "more about bug reporting.", QLatin1String(ABOUT_BUG_REPORTING_URL)); } } else { reportMessage = xi18nc("@info", "You cannot report this error, because " "%1 does not provide a bug reporting " "address.", crashedApp->name() ); } ui.infoLabel->setText(reportMessage); connect(ui.infoLabel, &QLabel::linkActivated, this, &DrKonqiDialog::linkActivated); ui.detailsTitleLabel->setText(QStringLiteral("%1").arg(i18nc("@label","Details:"))); ui.detailsLabel->setText(xi18nc("@info Note the time information is divided into date and time parts", "Executable: %1" " PID: %2 Signal: %3 (%4) " "Time: %5 %6", crashedApp->fakeExecutableBaseName(), crashedApp->pid(), crashedApp->signalName(), #if defined(Q_OS_UNIX) crashedApp->signalNumber(), #else //windows uses weird big numbers for exception codes, //so it doesn't make sense to display them in decimal QString().sprintf("0x%8x", crashedApp->signalNumber()), #endif crashedApp->datetime().date().toString(Qt::DefaultLocaleShortDate), crashedApp->datetime().time().toString() )); } void DrKonqiDialog::buildDialogButtons() { const CrashedApplication *crashedApp = DrKonqi::crashedApplication(); //Set dialog buttons m_buttonBox->setStandardButtons(QDialogButtonBox::Close); //Default debugger button and menu (only for developer mode): User2 DebuggerManager *debuggerManager = DrKonqi::debuggerManager(); m_debugButton = new QPushButton(this); KGuiItem2 debugItem(i18nc("@action:button this is the debug menu button label which contains the debugging applications", "&Debug"), QIcon::fromTheme(QStringLiteral("applications-development")), i18nc("@info:tooltip", "Starts a program to debug the crashed application.")); KGuiItem::assign(m_debugButton, debugItem); m_debugButton->setVisible(false); m_debugMenu = new QMenu(this); m_debugButton->setMenu(m_debugMenu); // Only add the debugger if requested by the config or if a KDevelop session is running. const bool showExternal = debuggerManager->showExternalDebuggers(); QList debuggers = debuggerManager->availableExternalDebuggers(); foreach(AbstractDebuggerLauncher *launcher, debuggers) { if (showExternal || qobject_cast(launcher)) { addDebugger(launcher); } } connect(debuggerManager, &DebuggerManager::externalDebuggerAdded, this, &DrKonqiDialog::addDebugger); connect(debuggerManager, &DebuggerManager::externalDebuggerRemoved, this, &DrKonqiDialog::removeDebugger); connect(debuggerManager, &DebuggerManager::debuggerRunning, this, &DrKonqiDialog::enableDebugMenu); //Report bug button: User1 QPushButton *reportButton = new QPushButton(m_buttonBox); KGuiItem2 reportItem(i18nc("@action:button", "Report &Bug"), QIcon::fromTheme(QStringLiteral("tools-report-bug")), i18nc("@info:tooltip", "Starts the bug report assistant.")); KGuiItem::assign(reportButton, reportItem); m_buttonBox->addButton(reportButton, QDialogButtonBox::ActionRole); reportButton->setEnabled(!crashedApp->bugReportAddress().isEmpty() && crashedApp->fakeExecutableBaseName() != QLatin1String("drkonqi") && - !DrKonqi::isSafer()); + !DrKonqi::isSafer() && !crashedApp->hasDeletedFiles()); connect(reportButton, &QPushButton::clicked, this, &DrKonqiDialog::startBugReportAssistant); //Restart application button KGuiItem2 restartItem(i18nc("@action:button", "&Restart Application"), QIcon::fromTheme(QStringLiteral("system-reboot")), i18nc("@info:tooltip", "Use this button to restart " "the crashed application.")); m_restartButton = new QPushButton(m_buttonBox); KGuiItem::assign(m_restartButton, restartItem); m_restartButton->setEnabled(!crashedApp->hasBeenRestarted() && crashedApp->fakeExecutableBaseName() != QLatin1String("drkonqi")); m_buttonBox->addButton(m_restartButton, QDialogButtonBox::ActionRole); connect(m_restartButton, &QAbstractButton::clicked, crashedApp, &CrashedApplication::restart); connect(crashedApp, &CrashedApplication::restarted, this, &DrKonqiDialog::applicationRestarted); //Close button QString tooltipText = i18nc("@info:tooltip", "Close this dialog (you will lose the crash information.)"); m_buttonBox->button(QDialogButtonBox::Close)->setToolTip(tooltipText); m_buttonBox->button(QDialogButtonBox::Close)->setWhatsThis(tooltipText); m_buttonBox->button(QDialogButtonBox::Close)->setFocus(); } void DrKonqiDialog::addDebugger(AbstractDebuggerLauncher *launcher) { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("applications-development")), i18nc("@action:inmenu 1 is the debugger name", "Debug in %1", launcher->name()), m_debugMenu); m_debugMenu->addAction(action); connect(action, &QAction::triggered, launcher, &AbstractDebuggerLauncher::start); m_debugMenuActions.insert(launcher, action); // Make sure that the debug button is the first button with action role to be // inserted, then add the other buttons. See removeDebugger below for more information. if (!m_debugButtonInBox) { auto buttons = m_buttonBox->buttons(); m_buttonBox->addButton(m_debugButton, QDialogButtonBox::ActionRole); m_debugButton->setVisible(true); for (QAbstractButton *button : buttons) { if (m_buttonBox->buttonRole(button) == QDialogButtonBox::ActionRole) { m_buttonBox->addButton(button, QDialogButtonBox::ActionRole); } } m_debugButtonInBox = true; } } void DrKonqiDialog::removeDebugger(AbstractDebuggerLauncher *launcher) { QAction *action = m_debugMenuActions.take(launcher); if ( action ) { m_debugMenu->removeAction(action); action->deleteLater(); // Remove the button from the box, otherwise the box will force // it visible as it calls show() explicitly. (QTBUG-3651) if (m_debugMenu->isEmpty()) { m_buttonBox->removeButton(m_debugButton); m_debugButton->setVisible(false); m_debugButton->setParent(this); m_debugButtonInBox = false; } } else { qCWarning(DRKONQI_LOG) << "Invalid launcher"; } } void DrKonqiDialog::enableDebugMenu(bool debuggerRunning) { m_debugButton->setEnabled(!debuggerRunning); } void DrKonqiDialog::startBugReportAssistant() { ReportAssistantDialog * bugReportAssistant = new ReportAssistantDialog(); bugReportAssistant->show(); connect(bugReportAssistant, &QObject::destroyed, this, &DrKonqiDialog::reject); hide(); } void DrKonqiDialog::linkActivated(const QString& link) { if (link == QLatin1String(ABOUT_BUG_REPORTING_URL)) { showAboutBugReporting(); } else if (link == DRKONQI_REPORT_BUG_URL) { QDesktopServices::openUrl(QUrl(link)); } else if (link.startsWith(QLatin1String("http"))) { qCWarning(DRKONQI_LOG) << "unexpected link"; QDesktopServices::openUrl(QUrl(link)); } } void DrKonqiDialog::showAboutBugReporting() { if (!m_aboutBugReportingDialog) { m_aboutBugReportingDialog = new AboutBugReportingDialog(); connect(this, &DrKonqiDialog::destroyed, m_aboutBugReportingDialog.data(), &AboutBugReportingDialog::close); } m_aboutBugReportingDialog->show(); m_aboutBugReportingDialog->raise(); m_aboutBugReportingDialog->activateWindow(); } void DrKonqiDialog::applicationRestarted(bool success) { m_restartButton->setEnabled(!success); }