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