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

0001 /*****************************************************************
0002  * drkonqi - The KDE Crash Handler
0003  *
0004  * SPDX-FileCopyrightText: 2000-2003 Hans Petter Bieker <bieker@kde.org>
0005  * SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net>
0006  * SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
0007  *
0008  * SPDX-License-Identifier: BSD-2-Clause
0009  *****************************************************************/
0010 #include "backtracegenerator.h"
0011 
0012 #include "config-drkonqi.h"
0013 #include "drkonqi.h"
0014 #include "drkonqi_debug.h"
0015 
0016 #include <QTemporaryDir>
0017 
0018 #include <KProcess>
0019 #include <KShell>
0020 
0021 #include "parser/backtraceparser.h"
0022 
0023 BacktraceGenerator::BacktraceGenerator(const Debugger &debugger, QObject *parent)
0024     : QObject(parent)
0025     , m_debugger(debugger)
0026     , m_supportsSymbolResolution(WITH_GDB12 && m_debugger.supportsCommandWithSymbolResolution())
0027 {
0028     m_parser = BacktraceParser::newParser(m_debugger.codeName(), this);
0029     m_parser->connectToGenerator(this);
0030 
0031 #ifdef BACKTRACE_PARSER_DEBUG
0032     m_debugParser = BacktraceParser::newParser(QString(), this); // uses the null parser
0033     m_debugParser->connectToGenerator(this);
0034 #endif
0035 }
0036 
0037 BacktraceGenerator::~BacktraceGenerator()
0038 {
0039     if (m_proc && m_proc->state() == QProcess::Running) {
0040         qCWarning(DRKONQI_LOG) << "Killing running debugger instance";
0041         m_proc->disconnect(this);
0042         m_proc->terminate();
0043         if (!m_proc->waitForFinished(10000)) {
0044             m_proc->kill();
0045             // lldb can become "stuck" on OS X; just mark m_proc as to be deleted later rather
0046             // than waiting a potentially very long time for it to heed the kill() request.
0047             m_proc->deleteLater();
0048         } else {
0049             delete m_proc;
0050         }
0051         delete m_temp;
0052     }
0053 }
0054 
0055 void BacktraceGenerator::start()
0056 {
0057     // they should always be null before entering this function.
0058     Q_ASSERT(!m_proc);
0059     Q_ASSERT(!m_temp);
0060 
0061     m_parsedBacktrace.clear();
0062 
0063     if (!m_debugger.isValid() || !m_debugger.isInstalled()) {
0064         qCWarning(DRKONQI_LOG) << "Debugger valid" << m_debugger.isValid() << "installed" << m_debugger.isInstalled();
0065         m_state = FailedToStart;
0066         Q_EMIT stateChanged();
0067         Q_EMIT failedToStart();
0068         return;
0069     }
0070 
0071     m_state = Loading;
0072     Q_EMIT stateChanged();
0073     Q_EMIT preparing();
0074     // DebuggerManager calls setBackendPrepared when it is ready for us to actually start.
0075 }
0076 
0077 void BacktraceGenerator::slotReadInput()
0078 {
0079     if (!m_proc) {
0080         // this can happen with lldb after we detected that it detached from the debuggee.
0081         return;
0082     }
0083 
0084     // we do not know if the output array ends in the middle of an utf-8 sequence
0085     m_output += m_proc->readAllStandardOutput();
0086 
0087     int pos;
0088     while ((pos = m_output.indexOf('\n')) != -1) {
0089         QString line = QString::fromLocal8Bit(m_output.constData(), pos + 1);
0090         m_output.remove(0, pos + 1);
0091 
0092         Q_EMIT newLine(line);
0093         line = line.simplified();
0094         if (line.startsWith(QLatin1String("Process ")) && line.endsWith(QLatin1String(" detached"))) {
0095             // lldb is acting on a detach command (in lldbrc)
0096             // Anything following this line doesn't interest us, and lldb has been known
0097             // to turn into a zombie instead of exiting, thereby blocking us.
0098             // Tell the process to quit if it's still running, and pretend it did.
0099             if (m_proc && m_proc->state() == QProcess::Running) {
0100                 m_proc->terminate();
0101                 if (!m_proc->waitForFinished(500)) {
0102                     m_proc->kill();
0103                 }
0104                 if (m_proc) {
0105                     slotProcessExited(0, QProcess::NormalExit);
0106                 }
0107             }
0108             return;
0109         }
0110     }
0111 }
0112 
0113 void BacktraceGenerator::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
0114 {
0115     // these are useless now
0116     m_proc->deleteLater();
0117     m_temp->deleteLater();
0118     m_proc = nullptr;
0119     m_temp = nullptr;
0120 
0121     // mark the end of the backtrace for the parser
0122     Q_EMIT newLine(QString());
0123 
0124     if (exitStatus != QProcess::NormalExit || exitCode != 0) {
0125         m_state = Failed;
0126         Q_EMIT stateChanged();
0127         Q_EMIT someError();
0128         return;
0129     }
0130 
0131     // no translation, string appears in the report
0132     QString tmp(QStringLiteral("Application: %progname (%execname), signal: %signame\n"));
0133     Debugger::expandString(tmp);
0134 
0135     m_parsedBacktrace = tmp + m_parser->informationLines() + m_parser->parsedBacktrace();
0136     m_state = Loaded;
0137     Q_EMIT stateChanged();
0138 
0139 #ifdef BACKTRACE_PARSER_DEBUG
0140     // append the raw unparsed backtrace
0141     m_parsedBacktrace += "\n------------ Unparsed Backtrace ------------\n";
0142     m_parsedBacktrace += m_debugParser->parsedBacktrace(); // it's not really parsed, it's from the null parser.
0143 #endif
0144 
0145     Q_EMIT done();
0146 }
0147 
0148 void BacktraceGenerator::slotOnErrorOccurred(QProcess::ProcessError error)
0149 {
0150     qCWarning(DRKONQI_LOG) << "Debugger process had an error" << error << m_proc->program() << m_proc->arguments() << m_proc->environment();
0151 
0152     // we mustn't keep these around...
0153     m_proc->deleteLater();
0154     m_temp->deleteLater();
0155     m_proc = nullptr;
0156     m_temp = nullptr;
0157 
0158     switch (error) {
0159     case QProcess::FailedToStart:
0160         m_state = FailedToStart;
0161         Q_EMIT stateChanged();
0162         Q_EMIT failedToStart();
0163         break;
0164     default:
0165         m_state = Failed;
0166         Q_EMIT stateChanged();
0167         Q_EMIT someError();
0168         break;
0169     }
0170 }
0171 
0172 void BacktraceGenerator::setBackendPrepared()
0173 {
0174     // they should always be null before entering this function.
0175     Q_ASSERT(!m_proc);
0176     Q_ASSERT(!m_temp);
0177 
0178     Q_ASSERT(m_state == Loading);
0179 
0180     Q_EMIT starting();
0181 
0182     m_proc = new KProcess;
0183     m_proc->setEnv(QStringLiteral("LC_ALL"), QStringLiteral("C")); // force C locale
0184 
0185     // Temporary directory for the preeamble.py to write data into, we can then conveniently pick it up from there.
0186     // Only useful for data that is not meant to appear in the trace (e.g. sentry payloads).
0187     if (!m_tempDirectory) {
0188         m_tempDirectory = std::make_unique<QTemporaryDir>();
0189     }
0190     if (!m_tempDirectory->isValid()) {
0191         qCWarning(DRKONQI_LOG) << "Failed to create temporary directory for generator!";
0192     } else {
0193 #if WITH_SENTRY
0194         m_proc->setEnv(QStringLiteral("DRKONQI_WITH_SENTRY"), QStringLiteral("1"));
0195 #endif
0196         m_proc->setEnv(QStringLiteral("DRKONQI_TMP_DIR"), m_tempDirectory->path());
0197         m_proc->setEnv(QStringLiteral("DRKONQI_VERSION"), QStringLiteral(PROJECT_VERSION));
0198         m_proc->setEnv(QStringLiteral("DRKONQI_APP_VERSION"), DrKonqi::appVersion());
0199         m_proc->setEnv(QStringLiteral("DRKONQI_SIGNAL"), QString::number(DrKonqi::signal()));
0200     }
0201 
0202     m_temp = new QTemporaryFile;
0203     m_temp->open();
0204     m_temp->write(m_debugger.backtraceBatchCommands().toLatin1());
0205     m_temp->write("\n", 1);
0206     m_temp->flush();
0207 
0208     auto preamble = new QTemporaryFile(m_proc);
0209     preamble->open();
0210     preamble->write(m_debugger.preambleCommands().toUtf8());
0211     preamble->write("\n", 1);
0212     preamble->flush();
0213 
0214     // start the debugger
0215     QString str = m_symbolResolution ? m_debugger.commandWithSymbolResolution() : m_debugger.command();
0216     Debugger::expandString(str, Debugger::ExpansionUsageShell, m_temp->fileName(), preamble->fileName());
0217 
0218     *m_proc << KShell::splitArgs(str);
0219     m_proc->setOutputChannelMode(KProcess::OnlyStdoutChannel);
0220     m_proc->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Text);
0221     // check if the debugger should take its input from a file we'll generate,
0222     // and take the appropriate steps if so
0223     QString stdinFile = m_debugger.backendValueOfParameter(QStringLiteral("ExecInputFile"));
0224     Debugger::expandString(stdinFile, Debugger::ExpansionUsageShell, m_temp->fileName(), preamble->fileName());
0225     if (!stdinFile.isEmpty() && QFile::exists(stdinFile)) {
0226         m_proc->setStandardInputFile(stdinFile);
0227     }
0228     connect(m_proc, &KProcess::readyReadStandardOutput, this, &BacktraceGenerator::slotReadInput);
0229     connect(m_proc, static_cast<void (KProcess::*)(int, QProcess::ExitStatus)>(&KProcess::finished), this, &BacktraceGenerator::slotProcessExited);
0230     connect(m_proc, &KProcess::errorOccurred, this, &BacktraceGenerator::slotOnErrorOccurred);
0231 
0232     m_proc->start();
0233 }
0234 
0235 bool BacktraceGenerator::debuggerIsGDB() const
0236 {
0237     return m_debugger.codeName() == QLatin1String("gdb");
0238 }
0239 
0240 QString BacktraceGenerator::debuggerName() const
0241 {
0242     return m_debugger.displayName();
0243 }
0244 
0245 QByteArray BacktraceGenerator::sentryPayload() const
0246 {
0247     const QString sentryPayloadFile = m_tempDirectory->path() + QLatin1String("/sentry_payload.json");
0248     QFile file(sentryPayloadFile);
0249     if (!file.open(QFile::ReadOnly)) {
0250         qCWarning(DRKONQI_LOG) << "Could not open sentry payload file" << sentryPayloadFile;
0251         return {};
0252     }
0253     return file.readAll();
0254 };