File indexing completed on 2024-03-24 17:01:25

0001 /*
0002     SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2021-2022 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 bool Debugger::supportsCommandWithSymbolResolution() const
0087 {
0088     if (!isValid() || !m_config->hasGroup(m_backend)) {
0089         return false;
0090     }
0091     return m_config->group(m_backend).hasKey("ExecWithSymbolResolution");
0092 }
0093 
0094 QString Debugger::commandWithSymbolResolution() const
0095 {
0096     if (!isValid() || !m_config->hasGroup(m_backend)) {
0097         return {};
0098     }
0099     return expandCommand(m_config->group(m_backend).readPathEntry("ExecWithSymbolResolution", command()));
0100 }
0101 
0102 QString Debugger::backtraceBatchCommands() const
0103 {
0104     if (!isValid() || !m_config->hasGroup(m_backend)) {
0105         return {};
0106     }
0107     return expandCommand(m_config->group(m_backend).readPathEntry("BatchCommands", QString()));
0108 }
0109 
0110 QString Debugger::preambleCommands() const
0111 {
0112     if (!isValid() || !m_config->hasGroup(m_backend)) {
0113         return {};
0114     }
0115     return expandCommand(m_config->group(m_backend).readPathEntry("PreambleCommands", QString()));
0116 }
0117 
0118 QString Debugger::expandCommand(const QString &command) const
0119 {
0120     static QHash<QString, QString> map = {
0121         {QStringLiteral("drkonqi_datadir"), QStandardPaths::locate(QStandardPaths::AppDataLocation, codeName(), QStandardPaths::LocateDirectory)},
0122     };
0123     return KMacroExpander::expandMacros(command, map);
0124 }
0125 
0126 bool Debugger::runInTerminal() const
0127 {
0128     return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readEntry("Terminal", false) : false;
0129 }
0130 
0131 QString Debugger::backendValueOfParameter(const QString &key) const
0132 {
0133     return (isValid() && m_config->hasGroup(m_backend)) ? m_config->group(m_backend).readEntry(key, QString()) : QString();
0134 }
0135 
0136 // static
0137 void Debugger::expandString(QString &str, ExpandStringUsage usage, const QString &tempFile, const QString &preambleFile)
0138 {
0139     const CrashedApplication *appInfo = DrKonqi::crashedApplication();
0140     const QHash<QString, QString> map = {
0141         {QLatin1String("progname"), appInfo->name()},
0142         {QLatin1String("execname"), appInfo->fakeExecutableBaseName()},
0143         {QLatin1String("execpath"), appInfo->executable().absoluteFilePath()},
0144         {QLatin1String("signum"), QString::number(appInfo->signalNumber())},
0145         {QLatin1String("signame"), appInfo->signalName()},
0146         {QLatin1String("pid"), QString::number(appInfo->pid())},
0147         {QLatin1String("tempfile"), tempFile},
0148         {QLatin1String("preamblefile"), preambleFile},
0149         {QLatin1String("thread"), QString::number(appInfo->thread())},
0150         {QStringLiteral("corefile"), appInfo->m_coreFile},
0151     };
0152 
0153     if (usage == ExpansionUsageShell) {
0154         str = KMacroExpander::expandMacrosShellQuote(str, map);
0155     } else {
0156         str = KMacroExpander::expandMacros(str, map);
0157     }
0158 }
0159 
0160 // static
0161 QList<Debugger> Debugger::availableDebuggers(const QString &path, const QString &backend)
0162 {
0163     const QStringList debuggerDirs{// Search from application path, this helps when deploying an application
0164                                    // as binary blob (e.g. Windows exe).
0165                                    QCoreApplication::applicationDirPath() + QLatin1Char('/') + path,
0166                                    // Search in default path
0167                                    QStandardPaths::locate(QStandardPaths::AppDataLocation, path, QStandardPaths::LocateDirectory)};
0168     const QStringList debuggerFiles = KFileUtils::findAllUniqueFiles(debuggerDirs);
0169 
0170     QList<Debugger> result;
0171     for (const auto &debuggerFile : debuggerFiles) {
0172         Debugger debugger;
0173         debugger.m_config = KSharedConfig::openConfig(debuggerFile);
0174         if (debugger.supportedBackends().contains(backend)) {
0175             debugger.setUsedBackend(backend);
0176             result << debugger;
0177         }
0178     }
0179     return result;
0180 }
0181 
0182 Debugger Debugger::findDebugger(const QList<Debugger> &debuggers, const QString &defaultDebuggerCodeName)
0183 {
0184     Debugger firstKnownGoodDebugger;
0185     Debugger preferredDebugger;
0186     for (const Debugger &debugger : debuggers) {
0187         qCDebug(DRKONQI_LOG) << "Check debugger if" << debugger.displayName() << "[" << debugger.codeName() << "]"
0188                              << "is installed:" << debugger.isInstalled();
0189         if (!firstKnownGoodDebugger.isValid() && debugger.isInstalled()) {
0190             firstKnownGoodDebugger = debugger;
0191         }
0192         if (debugger.codeName() == defaultDebuggerCodeName) {
0193             preferredDebugger = debugger;
0194         }
0195         if (firstKnownGoodDebugger.isValid() && preferredDebugger.isValid()) {
0196             break;
0197         }
0198     }
0199 
0200     if (!preferredDebugger.isInstalled()) {
0201         if (firstKnownGoodDebugger.isValid()) {
0202             preferredDebugger = firstKnownGoodDebugger;
0203         } else {
0204             qCWarning(DRKONQI_LOG) << "Unable to find an internal debugger that can work with the crash backend";
0205         }
0206     }
0207 
0208     return preferredDebugger;
0209 }