Skip to content

Commit 757013a

Browse files
committed
[lldb-dap] Add support for data breakpoint.
This implements functionality to handle DataBreakpointInfo request and SetDataBreakpoints request.
1 parent e82659f commit 757013a

File tree

9 files changed

+587
-34
lines changed

9 files changed

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