Skip to content

[lldb-dap] Refactor breakpoint related request handlers (NFC) #128550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ add_lldb_tool(lldb-dap
Handler/CompletionsHandler.cpp
Handler/ConfigurationDoneRequestHandler.cpp
Handler/ContinueRequestHandler.cpp
Handler/DataBreakpointInfoRequestHandler.cpp
Handler/DisconnectRequestHandler.cpp
Handler/EvaluateRequestHandler.cpp
Handler/ExceptionInfoRequestHandler.cpp
Expand All @@ -52,10 +53,15 @@ add_lldb_tool(lldb-dap
Handler/NextRequestHandler.cpp
Handler/RequestHandler.cpp
Handler/RestartRequestHandler.cpp
Handler/SetBreakpointsRequestHandler.cpp
Handler/SetDataBreakpointsRequestHandler.cpp
Handler/SetExceptionBreakpointsRequestHandler.cpp
Handler/SetFunctionBreakpointsRequestHandler.cpp
Handler/SetInstructionBreakpointsRequestHandler.cpp
Handler/StepInRequestHandler.cpp
Handler/StepInTargetsRequestHandler.cpp
Handler/TestGetTargetBreakpointsRequestHandler.cpp
Handler/StepOutRequestHandler.cpp
Handler/TestGetTargetBreakpointsRequestHandler.cpp

LINK_LIBS
liblldb
Expand Down
54 changes: 54 additions & 0 deletions lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1201,4 +1201,58 @@ DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) {
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 &registers;
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
4 changes: 4 additions & 0 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ struct Variables {
/// \return variableReference assigned to this expandable variable.
int64_t InsertVariable(lldb::SBValue variable, bool is_permanent);

lldb::SBValueList *GetTopLevelScope(int64_t variablesReference);

lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name);

/// Clear all scope variables and non-permanent expandable variables.
void Clear();
};
Expand Down
190 changes: 190 additions & 0 deletions lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//===-- DataBreakpointInfoRequestHandler.cpp ------------------------------===//
//
// 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 "EventHelper.h"
#include "JSONUtils.h"
#include "RequestHandler.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "llvm/ADT/StringExtras.h"

namespace lldb_dap {

// "DataBreakpointInfoRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
// "description": "Obtains information on a possible data breakpoint that
// could be set on an expression or variable.\nClients should only call this
// request if the corresponding capability `supportsDataBreakpoints` is
// true.", "properties": {
// "command": {
// "type": "string",
// "enum": [ "dataBreakpointInfo" ]
// },
// "arguments": {
// "$ref": "#/definitions/DataBreakpointInfoArguments"
// }
// },
// "required": [ "command", "arguments" ]
// }]
// },
// "DataBreakpointInfoArguments": {
// "type": "object",
// "description": "Arguments for `dataBreakpointInfo` request.",
// "properties": {
// "variablesReference": {
// "type": "integer",
// "description": "Reference to the variable container if the data
// breakpoint is requested for a child of the container. The
// `variablesReference` must have been obtained in the current suspended
// state. See 'Lifetime of Object References' in the Overview section for
// details."
// },
// "name": {
// "type": "string",
// "description": "The name of the variable's child to obtain data
// breakpoint information for.\nIf `variablesReference` isn't specified,
// this can be an expression."
// },
// "frameId": {
// "type": "integer",
// "description": "When `name` is an expression, evaluate it in the scope
// of this stack frame. If not specified, the expression is evaluated in
// the global scope. When `variablesReference` is specified, this property
// has no effect."
// }
// },
// "required": [ "name" ]
// },
// "DataBreakpointInfoResponse": {
// "allOf": [ { "$ref": "#/definitions/Response" }, {
// "type": "object",
// "description": "Response to `dataBreakpointInfo` request.",
// "properties": {
// "body": {
// "type": "object",
// "properties": {
// "dataId": {
// "type": [ "string", "null" ],
// "description": "An identifier for the data on which a data
// breakpoint can be registered with the `setDataBreakpoints`
// request or null if no data breakpoint is available. If a
// `variablesReference` or `frameId` is passed, the `dataId` is
// valid in the current suspended state, otherwise it's valid
// indefinitely. See 'Lifetime of Object References' in the Overview
// section for details. Breakpoints set using the `dataId` in the
// `setDataBreakpoints` request may outlive the lifetime of the
// associated `dataId`."
// },
// "description": {
// "type": "string",
// "description": "UI string that describes on what data the
// breakpoint is set on or why a data breakpoint is not available."
// },
// "accessTypes": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/DataBreakpointAccessType"
// },
// "description": "Attribute lists the available access types for a
// potential data breakpoint. A UI client could surface this
// information."
// },
// "canPersist": {
// "type": "boolean",
// "description": "Attribute indicates that a potential data
// breakpoint could be persisted across sessions."
// }
// },
// "required": [ "dataId", "description" ]
// }
// },
// "required": [ "body" ]
// }]
// }
void DataBreakpointInfoRequestHandler::operator()(
const llvm::json::Object &request) {
llvm::json::Object response;
FillResponse(request, response);
llvm::json::Object body;
lldb::SBError error;
llvm::json::Array accessTypes{"read", "write", "readWrite"};
const auto *arguments = request.getObject("arguments");
const auto variablesReference =
GetUnsigned(arguments, "variablesReference", 0);
llvm::StringRef name = GetString(arguments, "name");
lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
lldb::SBValue variable = dap.variables.FindVariable(variablesReference, name);
std::string addr, size;

if (variable.IsValid()) {
lldb::addr_t load_addr = variable.GetLoadAddress();
size_t byte_size = variable.GetByteSize();
if (load_addr == LLDB_INVALID_ADDRESS) {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"does not exist in memory, its location is " +
std::string(variable.GetLocation()));
} else if (byte_size == 0) {
body.try_emplace("dataId", nullptr);
body.try_emplace("description", "variable size is 0");
} else {
addr = llvm::utohexstr(load_addr);
size = llvm::utostr(byte_size);
}
} else if (variablesReference == 0 && frame.IsValid()) {
lldb::SBValue value = frame.EvaluateExpression(name.data());
if (value.GetError().Fail()) {
lldb::SBError error = value.GetError();
const char *error_cstr = error.GetCString();
body.try_emplace("dataId", nullptr);
body.try_emplace("description", error_cstr && error_cstr[0]
? std::string(error_cstr)
: "evaluation failed");
} else {
uint64_t load_addr = value.GetValueAsUnsigned();
lldb::SBData data = value.GetPointeeData();
if (data.IsValid()) {
size = llvm::utostr(data.GetByteSize());
addr = llvm::utohexstr(load_addr);
lldb::SBMemoryRegionInfo region;
lldb::SBError err =
dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
// Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
// request if SBProcess::GetMemoryRegionInfo returns error.
if (err.Success()) {
if (!(region.IsReadable() || region.IsWritable())) {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"memory region for address " + addr +
" has no read or write permissions");
}
}
} else {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"unable to get byte size for expression: " +
name.str());
}
}
} else {
body.try_emplace("dataId", nullptr);
body.try_emplace("description", "variable not found: " + name.str());
}

if (!body.getObject("dataId")) {
body.try_emplace("dataId", addr + "/" + size);
body.try_emplace("accessTypes", std::move(accessTypes));
body.try_emplace("description",
size + " bytes at " + addr + " " + name.str());
}
response.try_emplace("body", std::move(body));
dap.SendJSON(llvm::json::Value(std::move(response)));
}

} // namespace lldb_dap
44 changes: 44 additions & 0 deletions lldb/tools/lldb-dap/Handler/RequestHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,50 @@ class StepOutRequestHandler : public RequestHandler {
void operator()(const llvm::json::Object &request) override;
};

class SetBreakpointsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral getCommand() { return "setBreakpoints"; }
void operator()(const llvm::json::Object &request) override;
};

class SetExceptionBreakpointsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral getCommand() { return "setExceptionBreakpoints"; }
void operator()(const llvm::json::Object &request) override;
};

class SetFunctionBreakpointsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral getCommand() { return "setFunctionBreakpoints"; }
void operator()(const llvm::json::Object &request) override;
};

class DataBreakpointInfoRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral getCommand() { return "dataBreakpointInfo"; }
void operator()(const llvm::json::Object &request) override;
};

class SetDataBreakpointsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral getCommand() { return "setDataBreakpoints"; }
void operator()(const llvm::json::Object &request) override;
};

class SetInstructionBreakpointsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral getCommand() {
return "setInstructionBreakpoints";
}
void operator()(const llvm::json::Object &request) override;
};

class CompileUnitsRequestHandler : public RequestHandler {
public:
using RequestHandler::RequestHandler;
Expand Down
Loading