File indexing completed on 2024-04-21 05:26:41
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 <chrono> 0010 0011 #include <KCrash> 0012 0013 #include <QDBusConnection> 0014 #include <QDBusConnectionInterface> 0015 #include <QDBusMessage> 0016 #include <QDBusPendingCallWatcher> 0017 #include <QDBusReply> 0018 #include <QDebug> 0019 #include <QJsonDocument> 0020 #include <QJsonObject> 0021 #include <QProcess> 0022 #include <QScopeGuard> 0023 0024 #include <unistd.h> 0025 0026 // TODO: refactor the entire stack such that logs extraction happens in the processor and gets passed along the chain 0027 #include <systemd/sd-journal.h> 0028 0029 #include <coredump.h> 0030 0031 #include "bugzillaintegration/reportinterface.h" 0032 #include "crashedapplication.h" 0033 #include "debugger.h" 0034 #include "debuggermanager.h" 0035 #include "drkonqi.h" 0036 #include "drkonqi_debug.h" 0037 #include "linuxprocmapsparser.h" 0038 #include <coredumpexcavator.h> 0039 0040 using namespace std::chrono_literals; 0041 using namespace Qt::StringLiterals; 0042 0043 using namespace std::chrono_literals; 0044 using namespace Qt::StringLiterals; 0045 0046 namespace 0047 { 0048 // Special backend type for core-based debugging. A polkit helper excavates the core file for us and we then 0049 // invoke gdb on it. Side stepping coredumpctl. 0050 constexpr auto CORE_BACKEND_TYPE = "coredump-core"_L1; 0051 0052 [[nodiscard]] QString errnoError(const QString &msg, int err) 0053 { 0054 return msg + u": (%1) "_s.arg(QString::number(err)) + QString::fromLocal8Bit(strerror(err)); 0055 } 0056 0057 QList<EntriesHash> collectLogs(const QByteArray &cursor, sd_journal *context, const QStringList &matches) 0058 { 0059 Q_ASSERT(context); 0060 0061 // - Reset all matches 0062 // - seek to the cursor of the crash 0063 // - install all matches to filter only log output from the crashed app 0064 // - grab last N messages 0065 0066 sd_journal_flush_matches(context); 0067 if (auto err = sd_journal_seek_cursor(context, cursor.constData()); err != 0) { 0068 qCWarning(DRKONQI_LOG) << errnoError(u"Failed to seek to cursor"_s, -err); 0069 return {}; 0070 } 0071 0072 // make the cursor the current entry so we can read its time 0073 if (auto err = sd_journal_next(context); err < 0) { 0074 qCWarning(DRKONQI_LOG) << errnoError(u"Failed to read cursor"_s, -err); 0075 return {}; 0076 } 0077 0078 uint64_t core_time = 0; 0079 sd_id128_t boot_id; 0080 if (auto err = sd_journal_get_monotonic_usec(context, &core_time, &boot_id); err != 0) { 0081 qCWarning(DRKONQI_LOG) << errnoError(u"Failed to get core time"_s, -err); 0082 return {}; 0083 } 0084 0085 for (const auto &match : matches) { 0086 if (sd_journal_add_match(context, qUtf8Printable(match), 0) != 0) { 0087 qCWarning(DRKONQI_LOG) << "Failed to install custom match:" << match; 0088 return {}; 0089 } 0090 } 0091 0092 QList<EntriesHash> blobs; 0093 constexpr auto maxBacklog = 30; 0094 for (int i = 0; i < maxBacklog; i++) { 0095 if (auto err = sd_journal_previous(context); err <= 0) { // end of data 0096 if (err < 0) { // error 0097 qCWarning(DRKONQI_LOG) << errnoError(u"Failed to seek previous entry"_s, -err); 0098 } 0099 break; 0100 } 0101 0102 uint64_t time = 0; 0103 if (auto err = sd_journal_get_monotonic_usec(context, &time, &boot_id); err != 0) { 0104 qCWarning(DRKONQI_LOG) << errnoError(u"Failed to get entry time"_s, -err); 0105 break; // don't know when this entry is from, assume it's old 0106 } 0107 0108 std::chrono::microseconds timeDelta(core_time - time); 0109 if (timeDelta > 8s) { 0110 break; 0111 } 0112 0113 EntriesHash blob; 0114 const void *data = nullptr; 0115 size_t length = 0; 0116 SD_JOURNAL_FOREACH_DATA(context, data, length) 0117 { 0118 // size_t is uint, QBA uses int, make sure we don't overflow the int! 0119 int dataSize = static_cast<int>(length); 0120 Q_ASSERT(dataSize >= 0); 0121 Q_ASSERT(static_cast<quint64>(dataSize) == length); 0122 0123 QByteArray entry(static_cast<const char *>(data), dataSize); 0124 const auto offset = entry.indexOf('='); 0125 if (offset < 0) { 0126 qCWarning(DRKONQI_LOG) << "this entry looks funny it has no separating = character" << entry; 0127 continue; 0128 } 0129 0130 const QByteArray key = entry.left(offset); 0131 const QByteArray value = entry.mid(offset + 1); 0132 0133 blob.emplace(key, value); 0134 } 0135 blobs.push_back(blob); 0136 } 0137 0138 return blobs; 0139 } 0140 } // namespace 0141 0142 bool CoredumpBackend::init() 0143 { 0144 Q_ASSERT(!metadataPath().isEmpty()); 0145 Q_ASSERT_X(QFile::exists(metadataPath()), static_cast<const char *>(Q_FUNC_INFO), qUtf8Printable(metadataPath())); 0146 qCDebug(DRKONQI_LOG) << "loading metadata" << metadataPath(); 0147 0148 QFile file(metadataPath()); 0149 if (!file.open(QFile::ReadOnly)) { 0150 return false; 0151 } 0152 auto document = QJsonDocument::fromJson(file.readAll()); 0153 const auto journal = document[u"journal"_s].toObject().toVariantHash(); 0154 for (auto [key, value] : journal.asKeyValueRange()) { 0155 m_journalEntry.insert(key.toUtf8(), value.toByteArray()); 0156 } 0157 // conceivably the file contains no Journal group for unknown reasons 0158 Q_ASSERT_X(!m_journalEntry.isEmpty(), static_cast<const char *>(Q_FUNC_INFO), qUtf8Printable(metadataPath())); 0159 0160 AbstractDrKonqiBackend::init(); // calls constructCrashedApplication -> we need to have our members set before calling it 0161 0162 if (crashedApplication()->pid() <= 0) { 0163 qCWarning(DRKONQI_LOG) << "Invalid pid specified or it wasn't found in journald."; 0164 return false; 0165 } 0166 0167 connect(ReportInterface::self(), &ReportInterface::crashEventSent, this, [] { 0168 QFile file(metadataPath()); 0169 if (!file.open(QFile::ReadWrite)) { 0170 qCWarning(DRKONQI_LOG) << "Failed to open for writing" << metadataPath(); 0171 return; 0172 } 0173 auto object = QJsonDocument::fromJson(file.readAll()).object(); 0174 constexpr auto DRKONQI_KEY = "drkonqi"_L1; 0175 auto drkonqiObject = object[DRKONQI_KEY].toObject(); 0176 drkonqiObject[u"sentryEventId"_s] = ReportInterface::self()->sentryEventId(); 0177 object[DRKONQI_KEY] = drkonqiObject; 0178 file.reset(); 0179 file.write(QJsonDocument(object).toJson()); 0180 }); 0181 0182 return true; 0183 } 0184 0185 CrashedApplication *CoredumpBackend::constructCrashedApplication() 0186 { 0187 Q_ASSERT(!m_journalEntry.isEmpty()); 0188 0189 bool ok = false; 0190 // Journald timestamps are always micro seconds, coredumpd takes care to make sure this also the case for its timestamp. 0191 const std::chrono::microseconds timestamp(m_journalEntry["COREDUMP_TIMESTAMP"].toLong(&ok)); 0192 Q_ASSERT(ok); 0193 const auto datetime = QDateTime::fromMSecsSinceEpoch(std::chrono::duration_cast<std::chrono::milliseconds>(timestamp).count()); 0194 Q_ASSERT(ok); 0195 const QFileInfo executable(QString::fromUtf8(m_journalEntry["COREDUMP_EXE"])); 0196 const int signal = m_journalEntry["COREDUMP_SIGNAL"].toInt(&ok); 0197 Q_ASSERT(ok); 0198 const bool hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(executable.filePath(), m_journalEntry["COREDUMP_PROC_MAPS"], LinuxProc::Check::Stat); 0199 0200 Q_ASSERT_X(m_journalEntry["COREDUMP_PID"].toInt() == DrKonqi::pid(), 0201 static_cast<const char *>(Q_FUNC_INFO), 0202 qPrintable(QStringLiteral("journal: %1, drkonqi: %2").arg(QString::fromUtf8(m_journalEntry["COREDUMP_PID"]), QString::number(DrKonqi::pid())))); 0203 0204 auto expectedJournal = owning_ptr_call<sd_journal>(sd_journal_open, SD_JOURNAL_LOCAL_ONLY); 0205 Q_ASSERT(expectedJournal.ret == 0); 0206 Q_ASSERT(expectedJournal.value); 0207 0208 m_crashedApplication = std::make_unique<CrashedApplication>(DrKonqi::pid(), 0209 DrKonqi::thread(), 0210 signal, 0211 executable, 0212 DrKonqi::appVersion(), 0213 BugReportAddress(DrKonqi::bugAddress()), 0214 DrKonqi::programName(), 0215 DrKonqi::productName(), 0216 datetime, 0217 DrKonqi::isRestarted(), 0218 hasDeletedFiles); 0219 0220 m_crashedApplication->m_logs = collectLogs(m_journalEntry[Coredump::keyCursor()], 0221 expectedJournal.value.get(), 0222 { 0223 u"_PID=%1"_s.arg(QString::fromUtf8(m_journalEntry["COREDUMP_PID"])), 0224 u"_UID=%1"_s.arg(QString::fromUtf8(m_journalEntry["COREDUMP_UID"])), 0225 u"_COMM=%1"_s.arg(QString::fromUtf8(m_journalEntry["COREDUMP_COMM"])), 0226 u"_EXE=%1"_s.arg(QString::fromUtf8(m_journalEntry["COREDUMP_EXE"])), 0227 u"_BOOT_ID=%1"_s.arg(QString::fromUtf8(m_journalEntry["_BOOT_ID"])), 0228 }); 0229 0230 qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath(); 0231 qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists(); 0232 0233 return m_crashedApplication.get(); 0234 } 0235 0236 DebuggerManager *CoredumpBackend::constructDebuggerManager() 0237 { 0238 const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(CORE_BACKEND_TYPE); 0239 const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(CORE_BACKEND_TYPE); 0240 0241 const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, QStringLiteral("gdb"))); 0242 qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName(); 0243 0244 m_debuggerManager = new DebuggerManager(preferredDebugger, externalDebuggers, this); 0245 return m_debuggerManager; 0246 } 0247 0248 void CoredumpBackend::prepareForDebugger() 0249 { 0250 if (m_excavator) { 0251 m_excavator->excavateFrom(QString::fromUtf8(m_journalEntry["COREDUMP_FILENAME"])); 0252 return; 0253 } 0254 0255 m_excavator = std::make_unique<AutomaticCoredumpExcavator>(); 0256 connect(m_excavator.get(), &AutomaticCoredumpExcavator::failed, this, [this] { 0257 Q_EMIT failedToPrepare(); 0258 }); 0259 connect(m_excavator.get(), &AutomaticCoredumpExcavator::excavated, this, [this](const QString &corePath) { 0260 m_crashedApplication->m_coreFile = corePath; 0261 Q_EMIT preparedForDebugger(); 0262 }); 0263 m_excavator->excavateFrom(QString::fromUtf8(m_journalEntry["COREDUMP_FILENAME"])); 0264 } 0265 0266 #include "moc_coredumpbackend.cpp"