Skip to content

[lldb][lldb-dap] Support breakpoint info bytes #141122

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -1042,16 +1042,33 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
return self.send_recv(command_dict)

def request_dataBreakpointInfo(
self, variablesReference, name, frameIndex=0, threadId=None
self,
name: str,
variablesReference: Optional[int] = None,
frameIndex: Optional[int] = 0,
bytes_: Optional[int] = None,
asAddress: Optional[bool] = None,
):
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
if stackFrame is None:
return []
args_dict = {
"variablesReference": variablesReference,
"name": name,
"frameId": stackFrame["id"],
}
args_dict = {}
if asAddress is not None:
args_dict = {
"name": name,
"asAddress": asAddress,
"bytes": bytes_,
}
else:
thread_id = self.get_thread_id()
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=thread_id)
if stackFrame is None:
raise ValueError(
f"could not get stackframe for frameIndex: {frameIndex} and threadId: {thread_id}"
)
args_dict = {
"variablesReference": variablesReference,
"name": name,
"frameId": stackFrame["id"],
}

command_dict = {
"command": "dataBreakpointInfo",
"type": "request",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ def waitUntil(self, condition_callback):
time.sleep(0.5)
return False

def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
def verify_breakpoint_hit(
self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, reason: Optional[str] = None
):
"""Wait for the process we are debugging to stop, and verify we hit
any breakpoint location in the "breakpoint_ids" array.
"breakpoint_ids" should be a list of breakpoint ID strings
Expand All @@ -116,9 +118,10 @@ def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
body = stopped_event["body"]
if "reason" not in body:
continue
if (
body["reason"] != "breakpoint"
and body["reason"] != "instruction breakpoint"
if body["reason"] not in (
"breakpoint",
"instruction breakpoint",
"data breakpoint",
):
continue
if "description" not in body:
Expand All @@ -131,9 +134,10 @@ def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
# So when looking at the description we just want to make sure
# the right breakpoint matches and not worry about the actual
# location.
type_name = reason or "breakpoint"
description = body["description"]
for breakpoint_id in breakpoint_ids:
match_desc = f"breakpoint {breakpoint_id}."
match_desc = f"{type_name} {breakpoint_id}"
if match_desc in description:
return
self.assertTrue(False, f"breakpoint not hit, stopped_events={stopped_events}")
Expand Down Expand Up @@ -329,12 +333,16 @@ def continue_to_next_stop(self, timeout=DEFAULT_TIMEOUT):
self.do_continue()
return self.dap_server.wait_for_stopped(timeout)

def continue_to_breakpoint(self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT):
self.continue_to_breakpoints((breakpoint_id), timeout)
def continue_to_breakpoint(
self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT, reason: Optional[str] = None
):
self.continue_to_breakpoints([breakpoint_id], timeout, reason)

def continue_to_breakpoints(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
def continue_to_breakpoints(
self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, reason: Optional[str] = None
):
self.do_continue()
self.verify_breakpoint_hit(breakpoint_ids, timeout)
self.verify_breakpoint_hit(breakpoint_ids, timeout, reason)

def continue_to_exception_breakpoint(self, filter_label, timeout=DEFAULT_TIMEOUT):
self.do_continue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,8 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
if (region_info.GetRange() == qXfer_region_info.GetRange()) {
region_info.SetFlash(qXfer_region_info.GetFlash());
region_info.SetBlocksize(qXfer_region_info.GetBlocksize());
region_info.SetReadable(qXfer_region_info.GetReadable());
region_info.SetWritable(qXfer_region_info.GetWritable());
}
}
return error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def test_duplicate_start_addresses(self):
self.continue_to_next_stop()
self.dap_server.get_stackFrame()
# Test setting write watchpoint using expressions: &x, arr+2
response_x = self.dap_server.request_dataBreakpointInfo(0, "&x")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(0, "arr+2")
response_x = self.dap_server.request_dataBreakpointInfo("&x", 0)
response_arr_2 = self.dap_server.request_dataBreakpointInfo("arr+2", 0)
# Test response from dataBreakpointInfo request.
self.assertEqual(response_x["body"]["dataId"].split("/")[1], "4")
self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes)
Expand Down Expand Up @@ -56,6 +56,47 @@ def test_duplicate_start_addresses(self):
self.assertEqual(arr_2["value"], "42")
self.assertEqual(i_val, "2")

@skipIfWindows
def test_breakpoint_info_bytes(self):
"""Test supportBreakpointInfoBytes
Set the watchpoint on `var` variable address + 6 characters.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.cpp"
first_loop_break_line = line_number(source, "// first loop breakpoint")
self.set_source_breakpoints(source, [first_loop_break_line])
self.continue_to_next_stop()

# get the address of `var` variable
eval_response = self.dap_server.request_evaluate("&var", context="watch")
self.assertTrue(eval_response["success"])
var_address = eval_response["body"]["result"]

var_byte_watch_size = 5
bp_resp = self.dap_server.request_dataBreakpointInfo(
var_address, asAddress=True, bytes_=var_byte_watch_size
)
self.assertTrue(
bp_resp["success"], f"dataBreakpointInfo request failed: {bp_resp}"
)
resp_data_id = bp_resp["body"]["dataId"]
self.assertEqual(resp_data_id.split("/")[1], str(var_byte_watch_size))

data_breakpoints = [{"dataId": resp_data_id, "accessType": "write"}]
self.dap_server.request_setDataBreakpoint(data_breakpoints)

self.continue_to_breakpoint(breakpoint_id=1, reason="data breakpoint")
eval_response = self.dap_server.request_evaluate("var", context="watch")
self.assertTrue(eval_response["success"])
var_value = eval_response["body"]["result"]
self.assertEqual(var_value, '"HALLO"')

# Remove the watchpoint because once it leaves this function scope, the address can be
# be used by another variable or register.
self.dap_server.request_setDataBreakpoint([])
self.continue_to_exit()

@skipIfWindows
def test_expression(self):
"""Tests setting data breakpoints on expression."""
Expand All @@ -67,8 +108,8 @@ def test_expression(self):
self.continue_to_next_stop()
self.dap_server.get_stackFrame()
# Test setting write watchpoint using expressions: &x, arr+2
response_x = self.dap_server.request_dataBreakpointInfo(0, "&x")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(0, "arr+2")
response_x = self.dap_server.request_dataBreakpointInfo("&x", 0)
response_arr_2 = self.dap_server.request_dataBreakpointInfo("arr+2", 0)
# Test response from dataBreakpointInfo request.
self.assertEqual(response_x["body"]["dataId"].split("/")[1], "4")
self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes)
Expand Down Expand Up @@ -107,10 +148,10 @@ def test_functionality(self):
self.continue_to_next_stop()
self.dap_server.get_local_variables()
# Test write watchpoints on x, arr[2]
response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
response_x = self.dap_server.request_dataBreakpointInfo("x", 1)
arr = self.dap_server.get_local_variable("arr")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
arr["variablesReference"], "[2]"
"[2]", arr["variablesReference"]
)

# Test response from dataBreakpointInfo request.
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
int main(int argc, char const *argv[]) {
// Test for data breakpoint
char var[6] = "HELLO";
int x = 0;
int arr[4] = {1, 2, 3, 4};
for (int i = 0; i < 5; ++i) { // first loop breakpoint
Expand All @@ -10,6 +11,8 @@ int main(int argc, char const *argv[]) {
}
}

var[1] = 'A';

x = 1;
for (int i = 0; i < 10; ++i) { // second loop breakpoint
++x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//

#include "DAP.h"
#include "EventHelper.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
#include "lldb/API/SBMemoryRegionInfo.h"
Expand All @@ -16,12 +15,80 @@

namespace lldb_dap {

namespace {
std::vector<protocol::DataBreakpointAccessType>
GetBreakpointAccessTypes(lldb::SBMemoryRegionInfo region) {
std::vector<protocol::DataBreakpointAccessType> types;
if (region.IsReadable())
types.emplace_back(protocol::eDataBreakpointAccessTypeRead);
if (region.IsWritable())
types.emplace_back(protocol::eDataBreakpointAccessTypeWrite);
if (region.IsReadable() && region.IsWritable())
types.emplace_back(protocol::eDataBreakpointAccessTypeReadWrite);

return types;
}

llvm::Expected<protocol::DataBreakpointInfoResponseBody>
HandleDataBreakpointBytes(DAP &dap,
const protocol::DataBreakpointInfoArguments &args) {
const llvm::StringRef raw_address = args.name;

lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
if (raw_address.getAsInteger<lldb::addr_t>(0, load_addr)) {
return llvm::make_error<DAPError>(llvm::formatv("invalid address"),
llvm::inconvertibleErrorCode(), false);
}

if (lldb::SBAddress address(load_addr, dap.target); !address.IsValid()) {
return llvm::make_error<DAPError>(
llvm::formatv("address {:x} does not exist in the debuggee", load_addr),
llvm::inconvertibleErrorCode(), false);
}

const uint32_t byte_size =
args.bytes.value_or(dap.target.GetAddressByteSize());

lldb::SBMemoryRegionInfo region;
lldb::SBError err =
dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
std::vector<protocol::DataBreakpointAccessType> access_types =
GetBreakpointAccessTypes(region);

protocol::DataBreakpointInfoResponseBody response;
if (err.Fail()) {
response.dataId = std::nullopt;
response.description = err.GetCString();
return response;
}

if (access_types.empty()) {
response.dataId = std::nullopt;
response.description = llvm::formatv(
"memory region for address {} has no read or write permissions",
load_addr);
return response;
}

response.dataId = llvm::formatv("{:x-}/{}", load_addr, byte_size);
response.description =
llvm::formatv("{} bytes at {:x}", byte_size, load_addr);
response.accessTypes = std::move(access_types);

return response;
}
} // namespace

/// Obtains information on a possible data breakpoint that could be set on an
/// expression or variable. Clients should only call this request if the
/// corresponding capability supportsDataBreakpoints is true.
llvm::Expected<protocol::DataBreakpointInfoResponseBody>
DataBreakpointInfoRequestHandler::Run(
const protocol::DataBreakpointInfoArguments &args) const {

if (args.asAddress.value_or(false))
return HandleDataBreakpointBytes(dap, args);

protocol::DataBreakpointInfoResponseBody response;
lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId.value_or(UINT64_MAX));
lldb::SBValue variable = dap.variables.FindVariable(
Expand All @@ -40,7 +107,7 @@ DataBreakpointInfoRequestHandler::Run(
is_data_ok = false;
response.description = "variable size is 0";
} else {
addr = llvm::utohexstr(load_addr);
addr = llvm::utohexstr(load_addr, /*lowerCase=*/true);
size = llvm::utostr(byte_size);
}
} else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
Expand All @@ -57,7 +124,7 @@ DataBreakpointInfoRequestHandler::Run(
lldb::SBData data = value.GetPointeeData();
if (data.IsValid()) {
size = llvm::utostr(data.GetByteSize());
addr = llvm::utohexstr(load_addr);
addr = llvm::utohexstr(load_addr, /*lowerCase=*/true);
lldb::SBMemoryRegionInfo region;
lldb::SBError err =
dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
Expand Down Expand Up @@ -86,7 +153,7 @@ DataBreakpointInfoRequestHandler::Run(
response.accessTypes = {protocol::eDataBreakpointAccessTypeRead,
protocol::eDataBreakpointAccessTypeWrite,
protocol::eDataBreakpointAccessTypeReadWrite};
response.description = size + " bytes at " + addr + " " + args.name;
response.description = size + " bytes at 0x" + addr + " " + args.name;
}

return response;
Expand Down
3 changes: 3 additions & 0 deletions lldb/tools/lldb-dap/Handler/RequestHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ class DataBreakpointInfoRequestHandler
public:
using RequestHandler::RequestHandler;
static llvm::StringLiteral GetCommand() { return "dataBreakpointInfo"; }
FeatureSet GetSupportedFeatures() const override {
return {protocol::eAdapterFeatureDataBreakpointBytes};
}
llvm::Expected<protocol::DataBreakpointInfoResponseBody>
Run(const protocol::DataBreakpointInfoArguments &args) const override;
};
Expand Down
11 changes: 10 additions & 1 deletion lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,16 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
EmplaceSafeString(body, "description", desc_str);
}
} break;
case lldb::eStopReasonWatchpoint:
case lldb::eStopReasonWatchpoint: {
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);
lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(1);
std::string desc_str =
llvm::formatv("data breakpoint {0}.{1}", bp_id, bp_loc_id);
body.try_emplace("hitBreakpointIds",
llvm::json::Array{llvm::json::Value(bp_id)});
body.try_emplace("reason", "data breakpoint");
EmplaceSafeString(body, "description", desc_str);
} break;
case lldb::eStopReasonInstrumentation:
body.try_emplace("reason", "breakpoint");
break;
Expand Down
2 changes: 1 addition & 1 deletion lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ struct DataBreakpointInfoArguments {
/// pause on data access anywhere within that range.
/// Clients may set this property only if the `supportsDataBreakpointBytes`
/// capability is true.
std::optional<int64_t> bytes;
std::optional<uint64_t> bytes;

/// If `true`, the `name` is a memory address and the debugger should
/// interpret it as a decimal value, or hex value if it is prefixed with `0x`.
Expand Down
Loading