Warning, file /plasma/drkonqi/src/backtracegenerator.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 };