diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp index dd4d46a4..c29db082 100644 --- a/src/backtracewidget.cpp +++ b/src/backtracewidget.cpp @@ -1,409 +1,409 @@ /******************************************************************* * 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); 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().name())); + 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 lenght expecations // 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 += QStringLiteral("
  • ") + string + QStringLiteral("
  • "); } message += QStringLiteral("
"); KMessageBox::information(this, message, i18nc("messagebox title","Missing debug information packages")); } } diff --git a/src/debugger.cpp b/src/debugger.cpp index 94442bdf..190524ad 100644 --- a/src/debugger.cpp +++ b/src/debugger.cpp @@ -1,162 +1,162 @@ /* 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 "debugger.h" #include #include #include #include #include #include "crashedapplication.h" #include "drkonqi.h" #include "drkonqi_debug.h" //static QList Debugger::availableInternalDebuggers(const QString & backend) { return availableDebuggers(QStringLiteral("debuggers/internal"), backend); } //static QList Debugger::availableExternalDebuggers(const QString & backend) { return availableDebuggers(QStringLiteral("debuggers/external"), backend); } bool Debugger::isValid() const { return m_config; } bool Debugger::isInstalled() const { QString tryexec = tryExec(); if(tryexec.isEmpty()) { - qCDebug(DRKONQI_LOG) << "tryExec of" << name() << "is empty!"; + qCDebug(DRKONQI_LOG) << "tryExec of" << codeName() << "is empty!"; return false; } // Find for executable in PATH and in our application path return !QStandardPaths::findExecutable(tryexec).isEmpty() || !QStandardPaths::findExecutable(tryexec, {QCoreApplication::applicationDirPath()}).isEmpty(); } -QString Debugger::name() const +QString Debugger::displayName() const { return isValid() ? m_config->group("General").readEntry("Name") : QString(); } QString Debugger::codeName() const { //fall back to the "TryExec" string if "CodeName" is not specified. //for most debuggers those strings should be the same return isValid() ? m_config->group("General").readEntry("CodeName", tryExec()) : QString(); } QString Debugger::tryExec() const { return isValid() ? m_config->group("General").readEntry("TryExec") : QString(); } QStringList Debugger::supportedBackends() const { return isValid() ? m_config->group("General").readEntry("Backends") .split(QLatin1Char('|'), QString::SkipEmptyParts) : QStringList(); } void Debugger::setUsedBackend(const QString & backendName) { if (supportedBackends().contains(backendName)) { m_backend = backendName; } } QString Debugger::command() const { return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readPathEntry("Exec", QString()) : QString(); } QString Debugger::backtraceBatchCommands() const { return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readPathEntry("BatchCommands", QString()) : QString(); } bool Debugger::runInTerminal() const { return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readEntry("Terminal", false) : false; } QString Debugger::backendValueOfParameter(const QString &key) const { return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readEntry(key, QString()) : QString(); } //static void Debugger::expandString(QString & str, ExpandStringUsage usage, const QString & tempFile) { const CrashedApplication *appInfo = DrKonqi::crashedApplication(); const QHash map = { { QLatin1String("progname"), appInfo->name() }, { QLatin1String("execname"), appInfo->fakeExecutableBaseName() }, { QLatin1String("execpath"), appInfo->executable().absoluteFilePath() }, { QLatin1String("signum"), QString::number(appInfo->signalNumber()) }, { QLatin1String("signame"), appInfo->signalName() }, { QLatin1String("pid"), QString::number(appInfo->pid()) }, { QLatin1String("tempfile"), tempFile }, { QLatin1String("thread"), QString::number(appInfo->thread()) }, }; if (usage == ExpansionUsageShell) { str = KMacroExpander::expandMacrosShellQuote(str, map); } else { str = KMacroExpander::expandMacros(str, map); } } //static QList Debugger::availableDebuggers(const QString & path, const QString & backend) { QStringList debuggerDirs { // Search in default path QStandardPaths::locate(QStandardPaths::AppDataLocation, path, QStandardPaths::LocateDirectory), // Search from application path, this helps when deploying an application QString(QStringLiteral("%1/%2")).arg(QCoreApplication::applicationDirPath(), path) }; QList result; for (const auto & debuggerDir: debuggerDirs) { QStringList debuggers = QDir(debuggerDir).entryList(QDir::Files); for (const auto & debuggerFile : debuggers) { Debugger debugger; debugger.m_config = KSharedConfig::openConfig(QString(QStringLiteral("%1/%2")).arg(debuggerDir, debuggerFile)); if (debugger.supportedBackends().contains(backend)) { debugger.setUsedBackend(backend); result.append(debugger); } } } return result; } diff --git a/src/debugger.h b/src/debugger.h index 485d89cc..30e6ccb0 100644 --- a/src/debugger.h +++ b/src/debugger.h @@ -1,90 +1,90 @@ /* 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 DEBUGGER_H #define DEBUGGER_H #include #include class Debugger { public: static QList availableInternalDebuggers(const QString & backend); static QList availableExternalDebuggers(const QString & backend); /** Returns true if this Debugger instance is valid, or false otherwise. * Debugger instances are valid only if they have been constructed from * availableInternalDebuggers() or availableExternalDebuggers(). If they * have been constructed directly using the Debugger constructor, they are invalid. */ bool isValid() const; /** Returns true if this debugger is installed. This is determined by * looking for the executable that tryExec() returns. If it is in $PATH, * this method returns true. */ bool isInstalled() const; /** Returns the translatable name of the debugger (eg. "GDB") */ - QString name() const; + QString displayName() const; /** Returns the code name of the debugger (eg. "gdb"). */ QString codeName() const; /** Returns the executable name that drkonqi should check if it exists * to determine whether the debugger is installed */ QString tryExec() const; /** Returns a list with the drkonqi backends that this debugger supports */ QStringList supportedBackends() const; /** Sets the backend to be used. This function must be called before using * command(), backtraceBatchCommands() or runInTerminal(). */ void setUsedBackend(const QString & backendName); /** Returns the command that should be run to use the debugger */ QString command() const; /** Returns the commands that should be given to the debugger when * run in batch mode in order to generate a backtrace */ QString backtraceBatchCommands() const; /** If this is an external debugger, it returns whether it should be run in a terminal or not */ bool runInTerminal() const; /** Returns the value of the arbitrary configuration parameter @param key, or an empty QString if @param key isn't defined */ QString backendValueOfParameter(const QString &key) const; enum ExpandStringUsage { ExpansionUsagePlainText, ExpansionUsageShell }; static void expandString(QString & str, ExpandStringUsage usage = ExpansionUsagePlainText, const QString & tempFile = QString()); private: static QList availableDebuggers(const QString &path, const QString & backend); KSharedConfig::Ptr m_config; QString m_backend; }; #endif diff --git a/src/debuggerlaunchers.cpp b/src/debuggerlaunchers.cpp index 9e8d4c86..70c562ed 100644 --- a/src/debuggerlaunchers.cpp +++ b/src/debuggerlaunchers.cpp @@ -1,144 +1,144 @@ /* 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 "debuggerlaunchers.h" #include #include #include #include #include "drkonqi_debug.h" #include "detachedprocessmonitor.h" #include "drkonqi.h" #include "crashedapplication.h" #include "ptracer.h" DefaultDebuggerLauncher::DefaultDebuggerLauncher(const Debugger & debugger, DebuggerManager *parent) : AbstractDebuggerLauncher(parent), m_debugger(debugger) { m_monitor = new DetachedProcessMonitor(this); connect(m_monitor, &DetachedProcessMonitor::processFinished, this, &DefaultDebuggerLauncher::onProcessFinished); } QString DefaultDebuggerLauncher::name() const { - return m_debugger.name(); + return m_debugger.displayName(); } void DefaultDebuggerLauncher::start() { if ( static_cast(parent())->debuggerIsRunning() ) { qCWarning(DRKONQI_LOG) << "Another debugger is already running"; return; } QString str = m_debugger.command(); Debugger::expandString(str, Debugger::ExpansionUsageShell); emit starting(); int pid = KProcess::startDetached(KShell::splitArgs(str)); if ( pid > 0 ) { setPtracer(pid, DrKonqi::pid()); m_monitor->startMonitoring(pid); } else { qCWarning(DRKONQI_LOG) << "Could not start debugger:" << name(); emit finished(); } } void DefaultDebuggerLauncher::onProcessFinished() { setPtracer(QCoreApplication::applicationPid(), DrKonqi::pid()); emit finished(); } #if 0 TerminalDebuggerLauncher::TerminalDebuggerLauncher(const Debugger & debugger, DebuggerManager *parent) : DefaultDebuggerLauncher(debugger, parent) { } void TerminalDebuggerLauncher::start() { DefaultDebuggerLauncher::start(); //FIXME } #endif DBusInterfaceLauncher::DBusInterfaceLauncher(const QString &name, qint64 pid, DBusInterfaceAdaptor *parent) : AbstractDebuggerLauncher(parent), m_name(name), m_pid(pid) { } QString DBusInterfaceLauncher::name() const { return m_name; } void DBusInterfaceLauncher::start() { emit starting(); setPtracer(m_pid, DrKonqi::pid()); emit static_cast(parent())->acceptDebuggingApplication(m_name); } DBusInterfaceAdaptor::DBusInterfaceAdaptor(DebuggerManager *parent) : QDBusAbstractAdaptor(parent) { Q_ASSERT(parent); if (QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.drkonqi-%1").arg(pid()))) { QDBusConnection::sessionBus().registerObject(QStringLiteral("/debugger"), parent); } } int DBusInterfaceAdaptor::pid() { return DrKonqi::crashedApplication()->pid(); } void DBusInterfaceAdaptor::registerDebuggingApplication(const QString &name, qint64 pid) { if (!name.isEmpty() && !m_launchers.contains(name)) { auto launcher = new DBusInterfaceLauncher(name, pid, this); m_launchers.insert(name, launcher); static_cast(parent())->addDebugger(launcher, true); } } void DBusInterfaceAdaptor::debuggingFinished(const QString &name) { auto it = m_launchers.find(name); if (it != m_launchers.end()) { setPtracer(QCoreApplication::applicationPid(), DrKonqi::pid()); emit it.value()->finished(); } } void DBusInterfaceAdaptor::debuggerClosed(const QString &name) { auto it = m_launchers.find(name); if (it != m_launchers.end()) { emit it.value()->invalidated(); m_launchers.erase(it); } } diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp index 46289bb0..801abfbf 100644 --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -1,238 +1,241 @@ /* 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 "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() ) { //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))); if (DrKonqi::isKdeinit() || a->m_executable.fileName().startsWith(QLatin1String("python")) ) { a->m_fakeBaseName = DrKonqi::appName(); } } 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.name() << "is installed:" << debugger.isInstalled(); + 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); }