[Lldb-commits] [lldb] 207627f - [lldb-dap] Add data breakpoints for bytes (#167237)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Nov 28 07:20:24 PST 2025
Author: Sergei Druzhkov
Date: 2025-11-28T18:20:20+03:00
New Revision: 207627f20f06771edfbb1e779f94dfdc2ff7df26
URL: https://github.com/llvm/llvm-project/commit/207627f20f06771edfbb1e779f94dfdc2ff7df26
DIFF: https://github.com/llvm/llvm-project/commit/207627f20f06771edfbb1e779f94dfdc2ff7df26.diff
LOG: [lldb-dap] Add data breakpoints for bytes (#167237)
This patch adds support for `dataBreakpointInfoBytes` capability from
DAP. You can test this feature in VSCode (`Add data breakpoint at
address` button in breakpoints tab).
Added:
Modified:
lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
lldb/tools/lldb-dap/Handler/RequestHandler.h
lldb/tools/lldb-dap/JSONUtils.cpp
lldb/tools/lldb-dap/Watchpoint.cpp
Removed:
################################################################################
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 35a4f8934e961..4a7ba78b63993 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -1265,16 +1265,18 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
return response
def request_dataBreakpointInfo(
- self, variablesReference, name, frameIndex=0, threadId=None
+ self, variablesReference, name, size=None, frameIndex=0, threadId=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 = {"name": name}
+ if size is None:
+ args_dict["variablesReference"] = variablesReference
+ args_dict["frameId"] = stackFrame["id"]
+ else:
+ args_dict["asAddress"] = True
+ args_dict["bytes"] = size
command_dict = {
"command": "dataBreakpointInfo",
"type": "request",
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index 71ca60ebe8d34..c7d302cc2dea2 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -169,6 +169,7 @@ def verify_breakpoint_hit(self, breakpoint_ids: List[Union[int, str]]):
if (
body["reason"] != "breakpoint"
and body["reason"] != "instruction breakpoint"
+ and body["reason"] != "data breakpoint"
):
continue
if "hitBreakpointIds" not in body:
diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
index a542a318050dd..df029ca16d667 100644
--- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
@@ -39,18 +39,21 @@ def test_duplicate_start_addresses(self):
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
- self.assertEqual(
- set_response["body"]["breakpoints"],
- [{"verified": False}, {"verified": True}, {"verified": True}],
- )
+ breakpoints = set_response["body"]["breakpoints"]
+ self.assertEqual(len(breakpoints), 3)
+ self.assertFalse(breakpoints[0]["verified"])
+ self.assertTrue(breakpoints[1]["verified"])
+ self.assertTrue(breakpoints[2]["verified"])
- self.continue_to_next_stop()
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[2]["id"]])
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEqual(x_val, "2")
self.assertEqual(i_val, "1")
- self.continue_to_next_stop()
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[1]["id"]])
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEqual(arr_2["value"], "42")
@@ -79,18 +82,20 @@ def test_expression(self):
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
- self.assertEqual(
- set_response["body"]["breakpoints"],
- [{"verified": True}, {"verified": True}],
- )
+ breakpoints = set_response["body"]["breakpoints"]
+ self.assertEqual(len(breakpoints), 2)
+ self.assertTrue(breakpoints[0]["verified"])
+ self.assertTrue(breakpoints[1]["verified"])
- self.continue_to_next_stop()
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[0]["id"]])
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEqual(x_val, "2")
self.assertEqual(i_val, "1")
- self.continue_to_next_stop()
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[1]["id"]])
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEqual(arr_2["value"], "42")
@@ -123,18 +128,20 @@ def test_functionality(self):
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
- self.assertEqual(
- set_response["body"]["breakpoints"],
- [{"verified": True}, {"verified": True}],
- )
+ breakpoints = set_response["body"]["breakpoints"]
+ self.assertEqual(len(breakpoints), 2)
+ self.assertTrue(breakpoints[0]["verified"])
+ self.assertTrue(breakpoints[1]["verified"])
- self.continue_to_next_stop()
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[0]["id"]])
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEqual(x_val, "2")
self.assertEqual(i_val, "1")
- self.continue_to_next_stop()
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[1]["id"]])
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEqual(arr_2["value"], "42")
@@ -153,8 +160,11 @@ def test_functionality(self):
}
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
- self.assertEqual(set_response["body"]["breakpoints"], [{"verified": True}])
- self.continue_to_next_stop()
+ breakpoints = set_response["body"]["breakpoints"]
+ self.assertEqual(len(breakpoints), 1)
+ self.assertTrue(breakpoints[0]["verified"])
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[0]["id"]])
x_val = self.dap_server.get_local_variable_value("x")
self.assertEqual(x_val, "3")
@@ -167,7 +177,64 @@ def test_functionality(self):
}
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
- self.assertEqual(set_response["body"]["breakpoints"], [{"verified": True}])
- self.continue_to_next_stop()
+ breakpoints = set_response["body"]["breakpoints"]
+ self.assertEqual(len(breakpoints), 1)
+ self.assertTrue(breakpoints[0]["verified"])
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[0]["id"]])
x_val = self.dap_server.get_local_variable_value("x")
self.assertEqual(x_val, "10")
+
+ @skipIfWindows
+ def test_bytes(self):
+ """Tests setting data breakpoints on memory range."""
+ 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()
+ # Test write watchpoints on x, arr[2]
+ x = self.dap_server.get_local_variable("x")
+ response_x = self.dap_server.request_dataBreakpointInfo(
+ 0, x["memoryReference"], 4
+ )
+ arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
+ response_arr_2 = self.dap_server.request_dataBreakpointInfo(
+ 0, arr_2["memoryReference"], 4
+ )
+
+ # Test response from dataBreakpointInfo request.
+ self.assertEqual(
+ response_x["body"]["dataId"].split("/"), [x["memoryReference"][2:], "4"]
+ )
+ self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes)
+ self.assertEqual(
+ response_arr_2["body"]["dataId"].split("/"),
+ [arr_2["memoryReference"][2:], "4"],
+ )
+ self.assertEqual(response_arr_2["body"]["accessTypes"], self.accessTypes)
+ dataBreakpoints = [
+ {"dataId": response_x["body"]["dataId"], "accessType": "write"},
+ {"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
+ ]
+ set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
+ breakpoints = set_response["body"]["breakpoints"]
+ self.assertEqual(len(breakpoints), 2)
+ self.assertTrue(breakpoints[0]["verified"])
+ self.assertTrue(breakpoints[1]["verified"])
+
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[0]["id"]])
+ x_val = self.dap_server.get_local_variable_value("x")
+ i_val = self.dap_server.get_local_variable_value("i")
+ self.assertEqual(x_val, "2")
+ self.assertEqual(i_val, "1")
+
+ self.dap_server.request_continue()
+ self.verify_breakpoint_hit([breakpoints[1]["id"]])
+ arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
+ i_val = self.dap_server.get_local_variable_value("i")
+ self.assertEqual(arr_2["value"], "42")
+ self.assertEqual(i_val, "2")
+ self.dap_server.request_setDataBreakpoint([])
diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
index 87b93fc999ecd..245d92c18e59e 100644
--- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
@@ -7,15 +7,33 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
+#include "DAPError.h"
#include "EventHelper.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
+#include "lldb/API/SBAddress.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "llvm/ADT/StringExtras.h"
#include <optional>
namespace lldb_dap {
+static bool IsRW(DAP &dap, lldb::addr_t load_addr) {
+ if (!lldb::SBAddress(load_addr, dap.target).IsValid())
+ return false;
+ 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())) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// 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.
@@ -23,7 +41,6 @@ llvm::Expected<protocol::DataBreakpointInfoResponseBody>
DataBreakpointInfoRequestHandler::Run(
const protocol::DataBreakpointInfoArguments &args) const {
protocol::DataBreakpointInfoResponseBody response;
- lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
lldb::SBValue variable = dap.variables.FindVariable(
args.variablesReference.value_or(0), args.name);
std::string addr, size;
@@ -43,7 +60,8 @@ DataBreakpointInfoRequestHandler::Run(
addr = llvm::utohexstr(load_addr);
size = llvm::utostr(byte_size);
}
- } else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
+ } else if (lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
+ args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
lldb::SBValue value = frame.EvaluateExpression(args.name.c_str());
if (value.GetError().Fail()) {
lldb::SBError error = value.GetError();
@@ -58,17 +76,10 @@ DataBreakpointInfoRequestHandler::Run(
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())) {
- is_data_ok = false;
- response.description = "memory region for address " + addr +
- " has no read or write permissions";
- }
+ if (!IsRW(dap, load_addr)) {
+ is_data_ok = false;
+ response.description = "memory region for address " + addr +
+ " has no read or write permissions";
}
} else {
is_data_ok = false;
@@ -76,6 +87,17 @@ DataBreakpointInfoRequestHandler::Run(
"unable to get byte size for expression: " + args.name;
}
}
+ } else if (args.asAddress) {
+ size = llvm::utostr(args.bytes.value_or(dap.target.GetAddressByteSize()));
+ lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
+ if (llvm::StringRef(args.name).getAsInteger<lldb::addr_t>(0, load_addr))
+ return llvm::make_error<DAPError>(args.name + " is not a valid address",
+ llvm::inconvertibleErrorCode(), false);
+ addr = llvm::utohexstr(load_addr);
+ if (!IsRW(dap, load_addr))
+ return llvm::make_error<DAPError>("memory region for address " + addr +
+ " has no read or write permissions",
+ llvm::inconvertibleErrorCode(), false);
} else {
is_data_ok = false;
response.description = "variable not found: " + args.name;
@@ -86,7 +108,10 @@ DataBreakpointInfoRequestHandler::Run(
response.accessTypes = {protocol::eDataBreakpointAccessTypeRead,
protocol::eDataBreakpointAccessTypeWrite,
protocol::eDataBreakpointAccessTypeReadWrite};
- response.description = size + " bytes at " + addr + " " + args.name;
+ if (args.asAddress)
+ response.description = size + " bytes at " + addr;
+ else
+ response.description = size + " bytes at " + addr + " " + args.name;
}
return response;
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 65a52075ebd79..5d235352b7738 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -435,6 +435,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;
};
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 81eadae03bb48..5c4afa3fd2f62 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -677,7 +677,14 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
EmplaceSafeString(body, "description", desc_str);
}
} break;
- case lldb::eStopReasonWatchpoint:
+ case lldb::eStopReasonWatchpoint: {
+ body.try_emplace("reason", "data breakpoint");
+ lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);
+ body.try_emplace("hitBreakpointIds",
+ llvm::json::Array{llvm::json::Value(bp_id)});
+ EmplaceSafeString(body, "description",
+ llvm::formatv("data breakpoint {0}", bp_id).str());
+ } break;
case lldb::eStopReasonInstrumentation:
body.try_emplace("reason", "breakpoint");
break;
diff --git a/lldb/tools/lldb-dap/Watchpoint.cpp b/lldb/tools/lldb-dap/Watchpoint.cpp
index 0acc980890be8..e730e71c0dc31 100644
--- a/lldb/tools/lldb-dap/Watchpoint.cpp
+++ b/lldb/tools/lldb-dap/Watchpoint.cpp
@@ -45,6 +45,7 @@ protocol::Breakpoint Watchpoint::ToProtocolBreakpoint() {
breakpoint.message = m_error.GetCString();
} else {
breakpoint.verified = true;
+ breakpoint.id = m_wp.GetID();
}
return breakpoint;
More information about the lldb-commits
mailing list