File indexing completed on 2024-04-21 16:12:21

0001 /*
0002     SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "coredumpbackend.h"
0008 
0009 #include <KCrash>
0010 #include <QDebug>
0011 #include <QProcess>
0012 #include <QScopeGuard>
0013 #include <QSettings>
0014 
0015 #include <unistd.h>
0016 
0017 #include "crashedapplication.h"
0018 #include "debugger.h"
0019 #include "debuggermanager.h"
0020 #include "drkonqi.h"
0021 #include "drkonqi_debug.h"
0022 #include "linuxprocmapsparser.h"
0023 
0024 // Only use signal safe API here.
0025 //   man 7 signal-safety
0026 static void emergencySaveFunction(int signal)
0027 {
0028     // Should we crash while dealing with this crash, then make sure to remove the metadata file.
0029     // Otherwise the helper daemon will call us again, and again cause a crash, until the user manually removes the file.
0030     Q_UNUSED(signal);
0031     unlink(qPrintable(CoredumpBackend::metadataPath()));
0032 }
0033 
0034 bool CoredumpBackend::init()
0035 {
0036     KCrash::setEmergencySaveFunction(emergencySaveFunction);
0037 
0038     Q_ASSERT(!metadataPath().isEmpty());
0039     Q_ASSERT_X(QFile::exists(metadataPath()), static_cast<const char *>(Q_FUNC_INFO), qUtf8Printable(metadataPath()));
0040     qCDebug(DRKONQI_LOG) << "loading metadata" << metadataPath();
0041 
0042     QSettings metadata(metadataPath(), QSettings::IniFormat);
0043     metadata.beginGroup(QStringLiteral("Journal"));
0044     const QStringList keys = metadata.allKeys();
0045     for (const auto &key : keys) {
0046         m_journalEntry.insert(key.toUtf8(), metadata.value(key).toByteArray());
0047     }
0048     // conceivably the file contains no Journal group for unknown reasons
0049     Q_ASSERT_X(!m_journalEntry.isEmpty(), static_cast<const char *>(Q_FUNC_INFO), qUtf8Printable(metadataPath()));
0050 
0051     AbstractDrKonqiBackend::init(); // calls constructCrashedApplication -> we need to have our members set before calling it
0052 
0053     if (crashedApplication()->pid() <= 0) {
0054         qCWarning(DRKONQI_LOG) << "Invalid pid specified or it wasn't found in journald.";
0055         return false;
0056     }
0057 
0058     return true;
0059 }
0060 
0061 CrashedApplication *CoredumpBackend::constructCrashedApplication()
0062 {
0063     Q_ASSERT(!m_journalEntry.isEmpty());
0064 
0065     bool ok = false;
0066     // Journald timestamps are always micro seconds, coredumpd takes care to make sure this also the case for its timestamp.
0067     const std::chrono::microseconds timestamp(m_journalEntry["COREDUMP_TIMESTAMP"].toLong(&ok));
0068     Q_ASSERT(ok);
0069     const auto datetime = QDateTime::fromMSecsSinceEpoch(std::chrono::duration_cast<std::chrono::milliseconds>(timestamp).count());
0070     Q_ASSERT(ok);
0071     const QFileInfo executable(QString::fromUtf8(m_journalEntry["COREDUMP_EXE"]));
0072     const int signal = m_journalEntry["COREDUMP_SIGNAL"].toInt(&ok);
0073     Q_ASSERT(ok);
0074     const bool hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(executable.path(), m_journalEntry["COREDUMP_PROC_MAPS"], LinuxProc::Check::Stat);
0075 
0076     Q_ASSERT_X(m_journalEntry["COREDUMP_PID"].toInt() == DrKonqi::pid(),
0077                static_cast<const char *>(Q_FUNC_INFO),
0078                qPrintable(QStringLiteral("journal: %1, drkonqi: %2").arg(QString::fromUtf8(m_journalEntry["COREDUMP_PID"]), QString::number(DrKonqi::pid()))));
0079 
0080     m_crashedApplication = std::make_unique<CrashedApplication>(DrKonqi::pid(),
0081                                                                 DrKonqi::thread(),
0082                                                                 signal,
0083                                                                 executable,
0084                                                                 DrKonqi::appVersion(),
0085                                                                 BugReportAddress(DrKonqi::bugAddress()),
0086                                                                 DrKonqi::programName(),
0087                                                                 DrKonqi::productName(),
0088                                                                 datetime,
0089                                                                 DrKonqi::isRestarted(),
0090                                                                 hasDeletedFiles);
0091 
0092     qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath();
0093     qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists();
0094 
0095     return m_crashedApplication.get();
0096 }
0097 
0098 DebuggerManager *CoredumpBackend::constructDebuggerManager()
0099 {
0100     const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(m_backendType);
0101     const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(m_backendType);
0102 
0103     const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, QStringLiteral("gdb")));
0104     qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName();
0105 
0106     m_debuggerManager = new DebuggerManager(preferredDebugger, externalDebuggers, this);
0107     return m_debuggerManager;
0108 }
0109 
0110 void CoredumpBackend::prepareForDebugger()
0111 {
0112     if (m_preparationProc) {
0113         return; // Preparation in progress.
0114     }
0115 
0116     // Legacy coredumpd doesn't support debugger arguments. We'll have to actually extract the core and manually trace on it,
0117     // somewhat meh. When Ubuntu 20.04 either goes EOL or is deemed irrelevant enough we can remove the entire preparation
0118     // rigging (even in AbstractDrKonqiBackend).
0119     const bool needPreparation = (m_backendType == QLatin1String("coredumpd"));
0120     const bool alreadyPrepared = (m_coreDir != nullptr);
0121 
0122     if (!needPreparation || alreadyPrepared) {
0123         // Synthesize a signal.
0124         QMetaObject::invokeMethod(this, &CoredumpBackend::preparedForDebugger, Qt::QueuedConnection);
0125         return;
0126     }
0127 
0128     m_coreDir = std::make_unique<QTemporaryDir>(QDir::tempPath() + QStringLiteral("/kcrash-core"));
0129     Q_ASSERT(m_coreDir->isValid());
0130 
0131     const QString coreFile = m_coreDir->filePath(QStringLiteral("core"));
0132     m_preparationProc = std::make_unique<QProcess>();
0133     m_preparationProc->setProcessChannelMode(QProcess::ForwardedChannels);
0134     m_preparationProc->setProgram(QStringLiteral("coredumpctl"));
0135     m_preparationProc->setArguments({QStringLiteral("--output"), coreFile, QStringLiteral("dump"), QString::number(m_crashedApplication->pid())});
0136     QObject::connect(
0137         m_preparationProc.get(),
0138         &QProcess::finished,
0139         this,
0140         [this, coreFile](int exitCode, QProcess::ExitStatus exitStatus) {
0141             qDebug() << "Coredumpd core dumping completed" << exitCode << exitStatus << coreFile;
0142             // We dont really care if the dumping failed. The debugger will fail if the core file isn't there and on the UI
0143             // side there's not much point differentiating as the user won't be able to file a bug in any case.
0144             m_preparationProc = nullptr;
0145             m_crashedApplication->m_coreFile = coreFile;
0146             Q_EMIT preparedForDebugger();
0147         },
0148         Qt::QueuedConnection /* queue so we don't delete the object out from under it */);
0149     m_preparationProc->start();
0150 }