File indexing completed on 2024-04-28 05:48:33

0001 //
0002 // gdbbackend.cpp
0003 //
0004 // Description: Manages the interaction with GDB
0005 //
0006 //
0007 // SPDX-FileCopyrightText: 2008-2010 Ian Wakeling <ian.wakeling@ntlworld.com>
0008 // SPDX-FileCopyrightText: 2011 Kåre Särs <kare.sars@iki.fi>
0009 //
0010 //  SPDX-License-Identifier: LGPL-2.0-only
0011 
0012 #include "gdbbackend.h"
0013 #include "gdbmi/tokens.h"
0014 #include "hostprocess.h"
0015 
0016 #include <QDir>
0017 #include <QFile>
0018 #include <QFileInfo>
0019 #include <QJsonArray>
0020 #include <QJsonDocument>
0021 #include <QJsonObject>
0022 #include <QRegularExpression>
0023 #include <QStandardPaths>
0024 #include <QTimer>
0025 
0026 #include <KLocalizedString>
0027 #include <KMessageBox>
0028 
0029 #include <algorithm>
0030 #include <signal.h>
0031 #include <stdlib.h>
0032 
0033 // #define DEBUG_GDBMI
0034 
0035 static const dap::Scope LocalScope(0, i18n("Locals"));
0036 static const dap::Scope ThisScope(1, QStringLiteral("*this"));
0037 static const dap::Scope RegistersScope(2, i18n("CPU registers"));
0038 
0039 static constexpr int DATA_EVAL_THIS_CHECK = 0;
0040 static constexpr int DATA_EVAL_THIS_DEREF = 1;
0041 
0042 static constexpr int MAX_RESPONSE_ERRORS = 5;
0043 
0044 static QString printEvent(const QString &text)
0045 {
0046     return QStringLiteral("--> %1\n").arg(text);
0047 }
0048 
0049 bool GdbCommand::isMachineInterface() const
0050 {
0051     return !arguments.isEmpty() && arguments.first().startsWith(QLatin1Char('-'));
0052 }
0053 
0054 GdbCommand GdbCommand::parse(const QString &request)
0055 {
0056     GdbCommand cmd;
0057 
0058     cmd.arguments = QProcess::splitCommand(request);
0059     if (!cmd.arguments.isEmpty()) {
0060         const auto parts = gdbmi::GdbmiParser::splitCommand(cmd.arguments.at(0));
0061         if (parts.size() > 1) {
0062             cmd.arguments.replace(0, parts.last());
0063         }
0064     }
0065 
0066     return cmd;
0067 }
0068 
0069 bool GdbCommand::check(const QString &command) const
0070 {
0071     return !arguments.isEmpty() && (arguments.first() == command);
0072 }
0073 
0074 bool GdbCommand::check(const QString &part1, const QString &part2) const
0075 {
0076     return (arguments.size() >= 2) && (arguments.first() == part1) && (arguments.at(1) == part2);
0077 }
0078 
0079 void GdbBackend::enqueue(const QString &command)
0080 {
0081     m_nextCommands << PendingCommand{command, std::nullopt, Default};
0082 }
0083 
0084 void GdbBackend::enqueue(const QString &command, const QJsonValue &data, uint8_t captureMode)
0085 {
0086     m_nextCommands << PendingCommand{command, data, captureMode};
0087 }
0088 
0089 void GdbBackend::prepend(const QString &command)
0090 {
0091     m_nextCommands.prepend({command, std::nullopt, Default});
0092 }
0093 
0094 GdbBackend::GdbBackend(QObject *parent)
0095     : BackendInterface(parent)
0096     , m_debugProcess(nullptr)
0097     , m_state(none)
0098     , m_debugLocationChanged(true)
0099     , m_queryLocals(false)
0100     , m_parser(new gdbmi::GdbmiParser(this))
0101 {
0102     // variable parser
0103     connect(&m_variableParser, &GDBVariableParser::variable, this, &BackendInterface::variableInfo);
0104 
0105     connect(m_parser, &gdbmi::GdbmiParser::outputProduced, this, &GdbBackend::processMIStreamOutput);
0106     connect(m_parser, &gdbmi::GdbmiParser::recordProduced, this, &GdbBackend::processMIRecord);
0107     connect(m_parser, &gdbmi::GdbmiParser::parserError, this, &GdbBackend::onMIParserError);
0108 }
0109 
0110 GdbBackend::~GdbBackend()
0111 {
0112     if (m_debugProcess.state() != QProcess::NotRunning) {
0113         m_debugProcess.kill();
0114         m_debugProcess.blockSignals(true);
0115         m_debugProcess.waitForFinished();
0116     }
0117     disconnect(m_parser);
0118     m_parser->deleteLater();
0119 }
0120 
0121 bool GdbBackend::supportsMovePC() const
0122 {
0123     return m_capabilities.execJump.value_or(false) && canMove();
0124 }
0125 
0126 bool GdbBackend::supportsRunToCursor() const
0127 {
0128     return canMove();
0129 }
0130 
0131 bool GdbBackend::canSetBreakpoints() const
0132 {
0133     return m_gdbState != Disconnected;
0134 }
0135 
0136 bool GdbBackend::canMove() const
0137 {
0138     return (m_gdbState == Connected) || (m_gdbState == Stopped);
0139 }
0140 
0141 bool GdbBackend::canContinue() const
0142 {
0143     return canMove();
0144 }
0145 
0146 void GdbBackend::resetSession()
0147 {
0148     m_nextCommands.clear();
0149     m_currentThread.reset();
0150     m_stackFrames.clear();
0151     m_registerNames.clear();
0152 }
0153 
0154 void GdbBackend::runDebugger(const GDBTargetConf &conf, const QStringList &ioFifos)
0155 {
0156     // TODO correct remote flow (connected, interrupt, etc.)
0157     if (conf.executable.isEmpty()) {
0158         const QString msg = i18n("Please set the executable in the 'Settings' tab in the 'Debug' panel.");
0159         Q_EMIT backendError(msg, KTextEditor::Message::Error);
0160         return;
0161     }
0162 
0163     m_targetConf = conf;
0164 
0165     // no chance if no debugger configured
0166     if (m_targetConf.gdbCmd.isEmpty()) {
0167         const QString msg = i18n("No debugger selected. Please select one in the 'Settings' tab in the 'Debug' panel.");
0168         Q_EMIT backendError(msg, KTextEditor::Message::Error);
0169         return;
0170     }
0171 
0172     // only run debugger from PATH or the absolute executable path we specified
0173     const auto fullExecutable = QFileInfo(m_targetConf.gdbCmd).isAbsolute() ? m_targetConf.gdbCmd : safeExecutableName(m_targetConf.gdbCmd);
0174     if (fullExecutable.isEmpty()) {
0175         const QString msg =
0176             i18n("Debugger not found. Please make sure you have it installed in your system. The selected debugger is '%1'", m_targetConf.gdbCmd);
0177         Q_EMIT backendError(msg, KTextEditor::Message::Error);
0178         return;
0179     }
0180 
0181     if (ioFifos.size() == 3) {
0182         // XXX only supported by GDB, not LLDB
0183         m_ioPipeString = QStringLiteral("< %1 1> %2 2> %3").arg(ioFifos[0], ioFifos[1], ioFifos[2]);
0184     }
0185 
0186     if (m_state == none) {
0187         m_seq = 0;
0188         m_errorCounter = 0;
0189         resetSession();
0190         updateInspectable(false);
0191         m_outBuffer.clear();
0192         m_errBuffer.clear();
0193         setGdbState(Disconnected);
0194 
0195         // create a process to control GDB
0196         m_debugProcess.setWorkingDirectory(m_targetConf.workDir);
0197 
0198         connect(&m_debugProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::errorOccurred), this, &GdbBackend::slotError);
0199 
0200         connect(&m_debugProcess, &QProcess::readyReadStandardError, this, &GdbBackend::slotReadDebugStdErr);
0201 
0202         connect(&m_debugProcess, &QProcess::readyReadStandardOutput, this, &GdbBackend::slotReadDebugStdOut);
0203 
0204         connect(&m_debugProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &GdbBackend::slotDebugFinished);
0205 
0206         startHostProcess(m_debugProcess, fullExecutable, {QLatin1String("--interpreter=mi3")});
0207 
0208         enqueue(QStringLiteral("-gdb-set pagination off"));
0209         // for the sake of simplicity we try to restrict to synchronous mode
0210         enqueue(QStringLiteral("-gdb-set mi-async off"));
0211         enqueueProtocolHandshake();
0212         setState(ready, Connected);
0213     } else {
0214         enqueue(makeInitSequence());
0215     }
0216     issueNextCommandLater(std::nullopt);
0217 }
0218 
0219 void GdbBackend::issueNextCommandLater(const std::optional<State> &state)
0220 {
0221     if (state) {
0222         setState(*state);
0223     }
0224 
0225     // On startup the gdb prompt will trigger the "nextCommands",
0226     // here we have to trigger it manually.
0227     QTimer::singleShot(0, this, &GdbBackend::issueNextCommand);
0228 }
0229 
0230 void GdbBackend::enqueueProtocolHandshake()
0231 {
0232     m_capabilities.family = Unknown;
0233     m_capabilities.execRunStart.reset();
0234     m_capabilities.threadInfo.reset();
0235     m_capabilities.breakList.reset();
0236     m_capabilities.pendingBreakpoints.reset();
0237     m_capabilities.execJump.reset();
0238     // "version" only exists in lldb
0239     // data added to recognise this request from anything entered by the user
0240     enqueue(QStringLiteral("version"), QJsonValue(true), CaptureConsole | MuteLog);
0241     enqueue(QStringLiteral("-list-features"));
0242     enqueue(QStringLiteral("-info-gdb-mi-command thread-info"));
0243     enqueue(QStringLiteral("-info-gdb-mi-command break-list"));
0244     enqueue(QStringLiteral("-info-gdb-mi-command exec-jump"));
0245     enqueue(QStringLiteral("-info-gdb-mi-command data-list-changed-registers"));
0246     enqueue(QStringLiteral("-kate-init"), QJsonValue(1));
0247 }
0248 
0249 void GdbBackend::enqueue(const QStringList &commands, bool prepend)
0250 {
0251     if (commands.isEmpty()) {
0252         return;
0253     }
0254     if (prepend) {
0255         for (int n = commands.size() - 1; n >= 0; --n) {
0256             m_nextCommands.prepend({commands[n], std::nullopt, Default});
0257         }
0258     } else {
0259         for (const auto &cmd : commands) {
0260             enqueue(cmd);
0261         }
0262     }
0263 }
0264 
0265 QStringList GdbBackend::makeInitSequence()
0266 {
0267     m_requests.clear();
0268     QStringList sequence;
0269     // TODO adapt sequence for remote
0270     sequence << QStringLiteral("-file-exec-and-symbols \"%1\"").arg(m_targetConf.executable);
0271     if (m_capabilities.family != LLDB) {
0272         sequence << QStringLiteral("-exec-arguments %1 %2").arg(m_targetConf.arguments, m_ioPipeString);
0273     } else {
0274         sequence << QStringLiteral("-exec-arguments %1").arg(m_targetConf.arguments);
0275     }
0276     sequence << QStringLiteral("-inferior-tty-set /dev/null");
0277     for (const auto &initPart : m_targetConf.customInit) {
0278         sequence << initPart;
0279     }
0280     if (m_capabilities.breakList.value_or(false)) {
0281         sequence << QStringLiteral("-break-list");
0282     }
0283     return sequence;
0284 }
0285 
0286 void GdbBackend::enqueueThreadInfo()
0287 {
0288     if (!m_inspectable) {
0289         return;
0290     }
0291     if (m_capabilities.threadInfo.value_or(true)) {
0292         enqueue(QStringLiteral("-thread-info"));
0293     } else {
0294         enqueue(QStringLiteral("-thread-list-ids"));
0295     }
0296 }
0297 
0298 QStringList GdbBackend::makeRunSequence(bool stop)
0299 {
0300     QStringList sequence;
0301     if (stop) {
0302         if (m_capabilities.execRunStart.value_or(false)) {
0303             sequence << QStringLiteral("-exec-run --start");
0304         } else {
0305             sequence << QStringLiteral("-break-insert -t main");
0306             sequence << QStringLiteral("-exec-run");
0307         }
0308     } else {
0309         sequence << QStringLiteral("-exec-run");
0310     }
0311     if (m_capabilities.family == GDB) {
0312         sequence << QStringLiteral("-data-evaluate-expression \"setvbuf(stdout, 0, %1, 1024)\"").arg(_IOLBF);
0313     }
0314     return sequence;
0315 }
0316 
0317 bool GdbBackend::debuggerRunning() const
0318 {
0319     return (m_state != none);
0320 }
0321 
0322 bool GdbBackend::debuggerBusy() const
0323 {
0324     return (m_state == executingCmd) || !m_nextCommands.isEmpty();
0325 }
0326 
0327 int GdbBackend::findFirstBreakpoint(const QUrl &url, int line) const
0328 {
0329     for (auto it = m_breakpointTable.constBegin(); it != m_breakpointTable.constEnd(); ++it) {
0330         if ((url == it.value().file) && (line == it.value().line)) {
0331             return it.key();
0332         }
0333     }
0334     return -1;
0335 }
0336 
0337 QStringList GdbBackend::findAllBreakpoints(const QUrl &url, int line) const
0338 {
0339     QStringList out;
0340     for (auto it = m_breakpointTable.constBegin(); it != m_breakpointTable.constEnd(); ++it) {
0341         if ((url == it.value().file) && (line == it.value().line)) {
0342             out << QString::number(it.key());
0343         }
0344     }
0345     return out;
0346 }
0347 
0348 bool GdbBackend::hasBreakpoint(const QUrl &url, int line) const
0349 {
0350     return findFirstBreakpoint(url, line) >= 0;
0351 }
0352 
0353 QString GdbBackend::makeCmdBreakInsert(const QUrl &url, int line, bool pending, bool temporal) const
0354 {
0355     QString flags = temporal ? QLatin1String("-t") : QString();
0356     if (pending && m_capabilities.pendingBreakpoints.value_or(false)) {
0357         flags += QLatin1String(" -f");
0358     }
0359 
0360     return QStringLiteral("-break-insert %1 %2:%3").arg(flags).arg(url.path()).arg(line);
0361 }
0362 
0363 void GdbBackend::toggleBreakpoint(QUrl const &url, int line)
0364 {
0365     if (m_state != ready) {
0366         return;
0367     }
0368 
0369     QString cmd;
0370     const auto bpNumbers = findAllBreakpoints(url, line);
0371     if (bpNumbers.empty()) {
0372         cmd = makeCmdBreakInsert(url, line, true);
0373     } else {
0374         // delete all bpoints in that line
0375         cmd = QStringLiteral("-break-delete %1").arg(bpNumbers.join(QLatin1Char(' ')));
0376     }
0377     issueCommand(cmd);
0378 }
0379 
0380 void GdbBackend::slotError()
0381 {
0382     Q_EMIT backendError(i18n("Could not start debugger process"), KTextEditor::Message::Error);
0383 }
0384 
0385 void GdbBackend::slotReadDebugStdOut()
0386 {
0387     m_outBuffer += m_debugProcess.readAllStandardOutput();
0388 
0389 #ifdef DEBUG_GDBMI
0390     if (!m_outBuffer.isEmpty()) {
0391         Q_EMIT outputText(QStringLiteral("\n***(<gdbmi)\n%1\n***\n").arg(QString::fromLatin1(m_outBuffer)));
0392     }
0393 #endif
0394     do {
0395         int end = gdbmi::GdbmiParser::splitLines(m_outBuffer, false);
0396 
0397         if (end < 0) {
0398             break;
0399         }
0400         ++end;
0401         const auto head = m_parser->parseResponse(m_outBuffer.mid(0, end));
0402         if (!head.error) {
0403             m_outBuffer.remove(0, head.last);
0404         } else {
0405             m_outBuffer.remove(head.last, end);
0406         }
0407 #ifdef DEBUG_GDBMI
0408         if (!m_outBuffer.isEmpty()) {
0409             Q_EMIT outputText(QStringLiteral("\n***(<gdbmi)\n%1\n***\n").arg(QString::fromLatin1(m_outBuffer)));
0410         }
0411 #endif
0412     } while (!m_outBuffer.isEmpty());
0413 }
0414 
0415 void GdbBackend::slotReadDebugStdErr()
0416 {
0417     m_errBuffer += QString::fromLocal8Bit(m_debugProcess.readAllStandardError().data());
0418     int end = 0;
0419     // add whole lines at a time to the error list
0420     do {
0421         end = m_errBuffer.indexOf(QLatin1Char('\n'));
0422         if (end < 0) {
0423             break;
0424         }
0425         m_errBuffer.remove(0, end + 1);
0426     } while (1);
0427 
0428     Q_EMIT outputError(m_errBuffer + QLatin1String("\n"));
0429 }
0430 
0431 void GdbBackend::clearDebugLocation()
0432 {
0433     m_debugLocationChanged = true;
0434     Q_EMIT debugLocationChanged(QUrl(), -1);
0435 }
0436 
0437 void GdbBackend::slotDebugFinished(int /*exitCode*/, QProcess::ExitStatus status)
0438 {
0439     if (status != QProcess::NormalExit) {
0440         Q_EMIT outputText(i18n("*** gdb exited normally ***") + QLatin1Char('\n'));
0441         clearDebugLocation();
0442     }
0443 
0444     setState(none, Disconnected);
0445 
0446     // remove all old breakpoints
0447     for (auto it = m_breakpointTable.constBegin(); it != m_breakpointTable.constEnd(); ++it) {
0448         Q_EMIT breakPointCleared(it.value().file, it.value().line - 1);
0449     }
0450     m_breakpointTable.clear();
0451 
0452     Q_EMIT gdbEnded();
0453 }
0454 
0455 void GdbBackend::movePC(QUrl const &url, int line)
0456 {
0457     if ((m_state == ready) && m_capabilities.execJump.value_or(false)) {
0458         // jump if inferior is running, or run inferrior and stop at start
0459         enqueue(QStringLiteral("-kate-try-run 1"), QJsonValue());
0460         enqueue(QStringLiteral("-exec-jump %1:%2").arg(url.path()).arg(line));
0461         issueCommand(makeCmdBreakInsert(url, line, false, true));
0462     }
0463 }
0464 
0465 void GdbBackend::runToCursor(QUrl const &url, int line)
0466 {
0467     if (m_state == ready) {
0468         // continue if inferior running, or run inferior
0469         enqueue(QStringLiteral("-kate-try-run 0"), QJsonValue(QStringLiteral("-exec-continue")));
0470         issueCommand(makeCmdBreakInsert(url, line, true, true));
0471     }
0472 }
0473 
0474 void GdbBackend::slotInterrupt()
0475 {
0476     if (m_state == executingCmd) {
0477         m_debugLocationChanged = true;
0478     }
0479     if (!m_capabilities.async.value_or(false)) {
0480         const auto pid = m_debugProcess.processId();
0481         if (pid != 0) {
0482             ::kill(pid, SIGINT);
0483         }
0484     } else {
0485         issueCommand(QStringLiteral("-exec-interrupt"));
0486     }
0487 }
0488 
0489 void GdbBackend::slotKill()
0490 {
0491     if (inferiorRunning() && (m_state != ready)) {
0492         slotInterrupt();
0493         setState(ready);
0494     }
0495     // FIXME couldn't find a replacement for "kill", only -gdb-exit
0496     if (inferiorRunning()) {
0497         issueCommand(QStringLiteral("kill"));
0498     } else if (m_gdbState == Connected) {
0499         issueCommand(QStringLiteral("-gdb-exit"));
0500     }
0501 }
0502 
0503 void GdbBackend::slotReRun()
0504 {
0505     resetSession();
0506     if (inferiorRunning()) {
0507         slotKill();
0508     }
0509     enqueue(makeRunSequence(false));
0510     issueNextCommandLater(std::nullopt);
0511 }
0512 
0513 void GdbBackend::slotStepInto()
0514 {
0515     issueCommand(QStringLiteral("-kate-try-run 1"), QJsonValue(QStringLiteral("-exec-step")));
0516 }
0517 
0518 void GdbBackend::slotStepOver()
0519 {
0520     issueCommand(QStringLiteral("-kate-try-run 1"), QJsonValue(QStringLiteral("-exec-next")));
0521 }
0522 
0523 void GdbBackend::slotStepOut()
0524 {
0525     issueCommand(QStringLiteral("-kate-try-run 1"), QJsonValue(QStringLiteral("-exec-finish")));
0526 }
0527 
0528 void GdbBackend::slotContinue()
0529 {
0530     issueCommand(QStringLiteral("-kate-try-run 0"), QJsonValue(QStringLiteral("-exec-continue")));
0531 }
0532 
0533 void GdbBackend::processMIRecord(const gdbmi::Record &record)
0534 {
0535     m_errorCounter = 0;
0536     switch (record.category) {
0537     case gdbmi::Record::Status:
0538         break;
0539     case gdbmi::Record::Exec:
0540         processMIExec(record);
0541         break;
0542     case gdbmi::Record::Notify:
0543         processMINotify(record);
0544         break;
0545     case gdbmi::Record::Result:
0546         processMIResult(record);
0547         break;
0548     case gdbmi::Record::Prompt:
0549         processMIPrompt();
0550         break;
0551     }
0552 }
0553 
0554 void GdbBackend::processMIPrompt()
0555 {
0556     if ((m_state != ready) && (m_state != none)) {
0557         return;
0558     }
0559     if (m_captureOutput != Default) {
0560         // the last response has completely been processed at this point
0561         m_captureOutput = Default;
0562         m_capturedOutput.clear();
0563     }
0564     // we get here after initialization
0565     issueNextCommandLater(ready);
0566 }
0567 
0568 static QString formatRecordMessage(const gdbmi::Record &record)
0569 {
0570     return record.value[QLatin1String("msg")].toString() + QLatin1Char('\n');
0571 }
0572 
0573 static QString stoppedThreadsToString(const QJsonValue &value)
0574 {
0575     if (value.isString()) {
0576         return value.toString();
0577     } else if (value.isArray()) {
0578         QStringList parts;
0579         for (const auto &item : value.toArray()) {
0580             parts << item.toString();
0581         }
0582         return parts.join(QLatin1String(", "));
0583     }
0584     return QString();
0585 }
0586 
0587 static QString getFilename(const QJsonObject &item)
0588 {
0589     QString file = item[QLatin1String("fullname")].toString();
0590 
0591     // lldb returns "??" and "??/???" when it is not resolved
0592     if (file.isEmpty() || file.startsWith(QLatin1Char('?'))) {
0593         file = item[QLatin1String("filename")].toString();
0594     }
0595 
0596     if (file.isEmpty() || file.startsWith(QLatin1Char('?'))) {
0597         file = item[QLatin1String("file")].toString();
0598     }
0599 
0600     if (file.startsWith(QLatin1Char('?'))) {
0601         file.clear();
0602     }
0603 
0604     return file;
0605 }
0606 
0607 dap::StackFrame GdbBackend::parseFrame(const QJsonObject &object)
0608 {
0609     dap::StackFrame frame;
0610     frame.id = object[QLatin1String("level")].toString().toInt();
0611     frame.instructionPointerReference = object[QLatin1String("addr")].toString();
0612     frame.line = object[QLatin1String("line")].toString().toInt();
0613     frame.column = 0;
0614 
0615     auto file = getFilename(object);
0616     if (file.isEmpty()) {
0617         // not really an editable file, we mark with <> in order to avoid an opening attempt
0618         file = QLatin1String("<%1>").arg(object[QLatin1String("from")].toString());
0619     } else if (!file.contains(QLatin1Char('?'))) {
0620         const auto resolvedFile = resolveFileName(file, true).toLocalFile();
0621         if (!resolvedFile.isEmpty()) {
0622             file = resolvedFile;
0623         }
0624     }
0625     frame.source = dap::Source(file);
0626     const auto func = object[QLatin1String("func")].toString();
0627 
0628     frame.name = QStringLiteral("%1 at %2:%3").arg(!func.isEmpty() ? func : frame.instructionPointerReference.value()).arg(frame.source->path).arg(frame.line);
0629 
0630     return frame;
0631 }
0632 
0633 bool GdbBackend::inferiorRunning() const
0634 {
0635     return (m_gdbState == Running) || (m_gdbState == Stopped);
0636 }
0637 
0638 void GdbBackend::processMIExec(const gdbmi::Record &record)
0639 {
0640     const auto threadId = stoppedThreadsToString(record.value.value(QLatin1String("thread-id")));
0641     if (record.resultClass == QLatin1String("running")) {
0642         updateInspectable(false);
0643         // running
0644         setGdbState(Running);
0645         if (threadId == QLatin1String("all")) {
0646             Q_EMIT outputText(printEvent(i18n("all threads running")));
0647         } else {
0648             Q_EMIT outputText(printEvent(i18n("thread(s) running: %1", threadId)));
0649         }
0650     } else if (record.resultClass == QLatin1String("stopped")) {
0651         const auto stoppedThreads = record.value.value(QLatin1String("stopped-threads")).toString();
0652         const auto reason = record.value.value(QLatin1String("reason")).toString();
0653         QStringList text = {i18n("stopped (%1).", reason)};
0654         if (!threadId.isEmpty()) {
0655             text << QLatin1String(" ");
0656             if (stoppedThreads == QLatin1String("all")) {
0657                 text << i18n("Active thread: %1 (all threads stopped).", threadId);
0658             } else {
0659                 text << i18n("Active thread: %1.", threadId);
0660             }
0661         }
0662 
0663         if (reason.startsWith(QLatin1String("exited"))) {
0664             // stopped by exit
0665             clearDebugLocation();
0666             updateInspectable(false);
0667             m_nextCommands.clear();
0668             setGdbState(Connected);
0669             Q_EMIT programEnded();
0670         } else {
0671             // stopped by another reason
0672             updateInspectable(true);
0673             setGdbState(Stopped);
0674 
0675             const auto frame = parseFrame(record.value[QLatin1String("frame")].toObject());
0676 
0677             if (frame.source) {
0678                 text << QLatin1String(" ") << i18n("Current frame: %1:%2", frame.source->path, QString::number(frame.line));
0679             }
0680             m_debugLocationChanged = true;
0681             Q_EMIT debugLocationChanged(resolveFileName(frame.source->path), frame.line - 1);
0682         }
0683 
0684         Q_EMIT outputText(printEvent(text.join(QString())));
0685     }
0686 }
0687 
0688 void GdbBackend::processMINotify(const gdbmi::Record &record)
0689 {
0690     if (record.resultClass == QLatin1String("breakpoint-created")) {
0691         responseMIBreakInsert(record);
0692     } else if (record.resultClass == QLatin1String("breakpoint-deleted")) {
0693         notifyMIBreakpointDeleted(record);
0694     } else if (record.resultClass == QLatin1String("breakpoint-modified")) {
0695         notifyMIBreakpointModified(record);
0696     } else {
0697         QString data;
0698         if (record.resultClass.startsWith(QLatin1String("library-"))) {
0699             const auto target = record.value[QLatin1String("target-name")].toString();
0700             const auto host = record.value[QLatin1String("host-name")].toString();
0701 
0702             if (target == host) {
0703                 data = target;
0704             } else {
0705                 data = i18n("Host: %1. Target: %1", host, target);
0706             }
0707         } else {
0708             data = QString::fromLocal8Bit(QJsonDocument(record.value).toJson(QJsonDocument::Compact));
0709         }
0710 
0711         const auto msg = QStringLiteral("(%1) %2").arg(record.resultClass).arg(data);
0712         Q_EMIT outputText(printEvent(msg));
0713     }
0714 }
0715 
0716 void GdbBackend::processMIResult(const gdbmi::Record &record)
0717 {
0718     auto reqType = GdbCommand::None;
0719     bool isMI = true;
0720     QStringList args;
0721     std::optional<QJsonValue> commandData = std::nullopt;
0722     if (record.token && m_requests.contains(record.token.value())) {
0723         const auto command = m_requests.take(record.token.value());
0724         reqType = command.type;
0725         isMI = command.isMachineInterface();
0726         args = command.arguments;
0727         commandData = command.data;
0728     }
0729     if (isMI && (record.resultClass == QLatin1String("error")) && !(m_captureOutput & MuteLog)) {
0730         Q_EMIT outputError(m_lastCommand + QLatin1String("\n"));
0731         Q_EMIT outputError(formatRecordMessage(record));
0732     }
0733 
0734     bool isReady = true;
0735 
0736     switch (reqType) {
0737     case GdbCommand::BreakpointList:
0738         isReady = responseMIBreakpointList(record);
0739         break;
0740     case GdbCommand::ThreadInfo:
0741         isReady = responseMIThreadInfo(record);
0742         break;
0743     case GdbCommand::StackListFrames:
0744         isReady = responseMIStackListFrames(record);
0745         break;
0746     case GdbCommand::StackListVariables:
0747         isReady = responseMIStackListVariables(record);
0748         break;
0749     case GdbCommand::BreakInsert:
0750         isReady = responseMIBreakInsert(record);
0751         break;
0752     case GdbCommand::BreakDelete:
0753         isReady = responseMIBreakDelete(record, args);
0754         break;
0755     case GdbCommand::ListFeatures:
0756         isReady = responseMIListFeatures(record);
0757         break;
0758     case GdbCommand::DataEvaluateExpression:
0759         isReady = responseMIDataEvaluateExpression(record, commandData);
0760         break;
0761     case GdbCommand::Exit:
0762         isReady = responseMIExit(record);
0763         break;
0764     case GdbCommand::Kill:
0765         isReady = responseMIKill(record);
0766         break;
0767     case GdbCommand::InfoGdbMiCommand:
0768         isReady = responseMIInfoGdbCommand(record, args);
0769         break;
0770     case GdbCommand::LldbVersion:
0771         isReady = responseMILldbVersion(record);
0772         break;
0773     case GdbCommand::RegisterNames:
0774         isReady = responseMIRegisterNames(record);
0775         break;
0776     case GdbCommand::RegisterValues:
0777         isReady = responseMIRegisterValues(record);
0778         break;
0779     case GdbCommand::ChangedRegisters:
0780         isReady = responseMIChangedRegisters(record);
0781         break;
0782     case GdbCommand::Continue:
0783     case GdbCommand::Step:
0784     default:
0785         break;
0786     }
0787     if (isReady) {
0788         issueNextCommandLater(ready);
0789     } else {
0790         issueNextCommandLater(std::nullopt);
0791     }
0792 }
0793 
0794 void GdbBackend::clearFrames()
0795 {
0796     // clear cached frames
0797     m_stackFrames.clear();
0798     if (m_queryLocals) {
0799         // request empty panel
0800         Q_EMIT stackFrameInfo(-1, QString());
0801     }
0802 
0803     clearVariables();
0804 }
0805 
0806 void GdbBackend::clearVariables()
0807 {
0808     if (m_queryLocals) {
0809         Q_EMIT scopesInfo(QList<dap::Scope>{}, std::nullopt);
0810         Q_EMIT variableScopeOpened();
0811         Q_EMIT variableScopeClosed();
0812     }
0813 }
0814 
0815 bool GdbBackend::responseMIThreadInfo(const gdbmi::Record &record)
0816 {
0817     if (record.resultClass == QLatin1String("error")) {
0818         if (!m_capabilities.threadInfo) {
0819             // now we know threadInfo is not supported
0820             m_capabilities.threadInfo = false;
0821             // try again
0822             enqueueThreadInfo();
0823         }
0824         return true;
0825     }
0826 
0827     // clear table
0828     Q_EMIT threadInfo(dap::Thread(-1), false);
0829 
0830     int n_threads = 0;
0831 
0832     bool ok = false;
0833     const int currentThread = record.value[QLatin1String("current-thread-id")].toString().toInt(&ok);
0834     if (ok) {
0835         // list loaded, but not selected yet
0836         m_currentThread = -1;
0837     } else {
0838         // unexpected, abort
0839         updateInspectable(false);
0840         return true;
0841     }
0842 
0843     QString fCollection = QLatin1String("threads");
0844     QString fId = QLatin1String("id");
0845     bool hasName = true;
0846     if (!record.value.contains(fCollection)) {
0847         fCollection = QLatin1String("thread-ids");
0848         fId = QLatin1String("thread-id");
0849         hasName = false;
0850     }
0851 
0852     for (const auto &item : record.value[fCollection].toArray()) {
0853         const auto thread = item.toObject();
0854         dap::Thread info(thread[fId].toString().toInt());
0855         if (hasName) {
0856             info.name = thread[QLatin1String("name")].toString(thread[QLatin1String("target-id")].toString());
0857         }
0858 
0859         Q_EMIT threadInfo(info, currentThread == info.id);
0860         ++n_threads;
0861     }
0862 
0863     if (m_queryLocals) {
0864         if (n_threads > 0) {
0865             changeThread(currentThread);
0866         } else {
0867             updateInspectable(false);
0868         }
0869     }
0870 
0871     return true;
0872 }
0873 
0874 bool GdbBackend::responseMIExit(const gdbmi::Record &record)
0875 {
0876     if (record.resultClass != QLatin1String("exit")) {
0877         return true;
0878     }
0879     setState(none, Disconnected);
0880 
0881     // not ready
0882     return false;
0883 }
0884 
0885 bool GdbBackend::responseMIKill(const gdbmi::Record &record)
0886 {
0887     if (record.resultClass != QLatin1String("done")) {
0888         return true;
0889     }
0890     clearDebugLocation();
0891     setState(none, Connected);
0892     Q_EMIT programEnded();
0893 
0894     return false;
0895 }
0896 
0897 bool GdbBackend::responseMIInfoGdbCommand(const gdbmi::Record &record, const QStringList &args)
0898 {
0899     if (record.resultClass != QLatin1String("done")) {
0900         return true;
0901     }
0902 
0903     if (args.size() < 2) {
0904         return true;
0905     }
0906 
0907     const auto &command = args[1];
0908     const bool exists = record.value[QLatin1String("command")].toObject()[QLatin1String("exists")].toString() == QLatin1String("true");
0909 
0910     if (command == QLatin1String("thread-info")) {
0911         m_capabilities.threadInfo = exists;
0912     } else if (command == QLatin1String("break-list")) {
0913         m_capabilities.breakList = exists;
0914     } else if (command == QLatin1String("exec-jump")) {
0915         m_capabilities.execJump = exists;
0916     } else if (command == QLatin1String("data-list-changed-registers")) {
0917         m_capabilities.changedRegisters = exists;
0918     }
0919 
0920     return true;
0921 }
0922 
0923 bool GdbBackend::responseMILldbVersion(const gdbmi::Record &record)
0924 {
0925     bool isLLDB = false;
0926     if (record.resultClass == QLatin1String("done")) {
0927         isLLDB = std::any_of(m_capturedOutput.cbegin(), m_capturedOutput.cend(), [](const QString &line) {
0928             return line.toLower().contains(QLatin1String("lldb"));
0929         });
0930     }
0931     m_capabilities.family = isLLDB ? LLDB : GDB;
0932     // lldb-mi inherently uses the asynchronous mode
0933     m_capabilities.async = m_capabilities.family == LLDB;
0934 
0935     return true;
0936 }
0937 
0938 bool GdbBackend::responseMIStackListFrames(const gdbmi::Record &record)
0939 {
0940     if (record.resultClass == QLatin1String("error")) {
0941         return true;
0942     }
0943 
0944     // clear table
0945     clearFrames();
0946 
0947     for (const auto &item : record.value[QLatin1String("stack")].toArray()) {
0948         m_stackFrames << parseFrame(item.toObject()[QLatin1String("frame")].toObject());
0949     }
0950 
0951     m_currentFrame = -1;
0952     m_currentScope.reset();
0953     informStackFrame();
0954 
0955     if (!m_stackFrames.isEmpty()) {
0956         changeStackFrame(0);
0957     }
0958 
0959     return true;
0960 }
0961 
0962 bool GdbBackend::responseMIRegisterNames(const gdbmi::Record &record)
0963 {
0964     if (record.resultClass != QLatin1String("done")) {
0965         return true;
0966     }
0967 
0968     const auto names = record.value[QLatin1String("register-names")].toArray();
0969     m_registerNames.clear();
0970     m_registerNames.reserve(names.size());
0971     for (const auto &name : names) {
0972         m_registerNames << name.toString().trimmed();
0973     }
0974 
0975     return true;
0976 }
0977 
0978 bool GdbBackend::responseMIRegisterValues(const gdbmi::Record &record)
0979 {
0980     if (record.resultClass != QLatin1String("done")) {
0981         return true;
0982     }
0983 
0984     Q_EMIT variableScopeOpened();
0985     for (const auto &item : record.value[QLatin1String("register-values")].toArray()) {
0986         const auto var = item.toObject();
0987         bool ok = false;
0988         const int regIndex = var[QLatin1String("number")].toString().toInt(&ok);
0989         if (!ok || (regIndex < 0) || (regIndex >= m_registerNames.size())) {
0990             continue;
0991         }
0992         const auto &name = m_registerNames[regIndex];
0993         if (name.isEmpty()) {
0994             continue;
0995         }
0996         m_variableParser.insertVariable(m_registerNames[regIndex], var[QLatin1String("value")].toString(), QString(), m_changedRegisters.contains(regIndex));
0997     }
0998     Q_EMIT variableScopeClosed();
0999     return true;
1000 }
1001 
1002 bool GdbBackend::responseMIChangedRegisters(const gdbmi::Record &record)
1003 {
1004     if (record.resultClass != QLatin1String("done")) {
1005         return true;
1006     }
1007     for (const auto &item : record.value[QLatin1String("changed-registers")].toArray()) {
1008         bool ok = false;
1009         const int regIndex = item.toString().toInt(&ok);
1010         if (!ok) {
1011             continue;
1012         }
1013         m_changedRegisters.insert(regIndex);
1014     }
1015     return true;
1016 }
1017 
1018 bool GdbBackend::responseMIStackListVariables(const gdbmi::Record &record)
1019 {
1020     if (record.resultClass == QLatin1String("error")) {
1021         return true;
1022     }
1023 
1024     Q_EMIT variableScopeOpened();
1025     for (const auto &item : record.value[QLatin1String("variables")].toArray()) {
1026         const auto var = item.toObject();
1027         m_variableParser.insertVariable(var[QLatin1String("name")].toString(), var[QLatin1String("value")].toString(), var[QLatin1String("type")].toString());
1028     }
1029     Q_EMIT variableScopeClosed();
1030 
1031     return true;
1032 }
1033 
1034 bool GdbBackend::responseMIListFeatures(const gdbmi::Record &record)
1035 {
1036     if (record.resultClass != QLatin1String("done")) {
1037         return true;
1038     }
1039 
1040     for (const auto &item : record.value[QLatin1String("features")].toArray()) {
1041         const auto capability = item.toString();
1042         if (capability == QLatin1String("exec-run-start-option")) {
1043             // exec-run --start is not reliable in lldb
1044             m_capabilities.execRunStart = m_capabilities.family != LLDB;
1045         } else if (capability == QLatin1String("thread-info")) {
1046             m_capabilities.threadInfo = true;
1047         } else if (capability == QLatin1String("pending-breakpoints")) {
1048             m_capabilities.pendingBreakpoints = true;
1049         }
1050     }
1051 
1052     return true;
1053 }
1054 
1055 BreakPoint BreakPoint::parse(const QJsonObject &item)
1056 {
1057     const QString f_line = QLatin1String("line");
1058 
1059     BreakPoint breakPoint;
1060     breakPoint.number = item[QLatin1String("number")].toString(QLatin1String("1")).toInt();
1061     breakPoint.line = item[f_line].toString(QLatin1String("-1")).toInt();
1062 
1063     // file
1064     auto file = getFilename(item);
1065 
1066     if ((breakPoint.line < 0) || file.isEmpty()) {
1067         // try original-location
1068         QString pending = item[QLatin1String("original-location")].toString();
1069 
1070         // try pending
1071         const auto f_pending = QLatin1String("pending");
1072         if (pending.isEmpty()) {
1073             const auto &v_pending = item[f_pending];
1074             if (v_pending.isArray()) {
1075                 const auto values = v_pending.toArray();
1076                 if (!values.isEmpty()) {
1077                     pending = values.first().toString();
1078                 }
1079             } else {
1080                 pending = v_pending.toString();
1081             }
1082         }
1083         int sep = pending.lastIndexOf(QLatin1Char(':'));
1084         if (sep > 0) {
1085             if (breakPoint.line < 0) {
1086                 breakPoint.line = pending.mid(sep + 1).toInt();
1087             }
1088             if (file.isEmpty()) {
1089                 file = pending.left(sep);
1090                 if (file.startsWith(QLatin1Char('?'))) {
1091                     file.clear();
1092                 }
1093             }
1094         }
1095     }
1096 
1097     if ((breakPoint.line < 0) || file.isEmpty()) {
1098         // try locations
1099         const auto f_locations = QLatin1String("locations");
1100         if (item.contains(f_locations)) {
1101             for (const auto item_loc : item[f_locations].toArray()) {
1102                 const auto loc = item_loc.toObject();
1103                 if (breakPoint.line < 0) {
1104                     breakPoint.line = loc[f_line].toString(QLatin1String("-1")).toInt();
1105                 }
1106                 if (file.isEmpty()) {
1107                     file = getFilename(loc);
1108                     if (file.startsWith(QLatin1Char('?'))) {
1109                         file.clear();
1110                     }
1111                 }
1112                 if ((breakPoint.line > -1) && !file.isEmpty()) {
1113                     break;
1114                 }
1115             }
1116         }
1117     }
1118 
1119     if (!file.isEmpty()) {
1120         breakPoint.file = QUrl::fromLocalFile(file);
1121     }
1122 
1123     return breakPoint;
1124 }
1125 
1126 BreakPoint GdbBackend::parseBreakpoint(const QJsonObject &item)
1127 {
1128     // XXX in a breakpoint with multiple locations, only the first one is considered
1129     BreakPoint breakPoint = BreakPoint::parse(item);
1130     breakPoint.file = resolveFileName(breakPoint.file.toLocalFile());
1131 
1132     return breakPoint;
1133 }
1134 
1135 void GdbBackend::insertBreakpoint(const QJsonObject &item)
1136 {
1137     const BreakPoint breakPoint = parseBreakpoint(item);
1138     Q_EMIT breakPointSet(breakPoint.file, breakPoint.line - 1);
1139     m_breakpointTable[breakPoint.number] = std::move(breakPoint);
1140 }
1141 
1142 bool GdbBackend::responseMIBreakpointList(const gdbmi::Record &record)
1143 {
1144     if (record.resultClass != QLatin1String("done")) {
1145         return true;
1146     }
1147 
1148     Q_EMIT clearBreakpointMarks();
1149     m_breakpointTable.clear();
1150 
1151     for (const auto item : record.value[QLatin1String("body")].toArray()) {
1152         insertBreakpoint(item.toObject());
1153     }
1154 
1155     return true;
1156 }
1157 
1158 bool GdbBackend::responseMIBreakInsert(const gdbmi::Record &record)
1159 {
1160     if (record.resultClass == QLatin1String("error")) {
1161         // cancel pending commands
1162         m_nextCommands.clear();
1163         return true;
1164     }
1165 
1166     const auto bkpt = record.value[QLatin1String("bkpt")].toObject();
1167     if (bkpt.isEmpty()) {
1168         return true;
1169     }
1170 
1171     insertBreakpoint(bkpt);
1172 
1173     return true;
1174 }
1175 
1176 void GdbBackend::notifyMIBreakpointModified(const gdbmi::Record &record)
1177 {
1178     const auto bkpt = record.value[QLatin1String("bkpt")].toObject();
1179     if (bkpt.isEmpty()) {
1180         return;
1181     }
1182 
1183     const BreakPoint newBp = parseBreakpoint(bkpt);
1184     if (!m_breakpointTable.contains(newBp.number)) {
1185         // may be a pending breakpoint
1186         responseMIBreakInsert(record);
1187         return;
1188     }
1189 
1190     const auto &oldBp = m_breakpointTable[newBp.number];
1191 
1192     if ((oldBp.line != newBp.line) || (oldBp.file != newBp.file)) {
1193         const QUrl oldFile = oldBp.file;
1194         const int oldLine = oldBp.line;
1195         m_breakpointTable[newBp.number] = newBp;
1196         if (findFirstBreakpoint(oldFile, oldLine) < 0) {
1197             // this is the last bpoint in this line
1198             Q_EMIT breakPointCleared(oldFile, oldLine - 1);
1199         }
1200         Q_EMIT breakPointSet(newBp.file, newBp.line - 1);
1201     }
1202 }
1203 
1204 void GdbBackend::deleteBreakpoint(const int bpNumber)
1205 {
1206     if (!m_breakpointTable.contains(bpNumber)) {
1207         return;
1208     }
1209     const auto bp = m_breakpointTable.take(bpNumber);
1210     if (findFirstBreakpoint(bp.file, bp.line) < 0) {
1211         // this is the last bpoint in this line
1212         Q_EMIT breakPointCleared(bp.file, bp.line - 1);
1213     }
1214 }
1215 
1216 void GdbBackend::notifyMIBreakpointDeleted(const gdbmi::Record &record)
1217 {
1218     bool ok = false;
1219     const int bpNumber = record.value[QLatin1String("id")].toString().toInt(&ok);
1220     if (ok) {
1221         deleteBreakpoint(bpNumber);
1222     }
1223 }
1224 
1225 bool GdbBackend::responseMIBreakDelete(const gdbmi::Record &record, const QStringList &args)
1226 {
1227     if (record.resultClass != QLatin1String("done")) {
1228         return true;
1229     }
1230 
1231     // get breakpoints ids from arguments
1232     if (args.size() < 2) {
1233         return true;
1234     }
1235 
1236     for (int idx = 1; idx < args.size(); ++idx) {
1237         bool ok = false;
1238         const int bpNumber = args[idx].toInt(&ok);
1239         if (!ok) {
1240             continue;
1241         }
1242         deleteBreakpoint(bpNumber);
1243     }
1244 
1245     return true;
1246 }
1247 
1248 QString GdbBackend::makeFrameFlags() const
1249 {
1250     if (!m_currentThread || !m_currentFrame) {
1251         return QString();
1252     }
1253 
1254     if ((*m_currentFrame >= m_stackFrames.size()) || (*m_currentFrame < 0)) {
1255         return QString();
1256     }
1257 
1258     const int frameId = m_stackFrames[*m_currentFrame].id;
1259 
1260     return QLatin1String("--thread %1 --frame %2").arg(QString::number(*m_currentThread)).arg(frameId);
1261 }
1262 
1263 void GdbBackend::enqueueScopes()
1264 {
1265     if (!m_currentFrame || !m_currentThread) {
1266         return;
1267     }
1268     enqueue(QLatin1String("-data-evaluate-expression %1 \"this\"").arg(makeFrameFlags()), QJsonValue(DATA_EVAL_THIS_CHECK), MuteLog);
1269 }
1270 
1271 void GdbBackend::enqueueScopeVariables()
1272 {
1273     if (!m_currentFrame || !m_currentThread) {
1274         return;
1275     }
1276     if (m_pointerThis && (m_currentScope == ThisScope.variablesReference)) {
1277         // request *this
1278         enqueue(QLatin1String("-data-evaluate-expression %1 \"*this\"").arg(makeFrameFlags()), QJsonValue(DATA_EVAL_THIS_DEREF));
1279     } else if (m_currentScope == RegistersScope.variablesReference) {
1280         if (m_registerNames.isEmpty()) {
1281             enqueue(QLatin1String("-data-list-register-names"));
1282         }
1283         if (m_capabilities.changedRegisters.value_or(false)) {
1284             m_changedRegisters.clear();
1285             enqueue(QLatin1String("-data-list-changed-registers"));
1286         }
1287         enqueue(QLatin1String("-data-list-register-values --skip-unavailable r"));
1288     } else {
1289         // request locals
1290         enqueue(QLatin1String("-stack-list-variables %1 --all-values").arg(makeFrameFlags()));
1291     }
1292 }
1293 
1294 void GdbBackend::responseMIScopes(const gdbmi::Record &record)
1295 {
1296     m_pointerThis = record.resultClass != QLatin1String("error");
1297     if (!m_inspectable || !m_queryLocals) {
1298         return;
1299     }
1300 
1301     QList<dap::Scope> scopes = {LocalScope};
1302     if (m_pointerThis) {
1303         scopes << ThisScope;
1304     }
1305     scopes << RegistersScope;
1306 
1307     const auto activeScope = std::find_if(scopes.cbegin(), scopes.cend(), [this](const auto &scope) {
1308         return !m_watchedScope || (*m_watchedScope == scope.variablesReference);
1309     });
1310     if (activeScope != scopes.cend()) {
1311         m_watchedScope = activeScope->variablesReference;
1312     } else {
1313         m_watchedScope = scopes[0].variablesReference;
1314     }
1315 
1316     m_currentScope.reset();
1317 
1318     if (m_queryLocals) {
1319         // preload variables
1320         Q_EMIT scopesInfo(scopes, m_watchedScope);
1321         slotQueryLocals(true);
1322     }
1323 }
1324 
1325 void GdbBackend::responseMIThisScope(const gdbmi::Record &record)
1326 {
1327     if (record.resultClass == QLatin1String("error")) {
1328         return;
1329     }
1330 
1331     const auto value = record.value[QStringLiteral("value")].toString();
1332     const auto parent = dap::Variable(QString(), value, 0);
1333     Q_EMIT variableScopeOpened();
1334     m_variableParser.insertVariable(QStringLiteral("*this"), value, QString());
1335     Q_EMIT variableScopeClosed();
1336 }
1337 
1338 bool GdbBackend::responseMIDataEvaluateExpression(const gdbmi::Record &record, const std::optional<QJsonValue> &data)
1339 {
1340     if (data) {
1341         const int mode = data->toInt(-1);
1342         switch (mode) {
1343         case DATA_EVAL_THIS_CHECK:
1344             responseMIScopes(record);
1345             return true;
1346             break;
1347         case DATA_EVAL_THIS_DEREF:
1348             responseMIThisScope(record);
1349             return true;
1350             break;
1351         }
1352     }
1353 
1354     if (record.resultClass != QLatin1String("done")) {
1355         return true;
1356     }
1357 
1358     QString key;
1359     if (data) {
1360         key = data->toString(QLatin1String("$1"));
1361     } else {
1362         key = QLatin1String("$1");
1363     }
1364     Q_EMIT outputText(QStringLiteral("%1 = %2\n").arg(key).arg(record.value[QLatin1String("value")].toString()));
1365 
1366     return true;
1367 }
1368 
1369 void GdbBackend::onMIParserError(const QString &errorMessage)
1370 {
1371     QString message;
1372     ++m_errorCounter;
1373     const bool overflow = m_errorCounter > MAX_RESPONSE_ERRORS;
1374     if (overflow) {
1375         message = i18n("gdb-mi: Could not parse last response: %1. Too many consecutive errors. Shutting down.", errorMessage);
1376     } else {
1377         message = i18n("gdb-mi: Could not parse last response: %1", errorMessage);
1378     }
1379     m_nextCommands.clear();
1380     Q_EMIT backendError(message, KTextEditor::Message::Error);
1381 
1382     if (overflow) {
1383         m_debugProcess.kill();
1384     }
1385 }
1386 
1387 QString GdbBackend::slotPrintVariable(const QString &variable)
1388 {
1389     const QString cmd = QStringLiteral("-data-evaluate-expression \"%1\"").arg(gdbmi::quotedString(variable));
1390     issueCommand(cmd, QJsonValue(variable));
1391     return cmd;
1392 }
1393 
1394 void GdbBackend::issueCommand(QString const &cmd)
1395 {
1396     issueCommand(cmd, std::nullopt);
1397 }
1398 
1399 void GdbBackend::cmdKateInit()
1400 {
1401     // enqueue full init sequence
1402     updateInputReady(!debuggerBusy() && canMove(), true);
1403     enqueue(makeInitSequence(), true);
1404     issueNextCommandLater(std::nullopt);
1405 }
1406 
1407 void GdbBackend::cmdKateTryRun(const GdbCommand &command, const QJsonValue &data)
1408 {
1409     // enqueue command if running, or run inferior
1410     // 0 - run & continue
1411     // 1 - run & stop
1412     // command - data[str]
1413     if (!inferiorRunning()) {
1414         bool stop = false;
1415         if (command.arguments.size() > 1) {
1416             bool ok = false;
1417             const int val = command.arguments[1].toInt(&ok);
1418             if (ok) {
1419                 stop = val > 0;
1420             }
1421         }
1422         enqueue(makeRunSequence(stop), true);
1423     } else {
1424         const auto altCmd = data.toString();
1425         if (!altCmd.isEmpty()) {
1426             prepend(data.toString());
1427         }
1428     }
1429     issueNextCommandLater(std::nullopt);
1430 }
1431 
1432 void GdbBackend::issueCommand(const QString &cmd, const std::optional<QJsonValue> &data, uint8_t captureMode)
1433 {
1434     auto command = GdbCommand::parse(cmd);
1435     // macro command
1436     if (data) {
1437         if (command.check(QLatin1String("-kate-init"))) {
1438             cmdKateInit();
1439             return;
1440         } else if (command.check(QLatin1String("-kate-try-run"))) {
1441             cmdKateTryRun(command, *data);
1442             return;
1443         }
1444     }
1445     // real command
1446     if (m_state == ready) {
1447         setState(executingCmd);
1448 
1449         if (data) {
1450             command.data = data;
1451         }
1452         m_captureOutput = captureMode;
1453         QString newCmd;
1454 
1455         const bool isMI = command.isMachineInterface();
1456 
1457         if (isMI) {
1458             newCmd = cmd;
1459         } else {
1460             newCmd = QLatin1String("-interpreter-exec console \"%1\"").arg(gdbmi::quotedString(cmd));
1461         }
1462 
1463         if (command.check(QLatin1String("-break-list"))) {
1464             command.type = GdbCommand::BreakpointList;
1465         } else if (command.check(QLatin1String("-exec-continue")) || command.check(QLatin1String("continue")) || command.check(QLatin1String("c"))
1466                    || command.check(QLatin1String("fg"))) {
1467             // continue family
1468             command.type = GdbCommand::Continue;
1469         } else if (command.check(QLatin1String("-exec-step")) || command.check(QLatin1String("step")) || command.check(QLatin1String("s"))
1470                    || command.check(QLatin1String("-exec-finish")) || command.check(QLatin1String("finish")) || command.check(QLatin1String("fin"))
1471                    || command.check(QLatin1String("-exec-next")) || command.check(QLatin1String("next")) || command.check(QLatin1String("n"))) {
1472             command.type = GdbCommand::Step;
1473         } else if (command.check(QLatin1String("-thread-info")) || command.check(QLatin1String("-thread-list-ids"))) {
1474             command.type = GdbCommand::ThreadInfo;
1475         } else if (command.check(QLatin1String("-stack-list-frames"))) {
1476             command.type = GdbCommand::StackListFrames;
1477         } else if (command.check(QLatin1String("-stack-list-variables"))) {
1478             command.type = GdbCommand::StackListVariables;
1479         } else if (command.check(QLatin1String("-break-insert"))) {
1480             command.type = GdbCommand::BreakInsert;
1481         } else if (command.check(QLatin1String("-break-delete"))) {
1482             command.type = GdbCommand::BreakDelete;
1483         } else if (command.check(QLatin1String("-list-features"))) {
1484             command.type = GdbCommand::ListFeatures;
1485         } else if (command.check(QLatin1String("-data-evaluate-expression"))) {
1486             command.type = GdbCommand::DataEvaluateExpression;
1487         } else if (command.check(QLatin1String("-data-list-register-names"))) {
1488             command.type = GdbCommand::RegisterNames;
1489         } else if (command.check(QLatin1String("-data-list-register-values"))) {
1490             command.type = GdbCommand::RegisterValues;
1491         } else if (command.check(QLatin1String("-data-list-changed-registers"))) {
1492             command.type = GdbCommand::ChangedRegisters;
1493         } else if (command.check(QLatin1String("-gdb-exit"))) {
1494             command.type = GdbCommand::Exit;
1495         } else if (command.check(QLatin1String("-info-gdb-mi-command"))) {
1496             command.type = GdbCommand::InfoGdbMiCommand;
1497         } else if (command.check(QLatin1String("kill"))) {
1498             command.type = GdbCommand::Kill;
1499         } else if (command.check(QLatin1String("version")) && data) {
1500             command.type = GdbCommand::LldbVersion;
1501         }
1502 
1503         // register the response parsing type
1504         newCmd = QStringLiteral("%1%2").arg(m_seq).arg(newCmd);
1505         m_requests[m_seq++] = command;
1506 
1507         m_lastCommand = cmd;
1508 
1509         if (!isMI && !(m_captureOutput & MuteLog)) {
1510             Q_EMIT outputText(QStringLiteral("(gdb) %1\n").arg(cmd));
1511         }
1512 #ifdef DEBUG_GDBMI
1513         Q_EMIT outputText(QStringLiteral("\n***(gdbmi>)\n%1\n***\n").arg(newCmd));
1514 #endif
1515         m_debugProcess.write(qPrintable(newCmd));
1516         m_debugProcess.write("\n");
1517     }
1518 }
1519 
1520 void GdbBackend::updateInputReady(bool newState, bool force)
1521 {
1522     // refresh only when the state changed
1523     if (force || (m_lastInputReady != newState)) {
1524         m_lastInputReady = newState;
1525         Q_EMIT readyForInput(newState);
1526     }
1527 }
1528 
1529 void GdbBackend::setState(State newState, std::optional<GdbState> newGdbState)
1530 {
1531     m_state = newState;
1532     if (newGdbState) {
1533         m_gdbState = *newGdbState;
1534     }
1535 
1536     updateInputReady(!debuggerBusy() && canMove(), true);
1537 }
1538 
1539 void GdbBackend::setGdbState(GdbState newState)
1540 {
1541     m_gdbState = newState;
1542 
1543     updateInputReady(!debuggerBusy() && canMove(), true);
1544 }
1545 
1546 void GdbBackend::issueNextCommand()
1547 {
1548     if (m_state == ready) {
1549         if (!m_nextCommands.empty()) {
1550             const auto cmd = m_nextCommands.takeFirst();
1551             issueCommand(cmd.command, cmd.data, cmd.captureMode);
1552         } else {
1553             if (m_debugLocationChanged) {
1554                 m_debugLocationChanged = false;
1555                 if (m_queryLocals) {
1556                     slotQueryLocals(true);
1557                     issueNextCommand();
1558                     return;
1559                 }
1560             }
1561             updateInputReady(!debuggerBusy() && canMove());
1562         }
1563     }
1564 }
1565 
1566 QUrl GdbBackend::resolveFileName(const QString &fileName, bool silent)
1567 {
1568     QFileInfo fInfo = QFileInfo(fileName);
1569     // did we end up with an absolute path or a relative one?
1570     if (fInfo.exists()) {
1571         return QUrl::fromUserInput(fInfo.absoluteFilePath());
1572     }
1573 
1574     if (fInfo.isAbsolute()) {
1575         // we can not do anything just return the fileName
1576         return QUrl::fromUserInput(fileName);
1577     }
1578 
1579     // Now try to add the working path
1580     fInfo = QFileInfo(m_targetConf.workDir + fileName);
1581     if (fInfo.exists()) {
1582         return QUrl::fromUserInput(fInfo.absoluteFilePath());
1583     }
1584 
1585     // now try the executable path
1586     fInfo = QFileInfo(QFileInfo(m_targetConf.executable).absolutePath() + fileName);
1587     if (fInfo.exists()) {
1588         return QUrl::fromUserInput(fInfo.absoluteFilePath());
1589     }
1590 
1591     for (const QString &srcPath : qAsConst(m_targetConf.srcPaths)) {
1592         fInfo = QFileInfo(srcPath + QDir::separator() + fileName);
1593         if (fInfo.exists()) {
1594             return QUrl::fromUserInput(fInfo.absoluteFilePath());
1595         }
1596     }
1597 
1598     // we can not do anything just return the fileName
1599     if (!silent) {
1600         Q_EMIT sourceFileNotFound(fileName);
1601     }
1602     return QUrl::fromUserInput(fileName);
1603 }
1604 
1605 void GdbBackend::processMIStreamOutput(const gdbmi::StreamOutput &output)
1606 {
1607     switch (output.channel) {
1608     case gdbmi::StreamOutput::Console:
1609         if (m_captureOutput & CaptureConsole) {
1610             m_capturedOutput << output.message;
1611         }
1612         Q_EMIT outputText(output.message);
1613         break;
1614     case gdbmi::StreamOutput::Log:
1615         if (!(m_captureOutput & ~MuteLog)) {
1616             Q_EMIT outputError(output.message);
1617         }
1618         break;
1619     case gdbmi::StreamOutput::Output:
1620         Q_EMIT debuggeeOutput(dap::Output(output.message, dap::Output::Category::Stdout));
1621         break;
1622     }
1623 }
1624 
1625 void GdbBackend::informStackFrame()
1626 {
1627     if (!m_queryLocals) {
1628         return;
1629     }
1630 
1631     int level = 0;
1632 
1633     for (const auto &frame : m_stackFrames) {
1634         // emit stackFrameInfo
1635         // name at source:line
1636         QString info = frame.name;
1637         if (frame.source) {
1638             info = QStringLiteral("%1 at %2:%3").arg(info).arg(frame.source->path).arg(frame.line);
1639         }
1640 
1641         Q_EMIT stackFrameInfo(level, info);
1642 
1643         ++level;
1644     }
1645     Q_EMIT stackFrameInfo(-1, QString());
1646 }
1647 
1648 void GdbBackend::slotQueryLocals(bool query)
1649 {
1650     if (!debuggerRunning()) {
1651         return;
1652     }
1653     m_queryLocals = query;
1654 
1655 #ifdef DEBUG_GDBMI
1656     Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nSLOT QUERY LOCALS INIT %1\n***\n").arg(query));
1657 #endif
1658 
1659     if (!query) {
1660         return;
1661     }
1662 
1663     // !current thread -> no threads; current thread -> threads
1664     if (!m_currentThread) {
1665 // get threads
1666 #ifdef DEBUG_GDBMI
1667         Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nSLOT QUERY LOCALS THREAD INFO %1\n***\n").arg(query));
1668 #endif
1669         enqueueThreadInfo();
1670         issueNextCommandLater(std::nullopt);
1671     } else if (!m_currentFrame) {
1672 // get frames
1673 #ifdef DEBUG_GDBMI
1674         Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nSLOT QUERY LOCALS CHANGE THREAD %1\n***\n").arg(query));
1675 #endif
1676         changeThread(*m_currentThread);
1677     } else if (!m_watchedScope) {
1678 // get scopes
1679 #ifdef DEBUG_GDBMI
1680         Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nSLOT QUERY LOCALS CHANGE STACK FRAME %1\n***\n").arg(query));
1681 #endif
1682         changeStackFrame(*m_currentFrame);
1683     } else {
1684 // get variables
1685 #ifdef DEBUG_GDBMI
1686         Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nSLOT QUERY LOCALS CHANGE SCOPE %1\n***\n").arg(query));
1687 #endif
1688         changeScope(*m_watchedScope);
1689     }
1690 }
1691 
1692 QString GdbBackend::targetName() const
1693 {
1694     return m_targetConf.targetName;
1695 }
1696 
1697 void GdbBackend::setFileSearchPaths(const QStringList &paths)
1698 {
1699     m_targetConf.srcPaths = paths;
1700 }
1701 
1702 void GdbBackend::updateInspectable(bool inspectable)
1703 {
1704     m_inspectable = inspectable;
1705     m_currentThread.reset();
1706     m_currentFrame.reset();
1707     m_currentScope.reset();
1708     clearFrames();
1709     Q_EMIT scopesInfo(QList<dap::Scope>{}, std::nullopt);
1710 }
1711 
1712 void GdbBackend::changeStackFrame(int index)
1713 {
1714 #ifdef DEBUG_GDBMI
1715     Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nCHANGE FRAME %1\n***\n").arg(index));
1716 #endif
1717     if (!debuggerRunning() || !m_inspectable) {
1718         return;
1719     }
1720     if (!m_currentThread) {
1721         // unexpected state
1722         updateInspectable(false);
1723         return;
1724     }
1725     if ((m_stackFrames.size() < index) || (index < 0)) {
1726         // frame not found in stack
1727         return;
1728     }
1729 
1730     if (m_currentFrame && (*m_currentFrame == index)) {
1731         // already loaded
1732         return;
1733     }
1734 
1735     m_currentFrame = index;
1736 
1737     const auto &frame = m_stackFrames[index];
1738     if (frame.source) {
1739         Q_EMIT debugLocationChanged(resolveFileName(frame.source->path), frame.line - 1);
1740     }
1741 
1742     Q_EMIT stackFrameChanged(index);
1743 
1744     m_currentScope.reset();
1745     enqueueScopes();
1746     issueNextCommandLater(std::nullopt);
1747 }
1748 
1749 void GdbBackend::changeThread(int index)
1750 {
1751 #ifdef DEBUG_GDBMI
1752     Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nCHANGE THREAD %1\n***\n").arg(index));
1753 #endif
1754     if (!debuggerRunning() || !m_inspectable) {
1755         return;
1756     }
1757     if (!m_queryLocals) {
1758         return;
1759     }
1760     if (*m_currentThread && (*m_currentThread == index)) {
1761         // already loaded
1762         return;
1763     }
1764     m_currentThread = index;
1765 
1766     enqueue(QStringLiteral("-stack-list-frames --thread %1").arg(index));
1767     issueNextCommandLater(std::nullopt);
1768 }
1769 
1770 void GdbBackend::changeScope(int scopeId)
1771 {
1772 #ifdef DEBUG_GDBMI
1773     Q_EMIT outputText(QStringLiteral("\n***(gdbmi^)\nCHANGE SCOPE %1\n***\n").arg(scopeId));
1774 #endif
1775     if (!debuggerRunning() || !m_inspectable) {
1776         return;
1777     }
1778 
1779     m_watchedScope = scopeId;
1780 
1781     if (m_currentScope && (*m_currentScope == scopeId)) {
1782         // already loaded
1783         return;
1784     }
1785 
1786     m_currentScope = m_watchedScope;
1787 
1788     if (m_queryLocals) {
1789         enqueueScopeVariables();
1790         issueNextCommandLater(std::nullopt);
1791     }
1792 }
1793 
1794 #include "moc_gdbbackend.cpp"