Skip to content

Commit 8c56e78

Browse files
authored
[lldb-dap] Add support for data breakpoint. (llvm#81541)
This implements functionality to handle `DataBreakpointInfo` request and `SetDataBreakpoints` request. If variablesReference is 0 or not provided, interpret name as ${number of bytes}@${expression} to set data breakpoint at the given expression because the spec https://microsoft.github.io/debug-adapter-protocol/specification#Requests_DataBreakpointInfo doesn't say how the client could specify the number of bytes to watch. This is based on top of llvm#80753.
1 parent 99c5a66 commit 8c56e78

File tree

10 files changed

+549
-34
lines changed

10 files changed

+549
-34
lines changed

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,18 @@ def get_local_variable_value(self, name, frameIndex=0, threadId=None):
501501
return variable["value"]
502502
return None
503503

504+
def get_local_variable_child(self, name, child_name, frameIndex=0, threadId=None):
505+
local = self.get_local_variable(name, frameIndex, threadId)
506+
if local["variablesReference"] == 0:
507+
return None
508+
children = self.request_variables(local["variablesReference"])["body"][
509+
"variables"
510+
]
511+
for child in children:
512+
if child["name"] == child_name:
513+
return child
514+
return None
515+
504516
def replay_packets(self, replay_file_path):
505517
f = open(replay_file_path, "r")
506518
mode = "invalid"
@@ -895,6 +907,41 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
895907
}
896908
return self.send_recv(command_dict)
897909

910+
def request_dataBreakpointInfo(
911+
self, variablesReference, name, frameIndex=0, threadId=None
912+
):
913+
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
914+
if stackFrame is None:
915+
return []
916+
args_dict = {
917+
"variablesReference": variablesReference,
918+
"name": name,
919+
"frameId": stackFrame["id"],
920+
}
921+
command_dict = {
922+
"command": "dataBreakpointInfo",
923+
"type": "request",
924+
"arguments": args_dict,
925+
}
926+
return self.send_recv(command_dict)
927+
928+
def request_setDataBreakpoint(self, dataBreakpoints):
929+
"""dataBreakpoints is a list of dictionary with following fields:
930+
{
931+
dataId: (address in hex)/(size in bytes)
932+
accessType: read/write/readWrite
933+
[condition]: string
934+
[hitCondition]: string
935+
}
936+
"""
937+
args_dict = {"breakpoints": dataBreakpoints}
938+
command_dict = {
939+
"command": "setDataBreakpoints",
940+
"type": "request",
941+
"arguments": args_dict,
942+
}
943+
return self.send_recv(command_dict)
944+
898945
def request_compileUnits(self, moduleId):
899946
args_dict = {"moduleId": moduleId}
900947
command_dict = {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Test lldb-dap dataBreakpointInfo and setDataBreakpoints requests
3+
"""
4+
5+
from lldbsuite.test.decorators import *
6+
from lldbsuite.test.lldbtest import *
7+
import lldbdap_testcase
8+
9+
10+
class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase):
11+
def setUp(self):
12+
lldbdap_testcase.DAPTestCaseBase.setUp(self)
13+
self.accessTypes = ["read", "write", "readWrite"]
14+
15+
@skipIfWindows
16+
@skipIfRemote
17+
def test_expression(self):
18+
"""Tests setting data breakpoints on expression."""
19+
program = self.getBuildArtifact("a.out")
20+
self.build_and_launch(program)
21+
source = "main.cpp"
22+
first_loop_break_line = line_number(source, "// first loop breakpoint")
23+
self.set_source_breakpoints(source, [first_loop_break_line])
24+
self.continue_to_next_stop()
25+
self.dap_server.get_stackFrame()
26+
# Test setting write watchpoint using expressions: &x, arr+2
27+
response_x = self.dap_server.request_dataBreakpointInfo(0, "4@&x")
28+
response_arr_2 = self.dap_server.request_dataBreakpointInfo(0, "1@arr+2")
29+
# Test response from dataBreakpointInfo request.
30+
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
31+
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
32+
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "1")
33+
self.assertEquals(response_arr_2["body"]["accessTypes"], self.accessTypes)
34+
dataBreakpoints = [
35+
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
36+
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
37+
]
38+
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
39+
40+
self.dap_server.request_continue()
41+
self.dap_server.wait_for_stopped()
42+
x_val = self.dap_server.get_local_variable_value("x")
43+
i_val = self.dap_server.get_local_variable_value("i")
44+
self.assertEquals(x_val, "2")
45+
self.assertEquals(i_val, "1")
46+
47+
self.dap_server.request_continue()
48+
self.dap_server.wait_for_stopped()
49+
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
50+
i_val = self.dap_server.get_local_variable_value("i")
51+
self.assertEquals(arr_2["value"], "'z'")
52+
self.assertEquals(i_val, "2")
53+
54+
@skipIfWindows
55+
@skipIfRemote
56+
def test_functionality(self):
57+
"""Tests setting data breakpoints on variable."""
58+
program = self.getBuildArtifact("a.out")
59+
self.build_and_launch(program)
60+
source = "main.cpp"
61+
first_loop_break_line = line_number(source, "// first loop breakpoint")
62+
self.set_source_breakpoints(source, [first_loop_break_line])
63+
self.continue_to_next_stop()
64+
self.dap_server.get_local_variables()
65+
# Test write watchpoints on x, arr[2]
66+
response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
67+
arr = self.dap_server.get_local_variable("arr")
68+
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
69+
arr["variablesReference"], "[2]"
70+
)
71+
72+
# Test response from dataBreakpointInfo request.
73+
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
74+
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
75+
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "1")
76+
self.assertEquals(response_arr_2["body"]["accessTypes"], self.accessTypes)
77+
dataBreakpoints = [
78+
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
79+
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
80+
]
81+
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
82+
83+
self.continue_to_next_stop()
84+
x_val = self.dap_server.get_local_variable_value("x")
85+
i_val = self.dap_server.get_local_variable_value("i")
86+
self.assertEquals(x_val, "2")
87+
self.assertEquals(i_val, "1")
88+
89+
self.continue_to_next_stop()
90+
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
91+
i_val = self.dap_server.get_local_variable_value("i")
92+
self.assertEquals(arr_2["value"], "'z'")
93+
self.assertEquals(i_val, "2")
94+
self.dap_server.request_setDataBreakpoint([])
95+
96+
# Test hit condition
97+
second_loop_break_line = line_number(source, "// second loop breakpoint")
98+
breakpoint_ids = self.set_source_breakpoints(source, [second_loop_break_line])
99+
self.continue_to_breakpoints(breakpoint_ids)
100+
dataBreakpoints = [
101+
{
102+
"dataId": response_x["body"]["dataId"],
103+
"accessType": "write",
104+
"hitCondition": "2",
105+
}
106+
]
107+
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
108+
self.continue_to_next_stop()
109+
x_val = self.dap_server.get_local_variable_value("x")
110+
self.assertEquals(x_val, "3")
111+
112+
# Test condition
113+
dataBreakpoints = [
114+
{
115+
"dataId": response_x["body"]["dataId"],
116+
"accessType": "write",
117+
"condition": "x==10",
118+
}
119+
]
120+
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
121+
self.continue_to_next_stop()
122+
x_val = self.dap_server.get_local_variable_value("x")
123+
self.assertEquals(x_val, "10")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
int main(int argc, char const *argv[]) {
2+
// Test for data breakpoint
3+
int x = 0;
4+
char arr[4] = {'a', 'b', 'c', 'd'};
5+
for (int i = 0; i < 5; ++i) { // first loop breakpoint
6+
if (i == 1) {
7+
x = i + 1;
8+
} else if (i == 2) {
9+
arr[i] = 'z';
10+
}
11+
}
12+
13+
x = 1;
14+
for (int i = 0; i < 10; ++i) { // second loop breakpoint
15+
++x;
16+
}
17+
}

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ add_lldb_tool(lldb-dap
3737
RunInTerminal.cpp
3838
SourceBreakpoint.cpp
3939
DAP.cpp
40+
Watchpoint.cpp
4041

4142
LINK_LIBS
4243
liblldb

lldb/tools/lldb-dap/DAPForward.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct BreakpointBase;
1414
struct ExceptionBreakpoint;
1515
struct FunctionBreakpoint;
1616
struct SourceBreakpoint;
17+
struct Watchpoint;
1718
} // namespace lldb_dap
1819

1920
namespace lldb {
@@ -39,6 +40,7 @@ class SBStringList;
3940
class SBTarget;
4041
class SBThread;
4142
class SBValue;
43+
class SBWatchpoint;
4244
} // namespace lldb
4345

4446
#endif

lldb/tools/lldb-dap/Watchpoint.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===-- Watchpoint.cpp ------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Watchpoint.h"
10+
#include "DAP.h"
11+
#include "JSONUtils.h"
12+
#include "llvm/ADT/StringExtras.h"
13+
14+
namespace lldb_dap {
15+
Watchpoint::Watchpoint(const llvm::json::Object &obj) : BreakpointBase(obj) {
16+
llvm::StringRef dataId = GetString(obj, "dataId");
17+
std::string accessType = GetString(obj, "accessType").str();
18+
auto [addr_str, size_str] = dataId.split('/');
19+
lldb::addr_t addr;
20+
size_t size;
21+
llvm::to_integer(addr_str, addr, 16);
22+
llvm::to_integer(size_str, size);
23+
lldb::SBWatchpointOptions options;
24+
options.SetWatchpointTypeRead(accessType != "write");
25+
if (accessType != "read")
26+
options.SetWatchpointTypeWrite(lldb::eWatchpointWriteTypeOnModify);
27+
wp = g_dap.target.WatchpointCreateByAddress(addr, size, options, error);
28+
SetCondition();
29+
SetHitCondition();
30+
}
31+
32+
void Watchpoint::SetCondition() { wp.SetCondition(condition.c_str()); }
33+
34+
void Watchpoint::SetHitCondition() {
35+
uint64_t hitCount = 0;
36+
if (llvm::to_integer(hitCondition, hitCount))
37+
wp.SetIgnoreCount(hitCount - 1);
38+
}
39+
40+
void Watchpoint::CreateJsonObject(llvm::json::Object &object) {
41+
if (error.Success()) {
42+
object.try_emplace("verified", true);
43+
} else {
44+
object.try_emplace("verified", false);
45+
EmplaceSafeString(object, "message", error.GetCString());
46+
}
47+
}
48+
} // namespace lldb_dap

lldb/tools/lldb-dap/Watchpoint.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===-- Watchpoint.h --------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
10+
#define LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
11+
12+
#include "BreakpointBase.h"
13+
#include "lldb/API/SBError.h"
14+
#include "lldb/API/SBWatchpoint.h"
15+
#include "lldb/API/SBWatchpointOptions.h"
16+
17+
namespace lldb_dap {
18+
19+
struct Watchpoint : public BreakpointBase {
20+
// The LLDB breakpoint associated wit this watchpoint.
21+
lldb::SBWatchpoint wp;
22+
lldb::SBError error;
23+
24+
Watchpoint() = default;
25+
Watchpoint(const llvm::json::Object &obj);
26+
Watchpoint(lldb::SBWatchpoint wp) : wp(wp) {}
27+
28+
void SetCondition() override;
29+
void SetHitCondition() override;
30+
void CreateJsonObject(llvm::json::Object &object) override;
31+
};
32+
} // namespace lldb_dap
33+
34+
#endif

0 commit comments

Comments
 (0)