Warning, file /utilities/kate/addons/gdbplugin/dapbackend.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include <KMessageBox> 0007 #include <QDir> 0008 #include <QFileInfo> 0009 #include <QJsonDocument> 0010 #include <QRegularExpression> 0011 #include <pthread.h> 0012 0013 #include <KLocalizedString> 0014 0015 #include "dapbackend.h" 0016 #include "json_placeholders.h" 0017 0018 QString newLine(const QString &text) 0019 { 0020 return QStringLiteral("\n") + text; 0021 } 0022 0023 QString printEvent(const QString &text) 0024 { 0025 return QStringLiteral("\n--> %1").arg(text); 0026 } 0027 0028 DapBackend::DapBackend(QObject *parent) 0029 : BackendInterface(parent) 0030 , m_client(nullptr) 0031 , m_state(State::None) 0032 , m_requests(0) 0033 { 0034 } 0035 0036 void DapBackend::unsetClient() 0037 { 0038 if (m_client) { 0039 disconnect(m_client->bus()); 0040 disconnect(m_client); 0041 m_client->detach(); 0042 m_client->deleteLater(); 0043 m_client = nullptr; 0044 } 0045 resetState(State::None); 0046 shutdownUntil(); 0047 m_currentScope = std::nullopt; 0048 } 0049 0050 void DapBackend::resetState(State state) 0051 { 0052 m_requests = 0; 0053 m_runToCursor = std::nullopt; 0054 if (state != Running) { 0055 m_currentThread = std::nullopt; 0056 } 0057 m_watchedThread = std::nullopt; 0058 m_currentFrame = std::nullopt; 0059 m_commandQueue.clear(); 0060 m_restart = false; 0061 m_frames.clear(); 0062 m_task = Idle; 0063 setState(state); 0064 } 0065 0066 void DapBackend::setState(State state) 0067 { 0068 if (state == m_state) 0069 return; 0070 0071 m_state = state; 0072 Q_EMIT readyForInput(debuggerRunning()); 0073 0074 switch (m_state) { 0075 case State::Terminated: 0076 Q_EMIT programEnded(); 0077 if (continueShutdown()) { 0078 tryDisconnect(); 0079 } 0080 break; 0081 case State::Disconnected: 0082 if (continueShutdown()) { 0083 cmdShutdown(); 0084 } 0085 break; 0086 case State::PostMortem: 0087 shutdownUntil(); 0088 if (m_restart) { 0089 m_restart = false; 0090 start(); 0091 } 0092 break; 0093 case State::None: 0094 shutdownUntil(); 0095 break; 0096 default: 0097 break; 0098 } 0099 } 0100 0101 void DapBackend::setTaskState(Task state) 0102 { 0103 if (state == m_task) 0104 return; 0105 m_task = state; 0106 Q_EMIT readyForInput(debuggerRunning() && (m_task != Busy)); 0107 if ((m_task == Idle) && !m_commandQueue.isEmpty()) { 0108 issueCommand(m_commandQueue.takeFirst()); 0109 } 0110 } 0111 0112 void DapBackend::pushRequest() 0113 { 0114 ++m_requests; 0115 setTaskState(Busy); 0116 } 0117 0118 void DapBackend::popRequest() 0119 { 0120 if (m_requests > 0) { 0121 --m_requests; 0122 } 0123 setTaskState(m_requests > 0 ? Busy : Idle); 0124 } 0125 0126 dap::settings::ClientSettings &DapBackend::target2dap(const DAPTargetConf &target) 0127 { 0128 // resolve dynamic port 0129 auto settings = dap::settings::resolveClientPort(target.dapSettings->settings); 0130 if (!settings) { 0131 settings = target.dapSettings->settings; 0132 } 0133 // resolve user variables 0134 auto varMap = dap::settings::findReferences(*settings); 0135 for (auto it = target.variables.constBegin(); it != target.variables.constEnd(); ++it) { 0136 varMap[it.key()] = it.value().toJsonValue(); 0137 if (it.key() == QStringLiteral("file")) { 0138 m_file = it.value().toString(); 0139 } else if (it.key() == QStringLiteral("workdir")) { 0140 m_workDir = it.value().toString(); 0141 } 0142 } 0143 0144 const auto out = json::resolve(*settings, varMap); 0145 0146 Q_EMIT outputText(QString::fromLocal8Bit(QJsonDocument(out).toJson()) + QStringLiteral("\n")); 0147 0148 m_settings = dap::settings::ClientSettings(out); 0149 return *m_settings; 0150 } 0151 0152 void DapBackend::start() 0153 { 0154 if ((m_state != None) && (m_state != PostMortem)) { 0155 KMessageBox::error(nullptr, i18n("A debugging session is on course. Please, use re-run or stop the current session.")); 0156 return; 0157 } 0158 unsetClient(); 0159 0160 m_client = new dap::Client(*m_settings, this); 0161 0162 Q_EMIT debuggerCapabilitiesChanged(); 0163 0164 // connect 0165 connect(m_client->bus(), &dap::Bus::error, this, &DapBackend::onError); 0166 0167 connect(m_client, &dap::Client::finished, this, &DapBackend::onServerFinished); 0168 connect(m_client, &dap::Client::failed, [this] { 0169 onError(i18n("DAP backend failed")); 0170 }); 0171 0172 connect(m_client, &dap::Client::serverDisconnected, this, &DapBackend::onServerDisconnected); 0173 connect(m_client, &dap::Client::debuggeeExited, this, &DapBackend::onProgramEnded); 0174 connect(m_client, &dap::Client::debuggeeTerminated, this, &DapBackend::onTerminated); 0175 connect(m_client, &dap::Client::debuggeeStopped, this, &DapBackend::onStopped); 0176 connect(m_client, &dap::Client::capabilitiesReceived, this, &DapBackend::onCapabilitiesReceived); 0177 connect(m_client, &dap::Client::debuggeeRunning, this, &DapBackend::onRunning); 0178 connect(m_client, &dap::Client::debuggeeContinued, this, &DapBackend::onContinuedEvent); 0179 connect(m_client, &dap::Client::debuggingProcess, this, &DapBackend::onDebuggingProcess); 0180 0181 connect(m_client, &dap::Client::threads, this, &DapBackend::onThreads); 0182 connect(m_client, &dap::Client::stackTrace, this, &DapBackend::onStackTrace); 0183 connect(m_client, &dap::Client::initialized, this, &DapBackend::onInitialized); 0184 connect(m_client, &dap::Client::errorResponse, this, &DapBackend::onErrorResponse); 0185 connect(m_client, &dap::Client::outputProduced, this, &DapBackend::onOutputProduced); 0186 0187 connect(m_client, &dap::Client::threadChanged, this, &DapBackend::onThreadEvent); 0188 connect(m_client, &dap::Client::moduleChanged, this, &DapBackend::onModuleEvent); 0189 connect(m_client, &dap::Client::scopes, this, &DapBackend::onScopes); 0190 connect(m_client, &dap::Client::variables, this, &DapBackend::onVariables); 0191 connect(m_client, &dap::Client::modules, this, &DapBackend::onModules); 0192 connect(m_client, &dap::Client::sourceBreakpoints, this, &DapBackend::onSourceBreakpoints); 0193 connect(m_client, &dap::Client::breakpointChanged, this, &DapBackend::onBreakpointEvent); 0194 connect(m_client, &dap::Client::expressionEvaluated, this, &DapBackend::onExpressionEvaluated); 0195 connect(m_client, &dap::Client::gotoTargets, this, &DapBackend::onGotoTargets); 0196 0197 m_client->bus()->start(m_settings->busSettings); 0198 } 0199 0200 void DapBackend::runDebugger(const DAPTargetConf &conf) 0201 { 0202 m_targetName = conf.targetName; 0203 0204 target2dap(conf); 0205 0206 start(); 0207 } 0208 0209 void DapBackend::onTerminated() 0210 { 0211 Q_EMIT outputText(printEvent(i18n("program terminated"))); 0212 if (m_state < Terminated) { 0213 setState(Terminated); 0214 } 0215 } 0216 0217 bool DapBackend::tryDisconnect() 0218 { 0219 if (!isConnectedState()) { 0220 return false; 0221 } 0222 0223 Q_EMIT outputError(newLine(i18n("requesting disconnection"))); 0224 if (m_client) { 0225 m_client->requestDisconnect(); 0226 } else { 0227 setState(Disconnected); 0228 } 0229 return true; 0230 } 0231 0232 void DapBackend::cmdShutdown() 0233 { 0234 if (m_state == None) 0235 return; 0236 0237 Q_EMIT outputError(newLine(i18n("requesting shutdown"))); 0238 if (m_client) { 0239 m_client->bus()->close(); 0240 } else { 0241 setState(None); 0242 } 0243 } 0244 0245 bool DapBackend::tryTerminate() 0246 { 0247 if (!isRunningState()) 0248 return false; 0249 0250 if (!m_client->supportsTerminate()) { 0251 setState(Terminated); 0252 return false; 0253 } 0254 0255 m_client->requestTerminate(); 0256 return true; 0257 } 0258 0259 void DapBackend::onError(const QString &message) 0260 { 0261 Q_EMIT outputError(newLine(i18n("DAP backend: %1", message))); 0262 setState(PostMortem); 0263 } 0264 0265 void DapBackend::onStopped(const dap::StoppedEvent &info) 0266 { 0267 setState(Stopped); 0268 m_currentThread = m_watchedThread = info.threadId; 0269 0270 QStringList text = {i18n("stopped (%1).", info.reason)}; 0271 if (info.description) { 0272 text << QStringLiteral(" (%1)").arg(info.description.value()); 0273 } 0274 0275 if (info.threadId) { 0276 text << QStringLiteral(" "); 0277 if (info.allThreadsStopped && info.allThreadsStopped.value()) { 0278 text << i18n("Active thread: %1 (all threads stopped).", info.threadId.value()); 0279 } else { 0280 text << i18n("Active thread: %1.", info.threadId.value()); 0281 } 0282 } 0283 0284 if (info.hitBreakpointsIds) { 0285 text << QStringLiteral(" ") << i18n("Breakpoint(s) reached:"); 0286 for (const int b : info.hitBreakpointsIds.value()) { 0287 text << QStringLiteral(" [%1] ").arg(b); 0288 } 0289 } 0290 0291 Q_EMIT outputText(printEvent(text.join(QString()))); 0292 0293 // request stack trace 0294 if (m_currentThread) { 0295 pushRequest(); 0296 m_client->requestStackTrace(*m_currentThread); 0297 } 0298 0299 // request threads 0300 pushRequest(); 0301 m_client->requestThreads(); 0302 } 0303 0304 void DapBackend::onContinuedEvent(const dap::ContinuedEvent &info) 0305 { 0306 resetState(); 0307 Q_EMIT outputText(printEvent(i18n("(continued) thread %1", QString::number(info.threadId)))); 0308 if (info.allThreadsContinued) { 0309 Q_EMIT outputText(QStringLiteral(" (%1)").arg(i18n("all threads continued"))); 0310 } 0311 } 0312 0313 void DapBackend::onRunning() 0314 { 0315 setState(State::Running); 0316 Q_EMIT outputText(printEvent(i18n("(running)"))); 0317 // if there is not thread, request in case pause is called 0318 if (!m_currentThread) { 0319 pushRequest(); 0320 m_client->requestThreads(); 0321 } 0322 } 0323 0324 void DapBackend::onThreads(const QList<dap::Thread> &threads) 0325 { 0326 if (!m_currentThread) { 0327 if (!threads.isEmpty()) { 0328 m_currentThread = threads[0].id; 0329 } 0330 } else { 0331 Q_EMIT threadInfo(dap::Thread(-1), false); 0332 for (const auto &thread : threads) { 0333 Q_EMIT threadInfo(thread, thread.id == m_currentThread.value_or(-1)); 0334 } 0335 } 0336 popRequest(); 0337 } 0338 0339 void DapBackend::informStackFrame() 0340 { 0341 if (!m_queryLocals) 0342 return; 0343 0344 int level = 0; 0345 0346 for (const auto &frame : m_frames) { 0347 // emit stackFrameInfo 0348 // name at source:line 0349 QString info = frame.name; 0350 if (frame.source) { 0351 info = QStringLiteral("%1 at %2:%3").arg(info).arg(frame.source->path).arg(frame.line); 0352 } 0353 0354 Q_EMIT stackFrameInfo(level, info); 0355 0356 ++level; 0357 } 0358 Q_EMIT stackFrameInfo(-1, QString()); 0359 } 0360 0361 void DapBackend::onStackTrace(const int /* threadId */, const dap::StackTraceInfo &info) 0362 { 0363 m_currentFrame = std::nullopt; 0364 m_frames = info.stackFrames; 0365 informStackFrame(); 0366 0367 if (!m_frames.isEmpty()) { 0368 changeStackFrame(0); 0369 } 0370 popRequest(); 0371 } 0372 0373 void DapBackend::clearBreakpoints() 0374 { 0375 for (const auto &[url, breakpoints] : m_breakpoints) { 0376 const auto path = QUrl::fromLocalFile(url); 0377 for (const auto &bp : breakpoints) { 0378 if (bp && bp->line) { 0379 Q_EMIT breakPointCleared(path, bp->line.value() - 1); 0380 } 0381 } 0382 } 0383 Q_EMIT clearBreakpointMarks(); 0384 } 0385 0386 void DapBackend::onServerDisconnected() 0387 { 0388 if (!isConnectedState()) { 0389 return; 0390 } 0391 0392 clearBreakpoints(); 0393 0394 if (!m_restart) { 0395 m_breakpoints.clear(); 0396 m_wantedBreakpoints.clear(); 0397 } 0398 0399 setState(Disconnected); 0400 } 0401 0402 void DapBackend::onServerFinished() 0403 { 0404 Q_EMIT outputError(newLine(i18n("*** connection with server closed ***"))); 0405 0406 setState(PostMortem); 0407 } 0408 0409 void DapBackend::onProgramEnded(int exitCode) 0410 { 0411 Q_EMIT outputText(printEvent(i18n("program exited with code %1", exitCode))); 0412 } 0413 0414 void DapBackend::onInitialized() 0415 { 0416 Q_EMIT clearBreakpointMarks(); 0417 if (!m_wantedBreakpoints.empty()) { 0418 for (const auto &[url, breakpoints] : m_wantedBreakpoints) { 0419 m_breakpoints[url].clear(); 0420 pushRequest(); 0421 m_client->requestSetBreakpoints(url, breakpoints, true); 0422 } 0423 } 0424 shutdownUntil(PostMortem); 0425 Q_EMIT outputText(newLine(i18n("*** waiting for user actions ***"))); 0426 } 0427 0428 void DapBackend::onErrorResponse(const QString &summary, const std::optional<dap::Message> &message) 0429 { 0430 Q_EMIT outputError(newLine(i18n("error on response: %1", summary))); 0431 if (message) { 0432 Q_EMIT outputError(QStringLiteral(" {code %1: %2}").arg(message->id).arg(message->format)); 0433 } 0434 } 0435 0436 void DapBackend::onOutputProduced(const dap::Output &output) 0437 { 0438 if (output.output.isEmpty()) 0439 return; 0440 0441 if (output.isSpecialOutput() && !output.output.isEmpty()) { 0442 QString channel; 0443 switch (output.category) { 0444 case dap::Output::Category::Important: 0445 channel = i18n("important"); 0446 break; 0447 case dap::Output::Category::Telemetry: 0448 channel = i18n("telemetry"); 0449 break; 0450 default: 0451 break; 0452 } 0453 if (channel.isEmpty()) { 0454 Q_EMIT(outputError(newLine(output.output))); 0455 } else { 0456 Q_EMIT(outputError(QStringLiteral("\n(%1) %2").arg(channel).arg(output.output))); 0457 } 0458 } else { 0459 Q_EMIT debuggeeOutput(output); 0460 } 0461 } 0462 0463 void DapBackend::onDebuggingProcess(const dap::ProcessInfo &info) 0464 { 0465 QString out; 0466 if (info.systemProcessId) { 0467 out = i18n("debugging process [%1] %2", QString::number(info.systemProcessId.value()), info.name); 0468 } else { 0469 out = i18n("debugging process %1", info.name); 0470 } 0471 if (info.startMethod) { 0472 out += QStringLiteral(" (%1)").arg(i18n("Start method: %1", info.startMethod.value())); 0473 } 0474 Q_EMIT outputText(printEvent(out)); 0475 } 0476 0477 void DapBackend::onThreadEvent(const dap::ThreadEvent &info) 0478 { 0479 Q_EMIT outputText(printEvent(QStringLiteral("(%1) %2").arg(info.reason).arg(i18n("thread %1", QString::number(info.threadId))))); 0480 } 0481 0482 QString printModule(const dap::Module &module) 0483 { 0484 QString out = QStringLiteral("module %2: %1").arg(module.name); 0485 if (module.id_int) { 0486 out = out.arg(module.id_int.value()); 0487 } else if (module.id_str) { 0488 out = out.arg(module.id_str.value()); 0489 } 0490 if (module.isOptimized && module.isOptimized.value()) { 0491 out += QStringLiteral(" [optimized]"); 0492 } 0493 if (module.path) { 0494 out += QStringLiteral(": %1").arg(module.path.value()); 0495 } 0496 return out; 0497 } 0498 0499 QString printBreakpoint(const QString &sourceId, const dap::SourceBreakpoint &def, const std::optional<dap::Breakpoint> &bp, const int bId) 0500 { 0501 QString txtId = QStringLiteral("%1.").arg(bId); 0502 if (!bp) { 0503 txtId += QStringLiteral(" "); 0504 } else { 0505 if (bp->verified) { 0506 txtId += bp->id ? QString::number(bp->id.value()) : QStringLiteral("?"); 0507 } else { 0508 txtId += QStringLiteral("!"); 0509 } 0510 } 0511 0512 QStringList out = {QStringLiteral("[%1] %2: %3").arg(txtId).arg(sourceId).arg(def.line)}; 0513 if (def.column) { 0514 out << QStringLiteral(", %1").arg(def.column.value()); 0515 } 0516 if (bp) { 0517 if (bp->line) { 0518 out << QStringLiteral("->%1").arg(bp->line.value()); 0519 if (bp->endLine) { 0520 out << QStringLiteral("-%1").arg(bp->endLine.value()); 0521 } 0522 if (bp->column) { 0523 out << QStringLiteral(",%1").arg(bp->column.value()); 0524 if (bp->endColumn) { 0525 out << QStringLiteral("-%1").arg(bp->endColumn.value()); 0526 } 0527 } 0528 } 0529 } 0530 if (def.condition) { 0531 out << QStringLiteral(" when {%1}").arg(def.condition.value()); 0532 } 0533 if (def.hitCondition) { 0534 out << QStringLiteral(" hitcount {%1}").arg(def.hitCondition.value()); 0535 } 0536 if (bp && bp->message) { 0537 out << QStringLiteral(" (%1)").arg(bp->message.value()); 0538 } 0539 0540 return out.join(QString()); 0541 } 0542 0543 void DapBackend::onModuleEvent(const dap::ModuleEvent &info) 0544 { 0545 Q_EMIT outputText(printEvent(QStringLiteral("(%1) %2").arg(info.reason).arg(printModule(info.module)))); 0546 } 0547 0548 void DapBackend::changeScope(int scopeId) 0549 { 0550 if (!m_client) 0551 return; 0552 0553 if (m_currentScope && (*m_currentScope == scopeId)) 0554 return; 0555 0556 m_currentScope = scopeId; 0557 0558 if (m_queryLocals) { 0559 pushRequest(); 0560 m_client->requestVariables(scopeId); 0561 } 0562 } 0563 0564 void DapBackend::onScopes(const int /*frameId*/, const QList<dap::Scope> &scopes) 0565 { 0566 std::optional<int> activeScope = std::nullopt; 0567 0568 for (const auto &scope : scopes) { 0569 if (!m_currentScope || (*m_currentScope == scope.variablesReference)) { 0570 activeScope = scope.variablesReference; 0571 break; 0572 } 0573 } 0574 0575 if (activeScope) { 0576 m_currentScope = activeScope; 0577 } else if (!scopes.isEmpty()) { 0578 m_currentScope = scopes[0].variablesReference; 0579 } else { 0580 m_currentScope = std::nullopt; 0581 } 0582 0583 if (m_queryLocals) { 0584 // preload variables 0585 pushRequest(); 0586 m_client->requestVariables(*m_currentScope); 0587 Q_EMIT scopesInfo(scopes, m_currentScope); 0588 } 0589 popRequest(); 0590 } 0591 0592 void DapBackend::onVariables(const int variablesReference, const QList<dap::Variable> &variables) 0593 { 0594 if (!m_queryLocals) { 0595 popRequest(); 0596 return; 0597 } 0598 0599 const bool rootLevel = m_currentScope && (*m_currentScope == variablesReference); 0600 if (rootLevel) { 0601 Q_EMIT variableScopeOpened(); 0602 } 0603 0604 for (const auto &variable : variables) { 0605 Q_EMIT variableInfo(rootLevel ? 0 : variablesReference, variable); 0606 0607 if (rootLevel && (variable.variablesReference > 0)) { 0608 // TODO don't retrieve expensive variables 0609 // retrieve inner info 0610 pushRequest(); 0611 m_client->requestVariables(variable.variablesReference); 0612 } 0613 } 0614 0615 if (m_requests == 0) { 0616 Q_EMIT variableScopeClosed(); 0617 } 0618 0619 popRequest(); 0620 } 0621 0622 void DapBackend::onModules(const dap::ModulesInfo &modules) 0623 { 0624 for (const auto &mod : modules.modules) { 0625 Q_EMIT outputText(newLine(printModule(mod))); 0626 } 0627 popRequest(); 0628 } 0629 0630 void DapBackend::informBreakpointAdded(const QString &path, const dap::Breakpoint &bpoint) 0631 { 0632 if (bpoint.line) { 0633 Q_EMIT outputText(QStringLiteral("\n%1 %2:%3\n").arg(i18n("breakpoint set")).arg(path).arg(bpoint.line.value())); 0634 // zero based line expected 0635 Q_EMIT breakPointSet(QUrl::fromLocalFile(path), bpoint.line.value() - 1); 0636 } 0637 } 0638 0639 void DapBackend::informBreakpointRemoved(const QString &path, int line) 0640 { 0641 Q_EMIT outputText(QStringLiteral("\n%1 %2:%3\n").arg(i18n("breakpoint cleared")).arg(path).arg(line)); 0642 // zero based line expected 0643 Q_EMIT breakPointCleared(QUrl::fromLocalFile(path), line - 1); 0644 } 0645 0646 void DapBackend::onSourceBreakpoints(const QString &path, int reference, const std::optional<QList<dap::Breakpoint>> &breakpoints) 0647 { 0648 if (!breakpoints) { 0649 popRequest(); 0650 return; 0651 } 0652 0653 const auto id = dap::Source::getUnifiedId(path, reference); 0654 if (id.isEmpty()) { 0655 popRequest(); 0656 return; 0657 } 0658 0659 if (m_breakpoints.find(id) == m_breakpoints.end()) { 0660 m_breakpoints[id] = QList<std::optional<dap::Breakpoint>>(); 0661 m_breakpoints[id].reserve(breakpoints->size()); 0662 } 0663 0664 // if runToCursor is pending, a bpoint with hit condition has been added 0665 const bool withRunToCursor = m_runToCursor && (m_runToCursor->path == path); 0666 bool mustContinue = false; 0667 const auto &wanted = m_wantedBreakpoints[path]; 0668 0669 auto &table = m_breakpoints[id]; 0670 int pointIdx = 0; 0671 const int last = table.size(); 0672 for (const auto &point : *breakpoints) { 0673 if (pointIdx >= last) { 0674 // bpoint added 0675 table << point; 0676 informBreakpointAdded(id, point); 0677 } else if (!table[pointIdx]) { 0678 // bpoint added 0679 table[pointIdx] = point; 0680 informBreakpointAdded(id, point); 0681 } 0682 if (withRunToCursor) { 0683 if (wanted[pointIdx].line == m_runToCursor->line) { 0684 mustContinue = point.line.has_value(); 0685 m_runToCursor = std::nullopt; 0686 } 0687 } 0688 ++pointIdx; 0689 } 0690 0691 popRequest(); 0692 0693 if (mustContinue) { 0694 slotContinue(); 0695 } 0696 } 0697 0698 void DapBackend::onBreakpointEvent(const dap::BreakpointEvent &info) 0699 { 0700 QStringList parts = {i18n("(%1) breakpoint", info.reason)}; 0701 if (info.breakpoint.source) { 0702 parts << QStringLiteral(" ") << info.breakpoint.source->unifiedId(); 0703 } 0704 if (info.breakpoint.line) { 0705 parts << QStringLiteral(":%1").arg(info.breakpoint.line.value()); 0706 } 0707 0708 Q_EMIT outputText(printEvent(parts.join(QString()))); 0709 } 0710 0711 void DapBackend::onExpressionEvaluated(const QString &expression, const std::optional<dap::EvaluateInfo> &info) 0712 { 0713 QString result; 0714 if (info) { 0715 result = info->result; 0716 } else { 0717 result = i18n("<not evaluated>"); 0718 } 0719 0720 Q_EMIT outputText(QStringLiteral("\n(%1) = %2").arg(expression).arg(result)); 0721 0722 popRequest(); 0723 } 0724 0725 void DapBackend::onGotoTargets(const dap::Source &source, const int, const QList<dap::GotoTarget> &targets) 0726 { 0727 if (!targets.isEmpty() && m_currentThread) { 0728 Q_EMIT outputError(newLine(QStringLiteral("jump target %1:%2 (%3)").arg(source.unifiedId()).arg(targets[0].line).arg(targets[0].label))); 0729 m_client->requestGoto(*m_currentThread, targets[0].id); 0730 } 0731 popRequest(); 0732 } 0733 0734 void DapBackend::onCapabilitiesReceived(const dap::Capabilities &capabilities) 0735 { 0736 // can set breakpoints now 0737 setState(State::Initializing); 0738 0739 QStringList text = {QStringLiteral("\n%1:\n").arg(i18n("server capabilities"))}; 0740 const QString tpl = QStringLiteral("* %1 \n"); 0741 const auto format = [](const QString &field, bool value) { 0742 return QStringLiteral("* %1: %2\n").arg(field).arg(value ? i18n("supported") : i18n("unsupported")); 0743 }; 0744 0745 text << format(i18n("conditional breakpoints"), capabilities.supportsConditionalBreakpoints) 0746 << format(i18n("function breakpoints"), capabilities.supportsFunctionBreakpoints) 0747 << format(i18n("hit conditional breakpoints"), capabilities.supportsHitConditionalBreakpoints) 0748 << format(i18n("log points"), capabilities.supportsLogPoints) << format(i18n("modules request"), capabilities.supportsModulesRequest) 0749 << format(i18n("goto targets request"), capabilities.supportsGotoTargetsRequest) 0750 << format(i18n("terminate request"), capabilities.supportsTerminateRequest) 0751 << format(i18n("terminate debuggee"), capabilities.supportTerminateDebuggee); 0752 0753 Q_EMIT outputText(text.join(QString())); 0754 } 0755 0756 bool DapBackend::supportsMovePC() const 0757 { 0758 return isRunningState() && m_client && m_client->adapterCapabilities().supportsGotoTargetsRequest; 0759 } 0760 0761 bool DapBackend::supportsRunToCursor() const 0762 { 0763 return isAttachedState() && m_client && m_client->adapterCapabilities().supportsHitConditionalBreakpoints; 0764 } 0765 0766 bool DapBackend::isConnectedState() const 0767 { 0768 return m_client && (m_state != None) && (m_state != Disconnected) && (m_state != PostMortem); 0769 } 0770 0771 bool DapBackend::isAttachedState() const 0772 { 0773 return isConnectedState() && (m_state != Terminated); 0774 } 0775 0776 bool DapBackend::isRunningState() const 0777 { 0778 return (m_state == Running) || (m_state == Stopped); 0779 } 0780 0781 bool DapBackend::canSetBreakpoints() const 0782 { 0783 return isAttachedState(); 0784 } 0785 0786 bool DapBackend::canMove() const 0787 { 0788 return isRunningState(); 0789 } 0790 0791 bool DapBackend::canContinue() const 0792 { 0793 return (m_state == Initializing) || (m_state == Stopped); 0794 } 0795 0796 bool DapBackend::debuggerRunning() const 0797 { 0798 return m_client && (m_state != None); 0799 } 0800 0801 bool DapBackend::debuggerBusy() const 0802 { 0803 return debuggerRunning() && (m_task == Busy); 0804 } 0805 0806 std::optional<int> DapBackend::findBreakpoint(const QString &path, int line) const 0807 { 0808 if (m_breakpoints.find(path) == m_breakpoints.end()) 0809 return std::nullopt; 0810 0811 const auto &bpoints = m_breakpoints.at(path); 0812 int index = 0; 0813 for (const auto &bp : bpoints) { 0814 if (bp && bp->line && (line == bp->line)) { 0815 return index; 0816 } 0817 ++index; 0818 } 0819 return std::nullopt; 0820 } 0821 0822 std::optional<int> DapBackend::findBreakpointIntent(const QString &path, int line) const 0823 { 0824 if (m_wantedBreakpoints.find(path) == m_wantedBreakpoints.end()) { 0825 return std::nullopt; 0826 } 0827 0828 const auto &bpoints = m_wantedBreakpoints.at(path); 0829 int index = 0; 0830 for (const auto &bp : bpoints) { 0831 if (bp.line == line) { 0832 return index; 0833 } 0834 ++index; 0835 } 0836 return std::nullopt; 0837 } 0838 0839 bool DapBackend::hasBreakpoint(QUrl const &url, int line) const 0840 { 0841 return findBreakpoint(*resolveFilename(url.path()), line).has_value(); 0842 } 0843 0844 void DapBackend::toggleBreakpoint(QUrl const &url, int line) 0845 { 0846 const auto path = resolveOrWarn(url.path()); 0847 0848 if (!removeBreakpoint(path, line)) { 0849 insertBreakpoint(path, line); 0850 } 0851 } 0852 0853 bool DapBackend::removeBreakpoint(const QString &path, int line) 0854 { 0855 bool informed = false; 0856 // clear all breakpoints in the same line (there can be more than one) 0857 auto index = findBreakpoint(path, line); 0858 while (index) { 0859 m_wantedBreakpoints[path].removeAt(*index); 0860 m_breakpoints[path].removeAt(*index); 0861 if (!informed) { 0862 informBreakpointRemoved(path, line); 0863 informed = true; 0864 } 0865 index = findBreakpoint(path, line); 0866 } 0867 // clear all breakpoint intents in the same line 0868 index = findBreakpointIntent(path, line); 0869 while (index) { 0870 m_wantedBreakpoints[path].removeAt(*index); 0871 if (!informed) { 0872 informBreakpointRemoved(path, line); 0873 informed = true; 0874 } 0875 index = findBreakpointIntent(path, line); 0876 } 0877 0878 if (!informed) { 0879 return false; 0880 } 0881 0882 // update breakpoint table for this file 0883 pushRequest(); 0884 m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true); 0885 0886 return true; 0887 } 0888 0889 void DapBackend::insertBreakpoint(const QString &path, int line) 0890 { 0891 if (m_wantedBreakpoints.find(path) == m_wantedBreakpoints.end()) { 0892 m_wantedBreakpoints[path] = {dap::SourceBreakpoint(line)}; 0893 m_breakpoints[path] = {std::nullopt}; 0894 } else { 0895 m_wantedBreakpoints[path] << dap::SourceBreakpoint(line); 0896 m_breakpoints[path] << std::nullopt; 0897 } 0898 0899 pushRequest(); 0900 m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true); 0901 } 0902 0903 void DapBackend::movePC(QUrl const &url, int line) 0904 { 0905 if (!m_client) 0906 return; 0907 0908 if (m_state != State::Stopped) 0909 return; 0910 0911 if (!m_currentThread) 0912 return; 0913 0914 if (!m_client->adapterCapabilities().supportsGotoTargetsRequest) 0915 return; 0916 0917 const auto path = resolveOrWarn(url.path()); 0918 0919 pushRequest(); 0920 m_client->requestGotoTargets(path, line); 0921 } 0922 0923 void DapBackend::runToCursor(QUrl const &url, int line) 0924 { 0925 if (!m_client) 0926 return; 0927 0928 if (!m_client->adapterCapabilities().supportsHitConditionalBreakpoints) 0929 return; 0930 0931 const auto path = resolveOrWarn(url.path()); 0932 0933 dap::SourceBreakpoint bp(line); 0934 bp.hitCondition = QStringLiteral("<=1"); 0935 0936 if (m_wantedBreakpoints.find(path) == m_wantedBreakpoints.end()) { 0937 m_wantedBreakpoints[path] = {std::move(bp)}; 0938 m_breakpoints[path] = {std::nullopt}; 0939 } else { 0940 m_wantedBreakpoints[path] << std::move(bp); 0941 m_breakpoints[path] << std::nullopt; 0942 } 0943 0944 m_runToCursor = Cursor{line, path}; 0945 pushRequest(); 0946 m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true); 0947 } 0948 0949 void DapBackend::cmdEval(const QString &cmd) 0950 { 0951 int start = cmd.indexOf(QLatin1Char(' ')); 0952 0953 QString expression; 0954 if (start >= 0) { 0955 expression = cmd.mid(start).trimmed(); 0956 } 0957 if (expression.isEmpty()) { 0958 Q_EMIT outputError(newLine(i18n("syntax error: expression not found"))); 0959 return; 0960 } 0961 0962 std::optional<int> frameId = std::nullopt; 0963 if (m_currentFrame) 0964 frameId = m_frames[*m_currentFrame].id; 0965 0966 pushRequest(); 0967 m_client->requestWatch(expression, frameId); 0968 } 0969 0970 void DapBackend::cmdJump(const QString &cmd) 0971 { 0972 const static QRegularExpression rx_goto(QStringLiteral(R"--(^j[a-z]*\s+(\d+)(?:\s+(\S+))?$)--")); 0973 0974 const auto match = rx_goto.match(cmd); 0975 if (!match.hasMatch()) { 0976 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 0977 return; 0978 } 0979 0980 const auto &txtLine = match.captured(1); 0981 bool ok = false; 0982 const int line = txtLine.toInt(&ok); 0983 if (!ok) { 0984 Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine))); 0985 return; 0986 } 0987 0988 QString path = match.captured(2); 0989 if (path.isNull()) { 0990 if (!m_currentFrame) { 0991 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 0992 return; 0993 } 0994 const auto &frame = this->m_frames[*m_currentFrame]; 0995 if (!frame.source) { 0996 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 0997 return; 0998 } 0999 path = frame.source->unifiedId(); 1000 } 1001 1002 this->movePC(QUrl::fromLocalFile(path), line); 1003 } 1004 1005 void DapBackend::cmdRunToCursor(const QString &cmd) 1006 { 1007 const static QRegularExpression rx_goto(QStringLiteral(R"--(^to?\s+(\d+)(?:\s+(\S+))?$)--")); 1008 1009 const auto match = rx_goto.match(cmd); 1010 if (!match.hasMatch()) { 1011 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1012 return; 1013 } 1014 1015 const auto &txtLine = match.captured(1); 1016 bool ok = false; 1017 const int line = txtLine.toInt(&ok); 1018 if (!ok) { 1019 Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine))); 1020 return; 1021 } 1022 1023 QString path = match.captured(2); 1024 if (path.isNull()) { 1025 if (!m_currentFrame) { 1026 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 1027 return; 1028 } 1029 const auto &frame = this->m_frames[*m_currentFrame]; 1030 if (!frame.source) { 1031 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 1032 return; 1033 } 1034 path = frame.source->unifiedId(); 1035 } 1036 1037 this->runToCursor(QUrl::fromLocalFile(path), line); 1038 } 1039 1040 void DapBackend::cmdPause(const QString &cmd) 1041 { 1042 if (!m_client) 1043 return; 1044 1045 const static QRegularExpression rx_pause(QStringLiteral(R"--(^s[a-z]*(?:\s+(\d+))?\s*$)--")); 1046 1047 const auto match = rx_pause.match(cmd); 1048 if (!match.hasMatch()) { 1049 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1050 return; 1051 } 1052 1053 const auto &txtThread = match.captured(1); 1054 1055 int threadId; 1056 1057 if (!txtThread.isNull()) { 1058 bool ok = false; 1059 threadId = txtThread.toInt(&ok); 1060 if (!ok) { 1061 Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread))); 1062 return; 1063 } 1064 } else if (m_currentThread) { 1065 threadId = *m_currentThread; 1066 } else { 1067 Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd))); 1068 return; 1069 } 1070 1071 m_client->requestPause(threadId); 1072 } 1073 1074 void DapBackend::cmdContinue(const QString &cmd) 1075 { 1076 if (!m_client) 1077 return; 1078 1079 const static QRegularExpression rx_cont(QStringLiteral(R"--(^c[a-z]*(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--")); 1080 1081 const auto match = rx_cont.match(cmd); 1082 if (!match.hasMatch()) { 1083 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1084 return; 1085 } 1086 1087 const auto &txtThread = match.captured(QStringLiteral("ID")); 1088 1089 int threadId; 1090 1091 if (!txtThread.isNull()) { 1092 bool ok = false; 1093 threadId = txtThread.toInt(&ok); 1094 if (!ok) { 1095 Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread))); 1096 return; 1097 } 1098 } else if (m_currentThread) { 1099 threadId = *m_currentThread; 1100 } else { 1101 Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd))); 1102 return; 1103 } 1104 1105 const auto only = match.captured(QStringLiteral("ONLY")); 1106 1107 m_client->requestContinue(threadId, !only.isNull()); 1108 } 1109 1110 void DapBackend::cmdStepIn(const QString &cmd) 1111 { 1112 if (!m_client) 1113 return; 1114 1115 const static QRegularExpression rx_in(QStringLiteral(R"--(^in?(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--")); 1116 1117 const auto match = rx_in.match(cmd); 1118 if (!match.hasMatch()) { 1119 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1120 return; 1121 } 1122 1123 const auto &txtThread = match.captured(QStringLiteral("ID")); 1124 1125 int threadId; 1126 1127 if (!txtThread.isNull()) { 1128 bool ok = false; 1129 threadId = txtThread.toInt(&ok); 1130 if (!ok) { 1131 Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread))); 1132 return; 1133 } 1134 } else if (m_currentThread) { 1135 threadId = *m_currentThread; 1136 } else { 1137 Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd))); 1138 return; 1139 } 1140 1141 const auto only = match.captured(QStringLiteral("ONLY")); 1142 1143 m_client->requestStepIn(threadId, !only.isNull()); 1144 } 1145 1146 void DapBackend::cmdStepOut(const QString &cmd) 1147 { 1148 if (!m_client) 1149 return; 1150 1151 const static QRegularExpression rx_out(QStringLiteral(R"--(^o[a-z]*(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--")); 1152 1153 const auto match = rx_out.match(cmd); 1154 if (!match.hasMatch()) { 1155 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1156 return; 1157 } 1158 1159 const auto &txtThread = match.captured(QStringLiteral("ID")); 1160 1161 int threadId; 1162 1163 if (!txtThread.isNull()) { 1164 bool ok = false; 1165 threadId = txtThread.toInt(&ok); 1166 if (!ok) { 1167 Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread))); 1168 return; 1169 } 1170 } else if (m_currentThread) { 1171 threadId = *m_currentThread; 1172 } else { 1173 Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd))); 1174 return; 1175 } 1176 1177 const auto only = match.captured(QStringLiteral("ONLY")); 1178 1179 m_client->requestStepOut(threadId, !only.isNull()); 1180 } 1181 1182 void DapBackend::cmdNext(const QString &cmd) 1183 { 1184 if (!m_client) 1185 return; 1186 1187 const static QRegularExpression rx_next(QStringLiteral(R"--(^n[a-z]*(?:\s+(?P<ONLY>only))?(?:\s+(?P<ID>\d+))?\s*$)--")); 1188 1189 const auto match = rx_next.match(cmd); 1190 if (!match.hasMatch()) { 1191 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1192 return; 1193 } 1194 1195 const auto &txtThread = match.captured(QStringLiteral("ID")); 1196 1197 int threadId; 1198 1199 if (!txtThread.isNull()) { 1200 bool ok = false; 1201 threadId = txtThread.toInt(&ok); 1202 if (!ok) { 1203 Q_EMIT outputError(newLine(i18n("invalid thread id: %1", txtThread))); 1204 return; 1205 } 1206 } else if (m_currentThread) { 1207 threadId = *m_currentThread; 1208 } else { 1209 Q_EMIT outputError(newLine(i18n("thread id not specified: %1", cmd))); 1210 return; 1211 } 1212 1213 const auto only = match.captured(QStringLiteral("ONLY")); 1214 1215 m_client->requestNext(threadId, !only.isNull()); 1216 } 1217 1218 void DapBackend::cmdHelp(const QString & /*cmd*/) 1219 { 1220 QStringList out = {QString(), i18n("Available commands:")}; 1221 1222 const QString tpl = QStringLiteral("* %1"); 1223 1224 out << tpl.arg(QStringLiteral("h[elp]")) << tpl.arg(QStringLiteral("p[rint] [expression]")) << tpl.arg(QStringLiteral("c[ontinue] [only] [threadId]")) 1225 << tpl.arg(QStringLiteral("n[ext] [only] [threadId]")) << tpl.arg(QStringLiteral("i[n] [only] [threadId]")) 1226 << tpl.arg(QStringLiteral("o[ut] [only] [threadId]")) << tpl.arg(QStringLiteral("s[top] [threadId]")); 1227 1228 if (m_client->adapterCapabilities().supportsGotoTargetsRequest) { 1229 out << tpl.arg(QStringLiteral("j[ump] <line> [file]")); 1230 } 1231 if (m_client->adapterCapabilities().supportsHitConditionalBreakpoints) { 1232 out << tpl.arg(QStringLiteral("t[o] <line> [file]")); 1233 } 1234 1235 if (m_client->adapterCapabilities().supportsModulesRequest) { 1236 out << tpl.arg(QStringLiteral("m[odules]")); 1237 } 1238 1239 QString bcmd = QStringLiteral("b[reakpoint] <line>"); 1240 if (m_client->adapterCapabilities().supportsConditionalBreakpoints) { 1241 bcmd += QStringLiteral(" [when {condition}]"); 1242 } 1243 if (m_client->adapterCapabilities().supportsHitConditionalBreakpoints) { 1244 bcmd += QStringLiteral(" [hitcount {condition}]"); 1245 } 1246 bcmd += QStringLiteral(" [on <file>]"); 1247 1248 out << tpl.arg(QStringLiteral("bl[ist]")) << tpl.arg(bcmd) << tpl.arg(QStringLiteral("boff <line> [file]")); 1249 1250 out << tpl.arg(QStringLiteral("w[hereami]")); 1251 1252 Q_EMIT outputText(out.join(QStringLiteral("\n"))); 1253 } 1254 1255 void DapBackend::cmdListModules(const QString &) 1256 { 1257 if (!m_client) 1258 return; 1259 1260 if (!m_client->adapterCapabilities().supportsModulesRequest) { 1261 return; 1262 } 1263 1264 pushRequest(); 1265 m_client->requestModules(); 1266 } 1267 1268 void DapBackend::cmdListBreakpoints(const QString &) 1269 { 1270 int bId = 0; 1271 for (const auto &[url, breakpoints] : m_breakpoints) { 1272 const auto &sourceId = url; 1273 const auto &defs = m_wantedBreakpoints[sourceId]; 1274 int pointIdx = 0; 1275 for (const auto &b : breakpoints) { 1276 const auto &def = defs[pointIdx]; 1277 Q_EMIT outputText(newLine(printBreakpoint(sourceId, def, b, bId))); 1278 ++pointIdx; 1279 ++bId; 1280 } 1281 } 1282 } 1283 1284 void DapBackend::cmdBreakpointOn(const QString &cmd) 1285 { 1286 static const QRegularExpression rx_bon( 1287 QStringLiteral(R"--(^b\s+(\d+)(?:\s+when\s+\{(?P<COND>.+)\})?(?:\s*hitcont\s+\{(?P<HIT>.+)\})?(?:\s+on\s+(?P<SOURCE>.+))?\s*$)--")); 1288 1289 const auto match = rx_bon.match(cmd); 1290 if (!match.hasMatch()) { 1291 Q_EMIT outputText(newLine(i18n("syntax error: %1", cmd))); 1292 return; 1293 } 1294 const auto &txtLine = match.captured(1); 1295 bool ok = false; 1296 const int line = txtLine.toInt(&ok); 1297 if (!ok) { 1298 Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine))); 1299 return; 1300 } 1301 1302 dap::SourceBreakpoint bp(line); 1303 1304 bp.condition = match.captured(QStringLiteral("COND")); 1305 if (bp.condition->isNull()) { 1306 bp.condition = std::nullopt; 1307 } else if (!m_client->adapterCapabilities().supportsConditionalBreakpoints) { 1308 Q_EMIT outputError(newLine(i18n("conditional breakpoints are not supported by the server"))); 1309 return; 1310 } 1311 1312 bp.hitCondition = match.captured(QStringLiteral("HIT")); 1313 if (bp.hitCondition->isNull()) { 1314 bp.hitCondition = std::nullopt; 1315 } else if (!m_client->adapterCapabilities().supportsHitConditionalBreakpoints) { 1316 Q_EMIT outputError(newLine(i18n("hit conditional breakpoints are not supported by the server"))); 1317 return; 1318 } 1319 QString path = match.captured(QStringLiteral("SOURCE")); 1320 if (path.isNull()) { 1321 if (!m_currentFrame) { 1322 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 1323 return; 1324 } 1325 const auto &frame = this->m_frames[*m_currentFrame]; 1326 if (!frame.source) { 1327 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 1328 return; 1329 } 1330 path = resolveOrWarn(frame.source->unifiedId()); 1331 } else { 1332 path = resolveOrWarn(path); 1333 } 1334 1335 if (findBreakpoint(path, bp.line) || findBreakpointIntent(path, bp.line)) { 1336 Q_EMIT outputError(newLine(i18n("line %1 already has a breakpoint", txtLine))); 1337 return; 1338 } 1339 1340 m_wantedBreakpoints[path] << std::move(bp); 1341 m_breakpoints[path] << std::nullopt; 1342 1343 pushRequest(); 1344 m_client->requestSetBreakpoints(path, m_wantedBreakpoints[path], true); 1345 } 1346 1347 void DapBackend::cmdBreakpointOff(const QString &cmd) 1348 { 1349 const static QRegularExpression rx_boff(QStringLiteral(R"--(^bo[a-z]*?\s+(\d+)(?:\s+(\S+))?$)--")); 1350 1351 const auto match = rx_boff.match(cmd); 1352 if (!match.hasMatch()) { 1353 Q_EMIT outputError(newLine(i18n("syntax error: %1", cmd))); 1354 return; 1355 } 1356 1357 const auto &txtLine = match.captured(1); 1358 bool ok = false; 1359 const int line = txtLine.toInt(&ok); 1360 if (!ok) { 1361 Q_EMIT outputError(newLine(i18n("invalid line: %1", txtLine))); 1362 return; 1363 } 1364 1365 QString path = match.captured(2); 1366 if (path.isNull()) { 1367 if (!m_currentFrame) { 1368 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 1369 return; 1370 } 1371 const auto &frame = this->m_frames[*m_currentFrame]; 1372 if (!frame.source) { 1373 Q_EMIT outputError(newLine(i18n("file not specified: %1", cmd))); 1374 return; 1375 } 1376 path = frame.source->unifiedId(); 1377 } 1378 path = resolveOrWarn(path); 1379 1380 if (!removeBreakpoint(path, line)) { 1381 Q_EMIT outputError(newLine(i18n("breakpoint not found (%1:%2)", path, line))); 1382 } 1383 } 1384 1385 void DapBackend::cmdWhereami(const QString &) 1386 { 1387 QStringList parts = {newLine(i18n("Current thread: "))}; 1388 1389 if (m_currentThread) { 1390 parts << QString::number(*m_currentThread); 1391 } else { 1392 parts << i18n("none"); 1393 } 1394 1395 parts << newLine(i18n("Current frame: ")); 1396 if (m_currentFrame) { 1397 parts << QString::number(*m_currentFrame); 1398 } else { 1399 parts << i18n("none"); 1400 } 1401 1402 parts << newLine(i18n("Session state: ")); 1403 switch (m_state) { 1404 case Initializing: 1405 parts << i18n("initializing"); 1406 break; 1407 case Running: 1408 parts << i18n("running"); 1409 break; 1410 case Stopped: 1411 parts << i18n("stopped"); 1412 break; 1413 case Terminated: 1414 parts << i18n("terminated"); 1415 break; 1416 case Disconnected: 1417 parts << i18n("disconnected"); 1418 break; 1419 case PostMortem: 1420 parts << i18n("post mortem"); 1421 break; 1422 default: 1423 parts << i18n("none"); 1424 break; 1425 } 1426 1427 Q_EMIT outputText(parts.join(QString())); 1428 } 1429 1430 void DapBackend::issueCommand(QString const &command) 1431 { 1432 if (!m_client) 1433 return; 1434 1435 if (m_task == Busy) { 1436 m_commandQueue << command; 1437 return; 1438 } 1439 1440 QString cmd = command.trimmed(); 1441 1442 if (cmd.isEmpty()) 1443 return; 1444 1445 Q_EMIT outputText(QStringLiteral("\n(dap) %1").arg(command)); 1446 1447 if (cmd.startsWith(QLatin1Char('h'))) { 1448 cmdHelp(cmd); 1449 } else if (cmd.startsWith(QLatin1Char('c'))) { 1450 cmdContinue(cmd); 1451 } else if (cmd.startsWith(QLatin1Char('n'))) { 1452 cmdNext(cmd); 1453 } else if (cmd.startsWith(QLatin1Char('o'))) { 1454 cmdStepOut(cmd); 1455 } else if (cmd.startsWith(QLatin1Char('i'))) { 1456 cmdStepIn(cmd); 1457 } else if (cmd.startsWith(QLatin1Char('p'))) { 1458 cmdEval(cmd); 1459 } else if (cmd.startsWith(QLatin1Char('j'))) { 1460 cmdJump(cmd); 1461 } else if (cmd.startsWith(QLatin1Char('t'))) { 1462 cmdRunToCursor(cmd); 1463 } else if (cmd.startsWith(QLatin1Char('m'))) { 1464 cmdListModules(cmd); 1465 } else if (cmd.startsWith(QStringLiteral("bl"))) { 1466 cmdListBreakpoints(cmd); 1467 } else if (cmd.startsWith(QStringLiteral("bo"))) { 1468 cmdBreakpointOff(cmd); 1469 } else if (cmd.startsWith(QLatin1Char('b'))) { 1470 cmdBreakpointOn(cmd); 1471 } else if (cmd.startsWith(QLatin1Char('s'))) { 1472 cmdPause(cmd); 1473 } else if (cmd.startsWith(QLatin1Char('w'))) { 1474 cmdWhereami(cmd); 1475 } else { 1476 Q_EMIT outputError(newLine(i18n("command not found"))); 1477 } 1478 } 1479 1480 QString DapBackend::targetName() const 1481 { 1482 return m_targetName; 1483 } 1484 1485 void DapBackend::setFileSearchPaths(const QStringList & /*paths*/) 1486 { 1487 // TODO 1488 } 1489 1490 void DapBackend::slotInterrupt() 1491 { 1492 if (!isRunningState()) { 1493 return; 1494 } 1495 1496 if (!m_currentThread) { 1497 Q_EMIT outputError(newLine(i18n("missing thread id"))); 1498 return; 1499 } 1500 1501 m_client->requestPause(*m_currentThread); 1502 } 1503 1504 void DapBackend::slotStepInto() 1505 { 1506 if (!m_client) 1507 return; 1508 1509 if (m_state != State::Stopped) 1510 return; 1511 1512 if (!m_currentThread) 1513 return; 1514 1515 m_client->requestStepIn(*m_currentThread); 1516 } 1517 1518 void DapBackend::slotStepOut() 1519 { 1520 if (!m_client) 1521 return; 1522 1523 if (m_state != State::Stopped) 1524 return; 1525 1526 if (!m_currentThread) 1527 return; 1528 1529 m_client->requestStepOut(*m_currentThread); 1530 } 1531 1532 void DapBackend::slotStepOver() 1533 { 1534 if (!m_client) 1535 return; 1536 1537 if (m_state != State::Stopped) 1538 return; 1539 1540 if (!m_currentThread) 1541 return; 1542 1543 m_client->requestNext(*m_currentThread); 1544 } 1545 1546 void DapBackend::slotContinue() 1547 { 1548 if (!isAttachedState()) 1549 return; 1550 1551 if (m_state == State::Initializing) { 1552 m_client->requestConfigurationDone(); 1553 } else if (m_currentThread) { 1554 m_client->requestContinue(*m_currentThread); 1555 } 1556 } 1557 1558 void DapBackend::shutdownUntil(std::optional<State> state) 1559 { 1560 if (!state) { 1561 m_shutdown.target = std::nullopt; 1562 m_shutdown.userAction = std::nullopt; 1563 } else if (!m_shutdown.target || (*state > m_shutdown.target)) { 1564 // propagate until the deepest state 1565 m_shutdown.target = *state; 1566 } 1567 } 1568 1569 bool DapBackend::continueShutdown() const 1570 { 1571 return m_restart || (m_shutdown.target && (m_shutdown.target > m_state)); 1572 } 1573 1574 void DapBackend::slotKill() 1575 { 1576 if (!isConnectedState()) { 1577 setState(None); 1578 Q_EMIT readyForInput(false); 1579 Q_EMIT gdbEnded(); 1580 return; 1581 } 1582 // if it is running, interrupt instead of killing 1583 if (isRunningState() && !this->canContinue()) { 1584 slotInterrupt(); 1585 return; 1586 } 1587 1588 if (!m_shutdown.userAction) { 1589 if (isRunningState()) { 1590 shutdownUntil(PostMortem); 1591 tryTerminate(); 1592 } else { 1593 shutdownUntil(PostMortem); 1594 tryDisconnect(); 1595 } 1596 } else { 1597 switch (*m_shutdown.userAction) { 1598 case Polite: 1599 // try and shutdown server 1600 m_shutdown.userAction = Force; 1601 cmdShutdown(); 1602 break; 1603 case Force: 1604 // kill server 1605 Q_EMIT outputError(newLine(i18n("killing backend"))); 1606 unsetClient(); 1607 break; 1608 } 1609 } 1610 } 1611 1612 void DapBackend::slotReRun() 1613 { 1614 if (!m_client && m_settings) { 1615 start(); 1616 return; 1617 } 1618 1619 m_restart = true; 1620 slotKill(); 1621 } 1622 1623 void DapBackend::slotQueryLocals(bool display) 1624 { 1625 m_queryLocals = display; 1626 1627 if (!display) 1628 return; 1629 1630 if (!m_client) 1631 return; 1632 1633 if (m_currentFrame) { 1634 informStackFrame(); 1635 pushRequest(); 1636 m_client->requestScopes(m_frames[*m_currentFrame].id); 1637 } 1638 } 1639 1640 QString DapBackend::slotPrintVariable(const QString &variable) 1641 { 1642 const auto cmd = QStringLiteral("print %1").arg(variable); 1643 issueCommand(cmd); 1644 return cmd; 1645 } 1646 1647 void DapBackend::changeStackFrame(int index) 1648 { 1649 if (!debuggerRunning()) 1650 return; 1651 1652 if ((m_frames.size() < index) || (index < 0)) 1653 return; 1654 1655 if (m_currentFrame && (*m_currentFrame == index)) 1656 return; 1657 1658 m_currentFrame = index; 1659 1660 const auto &frame = m_frames[index]; 1661 if (frame.source) { 1662 const auto id = frame.source->unifiedId(); 1663 Q_EMIT outputText(QStringLiteral("\n") + i18n("Current frame [%3]: %1:%2 (%4)", id, QString::number(frame.line), QString::number(index), frame.name)); 1664 // zero-based line 1665 Q_EMIT debugLocationChanged(QUrl::fromLocalFile(resolveOrWarn(id)), frame.line - 1); 1666 } 1667 1668 Q_EMIT stackFrameChanged(index); 1669 1670 slotQueryLocals(m_queryLocals); 1671 } 1672 1673 void DapBackend::changeThread(int index) 1674 { 1675 if (!debuggerRunning()) 1676 return; 1677 1678 if (!m_queryLocals) 1679 return; 1680 1681 if (m_watchedThread && (*m_watchedThread == index)) 1682 return; 1683 1684 m_watchedThread = index; 1685 1686 pushRequest(); 1687 m_client->requestStackTrace(index); 1688 } 1689 1690 std::optional<QString> DapBackend::resolveFilename(const QString &filename, bool fallback) const 1691 { 1692 QFileInfo fInfo = QFileInfo(filename); 1693 if (fInfo.exists() && fInfo.isDir()) { 1694 return fInfo.absoluteFilePath(); 1695 } 1696 1697 if (fInfo.isAbsolute()) { 1698 return filename; 1699 } 1700 1701 // working path 1702 if (!m_workDir.isEmpty()) { 1703 const auto base = QDir(m_workDir); 1704 fInfo = QFileInfo(base.absoluteFilePath(filename)); 1705 if (fInfo.exists() && !fInfo.isDir()) { 1706 return fInfo.absoluteFilePath(); 1707 } 1708 } 1709 1710 // executable path 1711 if (!m_file.isEmpty()) { 1712 const auto base = QDir(QFileInfo(m_file).absolutePath()); 1713 fInfo = QFileInfo(base.absoluteFilePath(filename)); 1714 if (fInfo.exists() && !fInfo.isDir()) { 1715 return fInfo.absoluteFilePath(); 1716 } 1717 } 1718 1719 if (fallback) 1720 return filename; 1721 1722 return std::nullopt; 1723 } 1724 1725 QString DapBackend::resolveOrWarn(const QString &filename) 1726 { 1727 const auto path = resolveFilename(filename, false); 1728 1729 if (path) 1730 return *path; 1731 1732 Q_EMIT sourceFileNotFound(filename); 1733 1734 return filename; 1735 } 1736 1737 #include "moc_dapbackend.cpp"