File indexing completed on 2024-04-21 05:26:42

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 QString AbstractDrKonqiBackend::metadataPath()
0054 {
0055     static QString path = [] {
0056         const QString envPath = qEnvironmentVariable("DRKONQI_METADATA_FILE");
0057         qunsetenv("DRKONQI_METADATA_FILE");
0058         Q_ASSERT(!envPath.isEmpty());
0059         return envPath;
0060     }();
0061     return path;
0062 }
0063 
0064 KCrashBackend::~KCrashBackend()
0065 {
0066     continueAttachedProcess();
0067 }
0068 
0069 bool KCrashBackend::init()
0070 {
0071     AbstractDrKonqiBackend::init();
0072 
0073     // check whether the attached process exists and whether we have permissions to inspect it
0074     if (crashedApplication()->pid() <= 0) {
0075         qCWarning(DRKONQI_LOG) << "Invalid pid specified";
0076         return false;
0077     }
0078 
0079 #if !defined(Q_OS_WIN32)
0080     if (::kill(crashedApplication()->pid(), 0) < 0) {
0081         switch (errno) {
0082         case EPERM:
0083             qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process";
0084             break;
0085         case ESRCH:
0086             qCWarning(DRKONQI_LOG) << "The specified process does not exist.";
0087             break;
0088         default:
0089             break;
0090         }
0091         return false;
0092     }
0093 
0094     //--keeprunning means: generate backtrace instantly and let the process continue execution
0095     if (DrKonqi::isKeepRunning()) {
0096         stopAttachedProcess();
0097         debuggerManager()->backtraceGenerator()->start();
0098         connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess);
0099     } else {
0100         connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting);
0101         connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished);
0102 
0103         // stop the process to avoid high cpu usage by other threads (bug 175362).
0104         // if the process was started by kdeinit, we need to wait a bit for KCrash
0105         // to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else
0106         // if we stop it before this call, pending alarm signals will kill the
0107         // process when we try to continue it.
0108         QTimer::singleShot(2s, this, &KCrashBackend::stopAttachedProcess);
0109     }
0110 #endif
0111 
0112     // Handle drkonqi crashes
0113     s_pid = crashedApplication()->pid(); // copy pid for use by the crash handler, so that it is safer
0114     KCrash::setEmergencySaveFunction(emergencySaveFunction);
0115 
0116     return true;
0117 }
0118 
0119 CrashedApplication *KCrashBackend::constructCrashedApplication()
0120 {
0121     const auto pid = DrKonqi::pid();
0122     QFileInfo executable;
0123     QString fakeBaseName;
0124     bool hasDeletedFiles = false;
0125 
0126     // try to determine the executable that crashed
0127     const QString procPath(QStringLiteral("/proc/%1").arg(pid));
0128     const QString exeProcPath(procPath + QStringLiteral("/exe"));
0129     if (QFileInfo::exists(exeProcPath)) {
0130         // on linux, the fastest and most reliable way is to get the path from /proc
0131         qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path";
0132         const QString exePath = QFile::symLinkTarget(exeProcPath);
0133 
0134         executable.setFile(exePath);
0135         if (DrKonqi::isKdeinit() || executable.fileName().startsWith(QLatin1String("python"))) {
0136             fakeBaseName = DrKonqi::appName();
0137         }
0138 
0139         const QString mapsPath = procPath + QStringLiteral("/maps");
0140         QFile mapsFile(mapsPath);
0141         if (mapsFile.open(QFile::ReadOnly)) {
0142             hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(exePath, mapsFile.readAll(), LinuxProc::Check::DeletedMarker);
0143         } else {
0144             qCWarning(DRKONQI_LOG) << "failed to open maps file" << mapsPath;
0145         }
0146 
0147         qCDebug(DRKONQI_LOG) << "exe" << exePath << "has deleted files:" << hasDeletedFiles;
0148     } else {
0149         if (DrKonqi::isKdeinit()) {
0150             executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5")));
0151             fakeBaseName = DrKonqi::appName();
0152         } else {
0153             QFileInfo execPath(DrKonqi::appName());
0154             if (execPath.isAbsolute()) {
0155                 executable = execPath;
0156             } else if (!DrKonqi::appPath().isEmpty()) {
0157                 QDir execDir(DrKonqi::appPath());
0158                 executable = QFileInfo(execDir.absoluteFilePath(execPath.fileName()));
0159             } else {
0160                 executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName()));
0161             }
0162         }
0163     }
0164 
0165     qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath();
0166     qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists();
0167 
0168     return new CrashedApplication(pid,
0169                                   DrKonqi::thread(),
0170                                   DrKonqi::signal(),
0171                                   executable,
0172                                   DrKonqi::appVersion(),
0173                                   BugReportAddress(DrKonqi::bugAddress()),
0174                                   DrKonqi::programName(),
0175                                   DrKonqi::productName(),
0176                                   QDateTime::currentDateTime(),
0177                                   DrKonqi::isRestarted(),
0178                                   hasDeletedFiles,
0179                                   fakeBaseName,
0180                                   this);
0181 }
0182 
0183 DebuggerManager *KCrashBackend::constructDebuggerManager()
0184 {
0185     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("DrKonqi"));
0186 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED > 1070
0187     QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("lldb"));
0188 #elif !defined(Q_OS_WIN)
0189     QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb"));
0190 #else
0191     QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("cdb"));
0192 #endif
0193 
0194     const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash"));
0195     const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(QStringLiteral("KCrash"));
0196 
0197     const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, defaultDebuggerName));
0198     qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName();
0199     return new DebuggerManager(preferredDebugger, externalDebuggers, this);
0200 }
0201 
0202 void KCrashBackend::stopAttachedProcess()
0203 {
0204     if (m_state == ProcessRunning) {
0205         qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process";
0206         ::kill(crashedApplication()->pid(), SIGSTOP);
0207         m_state = ProcessStopped;
0208     }
0209 }
0210 
0211 void KCrashBackend::continueAttachedProcess()
0212 {
0213     if (m_state == ProcessStopped) {
0214         qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process";
0215         ::kill(crashedApplication()->pid(), SIGCONT);
0216         m_state = ProcessRunning;
0217     }
0218 }
0219 
0220 void KCrashBackend::onDebuggerStarting()
0221 {
0222     continueAttachedProcess();
0223     m_state = DebuggerRunning;
0224 }
0225 
0226 void KCrashBackend::onDebuggerFinished()
0227 {
0228     m_state = ProcessRunning;
0229     stopAttachedProcess();
0230 }
0231 
0232 // static
0233 qint64 KCrashBackend::s_pid = 0;
0234 
0235 // static
0236 void KCrashBackend::emergencySaveFunction(int signal)
0237 {
0238     // In case drkonqi itself crashes, we need to get rid of the process being debugged,
0239     // so we kill it, no matter what its state was.
0240     Q_UNUSED(signal);
0241     ::kill(s_pid, SIGKILL);
0242 }
0243 
0244 #include "moc_drkonqibackends.cpp"