File indexing completed on 2022-11-29 20:01:35

0001 /*
0002     SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 #include "debugger.h"
0008 
0009 #include <KConfig>
0010 #include <KConfigGroup>
0011 #include <KFileUtils>
0012 #include <KMacroExpander>
0013 #include <QCoreApplication>
0014 #include <QDir>
0015 
0016 #include "crashedapplication.h"
0017 #include "drkonqi.h"
0018 #include "drkonqi_debug.h"
0019 
0020 // static
0021 QList<Debugger> Debugger::availableInternalDebuggers(const QString &backend)
0022 {
0023     return availableDebuggers(QStringLiteral("debuggers/internal"), backend);
0024 }
0025 
0026 // static
0027 QList<Debugger> Debugger::availableExternalDebuggers(const QString &backend)
0028 {
0029     return availableDebuggers(QStringLiteral("debuggers/external"), backend);
0030 }
0031 
0032 bool Debugger::isValid() const
0033 {
0034     return m_config;
0035 }
0036 
0037 bool Debugger::isInstalled() const
0038 {
0039     QString tryexec = tryExec();
0040     if (tryexec.isEmpty()) {
0041         qCDebug(DRKONQI_LOG) << "tryExec of" << codeName() << "is empty!";
0042         return false;
0043     }
0044 
0045     // Find for executable in PATH and in our application path
0046     return !QStandardPaths::findExecutable(tryexec).isEmpty() || !QStandardPaths::findExecutable(tryexec, {QCoreApplication::applicationDirPath()}).isEmpty();
0047 }
0048 
0049 QString Debugger::displayName() const
0050 {
0051     return isValid() ? m_config->group("General").readEntry("Name") : QString();
0052 }
0053 
0054 QString Debugger::codeName() const
0055 {
0056     // fall back to the "TryExec" string if "CodeName" is not specified.
0057     // for most debuggers those strings should be the same
0058     return isValid() ? m_config->group("General").readEntry("CodeName", tryExec()) : QString();
0059 }
0060 
0061 QString Debugger::tryExec() const
0062 {
0063     return isValid() ? m_config->group("General").readEntry("TryExec") : QString();
0064 }
0065 
0066 QStringList Debugger::supportedBackends() const
0067 {
0068     return isValid() ? m_config->group("General").readEntry("Backends").split(QLatin1Char('|'), Qt::SkipEmptyParts) : QStringList();
0069 }
0070 
0071 void Debugger::setUsedBackend(const QString &backendName)
0072 {
0073     if (supportedBackends().contains(backendName)) {
0074         m_backend = backendName;
0075     }
0076 }
0077 
0078 QString Debugger::command() const
0079 {
0080     if (!isValid() || !m_config->hasGroup(m_backend)) {
0081         return {};
0082     }
0083     return expandCommand(m_config->group(m_backend).readPathEntry("Exec", QString()));
0084 }
0085 
0086 QString Debugger::backtraceBatchCommands() const
0087 {
0088     if (!isValid() || !m_config->hasGroup(m_backend)) {
0089         return {};
0090     }
0091     return expandCommand(m_config->group(m_backend).readPathEntry("BatchCommands", QString()));
0092 }
0093 
0094 QString Debugger::preambleCommands() const
0095 {
0096     if (!isValid() || !m_config->hasGroup(m_backend)) {
0097         return {};
0098     }
0099     return expandCommand(m_config->group(m_backend).readPathEntry("PreambleCommands", QString()));
0100 }
0101 
0102 QString Debugger::expandCommand(const QString &command) const
0103 {
0104     static QHash<QString, QString> map = {
0105         {QStringLiteral("drkonqi_datadir"), QStandardPaths::locate(QStandardPaths::AppDataLocation, codeName(), QStandardPaths::LocateDirectory)},
0106     };
0107     return KMacroExpander::expandMacros(command, map);
0108 }
0109 
0110 bool Debugger::runInTerminal() const
0111 {
0112     return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readEntry("Terminal", false) : false;
0113 }
0114 
0115 QString Debugger::backendValueOfParameter(const QString &key) const
0116 {
0117     return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readEntry(key, QString()) : QString();
0118 }
0119 
0120 // static
0121 void Debugger::expandString(QString &str, ExpandStringUsage usage, const QString &tempFile, const QString &preambleFile)
0122 {
0123     const CrashedApplication *appInfo = DrKonqi::crashedApplication();
0124     const QHash<QString, QString> map = {
0125         {QLatin1String("progname"), appInfo->name()},
0126         {QLatin1String("execname"), appInfo->fakeExecutableBaseName()},
0127         {QLatin1String("execpath"), appInfo->executable().absoluteFilePath()},
0128         {QLatin1String("signum"), QString::number(appInfo->signalNumber())},
0129         {QLatin1String("signame"), appInfo->signalName()},
0130         {QLatin1String("pid"), QString::number(appInfo->pid())},
0131         {QLatin1String("tempfile"), tempFile},
0132         {QLatin1String("preamblefile"), preambleFile},
0133         {QLatin1String("thread"), QString::number(appInfo->thread())},
0134         {QStringLiteral("corefile"), appInfo->m_coreFile},
0135     };
0136 
0137     if (usage == ExpansionUsageShell) {
0138         str = KMacroExpander::expandMacrosShellQuote(str, map);
0139     } else {
0140         str = KMacroExpander::expandMacros(str, map);
0141     }
0142 }
0143 
0144 // static
0145 QList<Debugger> Debugger::availableDebuggers(const QString &path, const QString &backend)
0146 {
0147     const QStringList debuggerDirs{// Search from application path, this helps when deploying an application
0148                                    // as binary blob (e.g. Windows exe).
0149                                    QCoreApplication::applicationDirPath() + QLatin1Char('/') + path,
0150                                    // Search in default path
0151                                    QStandardPaths::locate(QStandardPaths::AppDataLocation, path, QStandardPaths::LocateDirectory)};
0152     const QStringList debuggerFiles = KFileUtils::findAllUniqueFiles(debuggerDirs);
0153 
0154     QList<Debugger> result;
0155     for (const auto &debuggerFile : debuggerFiles) {
0156         Debugger debugger;
0157         debugger.m_config = KSharedConfig::openConfig(debuggerFile);
0158         if (debugger.supportedBackends().contains(backend)) {
0159             debugger.setUsedBackend(backend);
0160             result << debugger;
0161         }
0162     }
0163     return result;
0164 }
0165 
0166 Debugger Debugger::findDebugger(const QList<Debugger> &debuggers, const QString &defaultDebuggerCodeName)
0167 {
0168     Debugger firstKnownGoodDebugger;
0169     Debugger preferredDebugger;
0170     for (const Debugger &debugger : debuggers) {
0171         qCDebug(DRKONQI_LOG) << "Check debugger if" << debugger.displayName() << "[" << debugger.codeName() << "]"
0172                              << "is installed:" << debugger.isInstalled();
0173         if (!firstKnownGoodDebugger.isValid() && debugger.isInstalled()) {
0174             firstKnownGoodDebugger = debugger;
0175         }
0176         if (debugger.codeName() == defaultDebuggerCodeName) {
0177             preferredDebugger = debugger;
0178         }
0179         if (firstKnownGoodDebugger.isValid() && preferredDebugger.isValid()) {
0180             break;
0181         }
0182     }
0183 
0184     if (!preferredDebugger.isInstalled()) {
0185         if (firstKnownGoodDebugger.isValid()) {
0186             preferredDebugger = firstKnownGoodDebugger;
0187         } else {
0188             qCWarning(DRKONQI_LOG) << "Unable to find an internal debugger that can work with the crash backend";
0189         }
0190     }
0191 
0192     return preferredDebugger;
0193 }