1 /*
2     SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "coredumpbackend.h"
8 
9 #include <KCrash>
10 #include <QDebug>
11 #include <QProcess>
12 #include <QScopeGuard>
13 #include <QSettings>
14 #include <memory>
15 
16 #include <unistd.h>
17 
18 #include "crashedapplication.h"
19 #include "debugger.h"
20 #include "debuggermanager.h"
21 #include "drkonqi.h"
22 #include "drkonqi_debug.h"
23 #include "linuxprocmapsparser.h"
24 
25 // Only use signal safe API here.
26 //   man 7 signal-safety
emergencySaveFunction(int signal)27 static void emergencySaveFunction(int signal)
28 {
29     // Should we crash while dealing with this crash, then make sure to remove the metadata file.
30     // Otherwise the helper daemon will call us again, and again cause a crash, until the user manually removes the file.
31     Q_UNUSED(signal);
32     unlink(qPrintable(CoredumpBackend::metadataPath()));
33 }
34 
init()35 bool CoredumpBackend::init()
36 {
37     KCrash::setEmergencySaveFunction(emergencySaveFunction);
38 
39     Q_ASSERT(!metadataPath().isEmpty());
40     Q_ASSERT_X(QFile::exists(metadataPath()), static_cast<const char *>(Q_FUNC_INFO), qUtf8Printable(metadataPath()));
41     qCDebug(DRKONQI_LOG) << "loading metadata" << metadataPath();
42 
43     QSettings metadata(metadataPath(), QSettings::IniFormat);
44     metadata.beginGroup(QStringLiteral("Journal"));
45     const QStringList keys = metadata.allKeys();
46     for (const auto &key : keys) {
47         m_journalEntry.insert(key.toUtf8(), metadata.value(key).toByteArray());
48     }
49     // conceivably the file contains no Journal group for unknown reasons
50     Q_ASSERT_X(!m_journalEntry.isEmpty(), static_cast<const char *>(Q_FUNC_INFO), qUtf8Printable(metadataPath()));
51 
52     AbstractDrKonqiBackend::init(); // calls constructCrashedApplication -> we need to have our members set before calling it
53 
54     if (crashedApplication()->pid() <= 0) {
55         qCWarning(DRKONQI_LOG) << "Invalid pid specified or it wasn't found in journald.";
56         return false;
57     }
58 
59     return true;
60 }
61 
constructCrashedApplication()62 CrashedApplication *CoredumpBackend::constructCrashedApplication()
63 {
64     Q_ASSERT(!m_journalEntry.isEmpty());
65 
66     bool ok = false;
67     // Journald timestamps are always micro seconds, coredumpd takes care to make sure this also the case for its timestamp.
68     const std::chrono::microseconds timestamp(m_journalEntry["COREDUMP_TIMESTAMP"].toLong(&ok));
69     Q_ASSERT(ok);
70     const auto datetime = QDateTime::fromMSecsSinceEpoch(std::chrono::duration_cast<std::chrono::milliseconds>(timestamp).count());
71     Q_ASSERT(ok);
72     const QFileInfo executable(QString::fromUtf8(m_journalEntry["COREDUMP_EXE"]));
73     const int signal = m_journalEntry["COREDUMP_SIGNAL"].toInt(&ok);
74     Q_ASSERT(ok);
75     const bool hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(executable.path(), m_journalEntry["COREDUMP_PROC_MAPS"], LinuxProc::Check::Stat);
76 
77     Q_ASSERT_X(m_journalEntry["COREDUMP_PID"].toInt() == DrKonqi::pid(),
78                static_cast<const char *>(Q_FUNC_INFO),
79                qPrintable(QStringLiteral("journal: %1, drkonqi: %2").arg(QString::fromUtf8(m_journalEntry["COREDUMP_PID"]), QString::number(DrKonqi::pid()))));
80 
81     m_crashedApplication = std::make_unique<CrashedApplication>(DrKonqi::pid(),
82                                                                 DrKonqi::thread(),
83                                                                 signal,
84                                                                 executable,
85                                                                 DrKonqi::appVersion(),
86                                                                 BugReportAddress(DrKonqi::bugAddress()),
87                                                                 DrKonqi::programName(),
88                                                                 DrKonqi::productName(),
89                                                                 datetime,
90                                                                 DrKonqi::isRestarted(),
91                                                                 hasDeletedFiles);
92 
93     qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath();
94     qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists();
95 
96     return m_crashedApplication.get();
97 }
98 
constructDebuggerManager()99 DebuggerManager *CoredumpBackend::constructDebuggerManager()
100 {
101     const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(m_backendType);
102     const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(m_backendType);
103 
104     const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, QStringLiteral("gdb")));
105     qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName();
106 
107     m_debuggerManager = new DebuggerManager(preferredDebugger, externalDebuggers, this);
108     return m_debuggerManager;
109 }
110 
prepareForDebugger()111 void CoredumpBackend::prepareForDebugger()
112 {
113     if (m_preparationProc) {
114         return; // Preparation in progress.
115     }
116 
117     // Legacy coredumpd doesn't support debugger arguments. We'll have to actually extract the core and manually trace on it,
118     // somewhat meh. When Ubuntu 20.04 either goes EOL or is deemed irrelevant enough we can remove the entire preparation
119     // rigging (even in AbstractDrKonqiBackend).
120     const bool needPreparation = (m_backendType == QLatin1String("coredumpd"));
121     const bool alreadyPrepared = (m_coreDir != nullptr);
122 
123     if (!needPreparation || alreadyPrepared) {
124         // Synthesize a signal.
125         QMetaObject::invokeMethod(this, &CoredumpBackend::preparedForDebugger, Qt::QueuedConnection);
126         return;
127     }
128 
129     m_coreDir = std::make_unique<QTemporaryDir>(QDir::tempPath() + QStringLiteral("/kcrash-core"));
130     Q_ASSERT(m_coreDir->isValid());
131 
132     const QString coreFile = m_coreDir->filePath(QStringLiteral("core"));
133     m_preparationProc = std::make_unique<QProcess>();
134     m_preparationProc->setProcessChannelMode(QProcess::ForwardedChannels);
135     m_preparationProc->setProgram(QStringLiteral("coredumpctl"));
136     m_preparationProc->setArguments({QStringLiteral("--output"), coreFile, QStringLiteral("dump"), QString::number(m_crashedApplication->pid())});
137     QObject::connect(
138         m_preparationProc.get(),
139         &QProcess::finished,
140         this,
141         [this, coreFile](int exitCode, QProcess::ExitStatus exitStatus) {
142             qDebug() << "Coredumpd core dumping completed" << exitCode << exitStatus << coreFile;
143             // We dont really care if the dumping failed. The debugger will fail if the core file isn't there and on the UI
144             // side there's not much point differentiating as the user won't be able to file a bug in any case.
145             m_preparationProc = nullptr;
146             m_crashedApplication->m_coreFile = coreFile;
147             Q_EMIT preparedForDebugger();
148         },
149         Qt::QueuedConnection /* queue so we don't delete the object out from under it */);
150     m_preparationProc->start();
151 }
152