File indexing completed on 2022-11-29 20:01:34

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