Both spellings are considered correct and acceptable, with adapter being more common in American English. Given that DAP stands for Debug Adapter Protocol (with an e) let's go with that as the canonical spelling.
1226 lines
41 KiB
C++
1226 lines
41 KiB
C++
//===-- DAP.cpp -------------------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "DAP.h"
|
|
#include "Handler/ResponseHandler.h"
|
|
#include "JSONUtils.h"
|
|
#include "LLDBUtils.h"
|
|
#include "OutputRedirector.h"
|
|
#include "lldb/API/SBBreakpoint.h"
|
|
#include "lldb/API/SBCommandInterpreter.h"
|
|
#include "lldb/API/SBCommandReturnObject.h"
|
|
#include "lldb/API/SBLanguageRuntime.h"
|
|
#include "lldb/API/SBListener.h"
|
|
#include "lldb/API/SBProcess.h"
|
|
#include "lldb/API/SBStream.h"
|
|
#include "lldb/Utility/IOObject.h"
|
|
#include "lldb/Utility/Status.h"
|
|
#include "lldb/lldb-defines.h"
|
|
#include "lldb/lldb-enumerations.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/ScopeExit.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/Twine.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <utility>
|
|
|
|
#if defined(_WIN32)
|
|
#define NOMINMAX
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
using namespace lldb_dap;
|
|
|
|
namespace {
|
|
#ifdef _WIN32
|
|
const char DEV_NULL[] = "nul";
|
|
#else
|
|
const char DEV_NULL[] = "/dev/null";
|
|
#endif
|
|
} // namespace
|
|
|
|
namespace lldb_dap {
|
|
|
|
DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
|
|
lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
|
|
std::vector<std::string> pre_init_commands)
|
|
: name(std::move(name)), debug_adapter_path(path), log(log),
|
|
input(std::move(input)), output(std::move(output)),
|
|
broadcaster("lldb-dap"), exception_breakpoints(),
|
|
pre_init_commands(std::move(pre_init_commands)),
|
|
focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
|
|
enable_auto_variable_summaries(false),
|
|
enable_synthetic_child_debugging(false),
|
|
display_extended_backtrace(false),
|
|
restarting_process_id(LLDB_INVALID_PROCESS_ID),
|
|
configuration_done_sent(false), waiting_for_run_in_terminal(false),
|
|
progress_event_reporter(
|
|
[&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
|
|
reverse_request_seq(0), repl_mode(repl_mode) {}
|
|
|
|
DAP::~DAP() = default;
|
|
|
|
/// Return string with first character capitalized.
|
|
static std::string capitalize(llvm::StringRef str) {
|
|
if (str.empty())
|
|
return "";
|
|
return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str();
|
|
}
|
|
|
|
void DAP::PopulateExceptionBreakpoints() {
|
|
llvm::call_once(init_exception_breakpoints_flag, [this]() {
|
|
exception_breakpoints = std::vector<ExceptionBreakpoint>{};
|
|
|
|
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
|
|
exception_breakpoints->emplace_back(*this, "cpp_catch", "C++ Catch",
|
|
lldb::eLanguageTypeC_plus_plus);
|
|
exception_breakpoints->emplace_back(*this, "cpp_throw", "C++ Throw",
|
|
lldb::eLanguageTypeC_plus_plus);
|
|
}
|
|
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {
|
|
exception_breakpoints->emplace_back(
|
|
*this, "objc_catch", "Objective-C Catch", lldb::eLanguageTypeObjC);
|
|
exception_breakpoints->emplace_back(
|
|
*this, "objc_throw", "Objective-C Throw", lldb::eLanguageTypeObjC);
|
|
}
|
|
if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {
|
|
exception_breakpoints->emplace_back(*this, "swift_catch", "Swift Catch",
|
|
lldb::eLanguageTypeSwift);
|
|
exception_breakpoints->emplace_back(*this, "swift_throw", "Swift Throw",
|
|
lldb::eLanguageTypeSwift);
|
|
}
|
|
// Besides handling the hardcoded list of languages from above, we try to
|
|
// find any other languages that support exception breakpoints using the
|
|
// SB API.
|
|
for (int raw_lang = lldb::eLanguageTypeUnknown;
|
|
raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
|
|
lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);
|
|
|
|
// We first discard any languages already handled above.
|
|
if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
|
|
lang == lldb::eLanguageTypeSwift)
|
|
continue;
|
|
|
|
if (!lldb::SBDebugger::SupportsLanguage(lang))
|
|
continue;
|
|
|
|
const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
|
|
if (!name)
|
|
continue;
|
|
std::string raw_lang_name = name;
|
|
std::string capitalized_lang_name = capitalize(name);
|
|
|
|
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
|
|
const char *raw_throw_keyword =
|
|
lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
|
|
std::string throw_keyword =
|
|
raw_throw_keyword ? raw_throw_keyword : "throw";
|
|
|
|
exception_breakpoints->emplace_back(
|
|
*this, raw_lang_name + "_" + throw_keyword,
|
|
capitalized_lang_name + " " + capitalize(throw_keyword), lang);
|
|
}
|
|
|
|
if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
|
|
const char *raw_catch_keyword =
|
|
lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
|
|
std::string catch_keyword =
|
|
raw_catch_keyword ? raw_catch_keyword : "catch";
|
|
|
|
exception_breakpoints->emplace_back(
|
|
*this, raw_lang_name + "_" + catch_keyword,
|
|
capitalized_lang_name + " " + capitalize(catch_keyword), lang);
|
|
}
|
|
}
|
|
assert(!exception_breakpoints->empty() && "should not be empty");
|
|
});
|
|
}
|
|
|
|
ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const std::string &filter) {
|
|
// PopulateExceptionBreakpoints() is called after g_dap.debugger is created
|
|
// in a request-initialize.
|
|
//
|
|
// But this GetExceptionBreakpoint() method may be called before attaching, in
|
|
// which case, we may not have populated the filter yet.
|
|
//
|
|
// We also cannot call PopulateExceptionBreakpoints() in DAP::DAP() because
|
|
// we need SBDebugger::Initialize() to have been called before this.
|
|
//
|
|
// So just calling PopulateExceptionBreakoints(),which does lazy-populating
|
|
// seems easiest. Two other options include:
|
|
// + call g_dap.PopulateExceptionBreakpoints() in lldb-dap.cpp::main()
|
|
// right after the call to SBDebugger::Initialize()
|
|
// + Just call PopulateExceptionBreakpoints() to get a fresh list everytime
|
|
// we query (a bit overkill since it's not likely to change?)
|
|
PopulateExceptionBreakpoints();
|
|
|
|
for (auto &bp : *exception_breakpoints) {
|
|
if (bp.filter == filter)
|
|
return &bp;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
|
|
// See comment in the other GetExceptionBreakpoint().
|
|
PopulateExceptionBreakpoints();
|
|
|
|
for (auto &bp : *exception_breakpoints) {
|
|
if (bp.bp.GetID() == bp_id)
|
|
return &bp;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) {
|
|
in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true);
|
|
|
|
if (auto Error = out.RedirectTo([this](llvm::StringRef output) {
|
|
SendOutput(OutputType::Stdout, output);
|
|
}))
|
|
return Error;
|
|
|
|
if (overrideOut) {
|
|
auto fd = out.GetWriteFileDescriptor();
|
|
if (auto Error = fd.takeError())
|
|
return Error;
|
|
|
|
if (dup2(*fd, fileno(overrideOut)) == -1)
|
|
return llvm::errorCodeToError(llvm::errnoAsErrorCode());
|
|
}
|
|
|
|
if (auto Error = err.RedirectTo([this](llvm::StringRef output) {
|
|
SendOutput(OutputType::Stderr, output);
|
|
}))
|
|
return Error;
|
|
|
|
if (overrideErr) {
|
|
auto fd = err.GetWriteFileDescriptor();
|
|
if (auto Error = fd.takeError())
|
|
return Error;
|
|
|
|
if (dup2(*fd, fileno(overrideErr)) == -1)
|
|
return llvm::errorCodeToError(llvm::errnoAsErrorCode());
|
|
}
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
void DAP::StopEventHandlers() {
|
|
if (event_thread.joinable()) {
|
|
broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
|
|
event_thread.join();
|
|
}
|
|
if (progress_event_thread.joinable()) {
|
|
broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
|
|
progress_event_thread.join();
|
|
}
|
|
}
|
|
|
|
// Send the JSON in "json_str" to the "out" stream. Correctly send the
|
|
// "Content-Length:" field followed by the length, followed by the raw
|
|
// JSON bytes.
|
|
void DAP::SendJSON(const std::string &json_str) {
|
|
output.write_full("Content-Length: ");
|
|
output.write_full(llvm::utostr(json_str.size()));
|
|
output.write_full("\r\n\r\n");
|
|
output.write_full(json_str);
|
|
}
|
|
|
|
// Serialize the JSON value into a string and send the JSON packet to
|
|
// the "out" stream.
|
|
void DAP::SendJSON(const llvm::json::Value &json) {
|
|
std::string json_str;
|
|
llvm::raw_string_ostream strm(json_str);
|
|
strm << json;
|
|
static std::mutex mutex;
|
|
std::lock_guard<std::mutex> locker(mutex);
|
|
SendJSON(json_str);
|
|
|
|
if (log) {
|
|
auto now = std::chrono::duration<double>(
|
|
std::chrono::system_clock::now().time_since_epoch());
|
|
*log << llvm::formatv("{0:f9} {1} <-- ", now.count(), name).str()
|
|
<< std::endl
|
|
<< "Content-Length: " << json_str.size() << "\r\n\r\n"
|
|
<< llvm::formatv("{0:2}", json).str() << std::endl;
|
|
}
|
|
}
|
|
|
|
// Read a JSON packet from the "in" stream.
|
|
std::string DAP::ReadJSON() {
|
|
std::string length_str;
|
|
std::string json_str;
|
|
int length;
|
|
|
|
if (!input.read_expected(log, "Content-Length: "))
|
|
return json_str;
|
|
|
|
if (!input.read_line(log, length_str))
|
|
return json_str;
|
|
|
|
if (!llvm::to_integer(length_str, length))
|
|
return json_str;
|
|
|
|
if (!input.read_expected(log, "\r\n"))
|
|
return json_str;
|
|
|
|
if (!input.read_full(log, length, json_str))
|
|
return json_str;
|
|
|
|
if (log) {
|
|
auto now = std::chrono::duration<double>(
|
|
std::chrono::system_clock::now().time_since_epoch());
|
|
*log << llvm::formatv("{0:f9} {1} --> ", now.count(), name).str()
|
|
<< std::endl
|
|
<< "Content-Length: " << length << "\r\n\r\n";
|
|
}
|
|
return json_str;
|
|
}
|
|
|
|
// "OutputEvent": {
|
|
// "allOf": [ { "$ref": "#/definitions/Event" }, {
|
|
// "type": "object",
|
|
// "description": "Event message for 'output' event type. The event
|
|
// indicates that the target has produced some output.",
|
|
// "properties": {
|
|
// "event": {
|
|
// "type": "string",
|
|
// "enum": [ "output" ]
|
|
// },
|
|
// "body": {
|
|
// "type": "object",
|
|
// "properties": {
|
|
// "category": {
|
|
// "type": "string",
|
|
// "description": "The output category. If not specified,
|
|
// 'console' is assumed.",
|
|
// "_enum": [ "console", "stdout", "stderr", "telemetry" ]
|
|
// },
|
|
// "output": {
|
|
// "type": "string",
|
|
// "description": "The output to report."
|
|
// },
|
|
// "variablesReference": {
|
|
// "type": "number",
|
|
// "description": "If an attribute 'variablesReference' exists
|
|
// and its value is > 0, the output contains
|
|
// objects which can be retrieved by passing
|
|
// variablesReference to the VariablesRequest."
|
|
// },
|
|
// "source": {
|
|
// "$ref": "#/definitions/Source",
|
|
// "description": "An optional source location where the output
|
|
// was produced."
|
|
// },
|
|
// "line": {
|
|
// "type": "integer",
|
|
// "description": "An optional source location line where the
|
|
// output was produced."
|
|
// },
|
|
// "column": {
|
|
// "type": "integer",
|
|
// "description": "An optional source location column where the
|
|
// output was produced."
|
|
// },
|
|
// "data": {
|
|
// "type":["array","boolean","integer","null","number","object",
|
|
// "string"],
|
|
// "description": "Optional data to report. For the 'telemetry'
|
|
// category the data will be sent to telemetry, for
|
|
// the other categories the data is shown in JSON
|
|
// format."
|
|
// }
|
|
// },
|
|
// "required": ["output"]
|
|
// }
|
|
// },
|
|
// "required": [ "event", "body" ]
|
|
// }]
|
|
// }
|
|
void DAP::SendOutput(OutputType o, const llvm::StringRef output) {
|
|
if (output.empty())
|
|
return;
|
|
|
|
const char *category = nullptr;
|
|
switch (o) {
|
|
case OutputType::Console:
|
|
category = "console";
|
|
break;
|
|
case OutputType::Stdout:
|
|
category = "stdout";
|
|
break;
|
|
case OutputType::Stderr:
|
|
category = "stderr";
|
|
break;
|
|
case OutputType::Telemetry:
|
|
category = "telemetry";
|
|
break;
|
|
}
|
|
|
|
// Send each line of output as an individual event, including the newline if
|
|
// present.
|
|
::size_t idx = 0;
|
|
do {
|
|
::size_t end = output.find('\n', idx);
|
|
if (end == llvm::StringRef::npos)
|
|
end = output.size() - 1;
|
|
llvm::json::Object event(CreateEventObject("output"));
|
|
llvm::json::Object body;
|
|
body.try_emplace("category", category);
|
|
EmplaceSafeString(body, "output", output.slice(idx, end + 1).str());
|
|
event.try_emplace("body", std::move(body));
|
|
SendJSON(llvm::json::Value(std::move(event)));
|
|
idx = end + 1;
|
|
} while (idx < output.size());
|
|
}
|
|
|
|
// interface ProgressStartEvent extends Event {
|
|
// event: 'progressStart';
|
|
//
|
|
// body: {
|
|
// /**
|
|
// * An ID that must be used in subsequent 'progressUpdate' and
|
|
// 'progressEnd'
|
|
// * events to make them refer to the same progress reporting.
|
|
// * IDs must be unique within a debug session.
|
|
// */
|
|
// progressId: string;
|
|
//
|
|
// /**
|
|
// * Mandatory (short) title of the progress reporting. Shown in the UI to
|
|
// * describe the long running operation.
|
|
// */
|
|
// title: string;
|
|
//
|
|
// /**
|
|
// * The request ID that this progress report is related to. If specified a
|
|
// * debug adapter is expected to emit
|
|
// * progress events for the long running request until the request has
|
|
// been
|
|
// * either completed or cancelled.
|
|
// * If the request ID is omitted, the progress report is assumed to be
|
|
// * related to some general activity of the debug adapter.
|
|
// */
|
|
// requestId?: number;
|
|
//
|
|
// /**
|
|
// * If true, the request that reports progress may be canceled with a
|
|
// * 'cancel' request.
|
|
// * So this property basically controls whether the client should use UX
|
|
// that
|
|
// * supports cancellation.
|
|
// * Clients that don't support cancellation are allowed to ignore the
|
|
// * setting.
|
|
// */
|
|
// cancellable?: boolean;
|
|
//
|
|
// /**
|
|
// * Optional, more detailed progress message.
|
|
// */
|
|
// message?: string;
|
|
//
|
|
// /**
|
|
// * Optional progress percentage to display (value range: 0 to 100). If
|
|
// * omitted no percentage will be shown.
|
|
// */
|
|
// percentage?: number;
|
|
// };
|
|
// }
|
|
//
|
|
// interface ProgressUpdateEvent extends Event {
|
|
// event: 'progressUpdate';
|
|
//
|
|
// body: {
|
|
// /**
|
|
// * The ID that was introduced in the initial 'progressStart' event.
|
|
// */
|
|
// progressId: string;
|
|
//
|
|
// /**
|
|
// * Optional, more detailed progress message. If omitted, the previous
|
|
// * message (if any) is used.
|
|
// */
|
|
// message?: string;
|
|
//
|
|
// /**
|
|
// * Optional progress percentage to display (value range: 0 to 100). If
|
|
// * omitted no percentage will be shown.
|
|
// */
|
|
// percentage?: number;
|
|
// };
|
|
// }
|
|
//
|
|
// interface ProgressEndEvent extends Event {
|
|
// event: 'progressEnd';
|
|
//
|
|
// body: {
|
|
// /**
|
|
// * The ID that was introduced in the initial 'ProgressStartEvent'.
|
|
// */
|
|
// progressId: string;
|
|
//
|
|
// /**
|
|
// * Optional, more detailed progress message. If omitted, the previous
|
|
// * message (if any) is used.
|
|
// */
|
|
// message?: string;
|
|
// };
|
|
// }
|
|
|
|
void DAP::SendProgressEvent(uint64_t progress_id, const char *message,
|
|
uint64_t completed, uint64_t total) {
|
|
progress_event_reporter.Push(progress_id, message, completed, total);
|
|
}
|
|
|
|
void __attribute__((format(printf, 3, 4)))
|
|
DAP::SendFormattedOutput(OutputType o, const char *format, ...) {
|
|
char buffer[1024];
|
|
va_list args;
|
|
va_start(args, format);
|
|
int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
|
|
va_end(args);
|
|
SendOutput(
|
|
o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
|
|
}
|
|
|
|
ExceptionBreakpoint *DAP::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
|
|
const auto num = thread.GetStopReasonDataCount();
|
|
// Check to see if have hit an exception breakpoint and change the
|
|
// reason to "exception", but only do so if all breakpoints that were
|
|
// hit are exception breakpoints.
|
|
ExceptionBreakpoint *exc_bp = nullptr;
|
|
for (size_t i = 0; i < num; i += 2) {
|
|
// thread.GetStopReasonDataAtIndex(i) will return the bp ID and
|
|
// thread.GetStopReasonDataAtIndex(i+1) will return the location
|
|
// within that breakpoint. We only care about the bp ID so we can
|
|
// see if this is an exception breakpoint that is getting hit.
|
|
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
|
|
exc_bp = GetExceptionBreakpoint(bp_id);
|
|
// If any breakpoint is not an exception breakpoint, then stop and
|
|
// report this as a normal breakpoint
|
|
if (exc_bp == nullptr)
|
|
return nullptr;
|
|
}
|
|
return exc_bp;
|
|
}
|
|
|
|
lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) {
|
|
auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
|
|
return target.GetProcess().GetThreadByID(tid);
|
|
}
|
|
|
|
lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {
|
|
const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
|
|
lldb::SBProcess process = target.GetProcess();
|
|
// Upper 32 bits is the thread index ID
|
|
lldb::SBThread thread =
|
|
process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
|
|
// Lower 32 bits is the frame index
|
|
return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
|
|
}
|
|
|
|
llvm::json::Value DAP::CreateTopLevelScopes() {
|
|
llvm::json::Array scopes;
|
|
scopes.emplace_back(
|
|
CreateScope("Locals", VARREF_LOCALS, variables.locals.GetSize(), false));
|
|
scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS,
|
|
variables.globals.GetSize(), false));
|
|
scopes.emplace_back(CreateScope("Registers", VARREF_REGS,
|
|
variables.registers.GetSize(), false));
|
|
return llvm::json::Value(std::move(scopes));
|
|
}
|
|
|
|
ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
|
|
bool partial_expression) {
|
|
// Check for the escape hatch prefix.
|
|
if (!expression.empty() &&
|
|
llvm::StringRef(expression).starts_with(command_escape_prefix)) {
|
|
expression = expression.substr(command_escape_prefix.size());
|
|
return ReplMode::Command;
|
|
}
|
|
|
|
switch (repl_mode) {
|
|
case ReplMode::Variable:
|
|
return ReplMode::Variable;
|
|
case ReplMode::Command:
|
|
return ReplMode::Command;
|
|
case ReplMode::Auto:
|
|
// To determine if the expression is a command or not, check if the first
|
|
// term is a variable or command. If it's a variable in scope we will prefer
|
|
// that behavior and give a warning to the user if they meant to invoke the
|
|
// operation as a command.
|
|
//
|
|
// Example use case:
|
|
// int p and expression "p + 1" > variable
|
|
// int i and expression "i" > variable
|
|
// int var and expression "va" > command
|
|
std::pair<llvm::StringRef, llvm::StringRef> token =
|
|
llvm::getToken(expression);
|
|
|
|
// If the first token is not fully finished yet, we can't
|
|
// determine whether this will be a variable or a lldb command.
|
|
if (partial_expression && token.second.empty())
|
|
return ReplMode::Auto;
|
|
|
|
std::string term = token.first.str();
|
|
lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
|
|
bool term_is_command = interpreter.CommandExists(term.c_str()) ||
|
|
interpreter.UserCommandExists(term.c_str()) ||
|
|
interpreter.AliasExists(term.c_str());
|
|
bool term_is_variable = frame.FindVariable(term.c_str()).IsValid();
|
|
|
|
// If we have both a variable and command, warn the user about the conflict.
|
|
if (term_is_command && term_is_variable) {
|
|
llvm::errs()
|
|
<< "Warning: Expression '" << term
|
|
<< "' is both an LLDB command and variable. It will be evaluated as "
|
|
"a variable. To evaluate the expression as an LLDB command, use '"
|
|
<< command_escape_prefix << "' as a prefix.\n";
|
|
}
|
|
|
|
// Variables take preference to commands in auto, since commands can always
|
|
// be called using the command_escape_prefix
|
|
return term_is_variable ? ReplMode::Variable
|
|
: term_is_command ? ReplMode::Command
|
|
: ReplMode::Variable;
|
|
}
|
|
|
|
llvm_unreachable("enum cases exhausted.");
|
|
}
|
|
|
|
bool DAP::RunLLDBCommands(llvm::StringRef prefix,
|
|
llvm::ArrayRef<std::string> commands) {
|
|
bool required_command_failed = false;
|
|
std::string output =
|
|
::RunLLDBCommands(debugger, prefix, commands, required_command_failed);
|
|
SendOutput(OutputType::Console, output);
|
|
return !required_command_failed;
|
|
}
|
|
|
|
static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) {
|
|
return llvm::createStringError(
|
|
llvm::inconvertibleErrorCode(),
|
|
llvm::formatv(
|
|
"Failed to run {0} commands. See the Debug Console for more details.",
|
|
category)
|
|
.str()
|
|
.c_str());
|
|
}
|
|
|
|
llvm::Error
|
|
DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) {
|
|
if (!RunLLDBCommands("Running attachCommands:", attach_commands))
|
|
return createRunLLDBCommandsErrorMessage("attach");
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error
|
|
DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) {
|
|
if (!RunLLDBCommands("Running launchCommands:", launch_commands))
|
|
return createRunLLDBCommandsErrorMessage("launch");
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error DAP::RunInitCommands() {
|
|
if (!RunLLDBCommands("Running initCommands:", init_commands))
|
|
return createRunLLDBCommandsErrorMessage("initCommands");
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error DAP::RunPreInitCommands() {
|
|
if (!RunLLDBCommands("Running preInitCommands:", pre_init_commands))
|
|
return createRunLLDBCommandsErrorMessage("preInitCommands");
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error DAP::RunPreRunCommands() {
|
|
if (!RunLLDBCommands("Running preRunCommands:", pre_run_commands))
|
|
return createRunLLDBCommandsErrorMessage("preRunCommands");
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
void DAP::RunPostRunCommands() {
|
|
RunLLDBCommands("Running postRunCommands:", post_run_commands);
|
|
}
|
|
void DAP::RunStopCommands() {
|
|
RunLLDBCommands("Running stopCommands:", stop_commands);
|
|
}
|
|
|
|
void DAP::RunExitCommands() {
|
|
RunLLDBCommands("Running exitCommands:", exit_commands);
|
|
}
|
|
|
|
void DAP::RunTerminateCommands() {
|
|
RunLLDBCommands("Running terminateCommands:", terminate_commands);
|
|
}
|
|
|
|
lldb::SBTarget
|
|
DAP::CreateTargetFromArguments(const llvm::json::Object &arguments,
|
|
lldb::SBError &error) {
|
|
// Grab the name of the program we need to debug and create a target using
|
|
// the given program as an argument. Executable file can be a source of target
|
|
// architecture and platform, if they differ from the host. Setting exe path
|
|
// in launch info is useless because Target.Launch() will not change
|
|
// architecture and platform, therefore they should be known at the target
|
|
// creation. We also use target triple and platform from the launch
|
|
// configuration, if given, since in some cases ELF file doesn't contain
|
|
// enough information to determine correct arch and platform (or ELF can be
|
|
// omitted at all), so it is good to leave the user an apportunity to specify
|
|
// those. Any of those three can be left empty.
|
|
llvm::StringRef target_triple = GetString(arguments, "targetTriple");
|
|
llvm::StringRef platform_name = GetString(arguments, "platformName");
|
|
llvm::StringRef program = GetString(arguments, "program");
|
|
auto target = this->debugger.CreateTarget(
|
|
program.data(), target_triple.data(), platform_name.data(),
|
|
true, // Add dependent modules.
|
|
error);
|
|
|
|
if (error.Fail()) {
|
|
// Update message if there was an error.
|
|
error.SetErrorStringWithFormat(
|
|
"Could not create a target for a program '%s': %s.", program.data(),
|
|
error.GetCString());
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
void DAP::SetTarget(const lldb::SBTarget target) {
|
|
this->target = target;
|
|
|
|
if (target.IsValid()) {
|
|
// Configure breakpoint event listeners for the target.
|
|
lldb::SBListener listener = this->debugger.GetListener();
|
|
listener.StartListeningForEvents(
|
|
this->target.GetBroadcaster(),
|
|
lldb::SBTarget::eBroadcastBitBreakpointChanged);
|
|
listener.StartListeningForEvents(this->broadcaster,
|
|
eBroadcastBitStopEventThread);
|
|
}
|
|
}
|
|
|
|
PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
|
|
std::string json = ReadJSON();
|
|
if (json.empty())
|
|
return PacketStatus::EndOfFile;
|
|
|
|
llvm::StringRef json_sref(json);
|
|
llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
|
|
if (!json_value) {
|
|
auto error = json_value.takeError();
|
|
if (log) {
|
|
std::string error_str;
|
|
llvm::raw_string_ostream strm(error_str);
|
|
strm << error;
|
|
*log << "error: failed to parse JSON: " << error_str << std::endl
|
|
<< json << std::endl;
|
|
}
|
|
return PacketStatus::JSONMalformed;
|
|
}
|
|
|
|
if (log) {
|
|
*log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
|
|
}
|
|
|
|
llvm::json::Object *object_ptr = json_value->getAsObject();
|
|
if (!object_ptr) {
|
|
if (log)
|
|
*log << "error: json packet isn't a object" << std::endl;
|
|
return PacketStatus::JSONNotObject;
|
|
}
|
|
object = *object_ptr;
|
|
return PacketStatus::Success;
|
|
}
|
|
|
|
bool DAP::HandleObject(const llvm::json::Object &object) {
|
|
const auto packet_type = GetString(object, "type");
|
|
if (packet_type == "request") {
|
|
const auto command = GetString(object, "command");
|
|
|
|
auto new_handler_pos = request_handlers.find(command);
|
|
if (new_handler_pos != request_handlers.end()) {
|
|
(*new_handler_pos->second)(object);
|
|
return true; // Success
|
|
}
|
|
|
|
if (log)
|
|
*log << "error: unhandled command \"" << command.data() << "\""
|
|
<< std::endl;
|
|
return false; // Fail
|
|
}
|
|
|
|
if (packet_type == "response") {
|
|
auto id = GetSigned(object, "request_seq", 0);
|
|
|
|
std::unique_ptr<ResponseHandler> response_handler;
|
|
{
|
|
std::lock_guard<std::mutex> locker(call_mutex);
|
|
auto inflight = inflight_reverse_requests.find(id);
|
|
if (inflight != inflight_reverse_requests.end()) {
|
|
response_handler = std::move(inflight->second);
|
|
inflight_reverse_requests.erase(inflight);
|
|
}
|
|
}
|
|
|
|
if (!response_handler)
|
|
response_handler = std::make_unique<UnknownResponseHandler>("", id);
|
|
|
|
// Result should be given, use null if not.
|
|
if (GetBoolean(object, "success", false)) {
|
|
llvm::json::Value Result = nullptr;
|
|
if (auto *B = object.get("body")) {
|
|
Result = std::move(*B);
|
|
}
|
|
(*response_handler)(Result);
|
|
} else {
|
|
llvm::StringRef message = GetString(object, "message");
|
|
if (message.empty()) {
|
|
message = "Unknown error, response failed";
|
|
}
|
|
(*response_handler)(llvm::createStringError(
|
|
std::error_code(-1, std::generic_category()), message));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void DAP::SendTerminatedEvent() {
|
|
// Prevent races if the process exits while we're being asked to disconnect.
|
|
llvm::call_once(terminated_event_flag, [&] {
|
|
RunTerminateCommands();
|
|
// Send a "terminated" event
|
|
llvm::json::Object event(CreateTerminatedEventObject(target));
|
|
SendJSON(llvm::json::Value(std::move(event)));
|
|
});
|
|
}
|
|
|
|
lldb::SBError DAP::Disconnect() { return Disconnect(is_attach); }
|
|
|
|
lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
|
|
lldb::SBError error;
|
|
lldb::SBProcess process = target.GetProcess();
|
|
auto state = process.GetState();
|
|
switch (state) {
|
|
case lldb::eStateInvalid:
|
|
case lldb::eStateUnloaded:
|
|
case lldb::eStateDetached:
|
|
case lldb::eStateExited:
|
|
break;
|
|
case lldb::eStateConnected:
|
|
case lldb::eStateAttaching:
|
|
case lldb::eStateLaunching:
|
|
case lldb::eStateStepping:
|
|
case lldb::eStateCrashed:
|
|
case lldb::eStateSuspended:
|
|
case lldb::eStateStopped:
|
|
case lldb::eStateRunning:
|
|
debugger.SetAsync(false);
|
|
error = terminateDebuggee ? process.Kill() : process.Detach();
|
|
debugger.SetAsync(true);
|
|
break;
|
|
}
|
|
|
|
SendTerminatedEvent();
|
|
|
|
// Stop forwarding the debugger output and error handles.
|
|
out.Stop();
|
|
err.Stop();
|
|
|
|
disconnecting = true;
|
|
|
|
return error;
|
|
}
|
|
|
|
llvm::Error DAP::Loop() {
|
|
auto cleanup = llvm::make_scope_exit([this]() { StopEventHandlers(); });
|
|
while (!disconnecting) {
|
|
llvm::json::Object object;
|
|
lldb_dap::PacketStatus status = GetNextObject(object);
|
|
|
|
if (status == lldb_dap::PacketStatus::EndOfFile) {
|
|
break;
|
|
}
|
|
|
|
if (status != lldb_dap::PacketStatus::Success) {
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"failed to send packet");
|
|
}
|
|
|
|
if (!HandleObject(object)) {
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"unhandled packet");
|
|
}
|
|
}
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
lldb::SBError DAP::WaitForProcessToStop(uint32_t seconds) {
|
|
lldb::SBError error;
|
|
lldb::SBProcess process = target.GetProcess();
|
|
if (!process.IsValid()) {
|
|
error.SetErrorString("invalid process");
|
|
return error;
|
|
}
|
|
auto timeout_time =
|
|
std::chrono::steady_clock::now() + std::chrono::seconds(seconds);
|
|
while (std::chrono::steady_clock::now() < timeout_time) {
|
|
const auto state = process.GetState();
|
|
switch (state) {
|
|
case lldb::eStateAttaching:
|
|
case lldb::eStateConnected:
|
|
case lldb::eStateInvalid:
|
|
case lldb::eStateLaunching:
|
|
case lldb::eStateRunning:
|
|
case lldb::eStateStepping:
|
|
case lldb::eStateSuspended:
|
|
break;
|
|
case lldb::eStateDetached:
|
|
error.SetErrorString("process detached during launch or attach");
|
|
return error;
|
|
case lldb::eStateExited:
|
|
error.SetErrorString("process exited during launch or attach");
|
|
return error;
|
|
case lldb::eStateUnloaded:
|
|
error.SetErrorString("process unloaded during launch or attach");
|
|
return error;
|
|
case lldb::eStateCrashed:
|
|
case lldb::eStateStopped:
|
|
return lldb::SBError(); // Success!
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::microseconds(250));
|
|
}
|
|
error.SetErrorStringWithFormat("process failed to stop within %u seconds",
|
|
seconds);
|
|
return error;
|
|
}
|
|
|
|
void Variables::Clear() {
|
|
locals.Clear();
|
|
globals.Clear();
|
|
registers.Clear();
|
|
referenced_variables.clear();
|
|
}
|
|
|
|
int64_t Variables::GetNewVariableReference(bool is_permanent) {
|
|
if (is_permanent)
|
|
return next_permanent_var_ref++;
|
|
return next_temporary_var_ref++;
|
|
}
|
|
|
|
bool Variables::IsPermanentVariableReference(int64_t var_ref) {
|
|
return var_ref >= PermanentVariableStartIndex;
|
|
}
|
|
|
|
lldb::SBValue Variables::GetVariable(int64_t var_ref) const {
|
|
if (IsPermanentVariableReference(var_ref)) {
|
|
auto pos = referenced_permanent_variables.find(var_ref);
|
|
if (pos != referenced_permanent_variables.end())
|
|
return pos->second;
|
|
} else {
|
|
auto pos = referenced_variables.find(var_ref);
|
|
if (pos != referenced_variables.end())
|
|
return pos->second;
|
|
}
|
|
return lldb::SBValue();
|
|
}
|
|
|
|
int64_t Variables::InsertVariable(lldb::SBValue variable, bool is_permanent) {
|
|
int64_t var_ref = GetNewVariableReference(is_permanent);
|
|
if (is_permanent)
|
|
referenced_permanent_variables.insert(std::make_pair(var_ref, variable));
|
|
else
|
|
referenced_variables.insert(std::make_pair(var_ref, variable));
|
|
return var_ref;
|
|
}
|
|
|
|
bool StartDebuggingRequestHandler::DoExecute(
|
|
lldb::SBDebugger debugger, char **command,
|
|
lldb::SBCommandReturnObject &result) {
|
|
// Command format like: `start-debugging <launch|attach> <configuration>`
|
|
if (!command) {
|
|
result.SetError("Invalid use of start-debugging, expected format "
|
|
"`start-debugging <launch|attach> <configuration>`.");
|
|
return false;
|
|
}
|
|
|
|
if (!command[0] || llvm::StringRef(command[0]).empty()) {
|
|
result.SetError("start-debugging request type missing.");
|
|
return false;
|
|
}
|
|
|
|
if (!command[1] || llvm::StringRef(command[1]).empty()) {
|
|
result.SetError("start-debugging debug configuration missing.");
|
|
return false;
|
|
}
|
|
|
|
llvm::StringRef request{command[0]};
|
|
std::string raw_configuration{command[1]};
|
|
|
|
llvm::Expected<llvm::json::Value> configuration =
|
|
llvm::json::parse(raw_configuration);
|
|
|
|
if (!configuration) {
|
|
llvm::Error err = configuration.takeError();
|
|
std::string msg = "Failed to parse json configuration: " +
|
|
llvm::toString(std::move(err)) + "\n\n" +
|
|
raw_configuration;
|
|
result.SetError(msg.c_str());
|
|
return false;
|
|
}
|
|
|
|
dap.SendReverseRequest<LogFailureResponseHandler>(
|
|
"startDebugging",
|
|
llvm::json::Object{{"request", request},
|
|
{"configuration", std::move(*configuration)}});
|
|
|
|
result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger,
|
|
char **command,
|
|
lldb::SBCommandReturnObject &result) {
|
|
// Command format like: `repl-mode <variable|command|auto>?`
|
|
// If a new mode is not specified report the current mode.
|
|
if (!command || llvm::StringRef(command[0]).empty()) {
|
|
std::string mode;
|
|
switch (dap.repl_mode) {
|
|
case ReplMode::Variable:
|
|
mode = "variable";
|
|
break;
|
|
case ReplMode::Command:
|
|
mode = "command";
|
|
break;
|
|
case ReplMode::Auto:
|
|
mode = "auto";
|
|
break;
|
|
}
|
|
|
|
result.Printf("lldb-dap repl-mode %s.\n", mode.c_str());
|
|
result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
|
|
|
|
return true;
|
|
}
|
|
|
|
llvm::StringRef new_mode{command[0]};
|
|
|
|
if (new_mode == "variable") {
|
|
dap.repl_mode = ReplMode::Variable;
|
|
} else if (new_mode == "command") {
|
|
dap.repl_mode = ReplMode::Command;
|
|
} else if (new_mode == "auto") {
|
|
dap.repl_mode = ReplMode::Auto;
|
|
} else {
|
|
lldb::SBStream error_message;
|
|
error_message.Printf("Invalid repl-mode '%s'. Expected one of 'variable', "
|
|
"'command' or 'auto'.\n",
|
|
new_mode.data());
|
|
result.SetError(error_message.GetData());
|
|
return false;
|
|
}
|
|
|
|
result.Printf("lldb-dap repl-mode %s set.\n", new_mode.data());
|
|
result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
|
|
return true;
|
|
}
|
|
|
|
// Sends a DAP event with an optional body.
|
|
//
|
|
// See
|
|
// https://code.visualstudio.com/api/references/vscode-api#debug.onDidReceiveDebugSessionCustomEvent
|
|
bool SendEventRequestHandler::DoExecute(lldb::SBDebugger debugger,
|
|
char **command,
|
|
lldb::SBCommandReturnObject &result) {
|
|
// Command format like: `send-event <name> <body>?`
|
|
if (!command || !command[0] || llvm::StringRef(command[0]).empty()) {
|
|
result.SetError("Not enough arguments found, expected format "
|
|
"`lldb-dap send-event <name> <body>?`.");
|
|
return false;
|
|
}
|
|
|
|
llvm::StringRef name{command[0]};
|
|
// Events that are stateful and should be handled by lldb-dap internally.
|
|
const std::array internal_events{"breakpoint", "capabilities", "continued",
|
|
"exited", "initialize", "loadedSource",
|
|
"module", "process", "stopped",
|
|
"terminated", "thread"};
|
|
if (std::find(internal_events.begin(), internal_events.end(), name) !=
|
|
std::end(internal_events)) {
|
|
std::string msg =
|
|
llvm::formatv("Invalid use of lldb-dap send-event, event \"{0}\" "
|
|
"should be handled by lldb-dap internally.",
|
|
name)
|
|
.str();
|
|
result.SetError(msg.c_str());
|
|
return false;
|
|
}
|
|
|
|
llvm::json::Object event(CreateEventObject(name));
|
|
|
|
if (command[1] && !llvm::StringRef(command[1]).empty()) {
|
|
// See if we have unused arguments.
|
|
if (command[2]) {
|
|
result.SetError(
|
|
"Additional arguments found, expected `lldb-dap send-event "
|
|
"<name> <body>?`.");
|
|
return false;
|
|
}
|
|
|
|
llvm::StringRef raw_body{command[1]};
|
|
|
|
llvm::Expected<llvm::json::Value> body = llvm::json::parse(raw_body);
|
|
|
|
if (!body) {
|
|
llvm::Error err = body.takeError();
|
|
std::string msg = "Failed to parse custom event body: " +
|
|
llvm::toString(std::move(err));
|
|
result.SetError(msg.c_str());
|
|
return false;
|
|
}
|
|
|
|
event.try_emplace("body", std::move(*body));
|
|
}
|
|
|
|
dap.SendJSON(llvm::json::Value(std::move(event)));
|
|
result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
|
|
return true;
|
|
}
|
|
|
|
void DAP::SetFrameFormat(llvm::StringRef format) {
|
|
if (format.empty())
|
|
return;
|
|
lldb::SBError error;
|
|
frame_format = lldb::SBFormat(format.str().c_str(), error);
|
|
if (error.Fail()) {
|
|
SendOutput(OutputType::Console,
|
|
llvm::formatv(
|
|
"The provided frame format '{0}' couldn't be parsed: {1}\n",
|
|
format, error.GetCString())
|
|
.str());
|
|
}
|
|
}
|
|
|
|
void DAP::SetThreadFormat(llvm::StringRef format) {
|
|
if (format.empty())
|
|
return;
|
|
lldb::SBError error;
|
|
thread_format = lldb::SBFormat(format.str().c_str(), error);
|
|
if (error.Fail()) {
|
|
SendOutput(OutputType::Console,
|
|
llvm::formatv(
|
|
"The provided thread format '{0}' couldn't be parsed: {1}\n",
|
|
format, error.GetCString())
|
|
.str());
|
|
}
|
|
}
|
|
|
|
InstructionBreakpoint *
|
|
DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) {
|
|
for (auto &bp : instruction_breakpoints) {
|
|
if (bp.second.bp.GetID() == bp_id)
|
|
return &bp.second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
InstructionBreakpoint *
|
|
DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) {
|
|
const auto num = thread.GetStopReasonDataCount();
|
|
InstructionBreakpoint *inst_bp = nullptr;
|
|
for (size_t i = 0; i < num; i += 2) {
|
|
// thread.GetStopReasonDataAtIndex(i) will return the bp ID and
|
|
// thread.GetStopReasonDataAtIndex(i+1) will return the location
|
|
// within that breakpoint. We only care about the bp ID so we can
|
|
// see if this is an instruction breakpoint that is getting hit.
|
|
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
|
|
inst_bp = GetInstructionBreakpoint(bp_id);
|
|
// If any breakpoint is not an instruction breakpoint, then stop and
|
|
// report this as a normal breakpoint
|
|
if (inst_bp == nullptr)
|
|
return nullptr;
|
|
}
|
|
return inst_bp;
|
|
}
|
|
|
|
lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
|
|
switch (variablesReference) {
|
|
case VARREF_LOCALS:
|
|
return &locals;
|
|
case VARREF_GLOBALS:
|
|
return &globals;
|
|
case VARREF_REGS:
|
|
return ®isters;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
lldb::SBValue Variables::FindVariable(uint64_t variablesReference,
|
|
llvm::StringRef name) {
|
|
lldb::SBValue variable;
|
|
if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) {
|
|
bool is_duplicated_variable_name = name.contains(" @");
|
|
// variablesReference is one of our scopes, not an actual variable it is
|
|
// asking for a variable in locals or globals or registers
|
|
int64_t end_idx = top_scope->GetSize();
|
|
// Searching backward so that we choose the variable in closest scope
|
|
// among variables of the same name.
|
|
for (int64_t i = end_idx - 1; i >= 0; --i) {
|
|
lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
|
|
std::string variable_name = CreateUniqueVariableNameForDisplay(
|
|
curr_variable, is_duplicated_variable_name);
|
|
if (variable_name == name) {
|
|
variable = curr_variable;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// This is not under the globals or locals scope, so there are no duplicated
|
|
// names.
|
|
|
|
// We have a named item within an actual variable so we need to find it
|
|
// withing the container variable by name.
|
|
lldb::SBValue container = GetVariable(variablesReference);
|
|
variable = container.GetChildMemberWithName(name.data());
|
|
if (!variable.IsValid()) {
|
|
if (name.starts_with("[")) {
|
|
llvm::StringRef index_str(name.drop_front(1));
|
|
uint64_t index = 0;
|
|
if (!index_str.consumeInteger(0, index)) {
|
|
if (index_str == "]")
|
|
variable = container.GetChildAtIndex(index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return variable;
|
|
}
|
|
|
|
} // namespace lldb_dap
|