1592 lines · cpp
1//===-- DAP.cpp -------------------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "DAP.h"10#include "CommandPlugins.h"11#include "DAPLog.h"12#include "EventHelper.h"13#include "ExceptionBreakpoint.h"14#include "Handler/RequestHandler.h"15#include "Handler/ResponseHandler.h"16#include "JSONUtils.h"17#include "LLDBUtils.h"18#include "OutputRedirector.h"19#include "Protocol/ProtocolBase.h"20#include "Protocol/ProtocolEvents.h"21#include "Protocol/ProtocolRequests.h"22#include "Protocol/ProtocolTypes.h"23#include "ProtocolUtils.h"24#include "Transport.h"25#include "lldb/API/SBBreakpoint.h"26#include "lldb/API/SBCommandInterpreter.h"27#include "lldb/API/SBEvent.h"28#include "lldb/API/SBLanguageRuntime.h"29#include "lldb/API/SBListener.h"30#include "lldb/API/SBMutex.h"31#include "lldb/API/SBProcess.h"32#include "lldb/API/SBStream.h"33#include "lldb/Host/JSONTransport.h"34#include "lldb/Host/MainLoop.h"35#include "lldb/Host/MainLoopBase.h"36#include "lldb/Utility/Status.h"37#include "lldb/lldb-defines.h"38#include "lldb/lldb-enumerations.h"39#include "lldb/lldb-types.h"40#include "llvm/ADT/ArrayRef.h"41#include "llvm/ADT/STLExtras.h"42#include "llvm/ADT/ScopeExit.h"43#include "llvm/ADT/StringExtras.h"44#include "llvm/ADT/StringRef.h"45#include "llvm/ADT/Twine.h"46#include "llvm/Support/Chrono.h"47#include "llvm/Support/Error.h"48#include "llvm/Support/ErrorHandling.h"49#include "llvm/Support/FormatVariadic.h"50#include "llvm/Support/raw_ostream.h"51#include <algorithm>52#include <cassert>53#include <chrono>54#include <condition_variable>55#include <cstdarg>56#include <cstdint>57#include <cstdio>58#include <functional>59#include <future>60#include <memory>61#include <mutex>62#include <optional>63#include <string>64#include <thread>65#include <utility>66#include <variant>67 68#if defined(_WIN32)69#define NOMINMAX70#include <fcntl.h>71#include <io.h>72#include <windows.h>73#else74#include <unistd.h>75#endif76 77using namespace lldb_dap;78using namespace lldb_dap::protocol;79using namespace lldb_private;80 81namespace {82#ifdef _WIN3283const char DEV_NULL[] = "nul";84#else85const char DEV_NULL[] = "/dev/null";86#endif87} // namespace88 89namespace lldb_dap {90 91static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,92 const char *key) {93 lldb::SBStructuredData keyValue = data.GetValueForKey(key);94 if (!keyValue)95 return std::string();96 97 const size_t length = keyValue.GetStringValue(nullptr, 0);98 99 if (length == 0)100 return std::string();101 102 std::string str(length + 1, 0);103 keyValue.GetStringValue(&str[0], length + 1);104 return str;105}106 107static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data,108 const char *key) {109 lldb::SBStructuredData keyValue = data.GetValueForKey(key);110 111 if (!keyValue.IsValid())112 return 0;113 return keyValue.GetUnsignedIntegerValue();114}115 116/// Return string with first character capitalized.117static std::string capitalize(llvm::StringRef str) {118 if (str.empty())119 return "";120 return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str();121}122 123llvm::StringRef DAP::debug_adapter_path = "";124 125DAP::DAP(Log *log, const ReplMode default_repl_mode,126 std::vector<std::string> pre_init_commands, bool no_lldbinit,127 llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop)128 : log(log), transport(transport), broadcaster("lldb-dap"),129 progress_event_reporter(130 [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),131 repl_mode(default_repl_mode), no_lldbinit(no_lldbinit),132 m_client_name(client_name), m_loop(loop) {133 configuration.preInitCommands = std::move(pre_init_commands);134 RegisterRequests();135}136 137DAP::~DAP() = default;138 139void DAP::PopulateExceptionBreakpoints() {140 if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {141 exception_breakpoints.emplace_back(*this, "cpp_catch", "C++ Catch",142 lldb::eLanguageTypeC_plus_plus,143 eExceptionKindCatch);144 exception_breakpoints.emplace_back(*this, "cpp_throw", "C++ Throw",145 lldb::eLanguageTypeC_plus_plus,146 eExceptionKindThrow);147 }148 149 if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {150 exception_breakpoints.emplace_back(*this, "objc_catch", "Objective-C Catch",151 lldb::eLanguageTypeObjC,152 eExceptionKindCatch);153 exception_breakpoints.emplace_back(*this, "objc_throw", "Objective-C Throw",154 lldb::eLanguageTypeObjC,155 eExceptionKindThrow);156 }157 158 if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {159 exception_breakpoints.emplace_back(*this, "swift_catch", "Swift Catch",160 lldb::eLanguageTypeSwift,161 eExceptionKindCatch);162 exception_breakpoints.emplace_back(*this, "swift_throw", "Swift Throw",163 lldb::eLanguageTypeSwift,164 eExceptionKindThrow);165 }166 167 // Besides handling the hardcoded list of languages from above, we try to find168 // any other languages that support exception breakpoints using the SB API.169 for (int raw_lang = lldb::eLanguageTypeUnknown;170 raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {171 lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);172 173 // We first discard any languages already handled above.174 if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||175 lang == lldb::eLanguageTypeSwift)176 continue;177 178 if (!lldb::SBDebugger::SupportsLanguage(lang))179 continue;180 181 const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);182 if (!name)183 continue;184 std::string raw_lang_name = name;185 std::string capitalized_lang_name = capitalize(name);186 187 if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {188 const char *raw_throw_keyword =189 lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);190 std::string throw_keyword =191 raw_throw_keyword ? raw_throw_keyword : "throw";192 193 exception_breakpoints.emplace_back(194 *this, raw_lang_name + "_" + throw_keyword,195 capitalized_lang_name + " " + capitalize(throw_keyword), lang,196 eExceptionKindThrow);197 }198 199 if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {200 const char *raw_catch_keyword =201 lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);202 std::string catch_keyword =203 raw_catch_keyword ? raw_catch_keyword : "catch";204 205 exception_breakpoints.emplace_back(206 *this, raw_lang_name + "_" + catch_keyword,207 capitalized_lang_name + " " + capitalize(catch_keyword), lang,208 eExceptionKindCatch);209 }210 }211}212 213ExceptionBreakpoint *DAP::GetExceptionBreakpoint(llvm::StringRef filter) {214 for (auto &bp : exception_breakpoints) {215 if (bp.GetFilter() == filter)216 return &bp;217 }218 return nullptr;219}220 221ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {222 for (auto &bp : exception_breakpoints) {223 if (bp.GetID() == bp_id)224 return &bp;225 }226 return nullptr;227}228 229llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) {230 in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true);231 232 if (auto Error = out.RedirectTo(overrideOut, [this](llvm::StringRef output) {233 SendOutput(OutputType::Console, output);234 }))235 return Error;236 237 if (auto Error = err.RedirectTo(overrideErr, [this](llvm::StringRef output) {238 SendOutput(OutputType::Console, output);239 }))240 return Error;241 242 return llvm::Error::success();243}244 245void DAP::StopEventHandlers() {246 event_thread_sp.reset();247 248 // Clean up expired event threads from the session manager.249 DAPSessionManager::GetInstance().ReleaseExpiredEventThreads();250 251 // Still handle the progress thread normally since it's per-DAP instance.252 if (progress_event_thread.joinable()) {253 broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);254 progress_event_thread.join();255 }256}257 258// Serialize the JSON value into a string and send the JSON packet to259// the "out" stream.260void DAP::SendJSON(const llvm::json::Value &json) {261 // FIXME: Instead of parsing the output message from JSON, pass the `Message`262 // as parameter to `SendJSON`.263 Message message;264 llvm::json::Path::Root root;265 if (!fromJSON(json, message, root)) {266 DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}",267 m_client_name);268 return;269 }270 Send(message);271}272 273Id DAP::Send(const Message &message) {274 std::lock_guard<std::mutex> guard(call_mutex);275 Message msg = std::visit(276 [this](auto &&msg) -> Message {277 if (msg.seq == kCalculateSeq)278 msg.seq = seq++;279 return msg;280 },281 Message(message));282 283 if (const protocol::Event *event = std::get_if<protocol::Event>(&msg)) {284 if (llvm::Error err = transport.Send(*event))285 DAP_LOG_ERROR(log, std::move(err), "({0}) sending event failed",286 m_client_name);287 return event->seq;288 }289 290 if (const Request *req = std::get_if<Request>(&msg)) {291 if (llvm::Error err = transport.Send(*req))292 DAP_LOG_ERROR(log, std::move(err), "({0}) sending request failed",293 m_client_name);294 return req->seq;295 }296 297 if (const Response *resp = std::get_if<Response>(&msg)) {298 // FIXME: After all the requests have migrated from LegacyRequestHandler >299 // RequestHandler<> this should be handled in RequestHandler<>::operator().300 // If the debugger was interrupted, convert this response into a301 // 'cancelled' response because we might have a partial result.302 llvm::Error err = (debugger.InterruptRequested())303 ? transport.Send({304 /*request_seq=*/resp->request_seq,305 /*command=*/resp->command,306 /*success=*/false,307 /*message=*/eResponseMessageCancelled,308 /*body=*/std::nullopt,309 /*seq=*/resp->seq,310 })311 : transport.Send(*resp);312 if (err)313 DAP_LOG_ERROR(log, std::move(err), "({0}) sending response failed",314 m_client_name);315 return resp->seq;316 }317 318 llvm_unreachable("Unexpected message type");319}320 321// "OutputEvent": {322// "allOf": [ { "$ref": "#/definitions/Event" }, {323// "type": "object",324// "description": "Event message for 'output' event type. The event325// indicates that the target has produced some output.",326// "properties": {327// "event": {328// "type": "string",329// "enum": [ "output" ]330// },331// "body": {332// "type": "object",333// "properties": {334// "category": {335// "type": "string",336// "description": "The output category. If not specified,337// 'console' is assumed.",338// "_enum": [ "console", "stdout", "stderr", "telemetry" ]339// },340// "output": {341// "type": "string",342// "description": "The output to report."343// },344// "variablesReference": {345// "type": "number",346// "description": "If an attribute 'variablesReference' exists347// and its value is > 0, the output contains348// objects which can be retrieved by passing349// variablesReference to the VariablesRequest."350// },351// "source": {352// "$ref": "#/definitions/Source",353// "description": "An optional source location where the output354// was produced."355// },356// "line": {357// "type": "integer",358// "description": "An optional source location line where the359// output was produced."360// },361// "column": {362// "type": "integer",363// "description": "An optional source location column where the364// output was produced."365// },366// "data": {367// "type":["array","boolean","integer","null","number","object",368// "string"],369// "description": "Optional data to report. For the 'telemetry'370// category the data will be sent to telemetry, for371// the other categories the data is shown in JSON372// format."373// }374// },375// "required": ["output"]376// }377// },378// "required": [ "event", "body" ]379// }]380// }381void DAP::SendOutput(OutputType o, const llvm::StringRef output) {382 if (output.empty())383 return;384 385 const char *category = nullptr;386 switch (o) {387 case OutputType::Console:388 category = "console";389 break;390 case OutputType::Important:391 category = "important";392 break;393 case OutputType::Stdout:394 category = "stdout";395 break;396 case OutputType::Stderr:397 category = "stderr";398 break;399 case OutputType::Telemetry:400 category = "telemetry";401 break;402 }403 404 // Send each line of output as an individual event, including the newline if405 // present.406 ::size_t idx = 0;407 do {408 ::size_t end = output.find('\n', idx);409 if (end == llvm::StringRef::npos)410 end = output.size() - 1;411 llvm::json::Object event(CreateEventObject("output"));412 llvm::json::Object body;413 body.try_emplace("category", category);414 EmplaceSafeString(body, "output", output.slice(idx, end + 1).str());415 event.try_emplace("body", std::move(body));416 SendJSON(llvm::json::Value(std::move(event)));417 idx = end + 1;418 } while (idx < output.size());419}420 421// interface ProgressStartEvent extends Event {422// event: 'progressStart';423//424// body: {425// /**426// * An ID that must be used in subsequent 'progressUpdate' and427// 'progressEnd'428// * events to make them refer to the same progress reporting.429// * IDs must be unique within a debug session.430// */431// progressId: string;432//433// /**434// * Mandatory (short) title of the progress reporting. Shown in the UI to435// * describe the long running operation.436// */437// title: string;438//439// /**440// * The request ID that this progress report is related to. If specified a441// * debug adapter is expected to emit442// * progress events for the long running request until the request has443// been444// * either completed or cancelled.445// * If the request ID is omitted, the progress report is assumed to be446// * related to some general activity of the debug adapter.447// */448// requestId?: number;449//450// /**451// * If true, the request that reports progress may be canceled with a452// * 'cancel' request.453// * So this property basically controls whether the client should use UX454// that455// * supports cancellation.456// * Clients that don't support cancellation are allowed to ignore the457// * setting.458// */459// cancellable?: boolean;460//461// /**462// * Optional, more detailed progress message.463// */464// message?: string;465//466// /**467// * Optional progress percentage to display (value range: 0 to 100). If468// * omitted no percentage will be shown.469// */470// percentage?: number;471// };472// }473//474// interface ProgressUpdateEvent extends Event {475// event: 'progressUpdate';476//477// body: {478// /**479// * The ID that was introduced in the initial 'progressStart' event.480// */481// progressId: string;482//483// /**484// * Optional, more detailed progress message. If omitted, the previous485// * message (if any) is used.486// */487// message?: string;488//489// /**490// * Optional progress percentage to display (value range: 0 to 100). If491// * omitted no percentage will be shown.492// */493// percentage?: number;494// };495// }496//497// interface ProgressEndEvent extends Event {498// event: 'progressEnd';499//500// body: {501// /**502// * The ID that was introduced in the initial 'ProgressStartEvent'.503// */504// progressId: string;505//506// /**507// * Optional, more detailed progress message. If omitted, the previous508// * message (if any) is used.509// */510// message?: string;511// };512// }513 514void DAP::SendProgressEvent(uint64_t progress_id, const char *message,515 uint64_t completed, uint64_t total) {516 progress_event_reporter.Push(progress_id, message, completed, total);517}518 519void __attribute__((format(printf, 3, 4)))520DAP::SendFormattedOutput(OutputType o, const char *format, ...) {521 char buffer[1024];522 va_list args;523 va_start(args, format);524 int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);525 va_end(args);526 SendOutput(527 o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));528}529 530int32_t DAP::CreateSourceReference(lldb::addr_t address) {531 std::lock_guard<std::mutex> guard(m_source_references_mutex);532 auto iter = llvm::find(m_source_references, address);533 if (iter != m_source_references.end())534 return std::distance(m_source_references.begin(), iter) + 1;535 536 m_source_references.emplace_back(address);537 return static_cast<int32_t>(m_source_references.size());538}539 540std::optional<lldb::addr_t> DAP::GetSourceReferenceAddress(int32_t reference) {541 std::lock_guard<std::mutex> guard(m_source_references_mutex);542 if (reference <= LLDB_DAP_INVALID_SRC_REF)543 return std::nullopt;544 545 if (static_cast<size_t>(reference) > m_source_references.size())546 return std::nullopt;547 548 return m_source_references[reference - 1];549}550 551ExceptionBreakpoint *DAP::GetExceptionBPFromStopReason(lldb::SBThread &thread) {552 const auto num = thread.GetStopReasonDataCount();553 // Check to see if have hit an exception breakpoint and change the554 // reason to "exception", but only do so if all breakpoints that were555 // hit are exception breakpoints.556 ExceptionBreakpoint *exc_bp = nullptr;557 for (size_t i = 0; i < num; i += 2) {558 // thread.GetStopReasonDataAtIndex(i) will return the bp ID and559 // thread.GetStopReasonDataAtIndex(i+1) will return the location560 // within that breakpoint. We only care about the bp ID so we can561 // see if this is an exception breakpoint that is getting hit.562 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);563 exc_bp = GetExceptionBreakpoint(bp_id);564 // If any breakpoint is not an exception breakpoint, then stop and565 // report this as a normal breakpoint566 if (exc_bp == nullptr)567 return nullptr;568 }569 return exc_bp;570}571 572lldb::SBThread DAP::GetLLDBThread(lldb::tid_t tid) {573 return target.GetProcess().GetThreadByID(tid);574}575 576lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) {577 auto tid = GetInteger<int64_t>(arguments, "threadId")578 .value_or(LLDB_INVALID_THREAD_ID);579 return target.GetProcess().GetThreadByID(tid);580}581 582lldb::SBFrame DAP::GetLLDBFrame(uint64_t frame_id) {583 if (frame_id == LLDB_DAP_INVALID_FRAME_ID)584 return lldb::SBFrame();585 586 lldb::SBProcess process = target.GetProcess();587 // Upper 32 bits is the thread index ID588 lldb::SBThread thread =589 process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));590 // Lower 32 bits is the frame index591 return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));592}593 594lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {595 const auto frame_id = GetInteger<uint64_t>(arguments, "frameId")596 .value_or(LLDB_DAP_INVALID_FRAME_ID);597 return GetLLDBFrame(frame_id);598}599 600ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,601 bool partial_expression) {602 // Check for the escape hatch prefix.603 if (!expression.empty() &&604 llvm::StringRef(expression)605 .starts_with(configuration.commandEscapePrefix)) {606 expression = expression.substr(configuration.commandEscapePrefix.size());607 return ReplMode::Command;608 }609 610 switch (repl_mode) {611 case ReplMode::Variable:612 return ReplMode::Variable;613 case ReplMode::Command:614 return ReplMode::Command;615 case ReplMode::Auto:616 // To determine if the expression is a command or not, check if the first617 // term is a variable or command. If it's a variable in scope we will prefer618 // that behavior and give a warning to the user if they meant to invoke the619 // operation as a command.620 //621 // Example use case:622 // int p and expression "p + 1" > variable623 // int i and expression "i" > variable624 // int var and expression "va" > command625 std::pair<llvm::StringRef, llvm::StringRef> token =626 llvm::getToken(expression);627 628 // If the first token is not fully finished yet, we can't629 // determine whether this will be a variable or a lldb command.630 if (partial_expression && token.second.empty())631 return ReplMode::Auto;632 633 std::string term = token.first.str();634 lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();635 bool term_is_command = interpreter.CommandExists(term.c_str()) ||636 interpreter.UserCommandExists(term.c_str()) ||637 interpreter.AliasExists(term.c_str());638 bool term_is_variable = frame.FindVariable(term.c_str()).IsValid();639 640 // If we have both a variable and command, warn the user about the conflict.641 if (term_is_command && term_is_variable) {642 llvm::errs()643 << "Warning: Expression '" << term644 << "' is both an LLDB command and variable. It will be evaluated as "645 "a variable. To evaluate the expression as an LLDB command, use '"646 << configuration.commandEscapePrefix << "' as a prefix.\n";647 }648 649 // Variables take preference to commands in auto, since commands can always650 // be called using the command_escape_prefix651 return term_is_variable ? ReplMode::Variable652 : term_is_command ? ReplMode::Command653 : ReplMode::Variable;654 }655 656 llvm_unreachable("enum cases exhausted.");657}658 659std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) {660 if (!frame.IsValid())661 return std::nullopt;662 663 const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry();664 if (DisplayAssemblySource(debugger, frame_line_entry)) {665 const lldb::SBAddress frame_pc = frame.GetPCAddress();666 return ResolveAssemblySource(frame_pc);667 }668 669 return CreateSource(frame_line_entry.GetFileSpec());670}671 672std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) {673 lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address);674 if (DisplayAssemblySource(debugger, line_entry))675 return ResolveAssemblySource(address);676 677 if (!line_entry.IsValid())678 return std::nullopt;679 680 return CreateSource(line_entry.GetFileSpec());681}682 683std::optional<protocol::Source>684DAP::ResolveAssemblySource(lldb::SBAddress address) {685 lldb::SBSymbol symbol = address.GetSymbol();686 lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;687 std::string name;688 if (symbol.IsValid()) {689 load_addr = symbol.GetStartAddress().GetLoadAddress(target);690 name = symbol.GetName();691 } else {692 load_addr = address.GetLoadAddress(target);693 name = GetLoadAddressString(load_addr);694 }695 696 if (load_addr == LLDB_INVALID_ADDRESS)697 return std::nullopt;698 699 protocol::Source source;700 source.sourceReference = CreateSourceReference(load_addr);701 lldb::SBModule module = address.GetModule();702 if (module.IsValid()) {703 lldb::SBFileSpec file_spec = module.GetFileSpec();704 if (file_spec.IsValid()) {705 std::string path = GetSBFileSpecPath(file_spec);706 if (!path.empty())707 source.path = path + '`' + name;708 }709 }710 711 source.name = std::move(name);712 713 // Mark the source as deemphasized since users will only be able to view714 // assembly for these frames.715 source.presentationHint =716 protocol::Source::eSourcePresentationHintDeemphasize;717 718 return source;719}720 721bool DAP::RunLLDBCommands(llvm::StringRef prefix,722 llvm::ArrayRef<std::string> commands) {723 bool required_command_failed = false;724 std::string output = ::RunLLDBCommands(725 debugger, prefix, commands, required_command_failed,726 /*parse_command_directives*/ true, /*echo_commands*/ true);727 SendOutput(OutputType::Console, output);728 return !required_command_failed;729}730 731static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) {732 return llvm::createStringError(733 llvm::inconvertibleErrorCode(),734 llvm::formatv(735 "Failed to run {0} commands. See the Debug Console for more details.",736 category)737 .str()738 .c_str());739}740 741llvm::Error742DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) {743 if (!RunLLDBCommands("Running attachCommands:", attach_commands))744 return createRunLLDBCommandsErrorMessage("attach");745 return llvm::Error::success();746}747 748llvm::Error749DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) {750 if (!RunLLDBCommands("Running launchCommands:", launch_commands))751 return createRunLLDBCommandsErrorMessage("launch");752 return llvm::Error::success();753}754 755llvm::Error DAP::RunInitCommands() {756 if (!RunLLDBCommands("Running initCommands:", configuration.initCommands))757 return createRunLLDBCommandsErrorMessage("initCommands");758 return llvm::Error::success();759}760 761llvm::Error DAP::RunPreInitCommands() {762 if (!RunLLDBCommands("Running preInitCommands:",763 configuration.preInitCommands))764 return createRunLLDBCommandsErrorMessage("preInitCommands");765 return llvm::Error::success();766}767 768llvm::Error DAP::RunPreRunCommands() {769 if (!RunLLDBCommands("Running preRunCommands:", configuration.preRunCommands))770 return createRunLLDBCommandsErrorMessage("preRunCommands");771 return llvm::Error::success();772}773 774void DAP::RunPostRunCommands() {775 RunLLDBCommands("Running postRunCommands:", configuration.postRunCommands);776}777void DAP::RunStopCommands() {778 RunLLDBCommands("Running stopCommands:", configuration.stopCommands);779}780 781void DAP::RunExitCommands() {782 RunLLDBCommands("Running exitCommands:", configuration.exitCommands);783}784 785void DAP::RunTerminateCommands() {786 RunLLDBCommands("Running terminateCommands:",787 configuration.terminateCommands);788}789 790lldb::SBTarget DAP::CreateTarget(lldb::SBError &error) {791 // the given program as an argument. Executable file can be a source of target792 // architecture and platform, if they differ from the host. Setting exe path793 // in launch info is useless because Target.Launch() will not change794 // architecture and platform, therefore they should be known at the target795 // creation. We also use target triple and platform from the launch796 // configuration, if given, since in some cases ELF file doesn't contain797 // enough information to determine correct arch and platform (or ELF can be798 // omitted at all), so it is good to leave the user an opportunity to specify799 // those. Any of those three can be left empty.800 auto target = this->debugger.CreateTarget(801 /*filename=*/configuration.program.data(),802 /*target_triple=*/configuration.targetTriple.data(),803 /*platform_name=*/configuration.platformName.data(),804 /*add_dependent_modules=*/true, // Add dependent modules.805 error);806 807 return target;808}809 810void DAP::SetTarget(const lldb::SBTarget target) {811 this->target = target;812 813 if (target.IsValid()) {814 // Configure breakpoint event listeners for the target.815 lldb::SBListener listener = this->debugger.GetListener();816 listener.StartListeningForEvents(817 this->target.GetBroadcaster(),818 lldb::SBTarget::eBroadcastBitBreakpointChanged |819 lldb::SBTarget::eBroadcastBitModulesLoaded |820 lldb::SBTarget::eBroadcastBitModulesUnloaded |821 lldb::SBTarget::eBroadcastBitSymbolsLoaded |822 lldb::SBTarget::eBroadcastBitSymbolsChanged |823 lldb::SBTarget::eBroadcastBitNewTargetCreated);824 listener.StartListeningForEvents(this->broadcaster,825 eBroadcastBitStopEventThread);826 }827}828 829bool DAP::HandleObject(const Message &M) {830 TelemetryDispatcher dispatcher(&debugger);831 dispatcher.Set("client_name", m_client_name.str());832 if (const auto *req = std::get_if<Request>(&M)) {833 {834 std::lock_guard<std::mutex> guard(m_active_request_mutex);835 m_active_request = req;836 837 // Clear the interrupt request prior to invoking a handler.838 if (debugger.InterruptRequested())839 debugger.CancelInterruptRequest();840 }841 842 auto cleanup = llvm::make_scope_exit([&]() {843 std::scoped_lock<std::mutex> active_request_lock(m_active_request_mutex);844 m_active_request = nullptr;845 });846 847 auto handler_pos = request_handlers.find(req->command);848 dispatcher.Set("client_data",849 llvm::Twine("request_command:", req->command).str());850 if (handler_pos != request_handlers.end()) {851 handler_pos->second->Run(*req);852 return true; // Success853 }854 855 dispatcher.Set("error",856 llvm::Twine("unhandled-command:" + req->command).str());857 DAP_LOG(log, "({0}) error: unhandled command '{1}'", m_client_name,858 req->command);859 return false; // Fail860 }861 862 if (const auto *resp = std::get_if<Response>(&M)) {863 std::unique_ptr<ResponseHandler> response_handler;864 {865 std::lock_guard<std::mutex> guard(call_mutex);866 auto inflight = inflight_reverse_requests.find(resp->request_seq);867 if (inflight != inflight_reverse_requests.end()) {868 response_handler = std::move(inflight->second);869 inflight_reverse_requests.erase(inflight);870 }871 }872 873 if (!response_handler)874 response_handler =875 std::make_unique<UnknownResponseHandler>("", resp->request_seq);876 877 // Result should be given, use null if not.878 if (resp->success) {879 (*response_handler)(resp->body);880 dispatcher.Set("client_data",881 llvm::Twine("response_command:", resp->command).str());882 } else {883 llvm::StringRef message = "Unknown error, response failed";884 if (resp->message) {885 message =886 std::visit(llvm::makeVisitor(887 [](const std::string &message) -> llvm::StringRef {888 return message;889 },890 [](const protocol::ResponseMessage &message)891 -> llvm::StringRef {892 switch (message) {893 case protocol::eResponseMessageCancelled:894 return "cancelled";895 case protocol::eResponseMessageNotStopped:896 return "notStopped";897 }898 llvm_unreachable("unknown response message kind.");899 }),900 *resp->message);901 }902 dispatcher.Set("error", message.str());903 904 (*response_handler)(llvm::createStringError(905 std::error_code(-1, std::generic_category()), message));906 }907 908 return true;909 }910 911 dispatcher.Set("error", "Unsupported protocol message");912 DAP_LOG(log, "Unsupported protocol message");913 914 return false;915}916 917void DAP::SendTerminatedEvent() {918 // Prevent races if the process exits while we're being asked to disconnect.919 llvm::call_once(terminated_event_flag, [&] {920 RunTerminateCommands();921 // Send a "terminated" event922 llvm::json::Object event(CreateTerminatedEventObject(target));923 SendJSON(llvm::json::Value(std::move(event)));924 });925}926 927llvm::Error DAP::Disconnect() { return Disconnect(!is_attach); }928 929llvm::Error DAP::Disconnect(bool terminateDebuggee) {930 lldb::SBError error;931 lldb::SBProcess process = target.GetProcess();932 auto state = process.GetState();933 switch (state) {934 case lldb::eStateInvalid:935 case lldb::eStateUnloaded:936 case lldb::eStateDetached:937 case lldb::eStateExited:938 break;939 case lldb::eStateConnected:940 case lldb::eStateAttaching:941 case lldb::eStateLaunching:942 case lldb::eStateStepping:943 case lldb::eStateCrashed:944 case lldb::eStateSuspended:945 case lldb::eStateStopped:946 case lldb::eStateRunning: {947 ScopeSyncMode scope_sync_mode(debugger);948 error = terminateDebuggee ? process.Kill() : process.Detach();949 break;950 }951 }952 953 SendTerminatedEvent();954 TerminateLoop();955 return ToError(error);956}957 958bool DAP::IsCancelled(const protocol::Request &req) {959 std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex);960 return m_cancelled_requests.contains(req.seq);961}962 963void DAP::ClearCancelRequest(const CancelArguments &args) {964 std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex);965 if (args.requestId)966 m_cancelled_requests.erase(*args.requestId);967}968 969template <typename T>970static std::optional<T> getArgumentsIfRequest(const Request &req,971 llvm::StringLiteral command) {972 if (req.command != command)973 return std::nullopt;974 975 T args;976 llvm::json::Path::Root root;977 if (!fromJSON(req.arguments, args, root))978 return std::nullopt;979 980 return args;981}982 983void DAP::Received(const protocol::Event &event) {984 // no-op, no supported events from the client to the server as of DAP v1.68.985}986 987void DAP::Received(const protocol::Request &request) {988 if (request.command == "disconnect")989 m_disconnecting = true;990 991 const std::optional<CancelArguments> cancel_args =992 getArgumentsIfRequest<CancelArguments>(request, "cancel");993 if (cancel_args) {994 {995 std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex);996 if (cancel_args->requestId)997 m_cancelled_requests.insert(*cancel_args->requestId);998 }999 1000 // If a cancel is requested for the active request, make a best1001 // effort attempt to interrupt.1002 std::lock_guard<std::mutex> guard(m_active_request_mutex);1003 if (m_active_request && cancel_args->requestId == m_active_request->seq) {1004 DAP_LOG(log, "({0}) interrupting inflight request (command={1} seq={2})",1005 m_client_name, m_active_request->command, m_active_request->seq);1006 debugger.RequestInterrupt();1007 }1008 }1009 1010 std::lock_guard<std::mutex> guard(m_queue_mutex);1011 DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name,1012 request.command, request.seq);1013 m_queue.push_back(request);1014 m_queue_cv.notify_one();1015}1016 1017void DAP::Received(const protocol::Response &response) {1018 std::lock_guard<std::mutex> guard(m_queue_mutex);1019 DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name,1020 response.command, response.request_seq);1021 m_queue.push_back(response);1022 m_queue_cv.notify_one();1023}1024 1025void DAP::OnError(llvm::Error error) {1026 DAP_LOG_ERROR(log, std::move(error), "({1}) received error: {0}",1027 m_client_name);1028 TerminateLoop(/*failed=*/true);1029}1030 1031void DAP::OnClosed() {1032 DAP_LOG(log, "({0}) received EOF", m_client_name);1033 TerminateLoop();1034}1035 1036void DAP::TerminateLoop(bool failed) {1037 std::lock_guard<std::mutex> guard(m_queue_mutex);1038 if (m_disconnecting)1039 return; // Already disconnecting.1040 1041 m_error_occurred = failed;1042 m_disconnecting = true;1043 m_loop.AddPendingCallback(1044 [](MainLoopBase &loop) { loop.RequestTermination(); });1045}1046 1047void DAP::TransportHandler() {1048 auto scope_guard = llvm::make_scope_exit([this] {1049 std::lock_guard<std::mutex> guard(m_queue_mutex);1050 // Ensure we're marked as disconnecting when the reader exits.1051 m_disconnecting = true;1052 m_queue_cv.notify_all();1053 });1054 1055 auto handle = transport.RegisterMessageHandler(m_loop, *this);1056 if (!handle) {1057 DAP_LOG_ERROR(log, handle.takeError(),1058 "({1}) registering message handler failed: {0}",1059 m_client_name);1060 std::lock_guard<std::mutex> guard(m_queue_mutex);1061 m_error_occurred = true;1062 return;1063 }1064 1065 if (Status status = m_loop.Run(); status.Fail()) {1066 DAP_LOG_ERROR(log, status.takeError(), "({1}) MainLoop run failed: {0}",1067 m_client_name);1068 std::lock_guard<std::mutex> guard(m_queue_mutex);1069 m_error_occurred = true;1070 return;1071 }1072}1073 1074llvm::Error DAP::Loop() {1075 {1076 // Reset disconnect flag once we start the loop.1077 std::lock_guard<std::mutex> guard(m_queue_mutex);1078 m_disconnecting = false;1079 }1080 1081 auto thread = std::thread(std::bind(&DAP::TransportHandler, this));1082 1083 auto cleanup = llvm::make_scope_exit([this]() {1084 // FIXME: Merge these into the MainLoop handler.1085 out.Stop();1086 err.Stop();1087 StopEventHandlers();1088 1089 // Destroy the debugger when the session ends. This will trigger the1090 // debugger's destroy callbacks for earlier logging and clean-ups, rather1091 // than waiting for the termination of the lldb-dap process.1092 lldb::SBDebugger::Destroy(debugger);1093 });1094 1095 while (true) {1096 std::unique_lock<std::mutex> lock(m_queue_mutex);1097 m_queue_cv.wait(lock, [&] { return m_disconnecting || !m_queue.empty(); });1098 1099 if (m_disconnecting && m_queue.empty())1100 break;1101 1102 Message next = m_queue.front();1103 m_queue.pop_front();1104 1105 // Unlock while we're processing the event.1106 lock.unlock();1107 1108 if (!HandleObject(next))1109 return llvm::createStringError(llvm::inconvertibleErrorCode(),1110 "unhandled packet");1111 }1112 1113 // Don't wait to join the mainloop thread if our callback wasn't added1114 // successfully, or we'll wait forever.1115 if (m_loop.AddPendingCallback(1116 [](MainLoopBase &loop) { loop.RequestTermination(); }))1117 thread.join();1118 1119 if (m_error_occurred)1120 return llvm::createStringError(llvm::inconvertibleErrorCode(),1121 "DAP Loop terminated due to an internal "1122 "error, see DAP Logs for more information.");1123 return llvm::Error::success();1124}1125 1126lldb::SBError DAP::WaitForProcessToStop(std::chrono::seconds seconds) {1127 lldb::SBError error;1128 lldb::SBProcess process = target.GetProcess();1129 if (!process.IsValid()) {1130 error.SetErrorString("invalid process");1131 return error;1132 }1133 auto timeout_time =1134 std::chrono::steady_clock::now() + std::chrono::seconds(seconds);1135 while (std::chrono::steady_clock::now() < timeout_time) {1136 const auto state = process.GetState();1137 switch (state) {1138 case lldb::eStateUnloaded:1139 case lldb::eStateAttaching:1140 case lldb::eStateConnected:1141 case lldb::eStateInvalid:1142 case lldb::eStateLaunching:1143 case lldb::eStateRunning:1144 case lldb::eStateStepping:1145 case lldb::eStateSuspended:1146 break;1147 case lldb::eStateDetached:1148 error.SetErrorString("process detached during launch or attach");1149 return error;1150 case lldb::eStateExited:1151 error.SetErrorString("process exited during launch or attach");1152 return error;1153 case lldb::eStateCrashed:1154 case lldb::eStateStopped:1155 return lldb::SBError(); // Success!1156 }1157 std::this_thread::sleep_for(std::chrono::microseconds(250));1158 }1159 error.SetErrorString(1160 llvm::formatv("process failed to stop within {0}", seconds)1161 .str()1162 .c_str());1163 return error;1164}1165 1166void DAP::ConfigureSourceMaps() {1167 if (configuration.sourceMap.empty() && configuration.sourcePath.empty())1168 return;1169 1170 std::string sourceMapCommand;1171 llvm::raw_string_ostream strm(sourceMapCommand);1172 strm << "settings set target.source-map ";1173 1174 if (!configuration.sourceMap.empty()) {1175 for (const auto &kv : configuration.sourceMap) {1176 strm << "\"" << kv.first << "\" \"" << kv.second << "\" ";1177 }1178 } else if (!configuration.sourcePath.empty()) {1179 strm << "\".\" \"" << configuration.sourcePath << "\"";1180 }1181 1182 RunLLDBCommands("Setting source map:", {sourceMapCommand});1183}1184 1185void DAP::SetConfiguration(const protocol::Configuration &config,1186 bool is_attach) {1187 configuration = config;1188 stop_at_entry = config.stopOnEntry;1189 this->is_attach = is_attach;1190 1191 if (configuration.customFrameFormat)1192 SetFrameFormat(*configuration.customFrameFormat);1193 if (configuration.customThreadFormat)1194 SetThreadFormat(*configuration.customThreadFormat);1195}1196 1197void DAP::SetFrameFormat(llvm::StringRef format) {1198 lldb::SBError error;1199 frame_format = lldb::SBFormat(format.str().c_str(), error);1200 if (error.Fail()) {1201 SendOutput(OutputType::Console,1202 llvm::formatv(1203 "The provided frame format '{0}' couldn't be parsed: {1}\n",1204 format, error.GetCString())1205 .str());1206 }1207}1208 1209void DAP::SetThreadFormat(llvm::StringRef format) {1210 lldb::SBError error;1211 thread_format = lldb::SBFormat(format.str().c_str(), error);1212 if (error.Fail()) {1213 SendOutput(OutputType::Console,1214 llvm::formatv(1215 "The provided thread format '{0}' couldn't be parsed: {1}\n",1216 format, error.GetCString())1217 .str());1218 }1219}1220 1221InstructionBreakpoint *1222DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) {1223 for (auto &bp : instruction_breakpoints) {1224 if (bp.second.GetID() == bp_id)1225 return &bp.second;1226 }1227 return nullptr;1228}1229 1230InstructionBreakpoint *1231DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) {1232 const auto num = thread.GetStopReasonDataCount();1233 InstructionBreakpoint *inst_bp = nullptr;1234 for (size_t i = 0; i < num; i += 2) {1235 // thread.GetStopReasonDataAtIndex(i) will return the bp ID and1236 // thread.GetStopReasonDataAtIndex(i+1) will return the location1237 // within that breakpoint. We only care about the bp ID so we can1238 // see if this is an instruction breakpoint that is getting hit.1239 lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);1240 inst_bp = GetInstructionBreakpoint(bp_id);1241 // If any breakpoint is not an instruction breakpoint, then stop and1242 // report this as a normal breakpoint1243 if (inst_bp == nullptr)1244 return nullptr;1245 }1246 return inst_bp;1247}1248 1249protocol::Capabilities DAP::GetCapabilities() {1250 protocol::Capabilities capabilities;1251 1252 // Supported capabilities that are not specific to a single request.1253 capabilities.supportedFeatures = {1254 protocol::eAdapterFeatureLogPoints,1255 protocol::eAdapterFeatureSteppingGranularity,1256 protocol::eAdapterFeatureValueFormattingOptions,1257 };1258 1259 // Capabilities associated with specific requests.1260 for (auto &kv : request_handlers) {1261 llvm::SmallDenseSet<AdapterFeature, 1> features =1262 kv.second->GetSupportedFeatures();1263 capabilities.supportedFeatures.insert(features.begin(), features.end());1264 }1265 1266 // Available filters or options for the setExceptionBreakpoints request.1267 PopulateExceptionBreakpoints();1268 std::vector<protocol::ExceptionBreakpointsFilter> filters;1269 for (const auto &exc_bp : exception_breakpoints)1270 filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));1271 capabilities.exceptionBreakpointFilters = std::move(filters);1272 1273 // FIXME: This should be registered based on the supported languages?1274 std::vector<std::string> completion_characters;1275 completion_characters.emplace_back(".");1276 // FIXME: I wonder if we should remove this key... its very aggressive1277 // triggering and accepting completions.1278 completion_characters.emplace_back(" ");1279 completion_characters.emplace_back("\t");1280 capabilities.completionTriggerCharacters = std::move(completion_characters);1281 1282 // Put in non-DAP specification lldb specific information.1283 capabilities.lldbExtVersion = debugger.GetVersionString();1284 1285 return capabilities;1286}1287 1288protocol::Capabilities DAP::GetCustomCapabilities() {1289 protocol::Capabilities capabilities;1290 1291 // Add all custom capabilities here.1292 const llvm::DenseSet<AdapterFeature> all_custom_features = {1293 protocol::eAdapterFeatureSupportsModuleSymbolsRequest,1294 };1295 1296 for (auto &kv : request_handlers) {1297 llvm::SmallDenseSet<AdapterFeature, 1> features =1298 kv.second->GetSupportedFeatures();1299 1300 for (auto &feature : features) {1301 if (all_custom_features.contains(feature))1302 capabilities.supportedFeatures.insert(feature);1303 }1304 }1305 1306 return capabilities;1307}1308 1309void DAP::StartEventThread() {1310 // Get event thread for this debugger (creates it if it doesn't exist).1311 event_thread_sp = DAPSessionManager::GetInstance().GetEventThreadForDebugger(1312 debugger, this);1313}1314 1315void DAP::StartProgressEventThread() {1316 progress_event_thread = std::thread(&DAP::ProgressEventThread, this);1317}1318 1319void DAP::StartEventThreads() {1320 if (clientFeatures.contains(eClientFeatureProgressReporting))1321 StartProgressEventThread();1322 1323 StartEventThread();1324}1325 1326llvm::Error DAP::InitializeDebugger(int debugger_id,1327 lldb::user_id_t target_id) {1328 // Find the existing debugger by ID1329 debugger = lldb::SBDebugger::FindDebuggerWithID(debugger_id);1330 if (!debugger.IsValid()) {1331 return llvm::createStringError(1332 "Unable to find existing debugger for debugger ID");1333 }1334 1335 // Find the target within the debugger by its globally unique ID1336 lldb::SBTarget target = debugger.FindTargetByGloballyUniqueID(target_id);1337 if (!target.IsValid()) {1338 return llvm::createStringError(1339 "Unable to find existing target for target ID");1340 }1341 1342 // Set the target for this DAP session.1343 SetTarget(target);1344 StartEventThreads();1345 return llvm::Error::success();1346}1347 1348llvm::Error DAP::InitializeDebugger() {1349 debugger = lldb::SBDebugger::Create(/*argument_name=*/false);1350 1351 // Configure input/output/error file descriptors.1352 debugger.SetInputFile(in);1353 target = debugger.GetDummyTarget();1354 1355 llvm::Expected<int> out_fd = out.GetWriteFileDescriptor();1356 if (!out_fd)1357 return out_fd.takeError();1358 debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));1359 1360 llvm::Expected<int> err_fd = err.GetWriteFileDescriptor();1361 if (!err_fd)1362 return err_fd.takeError();1363 debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));1364 1365 // The sourceInitFile option is not part of the DAP specification. It is an1366 // extension used by the test suite to prevent sourcing `.lldbinit` and1367 // changing its behavior. The CLI flag --no-lldbinit takes precedence over1368 // the DAP parameter.1369 bool should_source_init_files = !no_lldbinit && sourceInitFile;1370 if (should_source_init_files) {1371 debugger.SkipLLDBInitFiles(false);1372 debugger.SkipAppInitFiles(false);1373 lldb::SBCommandReturnObject init;1374 auto interp = debugger.GetCommandInterpreter();1375 interp.SourceInitFileInGlobalDirectory(init);1376 interp.SourceInitFileInHomeDirectory(init);1377 }1378 1379 // Run initialization commands.1380 if (llvm::Error err = RunPreInitCommands())1381 return err;1382 1383 auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand(1384 "lldb-dap", "Commands for managing lldb-dap.");1385 1386 if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) {1387 cmd.AddCommand(1388 "start-debugging", new StartDebuggingCommand(*this),1389 "Sends a startDebugging request from the debug adapter to the client "1390 "to start a child debug session of the same type as the caller.");1391 }1392 1393 cmd.AddCommand(1394 "repl-mode", new ReplModeCommand(*this),1395 "Get or set the repl behavior of lldb-dap evaluation requests.");1396 cmd.AddCommand("send-event", new SendEventCommand(*this),1397 "Sends an DAP event to the client.");1398 1399 StartEventThreads();1400 return llvm::Error::success();1401}1402 1403void DAP::ProgressEventThread() {1404 lldb::SBListener listener("lldb-dap.progress.listener");1405 debugger.GetBroadcaster().AddListener(1406 listener, lldb::SBDebugger::eBroadcastBitProgress |1407 lldb::SBDebugger::eBroadcastBitExternalProgress);1408 broadcaster.AddListener(listener, eBroadcastBitStopProgressThread);1409 lldb::SBEvent event;1410 bool done = false;1411 while (!done) {1412 if (listener.WaitForEvent(UINT32_MAX, event)) {1413 const auto event_mask = event.GetType();1414 if (event.BroadcasterMatchesRef(broadcaster)) {1415 if (event_mask & eBroadcastBitStopProgressThread) {1416 done = true;1417 }1418 } else {1419 lldb::SBStructuredData data =1420 lldb::SBDebugger::GetProgressDataFromEvent(event);1421 1422 const uint64_t progress_id =1423 GetUintFromStructuredData(data, "progress_id");1424 const uint64_t completed = GetUintFromStructuredData(data, "completed");1425 const uint64_t total = GetUintFromStructuredData(data, "total");1426 const std::string details =1427 GetStringFromStructuredData(data, "details");1428 1429 if (completed == 0) {1430 if (total == UINT64_MAX) {1431 // This progress is non deterministic and won't get updated until it1432 // is completed. Send the "message" which will be the combined title1433 // and detail. The only other progress event for thus1434 // non-deterministic progress will be the completed event So there1435 // will be no need to update the detail.1436 const std::string message =1437 GetStringFromStructuredData(data, "message");1438 SendProgressEvent(progress_id, message.c_str(), completed, total);1439 } else {1440 // This progress is deterministic and will receive updates,1441 // on the progress creation event VSCode will save the message in1442 // the create packet and use that as the title, so we send just the1443 // title in the progressCreate packet followed immediately by a1444 // detail packet, if there is any detail.1445 const std::string title =1446 GetStringFromStructuredData(data, "title");1447 SendProgressEvent(progress_id, title.c_str(), completed, total);1448 if (!details.empty())1449 SendProgressEvent(progress_id, details.c_str(), completed, total);1450 }1451 } else {1452 // This progress event is either the end of the progress dialog, or an1453 // update with possible detail. The "detail" string we send to VS Code1454 // will be appended to the progress dialog's initial text from when it1455 // was created.1456 SendProgressEvent(progress_id, details.c_str(), completed, total);1457 }1458 }1459 }1460 }1461}1462 1463std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints(1464 const protocol::Source &source,1465 const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) {1466 std::vector<protocol::Breakpoint> response_breakpoints;1467 if (source.sourceReference) {1468 // Breakpoint set by assembly source.1469 auto &existing_breakpoints =1470 m_source_assembly_breakpoints[*source.sourceReference];1471 response_breakpoints =1472 SetSourceBreakpoints(source, breakpoints, existing_breakpoints);1473 } else {1474 // Breakpoint set by a regular source file.1475 const auto path = source.path.value_or("");1476 auto &existing_breakpoints = m_source_breakpoints[path];1477 response_breakpoints =1478 SetSourceBreakpoints(source, breakpoints, existing_breakpoints);1479 }1480 1481 return response_breakpoints;1482}1483 1484std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints(1485 const protocol::Source &source,1486 const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints,1487 SourceBreakpointMap &existing_breakpoints) {1488 std::vector<protocol::Breakpoint> response_breakpoints;1489 1490 SourceBreakpointMap request_breakpoints;1491 if (breakpoints) {1492 for (const auto &bp : *breakpoints) {1493 SourceBreakpoint src_bp(*this, bp);1494 std::pair<uint32_t, uint32_t> bp_pos(src_bp.GetLine(),1495 src_bp.GetColumn());1496 request_breakpoints.try_emplace(bp_pos, src_bp);1497 1498 const auto [iv, inserted] =1499 existing_breakpoints.try_emplace(bp_pos, src_bp);1500 // We check if this breakpoint already exists to update it.1501 if (inserted) {1502 if (llvm::Error error = iv->second.SetBreakpoint(source)) {1503 protocol::Breakpoint invalid_breakpoint;1504 invalid_breakpoint.message = llvm::toString(std::move(error));1505 invalid_breakpoint.verified = false;1506 response_breakpoints.push_back(std::move(invalid_breakpoint));1507 existing_breakpoints.erase(iv);1508 continue;1509 }1510 } else {1511 iv->second.UpdateBreakpoint(src_bp);1512 }1513 1514 protocol::Breakpoint response_breakpoint =1515 iv->second.ToProtocolBreakpoint();1516 1517 if (!response_breakpoint.source)1518 response_breakpoint.source = source;1519 if (!response_breakpoint.line &&1520 src_bp.GetLine() != LLDB_INVALID_LINE_NUMBER)1521 response_breakpoint.line = src_bp.GetLine();1522 if (!response_breakpoint.column &&1523 src_bp.GetColumn() != LLDB_INVALID_COLUMN_NUMBER)1524 response_breakpoint.column = src_bp.GetColumn();1525 response_breakpoints.push_back(std::move(response_breakpoint));1526 }1527 }1528 1529 // Delete any breakpoints in this source file that aren't in the1530 // request_bps set. There is no call to remove breakpoints other than1531 // calling this function with a smaller or empty "breakpoints" list.1532 for (auto it = existing_breakpoints.begin();1533 it != existing_breakpoints.end();) {1534 auto request_pos = request_breakpoints.find(it->first);1535 if (request_pos == request_breakpoints.end()) {1536 // This breakpoint no longer exists in this source file, delete it1537 target.BreakpointDelete(it->second.GetID());1538 it = existing_breakpoints.erase(it);1539 } else {1540 ++it;1541 }1542 }1543 1544 return response_breakpoints;1545}1546 1547void DAP::RegisterRequests() {1548 RegisterRequest<AttachRequestHandler>();1549 RegisterRequest<BreakpointLocationsRequestHandler>();1550 RegisterRequest<CancelRequestHandler>();1551 RegisterRequest<CompletionsRequestHandler>();1552 RegisterRequest<ConfigurationDoneRequestHandler>();1553 RegisterRequest<ContinueRequestHandler>();1554 RegisterRequest<DataBreakpointInfoRequestHandler>();1555 RegisterRequest<DisassembleRequestHandler>();1556 RegisterRequest<DisconnectRequestHandler>();1557 RegisterRequest<EvaluateRequestHandler>();1558 RegisterRequest<ExceptionInfoRequestHandler>();1559 RegisterRequest<InitializeRequestHandler>();1560 RegisterRequest<LaunchRequestHandler>();1561 RegisterRequest<LocationsRequestHandler>();1562 RegisterRequest<NextRequestHandler>();1563 RegisterRequest<PauseRequestHandler>();1564 RegisterRequest<ReadMemoryRequestHandler>();1565 RegisterRequest<RestartRequestHandler>();1566 RegisterRequest<ScopesRequestHandler>();1567 RegisterRequest<SetBreakpointsRequestHandler>();1568 RegisterRequest<SetDataBreakpointsRequestHandler>();1569 RegisterRequest<SetExceptionBreakpointsRequestHandler>();1570 RegisterRequest<SetFunctionBreakpointsRequestHandler>();1571 RegisterRequest<SetInstructionBreakpointsRequestHandler>();1572 RegisterRequest<SetVariableRequestHandler>();1573 RegisterRequest<SourceRequestHandler>();1574 RegisterRequest<StackTraceRequestHandler>();1575 RegisterRequest<StepInRequestHandler>();1576 RegisterRequest<StepInTargetsRequestHandler>();1577 RegisterRequest<StepOutRequestHandler>();1578 RegisterRequest<ThreadsRequestHandler>();1579 RegisterRequest<VariablesRequestHandler>();1580 RegisterRequest<WriteMemoryRequestHandler>();1581 1582 // Custom requests1583 RegisterRequest<CompileUnitsRequestHandler>();1584 RegisterRequest<ModulesRequestHandler>();1585 RegisterRequest<ModuleSymbolsRequestHandler>();1586 1587 // Testing requests1588 RegisterRequest<TestGetTargetBreakpointsRequestHandler>();1589}1590 1591} // namespace lldb_dap1592