Skip to content

Commit 207627f

Browse files
authored
[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).
1 parent 3a766dc commit 207627f

File tree

7 files changed

+149
-43
lines changed

7 files changed

+149
-43
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,16 +1265,18 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
12651265
return response
12661266

12671267
def request_dataBreakpointInfo(
1268-
self, variablesReference, name, frameIndex=0, threadId=None
1268+
self, variablesReference, name, size=None, frameIndex=0, threadId=None
12691269
):
12701270
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
12711271
if stackFrame is None:
12721272
return []
1273-
args_dict = {
1274-
"variablesReference": variablesReference,
1275-
"name": name,
1276-
"frameId": stackFrame["id"],
1277-
}
1273+
args_dict = {"name": name}
1274+
if size is None:
1275+
args_dict["variablesReference"] = variablesReference
1276+
args_dict["frameId"] = stackFrame["id"]
1277+
else:
1278+
args_dict["asAddress"] = True
1279+
args_dict["bytes"] = size
12781280
command_dict = {
12791281
"command": "dataBreakpointInfo",
12801282
"type": "request",

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def verify_breakpoint_hit(self, breakpoint_ids: List[Union[int, str]]):
169169
if (
170170
body["reason"] != "breakpoint"
171171
and body["reason"] != "instruction breakpoint"
172+
and body["reason"] != "data breakpoint"
172173
):
173174
continue
174175
if "hitBreakpointIds" not in body:

lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,21 @@ def test_duplicate_start_addresses(self):
3939
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
4040
]
4141
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
42-
self.assertEqual(
43-
set_response["body"]["breakpoints"],
44-
[{"verified": False}, {"verified": True}, {"verified": True}],
45-
)
42+
breakpoints = set_response["body"]["breakpoints"]
43+
self.assertEqual(len(breakpoints), 3)
44+
self.assertFalse(breakpoints[0]["verified"])
45+
self.assertTrue(breakpoints[1]["verified"])
46+
self.assertTrue(breakpoints[2]["verified"])
4647

47-
self.continue_to_next_stop()
48+
self.dap_server.request_continue()
49+
self.verify_breakpoint_hit([breakpoints[2]["id"]])
4850
x_val = self.dap_server.get_local_variable_value("x")
4951
i_val = self.dap_server.get_local_variable_value("i")
5052
self.assertEqual(x_val, "2")
5153
self.assertEqual(i_val, "1")
5254

53-
self.continue_to_next_stop()
55+
self.dap_server.request_continue()
56+
self.verify_breakpoint_hit([breakpoints[1]["id"]])
5457
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
5558
i_val = self.dap_server.get_local_variable_value("i")
5659
self.assertEqual(arr_2["value"], "42")
@@ -79,18 +82,20 @@ def test_expression(self):
7982
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
8083
]
8184
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
82-
self.assertEqual(
83-
set_response["body"]["breakpoints"],
84-
[{"verified": True}, {"verified": True}],
85-
)
85+
breakpoints = set_response["body"]["breakpoints"]
86+
self.assertEqual(len(breakpoints), 2)
87+
self.assertTrue(breakpoints[0]["verified"])
88+
self.assertTrue(breakpoints[1]["verified"])
8689

87-
self.continue_to_next_stop()
90+
self.dap_server.request_continue()
91+
self.verify_breakpoint_hit([breakpoints[0]["id"]])
8892
x_val = self.dap_server.get_local_variable_value("x")
8993
i_val = self.dap_server.get_local_variable_value("i")
9094
self.assertEqual(x_val, "2")
9195
self.assertEqual(i_val, "1")
9296

93-
self.continue_to_next_stop()
97+
self.dap_server.request_continue()
98+
self.verify_breakpoint_hit([breakpoints[1]["id"]])
9499
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
95100
i_val = self.dap_server.get_local_variable_value("i")
96101
self.assertEqual(arr_2["value"], "42")
@@ -123,18 +128,20 @@ def test_functionality(self):
123128
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
124129
]
125130
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
126-
self.assertEqual(
127-
set_response["body"]["breakpoints"],
128-
[{"verified": True}, {"verified": True}],
129-
)
131+
breakpoints = set_response["body"]["breakpoints"]
132+
self.assertEqual(len(breakpoints), 2)
133+
self.assertTrue(breakpoints[0]["verified"])
134+
self.assertTrue(breakpoints[1]["verified"])
130135

131-
self.continue_to_next_stop()
136+
self.dap_server.request_continue()
137+
self.verify_breakpoint_hit([breakpoints[0]["id"]])
132138
x_val = self.dap_server.get_local_variable_value("x")
133139
i_val = self.dap_server.get_local_variable_value("i")
134140
self.assertEqual(x_val, "2")
135141
self.assertEqual(i_val, "1")
136142

137-
self.continue_to_next_stop()
143+
self.dap_server.request_continue()
144+
self.verify_breakpoint_hit([breakpoints[1]["id"]])
138145
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
139146
i_val = self.dap_server.get_local_variable_value("i")
140147
self.assertEqual(arr_2["value"], "42")
@@ -153,8 +160,11 @@ def test_functionality(self):
153160
}
154161
]
155162
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
156-
self.assertEqual(set_response["body"]["breakpoints"], [{"verified": True}])
157-
self.continue_to_next_stop()
163+
breakpoints = set_response["body"]["breakpoints"]
164+
self.assertEqual(len(breakpoints), 1)
165+
self.assertTrue(breakpoints[0]["verified"])
166+
self.dap_server.request_continue()
167+
self.verify_breakpoint_hit([breakpoints[0]["id"]])
158168
x_val = self.dap_server.get_local_variable_value("x")
159169
self.assertEqual(x_val, "3")
160170

@@ -167,7 +177,64 @@ def test_functionality(self):
167177
}
168178
]
169179
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
170-
self.assertEqual(set_response["body"]["breakpoints"], [{"verified": True}])
171-
self.continue_to_next_stop()
180+
breakpoints = set_response["body"]["breakpoints"]
181+
self.assertEqual(len(breakpoints), 1)
182+
self.assertTrue(breakpoints[0]["verified"])
183+
self.dap_server.request_continue()
184+
self.verify_breakpoint_hit([breakpoints[0]["id"]])
172185
x_val = self.dap_server.get_local_variable_value("x")
173186
self.assertEqual(x_val, "10")
187+
188+
@skipIfWindows
189+
def test_bytes(self):
190+
"""Tests setting data breakpoints on memory range."""
191+
program = self.getBuildArtifact("a.out")
192+
self.build_and_launch(program)
193+
source = "main.cpp"
194+
first_loop_break_line = line_number(source, "// first loop breakpoint")
195+
self.set_source_breakpoints(source, [first_loop_break_line])
196+
self.continue_to_next_stop()
197+
# Test write watchpoints on x, arr[2]
198+
x = self.dap_server.get_local_variable("x")
199+
response_x = self.dap_server.request_dataBreakpointInfo(
200+
0, x["memoryReference"], 4
201+
)
202+
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
203+
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
204+
0, arr_2["memoryReference"], 4
205+
)
206+
207+
# Test response from dataBreakpointInfo request.
208+
self.assertEqual(
209+
response_x["body"]["dataId"].split("/"), [x["memoryReference"][2:], "4"]
210+
)
211+
self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes)
212+
self.assertEqual(
213+
response_arr_2["body"]["dataId"].split("/"),
214+
[arr_2["memoryReference"][2:], "4"],
215+
)
216+
self.assertEqual(response_arr_2["body"]["accessTypes"], self.accessTypes)
217+
dataBreakpoints = [
218+
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
219+
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
220+
]
221+
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
222+
breakpoints = set_response["body"]["breakpoints"]
223+
self.assertEqual(len(breakpoints), 2)
224+
self.assertTrue(breakpoints[0]["verified"])
225+
self.assertTrue(breakpoints[1]["verified"])
226+
227+
self.dap_server.request_continue()
228+
self.verify_breakpoint_hit([breakpoints[0]["id"]])
229+
x_val = self.dap_server.get_local_variable_value("x")
230+
i_val = self.dap_server.get_local_variable_value("i")
231+
self.assertEqual(x_val, "2")
232+
self.assertEqual(i_val, "1")
233+
234+
self.dap_server.request_continue()
235+
self.verify_breakpoint_hit([breakpoints[1]["id"]])
236+
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
237+
i_val = self.dap_server.get_local_variable_value("i")
238+
self.assertEqual(arr_2["value"], "42")
239+
self.assertEqual(i_val, "2")
240+
self.dap_server.request_setDataBreakpoint([])

lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,40 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "DAP.h"
10+
#include "DAPError.h"
1011
#include "EventHelper.h"
1112
#include "Protocol/ProtocolTypes.h"
1213
#include "RequestHandler.h"
14+
#include "lldb/API/SBAddress.h"
1315
#include "lldb/API/SBMemoryRegionInfo.h"
1416
#include "llvm/ADT/StringExtras.h"
1517
#include <optional>
1618

1719
namespace lldb_dap {
1820

21+
static bool IsRW(DAP &dap, lldb::addr_t load_addr) {
22+
if (!lldb::SBAddress(load_addr, dap.target).IsValid())
23+
return false;
24+
lldb::SBMemoryRegionInfo region;
25+
lldb::SBError err =
26+
dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
27+
// Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
28+
// request if SBProcess::GetMemoryRegionInfo returns error.
29+
if (err.Success()) {
30+
if (!(region.IsReadable() || region.IsWritable())) {
31+
return false;
32+
}
33+
}
34+
return true;
35+
}
36+
1937
/// Obtains information on a possible data breakpoint that could be set on an
2038
/// expression or variable. Clients should only call this request if the
2139
/// corresponding capability supportsDataBreakpoints is true.
2240
llvm::Expected<protocol::DataBreakpointInfoResponseBody>
2341
DataBreakpointInfoRequestHandler::Run(
2442
const protocol::DataBreakpointInfoArguments &args) const {
2543
protocol::DataBreakpointInfoResponseBody response;
26-
lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
2744
lldb::SBValue variable = dap.variables.FindVariable(
2845
args.variablesReference.value_or(0), args.name);
2946
std::string addr, size;
@@ -43,7 +60,8 @@ DataBreakpointInfoRequestHandler::Run(
4360
addr = llvm::utohexstr(load_addr);
4461
size = llvm::utostr(byte_size);
4562
}
46-
} else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
63+
} else if (lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
64+
args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
4765
lldb::SBValue value = frame.EvaluateExpression(args.name.c_str());
4866
if (value.GetError().Fail()) {
4967
lldb::SBError error = value.GetError();
@@ -58,24 +76,28 @@ DataBreakpointInfoRequestHandler::Run(
5876
if (data.IsValid()) {
5977
size = llvm::utostr(data.GetByteSize());
6078
addr = llvm::utohexstr(load_addr);
61-
lldb::SBMemoryRegionInfo region;
62-
lldb::SBError err =
63-
dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
64-
// Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
65-
// request if SBProcess::GetMemoryRegionInfo returns error.
66-
if (err.Success()) {
67-
if (!(region.IsReadable() || region.IsWritable())) {
68-
is_data_ok = false;
69-
response.description = "memory region for address " + addr +
70-
" has no read or write permissions";
71-
}
79+
if (!IsRW(dap, load_addr)) {
80+
is_data_ok = false;
81+
response.description = "memory region for address " + addr +
82+
" has no read or write permissions";
7283
}
7384
} else {
7485
is_data_ok = false;
7586
response.description =
7687
"unable to get byte size for expression: " + args.name;
7788
}
7889
}
90+
} else if (args.asAddress) {
91+
size = llvm::utostr(args.bytes.value_or(dap.target.GetAddressByteSize()));
92+
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
93+
if (llvm::StringRef(args.name).getAsInteger<lldb::addr_t>(0, load_addr))
94+
return llvm::make_error<DAPError>(args.name + " is not a valid address",
95+
llvm::inconvertibleErrorCode(), false);
96+
addr = llvm::utohexstr(load_addr);
97+
if (!IsRW(dap, load_addr))
98+
return llvm::make_error<DAPError>("memory region for address " + addr +
99+
" has no read or write permissions",
100+
llvm::inconvertibleErrorCode(), false);
79101
} else {
80102
is_data_ok = false;
81103
response.description = "variable not found: " + args.name;
@@ -86,7 +108,10 @@ DataBreakpointInfoRequestHandler::Run(
86108
response.accessTypes = {protocol::eDataBreakpointAccessTypeRead,
87109
protocol::eDataBreakpointAccessTypeWrite,
88110
protocol::eDataBreakpointAccessTypeReadWrite};
89-
response.description = size + " bytes at " + addr + " " + args.name;
111+
if (args.asAddress)
112+
response.description = size + " bytes at " + addr;
113+
else
114+
response.description = size + " bytes at " + addr + " " + args.name;
90115
}
91116

92117
return response;

lldb/tools/lldb-dap/Handler/RequestHandler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,9 @@ class DataBreakpointInfoRequestHandler
435435
public:
436436
using RequestHandler::RequestHandler;
437437
static llvm::StringLiteral GetCommand() { return "dataBreakpointInfo"; }
438+
FeatureSet GetSupportedFeatures() const override {
439+
return {protocol::eAdapterFeatureDataBreakpointBytes};
440+
}
438441
llvm::Expected<protocol::DataBreakpointInfoResponseBody>
439442
Run(const protocol::DataBreakpointInfoArguments &args) const override;
440443
};

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,14 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread,
677677
EmplaceSafeString(body, "description", desc_str);
678678
}
679679
} break;
680-
case lldb::eStopReasonWatchpoint:
680+
case lldb::eStopReasonWatchpoint: {
681+
body.try_emplace("reason", "data breakpoint");
682+
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);
683+
body.try_emplace("hitBreakpointIds",
684+
llvm::json::Array{llvm::json::Value(bp_id)});
685+
EmplaceSafeString(body, "description",
686+
llvm::formatv("data breakpoint {0}", bp_id).str());
687+
} break;
681688
case lldb::eStopReasonInstrumentation:
682689
body.try_emplace("reason", "breakpoint");
683690
break;

lldb/tools/lldb-dap/Watchpoint.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ protocol::Breakpoint Watchpoint::ToProtocolBreakpoint() {
4545
breakpoint.message = m_error.GetCString();
4646
} else {
4747
breakpoint.verified = true;
48+
breakpoint.id = m_wp.GetID();
4849
}
4950

5051
return breakpoint;

0 commit comments

Comments
 (0)