Skip to content

Commit df6f756

Browse files
authored
Re-land [lldb-dap] Add support for data breakpoint. (llvm#81909)
This implements functionality to handle DataBreakpointInfo request and SetDataBreakpoints request. Previous commit llvm@8c56e78 was reverted because setting 1 byte watchpoint failed in the new test on ARM64. So, I changed the test to setting 4 byte watchpoint instead, and hope this won't break it again. It also adds the fixes from llvm#81680.
1 parent 7f71fa9 commit df6f756

File tree

9 files changed

+590
-34
lines changed

9 files changed

+590
-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: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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, "&x")
28+
response_arr_2 = self.dap_server.request_dataBreakpointInfo(0, "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], "4")
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+
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
39+
self.assertEquals(
40+
set_response["body"]["breakpoints"],
41+
[{"verified": True}, {"verified": True}],
42+
)
43+
44+
self.continue_to_next_stop()
45+
x_val = self.dap_server.get_local_variable_value("x")
46+
i_val = self.dap_server.get_local_variable_value("i")
47+
self.assertEquals(x_val, "2")
48+
self.assertEquals(i_val, "1")
49+
50+
self.continue_to_next_stop()
51+
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
52+
i_val = self.dap_server.get_local_variable_value("i")
53+
self.assertEquals(arr_2["value"], "42")
54+
self.assertEquals(i_val, "2")
55+
56+
@skipIfWindows
57+
@skipIfRemote
58+
def test_functionality(self):
59+
"""Tests setting data breakpoints on variable."""
60+
program = self.getBuildArtifact("a.out")
61+
self.build_and_launch(program)
62+
source = "main.cpp"
63+
first_loop_break_line = line_number(source, "// first loop breakpoint")
64+
self.set_source_breakpoints(source, [first_loop_break_line])
65+
self.continue_to_next_stop()
66+
self.dap_server.get_local_variables()
67+
# Test write watchpoints on x, arr[2]
68+
response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
69+
arr = self.dap_server.get_local_variable("arr")
70+
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
71+
arr["variablesReference"], "[2]"
72+
)
73+
74+
# Test response from dataBreakpointInfo request.
75+
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
76+
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
77+
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "4")
78+
self.assertEquals(response_arr_2["body"]["accessTypes"], self.accessTypes)
79+
dataBreakpoints = [
80+
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
81+
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
82+
]
83+
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
84+
self.assertEquals(
85+
set_response["body"]["breakpoints"],
86+
[{"verified": True}, {"verified": True}],
87+
)
88+
89+
self.continue_to_next_stop()
90+
x_val = self.dap_server.get_local_variable_value("x")
91+
i_val = self.dap_server.get_local_variable_value("i")
92+
self.assertEquals(x_val, "2")
93+
self.assertEquals(i_val, "1")
94+
95+
self.continue_to_next_stop()
96+
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
97+
i_val = self.dap_server.get_local_variable_value("i")
98+
self.assertEquals(arr_2["value"], "42")
99+
self.assertEquals(i_val, "2")
100+
self.dap_server.request_setDataBreakpoint([])
101+
102+
# Test hit condition
103+
second_loop_break_line = line_number(source, "// second loop breakpoint")
104+
breakpoint_ids = self.set_source_breakpoints(source, [second_loop_break_line])
105+
self.continue_to_breakpoints(breakpoint_ids)
106+
dataBreakpoints = [
107+
{
108+
"dataId": response_x["body"]["dataId"],
109+
"accessType": "write",
110+
"hitCondition": "2",
111+
}
112+
]
113+
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
114+
self.assertEquals(set_response["body"]["breakpoints"], [{"verified": True}])
115+
self.continue_to_next_stop()
116+
x_val = self.dap_server.get_local_variable_value("x")
117+
self.assertEquals(x_val, "3")
118+
119+
# Test condition
120+
dataBreakpoints = [
121+
{
122+
"dataId": response_x["body"]["dataId"],
123+
"accessType": "write",
124+
"condition": "x==10",
125+
}
126+
]
127+
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
128+
self.assertEquals(set_response["body"]["breakpoints"], [{"verified": True}])
129+
self.continue_to_next_stop()
130+
x_val = self.dap_server.get_local_variable_value("x")
131+
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+
int arr[4] = {1, 2, 3, 4};
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] = 42;
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)