// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "dapengine.h" #include "cmakedapengine.h" #include "dapclient.h" #include "gdbdapengine.h" #include "lldbdapengine.h" #include "pydapengine.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace Utils; namespace Debugger::Internal { VariablesHandler::VariablesHandler(DapEngine *dapEngine) : m_dapEngine(dapEngine) {} void VariablesHandler::addVariable(const QString &iname, int variablesReference) { VariableItem varItem = {iname, variablesReference}; bool wasEmpty = m_queue.empty(); bool inserted = false; for (auto i = m_queue.begin(); i != m_queue.end(); ++i) { if (i->iname > iname) { m_queue.insert(i, varItem); inserted = true; break; } } if (!inserted) m_queue.push_back(varItem); if (wasEmpty) { startHandling(); } } void VariablesHandler::handleNext() { if (m_queue.empty()) return; m_queue.pop_front(); startHandling(); } void VariablesHandler::startHandling() { if (m_queue.empty()) return; m_currentVarItem = m_queue.front(); WatchItem *watchItem = m_dapEngine->watchHandler()->findItem(m_currentVarItem.iname); int variablesReference = m_currentVarItem.variablesReference; if (variablesReference == -1 && watchItem && watchItem->iname.startsWith("watch.") && watchItem->iname.split('.').size() == 2) { watchItem->removeChildren(); m_dapEngine->dapClient()->evaluateVariable(watchItem->name, m_dapEngine->currentStackFrameId()); return; } if (variablesReference == -1) { if (watchItem) { variablesReference = watchItem->variablesReference; } else { handleNext(); return; } } if (variablesReference == 0) { handleNext(); return; } m_dapEngine->dapClient()->variables(variablesReference); } DapEngine::DapEngine() : m_variablesHandler(std::make_unique(this)) {} void DapEngine::executeDebuggerCommand(const QString &/*command*/) { QTC_ASSERT(state() == InferiorStopOk, qCDebug(logCategory()) << state()); // if (state() == DebuggerNotReady) { // showMessage("DAP PROCESS NOT RUNNING, PLAIN CMD IGNORED: " + command); // return; // } // QTC_ASSERT(m_proc.isRunning(), notifyEngineIll()); // postDirectCommand(command); } void DapEngine::runCommand(const DebuggerCommand &cmd) { if (state() == EngineSetupRequested) { // cmd has been triggered too early showMessage("IGNORED COMMAND: " + cmd.function); return; } QTC_ASSERT(m_dapClient->dataProvider()->isRunning(), notifyEngineIll()); // postDirectCommand(cmd.args.toObject()); // const QByteArray data = QJsonDocument(cmd.args.toObject()).toJson(QJsonDocument::Compact); // m_proc.writeRaw("Content-Length: " + QByteArray::number(data.size()) + "\r\n" + data + "\r\n"); // showMessage(QString::fromUtf8(data), LogInput); } void DapEngine::shutdownInferior() { QTC_ASSERT(state() == InferiorShutdownRequested, qCDebug(logCategory()) << state()); m_dapClient->sendDisconnect(); qCDebug(logCategory()) << "DapEngine::shutdownInferior()"; notifyInferiorShutdownFinished(); } void DapEngine::shutdownEngine() { QTC_ASSERT(state() == EngineShutdownRequested, qCDebug(logCategory()) << state()); m_dapClient->sendTerminate(); qCDebug(logCategory()) << "DapEngine::shutdownEngine()"; m_dapClient->dataProvider()->kill(); } void DapEngine::handleDapStarted() { notifyEngineSetupOk(); QTC_ASSERT(state() == EngineRunRequested, qCDebug(logCategory()) << state()); m_dapClient->sendInitialize(); qCDebug(logCategory()) << "handleDapStarted"; } void DapEngine::handleDapInitialize() { QTC_ASSERT(state() == EngineRunRequested, qCDebug(logCategory()) << state()); m_dapClient->sendLaunch(runParameters().inferior().command); qCDebug(logCategory()) << "handleDapLaunch"; } void DapEngine::handleDapEventInitialized() { QTC_ASSERT(state() == EngineRunRequested, qCDebug(logCategory()) << state()); m_dapClient->sendConfigurationDone(); qCDebug(logCategory()) << "handleDapConfigurationDone"; } void DapEngine::handleDapConfigurationDone() { notifyEngineRunAndInferiorRunOk(); } void DapEngine::interruptInferior() { m_dapClient->sendPause(); } void DapEngine::executeStepIn(bool) { if (m_currentThreadId == -1) return; notifyInferiorRunRequested(); m_dapClient->sendStepIn(m_currentThreadId); } void DapEngine::executeStepOut() { if (m_currentThreadId == -1) return; notifyInferiorRunRequested(); m_dapClient->sendStepOut(m_currentThreadId); } void DapEngine::executeStepOver(bool) { if (m_currentThreadId == -1) return; notifyInferiorRunRequested(); m_dapClient->sendStepOver(m_currentThreadId); } void DapEngine::continueInferior() { notifyInferiorRunRequested(); m_dapClient->sendContinue(m_currentThreadId); } void DapEngine::executeRunToLine(const ContextData &data) { // Add one-shot breakpoint BreakpointParameters bp; bp.oneShot = true; if (data.address) { bp.type = BreakpointByAddress; bp.address = data.address; } else { bp.type = BreakpointByFileAndLine; bp.fileName = data.fileName; bp.textPosition = data.textPosition; } BreakpointManager::createBreakpointForEngine(bp, this); } void DapEngine::executeRunToFunction(const QString &functionName) { Q_UNUSED(functionName) QTC_CHECK("FIXME: DapEngine::runToFunctionExec()" && false); } void DapEngine::executeJumpToLine(const ContextData &data) { Q_UNUSED(data) QTC_CHECK("FIXME: DapEngine::jumpToLineExec()" && false); } void DapEngine::activateFrame(int frameIndex) { if (state() != InferiorStopOk && state() != InferiorUnrunnable) return; StackHandler *handler = stackHandler(); QTC_ASSERT(frameIndex < handler->stackSize(), return); handler->setCurrentIndex(frameIndex); gotoLocation(handler->currentFrame()); m_currentStackFrameId = handler->currentFrame().debuggerId; m_dapClient->scopes(m_currentStackFrameId); } void DapEngine::selectThread(const Thread &thread) { Q_UNUSED(thread) m_currentThreadId = thread->id().toInt(); threadsHandler()->setCurrentThread(thread); updateLocals(); } bool DapEngine::acceptsBreakpoint(const BreakpointParameters &) const { return true; } static void setBreakpointParameters(QJsonObject &bp, const QString &condition, int ignoreCount) { if (!condition.isEmpty()) bp["condition"] = condition; if (ignoreCount > 0) bp["hitCondition"] = QString::number(ignoreCount); } static QJsonObject createBreakpoint(const BreakpointParameters ¶ms) { if (params.fileName.isEmpty()) return QJsonObject(); QJsonObject bp; bp["line"] = params.textPosition.line; setBreakpointParameters(bp, params.condition, params.ignoreCount); return bp; } static QJsonObject createFunctionBreakpoint(const BreakpointParameters ¶ms) { if (params.functionName.isEmpty()) return QJsonObject(); QJsonObject bp; bp["name"] = params.functionName; setBreakpointParameters(bp, params.condition, params.ignoreCount); return bp; } void DapEngine::insertBreakpoint(const Breakpoint &bp) { QTC_ASSERT(bp, return); QTC_CHECK(bp->state() == BreakpointInsertionRequested); notifyBreakpointInsertProceeding(bp); BreakpointParameters parameters = bp->requestedParameters(); if (!parameters.enabled) { // hack for disabling breakpoints parameters.pending = false; bp->setParameters(parameters); notifyBreakpointInsertOk(bp); return; } if (parameters.type == BreakpointByFunction && m_dapClient->capabilities().supportsFunctionBreakpoints) { qDebug() << "BreakpointByFunction" << parameters.type << bp->functionName(); dapInsertFunctionBreakpoint(bp); return; } dapInsertBreakpoint(bp); } void DapEngine::dapInsertFunctionBreakpoint(const Breakpoint &bp) { QJsonArray breakpoints; for (const auto &breakpoint : breakHandler()->breakpoints()) { const BreakpointParameters &bpParams = breakpoint->requestedParameters(); QJsonObject jsonBp = createFunctionBreakpoint(bpParams); if (!jsonBp.isEmpty() && bpParams.type == BreakpointByFunction && bpParams.enabled) { breakpoints.append(jsonBp); } } m_dapClient->setFunctionBreakpoints(breakpoints); qCDebug(logCategory()) << "insertBreakpoint" << bp->modelId() << bp->responseId(); } void DapEngine::dapInsertBreakpoint(const Breakpoint &bp) { const BreakpointParameters ¶ms = bp->requestedParameters(); QJsonArray breakpoints; for (const auto &breakpoint : breakHandler()->breakpoints()) { const BreakpointParameters &bpParams = breakpoint->requestedParameters(); QJsonObject jsonBp = createBreakpoint(bpParams); if (!jsonBp.isEmpty() && params.fileName.path() == bpParams.fileName.path() && bpParams.enabled) { breakpoints.append(jsonBp); } } m_dapClient->setBreakpoints(breakpoints, params.fileName); qCDebug(logCategory()) << "insertBreakpoint" << bp->modelId() << bp->responseId(); } void DapEngine::updateBreakpoint(const Breakpoint &bp) { BreakpointParameters parameters = bp->requestedParameters(); notifyBreakpointChangeProceeding(bp); auto updateBp = [this, bp](void (DapEngine::*insert)(const Breakpoint &), void (DapEngine::*remove)(const Breakpoint &)) { if (bp->isEnabled()) (this->*remove)(bp); else (this->*insert)(bp); }; if (parameters.enabled != bp->isEnabled()) { if (parameters.type == BreakpointByFunction) { updateBp(&DapEngine::dapInsertFunctionBreakpoint, &DapEngine ::dapRemoveFunctionBreakpoint); return; } updateBp(&DapEngine::dapInsertBreakpoint, &DapEngine::dapRemoveBreakpoint); } } void DapEngine::removeBreakpoint(const Breakpoint &bp) { QTC_ASSERT(bp, return); QTC_CHECK(bp->state() == BreakpointRemoveRequested); notifyBreakpointRemoveProceeding(bp); dapRemoveBreakpoint(bp); } void DapEngine::dapRemoveBreakpoint(const Breakpoint &bp) { const BreakpointParameters ¶ms = bp->requestedParameters(); QJsonArray breakpoints; for (const auto &breakpoint : breakHandler()->breakpoints()) { const BreakpointParameters &bpParams = breakpoint->requestedParameters(); if (breakpoint->responseId() != bp->responseId() && params.fileName == bpParams.fileName && bpParams.enabled) { QJsonObject jsonBp = createBreakpoint(bpParams); breakpoints.append(jsonBp); } } if (params.type == BreakpointByFunction && m_dapClient->capabilities().supportsFunctionBreakpoints) { dapRemoveFunctionBreakpoint(bp); return; } m_dapClient->setBreakpoints(breakpoints, params.fileName); qCDebug(logCategory()) << "removeBreakpoint" << bp->modelId() << bp->responseId(); } void DapEngine::dapRemoveFunctionBreakpoint(const Breakpoint &bp) { QJsonArray breakpoints; for (const auto &breakpoint : breakHandler()->breakpoints()) { const BreakpointParameters &bpParams = breakpoint->requestedParameters(); if (breakpoint->responseId() != bp->responseId() && bpParams.type == BreakpointByFunction && bpParams.enabled) { QJsonObject jsonBp = createFunctionBreakpoint(bpParams); breakpoints.append(jsonBp); } } m_dapClient->setFunctionBreakpoints(breakpoints); qCDebug(logCategory()) << "removeBreakpoint" << bp->modelId() << bp->responseId(); } void DapEngine::loadSymbols(const Utils::FilePath &/*moduleName*/) { } void DapEngine::loadAllSymbols() { } void DapEngine::reloadModules() { runCommand({"listModules"}); } void DapEngine::refreshModules(const GdbMi &modules) { ModulesHandler *handler = modulesHandler(); handler->beginUpdateAll(); for (const GdbMi &item : modules) { Module module; module.moduleName = item["name"].data(); QString path = item["value"].data(); int pos = path.indexOf("' from '"); if (pos != -1) { path = path.mid(pos + 8); if (path.size() >= 2) path.chop(2); } else if (path.startsWith("")) { path = "(builtin)"; } module.modulePath = FilePath::fromString(path); handler->updateModule(module); } handler->endUpdateAll(); } void DapEngine::refreshState(const GdbMi &reportedState) { QString newState = reportedState.data(); if (newState == "stopped") { notifyInferiorSpontaneousStop(); updateAll(); } else if (newState == "inferiorexited") { notifyInferiorExited(); } } void DapEngine::refreshLocation(const GdbMi &reportedLocation) { StackFrame frame; frame.file = FilePath::fromString(reportedLocation["file"].data()); frame.line = reportedLocation["line"].toInt(); frame.usable = frame.file.isReadableFile(); if (state() == InferiorRunOk) { showMessage(QString("STOPPED AT: %1:%2").arg(frame.file.toUserOutput()).arg(frame.line)); gotoLocation(frame); notifyInferiorSpontaneousStop(); updateAll(); } } void DapEngine::refreshSymbols(const GdbMi &symbols) { QString moduleName = symbols["module"].data(); Symbols syms; for (const GdbMi &item : symbols["symbols"]) { Symbol symbol; symbol.name = item["name"].data(); syms.append(symbol); } showModuleSymbols(FilePath::fromString(moduleName), syms); } bool DapEngine::canHandleToolTip(const DebuggerToolTipContext &) const { return state() == InferiorStopOk; } void DapEngine::updateItem(const QString &iname) { WatchItem *item = watchHandler()->findItem(iname); if (item && m_variablesHandler->currentItem().iname != item->iname) m_variablesHandler->addVariable(item->iname, item->variablesReference); } void DapEngine::reexpandItems(const QSet &inames) { QSet expandedInames = inames; const auto &watcherNames = watchHandler()->watcherNames(); for (auto it = watcherNames.begin(); it != watcherNames.end(); ++it) expandedInames.insert(watchHandler()->watcherName(it.key())); QList inamesVector = expandedInames.values(); inamesVector.sort(); for (const QString &iname : std::as_const(inamesVector)) { if (iname.startsWith("local.") || iname.startsWith("watch.")) m_variablesHandler->addVariable(iname, -1); } } void DapEngine::doUpdateLocals(const UpdateParameters ¶ms) { m_variablesHandler->addVariable(params.partialVariable, -1); } QString DapEngine::errorMessage(QProcess::ProcessError error) const { switch (error) { case QProcess::FailedToStart: return Tr::tr("The DAP process failed to start. Either the " "invoked program \"%1\" is missing, or you may have insufficient " "permissions to invoke the program.") .arg(m_dapClient->dataProvider()->executable()); case QProcess::Crashed: return Tr::tr("The DAP process crashed some time after starting " "successfully."); case QProcess::Timedout: return Tr::tr("The last waitFor...() function timed out. " "The state of QProcess is unchanged, and you can try calling " "waitFor...() again."); case QProcess::WriteError: return Tr::tr("An error occurred when attempting to write " "to the DAP process. For example, the process may not be running, " "or it may have closed its input channel."); case QProcess::ReadError: return Tr::tr("An error occurred when attempting to read from " "the DAP process. For example, the process may not be running."); default: return Tr::tr("An unknown error in the DAP process occurred.") + ' '; } } void DapEngine::handleDapDone() { if (state() == DebuggerFinished) return; if (m_dapClient->dataProvider()->result() == ProcessResult::StartFailed) { notifyEngineSetupFailed(); showMessage("ADAPTER START FAILED"); ICore::showWarningWithOptions(Tr::tr("Adapter start failed"), m_dapClient->dataProvider()->exitMessage()); return; } const QProcess::ProcessError error = m_dapClient->dataProvider()->error(); if (error != QProcess::UnknownError) { showMessage("HANDLE DAP ERROR"); if (error != QProcess::Crashed) AsynchronousMessageBox::critical(Tr::tr("DAP I/O Error"), errorMessage(error)); if (error == QProcess::FailedToStart) return; } showMessage(QString("DAP PROCESS FINISHED, status %1, code %2") .arg(m_dapClient->dataProvider()->exitStatus()).arg(m_dapClient->dataProvider()->exitCode())); notifyEngineSpontaneousShutdown(); } void DapEngine::readDapStandardError() { QString err = m_dapClient->dataProvider()->readAllStandardError(); qCDebug(logCategory()) << "DAP STDERR:" << err; //qWarning() << "Unexpected DAP stderr:" << err; showMessage("Unexpected DAP stderr: " + err); //handleOutput(err); } void DapEngine::handleResponse(DapResponseType type, const QJsonObject &response) { const QString command = response.value("command").toString(); const bool success = response.value("success").toBool(); switch (type) { case DapResponseType::Initialize: qCDebug(logCategory()) << "initialize success"; handleDapInitialize(); break; case DapResponseType::ConfigurationDone: showMessage("configurationDone", LogDebug); qCDebug(logCategory()) << "configurationDone success"; handleDapConfigurationDone(); break; case DapResponseType::Continue: showMessage("continue", LogDebug); qCDebug(logCategory()) << "continue success"; notifyInferiorRunOk(); break; case DapResponseType::StackTrace: handleStackTraceResponse(response); break; case DapResponseType::Scopes: handleScopesResponse(response); break; case DapResponseType::Variables: { auto variables = response.value("body").toObject().value("variables").toArray(); refreshLocals(variables); break; } case DapResponseType::StepIn: case DapResponseType::StepOut: case DapResponseType::StepOver: if (success) { showMessage(command, LogDebug); notifyInferiorRunOk(); } else { notifyInferiorRunFailed(); } break; case DapResponseType::DapThreads: handleThreadsResponse(response); break; case DapResponseType::Evaluate: handleEvaluateResponse(response); break; case DapResponseType::SetFunctionBreakpoints: case DapResponseType::SetBreakpoints: handleBreakpointResponse(response); break; case DapResponseType::Launch: if (!success) { notifyEngineRunFailed(); AsynchronousMessageBox::critical( Tr::tr("Failed to Start Application"), Tr::tr("\"%1\" could not be started. Error message: %2") .arg(runParameters().inferior().command.toUserOutput()) .arg(response.value("message").toString())); } break; default: showMessage("UNKNOWN RESPONSE:" + command); }; if (!success) { showMessage(QString("DAP COMMAND FAILED: %1").arg(command)); qCDebug(logCategory()) << "DAP COMMAND FAILED:" << command; return; } } void DapEngine::handleStackTraceResponse(const QJsonObject &response) { QJsonArray stackFrames = response.value("body").toObject().value("stackFrames").toArray(); if (stackFrames.isEmpty()) return; QJsonObject stackFrame = stackFrames[0].toObject(); const FilePath file = FilePath::fromString( stackFrame.value("source").toObject().value("path").toString()); const int line = stackFrame.value("line").toInt(); qCDebug(logCategory()) << "stackTrace success" << file << line; gotoLocation(Location(file, line)); refreshStack(stackFrames); m_currentStackFrameId = stackFrame.value("id").toInt(); m_dapClient->scopes(m_currentStackFrameId); } void DapEngine::handleScopesResponse(const QJsonObject &response) { if (!response.value("success").toBool()) return; watchHandler()->resetValueCache(); watchHandler()->notifyUpdateStarted(); const QJsonArray scopes = response.value("body").toObject().value("scopes").toArray(); for (const QJsonValueConstRef &scope : scopes) { const QString name = scope.toObject().value("name").toString(); if (name == "Registers") continue; m_variablesHandler->addVariable("", scope.toObject().value("variablesReference").toInt()); } if (m_variablesHandler->queueSize() == 0) { watchHandler()->notifyUpdateFinished(); } } void DapEngine::handleThreadsResponse(const QJsonObject &response) { const QJsonArray threads = response.value("body").toObject().value("threads").toArray(); if (threads.isEmpty()) return; ThreadsHandler *handler = threadsHandler(); for (const QJsonValueConstRef &thread : threads) { ThreadData threadData; threadData.id = QString::number(thread.toObject().value("id").toInt()); threadData.name = thread.toObject().value("name").toString(); handler->updateThread(threadData); } if (m_currentThreadId) { Thread thread = threadsHandler()->threadForId(QString::number(m_currentThreadId)); if (thread && thread != threadsHandler()->currentThread()) handler->setCurrentThread(thread); } } void DapEngine::handleEvaluateResponse(const QJsonObject &response) { WatchItem *watchItem = watchHandler()->findItem( m_variablesHandler->currentItem().iname); if (watchItem && response.value("body").toObject().contains("variablesReference")) { watchItem->variablesReference = response.value("body").toObject().value("variablesReference").toInt(); watchItem->value = response.value("body").toObject().value("result").toString(); watchItem->type = response.value("body").toObject().value("type").toString(); watchItem->wantsChildren = watchItem->variablesReference > 0; watchItem->updateValueCache(); watchItem->update(); m_variablesHandler->addVariable(watchItem->iname, watchItem->variablesReference); } m_variablesHandler->handleNext(); } void DapEngine::handleBreakpointResponse(const QJsonObject &response) { const QJsonObject body = response.value("body").toObject(); const QJsonArray breakpoints = body.value("breakpoints").toArray(); QHash map; for (const QJsonValueConstRef &jsonbp : breakpoints) { QJsonObject breakpoint = jsonbp.toObject(); QString fileName = breakpoint.value("source").toObject().value("path").toString(); int line = breakpoint.value("line").toInt(); map.insert(fileName + ":" + QString::number(line), breakpoint); } const Breakpoints bps = breakHandler()->breakpoints(); for (const Breakpoint &bp : bps) { BreakpointParameters parameters = bp->requestedParameters(); QString mapKey = parameters.fileName.toUrlishString() + ":" + QString::number(parameters.textPosition.line); if (map.find(mapKey) != map.end()) { if (bp->state() == BreakpointRemoveProceeding) { notifyBreakpointRemoveFailed(bp); } else if (bp->state() == BreakpointInsertionProceeding && !map.value(mapKey).value("verified").toBool()) { notifyBreakpointInsertFailed(bp); } else if (bp->state() == BreakpointInsertionProceeding) { parameters.pending = false; bp->setParameters(parameters); notifyBreakpointInsertOk(bp); if (parameters.oneShot) continueInferior(); } if (!bp.isNull()) bp->setResponseId(QString::number(map.value(mapKey).value("id").toInt())); map.remove(mapKey); } else { if (bp->state() == BreakpointRemoveProceeding) { notifyBreakpointRemoveOk(bp); } } if (!bp.isNull() && bp->state() == BreakpointUpdateProceeding) { BreakpointParameters parameters = bp->requestedParameters(); if (parameters.enabled != bp->isEnabled()) { parameters.pending = false; bp->setParameters(parameters); notifyBreakpointChangeOk(bp); continue; } } } for (const Breakpoint &bp : breakHandler()->breakpoints()) { if (bp->state() == BreakpointInsertionProceeding) { if (!bp->isEnabled()) continue; FilePath path = bp->requestedParameters().fileName; int line = bp->requestedParameters().textPosition.line; QJsonObject jsonBreakpoint; QString key; for (auto it = map.cbegin(); it != map.cend(); ++it) { const QJsonObject breakpoint = *it; if (path == bp->requestedParameters().fileName && abs(breakpoint.value("line").toInt() - line) < abs(jsonBreakpoint.value("line").toInt() - line)) { jsonBreakpoint = breakpoint; key = it.key(); } } if (!jsonBreakpoint.isEmpty() && jsonBreakpoint.value("verified").toBool()) { BreakpointParameters parameters = bp->requestedParameters(); parameters.pending = false; parameters.textPosition.line = jsonBreakpoint.value("line").toInt(); parameters.textPosition.column = jsonBreakpoint.value("column").toInt(); bp->setParameters(parameters); bp->setResponseId(QString::number(jsonBreakpoint.value("id").toInt())); notifyBreakpointInsertOk(bp); if (parameters.oneShot) continueInferior(); map.remove(key); } } } } void DapEngine::handleEvent(DapEventType type, const QJsonObject &event) { const QString eventType = event.value("event").toString(); const QJsonObject body = event.value("body").toObject(); showMessage(eventType, LogDebug); switch (type) { case DapEventType::Initialized: qCDebug(logCategory()) << "initialize success"; claimInitialBreakpoints(); handleDapEventInitialized(); break; case DapEventType::Stopped: handleStoppedEvent(event); break; case DapEventType::Exited: notifyInferiorExited(); break; case DapEventType::DapThread: m_dapClient->threads(); if (body.value("reason").toString() == "started" && body.value("threadId").toInt() == 1) claimInitialBreakpoints(); break; case DapEventType::Output: { const QString category = body.value("category").toString(); const QString output = body.value("output").toString(); if (category == "stdout") showMessage(output, AppOutput); else if (category == "stderr") showMessage(output, AppError); else showMessage(output, LogDebug); break; } default: showMessage("UNKNOWN EVENT:" + eventType); }; } void DapEngine::handleStoppedEvent(const QJsonObject &event) { const QJsonObject body = event.value("body").toObject(); m_currentThreadId = body.value("threadId").toInt(); if (body.value("reason").toString() == "breakpoint") { QString id = QString::number(body.value("hitBreakpointIds").toArray().first().toInteger()); Breakpoint bp = breakHandler()->findBreakpointByResponseId(id); if (bp) { const BreakpointParameters ¶ms = bp->requestedParameters(); gotoLocation(Location(params.fileName, params.textPosition)); if (params.oneShot) bp->globalBreakpoint()->deleteBreakpoint(); } } if (state() == InferiorStopRequested) notifyInferiorStopOk(); else notifyInferiorSpontaneousStop(); m_dapClient->stackTrace(m_currentThreadId); m_dapClient->threads(); } void DapEngine::refreshLocals(const QJsonArray &variables) { WatchItem *currentItem = watchHandler()->findItem(m_variablesHandler->currentItem().iname); if (currentItem && currentItem->iname.startsWith("watch")) currentItem->removeChildren(); for (const auto &variable : variables) { WatchItem *item = new WatchItem; const QString name = variable.toObject().value("name").toString(); item->iname = (currentItem ? currentItem->iname : "local") + "." + name; item->name = name; item->type = variable.toObject().value("type").toString(); item->value = variable.toObject().value("value").toString(); item->address = variable.toObject().value("address").toInt(); item->type = variable.toObject().value("type").toString(); item->variablesReference = variable.toObject().value("variablesReference").toInt(); item->wantsChildren = item->variablesReference > 0; qCDebug(logCategory()) << "variable" << item->iname << item->variablesReference; if (currentItem) currentItem->appendChild(item); else watchHandler()->insertItem(item); } if (currentItem) { QModelIndex idx = watchHandler()->model()->indexForItem(currentItem); if (idx.isValid() && idx.data(LocalsExpandedRole).toBool()) { emit watchHandler()->model()->inameIsExpanded(currentItem->iname); emit watchHandler()->model()->itemIsExpanded(idx); } } if (m_variablesHandler->queueSize() == 1 && currentItem == nullptr) { watchHandler()->notifyUpdateFinished(); } m_variablesHandler->handleNext(); } void DapEngine::refreshStack(const QJsonArray &stackFrames) { StackHandler *handler = stackHandler(); StackFrames frames; for (const auto &value : stackFrames) { StackFrame frame; QJsonObject item = value.toObject(); frame.level = item.value("id").toString(); frame.function = item.value("name").toString(); frame.line = item.value("line").toInt(); QJsonObject source = item.value("source").toObject(); frame.file = FilePath::fromString(source.value("path").toString()); frame.address = item.value("instructionPointerReference").toInt(); frame.usable = frame.file.isReadableFile(); frame.debuggerId = item.value("id").toInt(); frames.append(frame); } handler->setFrames(frames, false); int index = stackHandler()->firstUsableIndex(); handler->setCurrentIndex(index); if (index >= 0 && index < handler->stackSize()) gotoLocation(handler->frameAt(index)); } void DapEngine::reloadFullStack() { updateAll(); } void DapEngine::updateAll() { runCommand({"stackListFrames"}); updateLocals(); } void DapEngine::updateLocals() { m_dapClient->stackTrace(m_currentThreadId); } bool DapEngine::hasCapability(unsigned cap) const { return cap & (ReloadModuleCapability | BreakConditionCapability | ShowModuleSymbolsCapability | RunToLineCapability | AddWatcherCapability); } void DapEngine::claimInitialBreakpoints() { BreakpointManager::claimBreakpointsForEngine(this); qCDebug(logCategory()) << "claimInitialBreakpoints"; } void DapEngine::connectDataGeneratorSignals() { if (!m_dapClient) return; connect(m_dapClient, &DapClient::started, this, &DapEngine::handleDapStarted); connect(m_dapClient, &DapClient::done, this, &DapEngine::handleDapDone); connect(m_dapClient, &DapClient::readyReadStandardError, this, &DapEngine::readDapStandardError); connect(m_dapClient, &DapClient::responseReady, this, &DapEngine::handleResponse); connect(m_dapClient, &DapClient::eventReady, this, &DapEngine::handleEvent); } DebuggerEngine *createDapEngine(Utils::Id runMode) { if (runMode == ProjectExplorer::Constants::DAP_CMAKE_DEBUG_RUN_MODE) return new CMakeDapEngine; if (runMode == ProjectExplorer::Constants::DAP_GDB_DEBUG_RUN_MODE) return new GdbDapEngine; if (runMode == ProjectExplorer::Constants::DAP_LLDB_DEBUG_RUN_MODE) return new LldbDapEngine; if (runMode == ProjectExplorer::Constants::DAP_PY_DEBUG_RUN_MODE) return new PyDapEngine; return nullptr; } } // Debugger::Internal