File indexing completed on 2024-04-21 05:26:40

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_sentryPayload = [this]() -> QByteArray {
0137         const QString sentryPayloadFile = m_tempDirectory->path() + QLatin1String("/sentry_payload.json");
0138         QFile file(sentryPayloadFile);
0139         if (!file.open(QFile::ReadOnly)) {
0140             qCWarning(DRKONQI_LOG) << "Could not open sentry payload file" << sentryPayloadFile;
0141             return {};
0142         }
0143         return file.readAll();
0144     }();
0145     m_state = Loaded;
0146     Q_EMIT stateChanged();
0147 
0148 #ifdef BACKTRACE_PARSER_DEBUG
0149     // append the raw unparsed backtrace
0150     m_parsedBacktrace += "\n------------ Unparsed Backtrace ------------\n";
0151     m_parsedBacktrace += m_debugParser->parsedBacktrace(); // it's not really parsed, it's from the null parser.
0152 #endif
0153 
0154     Q_EMIT done();
0155 }
0156 
0157 void BacktraceGenerator::slotOnErrorOccurred(QProcess::ProcessError error)
0158 {
0159     qCWarning(DRKONQI_LOG) << "Debugger process had an error" << error << m_proc->program() << m_proc->arguments() << m_proc->environment();
0160 
0161     // we mustn't keep these around...
0162     m_proc->deleteLater();
0163     m_temp->deleteLater();
0164     m_proc = nullptr;
0165     m_temp = nullptr;
0166 
0167     switch (error) {
0168     case QProcess::FailedToStart:
0169         m_state = FailedToStart;
0170         Q_EMIT stateChanged();
0171         Q_EMIT failedToStart();
0172         break;
0173     default:
0174         m_state = Failed;
0175         Q_EMIT stateChanged();
0176         Q_EMIT someError();
0177         break;
0178     }
0179 }
0180 
0181 void BacktraceGenerator::setBackendPrepared()
0182 {
0183     // they should always be null before entering this function.
0184     Q_ASSERT(!m_proc);
0185     Q_ASSERT(!m_temp);
0186 
0187     Q_ASSERT(m_state == Loading);
0188 
0189     Q_EMIT starting();
0190 
0191     m_proc = new KProcess;
0192     m_proc->setEnv(QStringLiteral("LC_ALL"), QStringLiteral("C.UTF-8")); // force C locale
0193 
0194     // Temporary directory for the preamble.py to write data into, we can then conveniently pick it up from there.
0195     // Only useful for data that is not meant to appear in the trace (e.g. sentry payloads).
0196     if (!m_tempDirectory) {
0197         m_tempDirectory = std::make_unique<QTemporaryDir>();
0198     }
0199     if (!m_tempDirectory->isValid()) {
0200         qCWarning(DRKONQI_LOG) << "Failed to create temporary directory for generator!";
0201     } else {
0202         m_proc->setEnv(QStringLiteral("DRKONQI_TMP_DIR"), m_tempDirectory->path());
0203         m_proc->setEnv(QStringLiteral("DRKONQI_VERSION"), QStringLiteral(PROJECT_VERSION));
0204         m_proc->setEnv(QStringLiteral("DRKONQI_APP_VERSION"), DrKonqi::appVersion());
0205         m_proc->setEnv(QStringLiteral("DRKONQI_SIGNAL"), QString::number(DrKonqi::signal()));
0206     }
0207 
0208     m_temp = new QTemporaryFile;
0209     m_temp->open();
0210     m_temp->write(m_debugger.backtraceBatchCommands().toLatin1());
0211     m_temp->write("\n", 1);
0212     m_temp->flush();
0213 
0214     auto preamble = new QTemporaryFile(m_proc);
0215     preamble->open();
0216     preamble->write(m_debugger.preambleCommands().toUtf8());
0217     preamble->write("\n", 1);
0218     preamble->flush();
0219 
0220     // start the debugger
0221     QString str = m_symbolResolution ? m_debugger.commandWithSymbolResolution() : m_debugger.command();
0222     Debugger::expandString(str, Debugger::ExpansionUsageShell, m_temp->fileName(), preamble->fileName());
0223 
0224     *m_proc << KShell::splitArgs(str);
0225     m_proc->setOutputChannelMode(KProcess::OnlyStdoutChannel);
0226     m_proc->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Text);
0227     // check if the debugger should take its input from a file we'll generate,
0228     // and take the appropriate steps if so
0229     QString stdinFile = m_debugger.backendValueOfParameter(QStringLiteral("ExecInputFile"));
0230     Debugger::expandString(stdinFile, Debugger::ExpansionUsageShell, m_temp->fileName(), preamble->fileName());
0231     if (!stdinFile.isEmpty() && QFile::exists(stdinFile)) {
0232         m_proc->setStandardInputFile(stdinFile);
0233     }
0234     connect(m_proc, &KProcess::readyReadStandardOutput, this, &BacktraceGenerator::slotReadInput);
0235     connect(m_proc, static_cast<void (KProcess::*)(int, QProcess::ExitStatus)>(&KProcess::finished), this, &BacktraceGenerator::slotProcessExited);
0236     connect(m_proc, &KProcess::errorOccurred, this, &BacktraceGenerator::slotOnErrorOccurred);
0237 
0238     qCDebug(DRKONQI_LOG) << "Starting debugger" << m_proc->program() << m_proc->arguments();
0239     m_proc->start();
0240 }
0241 
0242 bool BacktraceGenerator::debuggerIsGDB() const
0243 {
0244     return m_debugger.codeName() == QLatin1String("gdb");
0245 }
0246 
0247 QString BacktraceGenerator::debuggerName() const
0248 {
0249     return m_debugger.displayName();
0250 }
0251 
0252 QByteArray BacktraceGenerator::sentryPayload() const
0253 {
0254     return m_sentryPayload;
0255 };
0256 
0257 void BacktraceGenerator::setBackendFailed()
0258 {
0259     // Shouldn't have been set yet
0260     Q_ASSERT(!m_proc);
0261     Q_ASSERT(!m_temp);
0262     m_proc = nullptr;
0263     m_temp = nullptr;
0264 
0265     m_state = FailedToStart;
0266     Q_EMIT stateChanged();
0267     Q_EMIT failedToStart();
0268 }
0269 
0270 bool BacktraceGenerator::hasAnyFailure()
0271 {
0272     return m_state == State::Failed || m_state == State::FailedToStart;
0273 }
0274 
0275 #include "moc_backtracegenerator.cpp"