File indexing completed on 2024-04-14 15:32:47

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 "drkonqibackends.h"
0008 
0009 #include <cerrno>
0010 #include <chrono>
0011 #include <cstdlib>
0012 #include <signal.h>
0013 #include <sys/types.h>
0014 
0015 #include <QDir>
0016 #include <QRegularExpression>
0017 #include <QTimer>
0018 
0019 #include "drkonqi_debug.h"
0020 #include <KConfig>
0021 #include <KConfigGroup>
0022 #include <KCrash>
0023 #include <QStandardPaths>
0024 
0025 #include "backtracegenerator.h"
0026 #include "coredump/metadata.h"
0027 #include "crashedapplication.h"
0028 #include "debugger.h"
0029 #include "debuggermanager.h"
0030 #include "drkonqi.h"
0031 #include "linuxprocmapsparser.h"
0032 
0033 #ifdef Q_OS_MACOS
0034 #include <AvailabilityMacros.h>
0035 #endif
0036 
0037 using namespace std::chrono_literals;
0038 
0039 AbstractDrKonqiBackend::~AbstractDrKonqiBackend() = default;
0040 
0041 bool AbstractDrKonqiBackend::init()
0042 {
0043     m_crashedApplication = constructCrashedApplication();
0044     m_debuggerManager = constructDebuggerManager();
0045     return true;
0046 }
0047 
0048 void AbstractDrKonqiBackend::prepareForDebugger()
0049 {
0050     Q_EMIT preparedForDebugger();
0051 }
0052 
0053 void AbstractDrKonqiBackend::cleanup()
0054 {
0055     // Cleanup of a potential metadata.
0056     // NOTE: This MUST be run regardless of the backend. If drkonqi has not crashed or quit because of a session logout
0057     // we must clean the metadata. Otherwise the coredump helper may produce a second drkonqi instance.
0058     // Lack of a metadata file indicates that drkonqi had been run on the crash already.
0059     qCDebug(DRKONQI_LOG) << "Cleaning up" << metadataPath();
0060     if (!metadataPath().isEmpty()) {
0061         QFile::remove(metadataPath());
0062     }
0063 }
0064 
0065 static QString resolveMetadataPath()
0066 {
0067     const QString envPath = qEnvironmentVariable("DRKONQI_METADATA_FILE");
0068     if (!envPath.isEmpty()) {
0069         qunsetenv("DRKONQI_METADATA_FILE");
0070         return envPath;
0071     }
0072     return Metadata::resolveMetadataPath(DrKonqi::pid()); // resolve manually
0073 }
0074 
0075 QString AbstractDrKonqiBackend::metadataPath()
0076 {
0077     static QString path = resolveMetadataPath();
0078     return path;
0079 }
0080 
0081 KCrashBackend::~KCrashBackend()
0082 {
0083     continueAttachedProcess();
0084 }
0085 
0086 bool KCrashBackend::init()
0087 {
0088     AbstractDrKonqiBackend::init();
0089 
0090     // check whether the attached process exists and whether we have permissions to inspect it
0091     if (crashedApplication()->pid() <= 0) {
0092         qCWarning(DRKONQI_LOG) << "Invalid pid specified";
0093         return false;
0094     }
0095 
0096 #if !defined(Q_OS_WIN32)
0097     if (::kill(crashedApplication()->pid(), 0) < 0) {
0098         switch (errno) {
0099         case EPERM:
0100             qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process";
0101             break;
0102         case ESRCH:
0103             qCWarning(DRKONQI_LOG) << "The specified process does not exist.";
0104             break;
0105         default:
0106             break;
0107         }
0108         return false;
0109     }
0110 
0111     //--keeprunning means: generate backtrace instantly and let the process continue execution
0112     if (DrKonqi::isKeepRunning()) {
0113         stopAttachedProcess();
0114         debuggerManager()->backtraceGenerator()->start();
0115         connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess);
0116     } else {
0117         connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting);
0118         connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished);
0119 
0120         // stop the process to avoid high cpu usage by other threads (bug 175362).
0121         // if the process was started by kdeinit, we need to wait a bit for KCrash
0122         // to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else
0123         // if we stop it before this call, pending alarm signals will kill the
0124         // process when we try to continue it.
0125         QTimer::singleShot(2s, this, &KCrashBackend::stopAttachedProcess);
0126     }
0127 #endif
0128 
0129     // Handle drkonqi crashes
0130     s_pid = crashedApplication()->pid(); // copy pid for use by the crash handler, so that it is safer
0131     KCrash::setEmergencySaveFunction(emergencySaveFunction);
0132 
0133     return true;
0134 }
0135 
0136 CrashedApplication *KCrashBackend::constructCrashedApplication()
0137 {
0138     const auto pid = DrKonqi::pid();
0139     QFileInfo executable;
0140     QString fakeBaseName;
0141     bool hasDeletedFiles = false;
0142 
0143     // try to determine the executable that crashed
0144     const QString procPath(QStringLiteral("/proc/%1").arg(pid));
0145     const QString exeProcPath(procPath + QStringLiteral("/exe"));
0146     if (QFileInfo::exists(exeProcPath)) {
0147         // on linux, the fastest and most reliable way is to get the path from /proc
0148         qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path";
0149         const QString exePath = QFile::symLinkTarget(exeProcPath);
0150 
0151         executable.setFile(exePath);
0152         if (DrKonqi::isKdeinit() || executable.fileName().startsWith(QLatin1String("python"))) {
0153             fakeBaseName = DrKonqi::appName();
0154         }
0155 
0156         const QString mapsPath = procPath + QStringLiteral("/maps");
0157         QFile mapsFile(mapsPath);
0158         if (mapsFile.open(QFile::ReadOnly)) {
0159             hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(exePath, mapsFile.readAll(), LinuxProc::Check::DeletedMarker);
0160         } else {
0161             qCWarning(DRKONQI_LOG) << "failed to open maps file" << mapsPath;
0162         }
0163 
0164         qCDebug(DRKONQI_LOG) << "exe" << exePath << "has deleted files:" << hasDeletedFiles;
0165     } else {
0166         if (DrKonqi::isKdeinit()) {
0167             executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5")));
0168             fakeBaseName = DrKonqi::appName();
0169         } else {
0170             QFileInfo execPath(DrKonqi::appName());
0171             if (execPath.isAbsolute()) {
0172                 executable = execPath;
0173             } else if (!DrKonqi::appPath().isEmpty()) {
0174                 QDir execDir(DrKonqi::appPath());
0175                 executable = QFileInfo(execDir.absoluteFilePath(execPath.fileName()));
0176             } else {
0177                 executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName()));
0178             }
0179         }
0180     }
0181 
0182     qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath();
0183     qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists();
0184 
0185     return new CrashedApplication(pid,
0186                                   DrKonqi::thread(),
0187                                   DrKonqi::signal(),
0188                                   executable,
0189                                   DrKonqi::appVersion(),
0190                                   BugReportAddress(DrKonqi::bugAddress()),
0191                                   DrKonqi::programName(),
0192                                   DrKonqi::productName(),
0193                                   QDateTime::currentDateTime(),
0194                                   DrKonqi::isRestarted(),
0195                                   hasDeletedFiles,
0196                                   fakeBaseName,
0197                                   this);
0198 }
0199 
0200 DebuggerManager *KCrashBackend::constructDebuggerManager()
0201 {
0202     KConfigGroup config(KSharedConfig::openConfig(), "DrKonqi");
0203 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED > 1070
0204     QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("lldb"));
0205 #elif !defined(Q_OS_WIN)
0206     QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb"));
0207 #else
0208     QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("cdb"));
0209 #endif
0210 
0211     const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash"));
0212     const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(QStringLiteral("KCrash"));
0213 
0214     const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, defaultDebuggerName));
0215     qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName();
0216     return new DebuggerManager(preferredDebugger, externalDebuggers, this);
0217 }
0218 
0219 void KCrashBackend::stopAttachedProcess()
0220 {
0221     if (m_state == ProcessRunning) {
0222         qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process";
0223         ::kill(crashedApplication()->pid(), SIGSTOP);
0224         m_state = ProcessStopped;
0225     }
0226 }
0227 
0228 void KCrashBackend::continueAttachedProcess()
0229 {
0230     if (m_state == ProcessStopped) {
0231         qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process";
0232         ::kill(crashedApplication()->pid(), SIGCONT);
0233         m_state = ProcessRunning;
0234     }
0235 }
0236 
0237 void KCrashBackend::onDebuggerStarting()
0238 {
0239     continueAttachedProcess();
0240     m_state = DebuggerRunning;
0241 }
0242 
0243 void KCrashBackend::onDebuggerFinished()
0244 {
0245     m_state = ProcessRunning;
0246     stopAttachedProcess();
0247 }
0248 
0249 // static
0250 qint64 KCrashBackend::s_pid = 0;
0251 
0252 // static
0253 void KCrashBackend::emergencySaveFunction(int signal)
0254 {
0255     // In case drkonqi itself crashes, we need to get rid of the process being debugged,
0256     // so we kill it, no matter what its state was.
0257     Q_UNUSED(signal);
0258     ::kill(s_pid, SIGKILL);
0259 }