Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-land [lldb-dap] Add support for data breakpoint. #81909

Merged
merged 2 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,18 @@ def get_local_variable_value(self, name, frameIndex=0, threadId=None):
return variable["value"]
return None

def get_local_variable_child(self, name, child_name, frameIndex=0, threadId=None):
local = self.get_local_variable(name, frameIndex, threadId)
if local["variablesReference"] == 0:
return None
children = self.request_variables(local["variablesReference"])["body"][
"variables"
]
for child in children:
if child["name"] == child_name:
return child
return None

def replay_packets(self, replay_file_path):
f = open(replay_file_path, "r")
mode = "invalid"
Expand Down Expand Up @@ -895,6 +907,41 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non
}
return self.send_recv(command_dict)

def request_dataBreakpointInfo(
self, variablesReference, name, 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"],
}
command_dict = {
"command": "dataBreakpointInfo",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)

def request_setDataBreakpoint(self, dataBreakpoints):
"""dataBreakpoints is a list of dictionary with following fields:
{
dataId: (address in hex)/(size in bytes)
accessType: read/write/readWrite
[condition]: string
[hitCondition]: string
}
"""
args_dict = {"breakpoints": dataBreakpoints}
command_dict = {
"command": "setDataBreakpoints",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)

def request_compileUnits(self, moduleId):
args_dict = {"moduleId": moduleId}
command_dict = {
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/databreakpoint/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
Test lldb-dap dataBreakpointInfo and setDataBreakpoints requests
"""

from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase


class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase):
def setUp(self):
lldbdap_testcase.DAPTestCaseBase.setUp(self)
self.accessTypes = ["read", "write", "readWrite"]

@skipIfWindows
@skipIfRemote
def test_expression(self):
"""Tests setting data breakpoints on expression."""
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()
self.dap_server.get_stackFrame()
# Test setting write watchpoint using expressions: &x, arr+2
response_x = self.dap_server.request_dataBreakpointInfo(0, "&x")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(0, "arr+2")
# Test response from dataBreakpointInfo request.
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "4")
self.assertEquals(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)
self.assertEquals(
set_response["body"]["breakpoints"],
[{"verified": True}, {"verified": True}],
)

self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(x_val, "2")
self.assertEquals(i_val, "1")

self.continue_to_next_stop()
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(arr_2["value"], "42")
self.assertEquals(i_val, "2")

@skipIfWindows
@skipIfRemote
def test_functionality(self):
"""Tests setting data breakpoints on variable."""
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()
self.dap_server.get_local_variables()
# Test write watchpoints on x, arr[2]
response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
arr = self.dap_server.get_local_variable("arr")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
arr["variablesReference"], "[2]"
)

# Test response from dataBreakpointInfo request.
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "4")
self.assertEquals(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)
self.assertEquals(
set_response["body"]["breakpoints"],
[{"verified": True}, {"verified": True}],
)

self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(x_val, "2")
self.assertEquals(i_val, "1")

self.continue_to_next_stop()
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(arr_2["value"], "42")
self.assertEquals(i_val, "2")
self.dap_server.request_setDataBreakpoint([])

# Test hit condition
second_loop_break_line = line_number(source, "// second loop breakpoint")
breakpoint_ids = self.set_source_breakpoints(source, [second_loop_break_line])
self.continue_to_breakpoints(breakpoint_ids)
dataBreakpoints = [
{
"dataId": response_x["body"]["dataId"],
"accessType": "write",
"hitCondition": "2",
}
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
self.assertEquals(set_response["body"]["breakpoints"], [{"verified": True}])
self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
self.assertEquals(x_val, "3")

# Test condition
dataBreakpoints = [
{
"dataId": response_x["body"]["dataId"],
"accessType": "write",
"condition": "x==10",
}
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
self.assertEquals(set_response["body"]["breakpoints"], [{"verified": True}])
self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
self.assertEquals(x_val, "10")
17 changes: 17 additions & 0 deletions lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
int main(int argc, char const *argv[]) {
// Test for data breakpoint
int x = 0;
int arr[4] = {1, 2, 3, 4};
for (int i = 0; i < 5; ++i) { // first loop breakpoint
if (i == 1) {
x = i + 1;
} else if (i == 2) {
arr[i] = 42;
}
}

x = 1;
for (int i = 0; i < 10; ++i) { // second loop breakpoint
++x;
}
}
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ add_lldb_tool(lldb-dap
RunInTerminal.cpp
SourceBreakpoint.cpp
DAP.cpp
Watchpoint.cpp

LINK_LIBS
liblldb
Expand Down
2 changes: 2 additions & 0 deletions lldb/tools/lldb-dap/DAPForward.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct BreakpointBase;
struct ExceptionBreakpoint;
struct FunctionBreakpoint;
struct SourceBreakpoint;
struct Watchpoint;
} // namespace lldb_dap

namespace lldb {
Expand All @@ -39,6 +40,7 @@ class SBStringList;
class SBTarget;
class SBThread;
class SBValue;
class SBWatchpoint;
} // namespace lldb

#endif
48 changes: 48 additions & 0 deletions lldb/tools/lldb-dap/Watchpoint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//===-- Watchpoint.cpp ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Watchpoint.h"
#include "DAP.h"
#include "JSONUtils.h"
#include "llvm/ADT/StringExtras.h"

namespace lldb_dap {
Watchpoint::Watchpoint(const llvm::json::Object &obj) : BreakpointBase(obj) {
llvm::StringRef dataId = GetString(obj, "dataId");
std::string accessType = GetString(obj, "accessType").str();
auto [addr_str, size_str] = dataId.split('/');
lldb::addr_t addr;
size_t size;
llvm::to_integer(addr_str, addr, 16);
llvm::to_integer(size_str, size);
lldb::SBWatchpointOptions options;
options.SetWatchpointTypeRead(accessType != "write");
if (accessType != "read")
options.SetWatchpointTypeWrite(lldb::eWatchpointWriteTypeOnModify);
wp = g_dap.target.WatchpointCreateByAddress(addr, size, options, error);
SetCondition();
SetHitCondition();
}

void Watchpoint::SetCondition() { wp.SetCondition(condition.c_str()); }

void Watchpoint::SetHitCondition() {
uint64_t hitCount = 0;
if (llvm::to_integer(hitCondition, hitCount))
wp.SetIgnoreCount(hitCount - 1);
}

void Watchpoint::CreateJsonObject(llvm::json::Object &object) {
if (error.Success()) {
object.try_emplace("verified", true);
} else {
object.try_emplace("verified", false);
EmplaceSafeString(object, "message", error.GetCString());
}
}
} // namespace lldb_dap
34 changes: 34 additions & 0 deletions lldb/tools/lldb-dap/Watchpoint.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===-- Watchpoint.h --------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
#define LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H

#include "BreakpointBase.h"
#include "lldb/API/SBError.h"
#include "lldb/API/SBWatchpoint.h"
#include "lldb/API/SBWatchpointOptions.h"

namespace lldb_dap {

struct Watchpoint : public BreakpointBase {
// The LLDB breakpoint associated wit this watchpoint.
lldb::SBWatchpoint wp;
lldb::SBError error;

Watchpoint() = default;
Watchpoint(const llvm::json::Object &obj);
Watchpoint(lldb::SBWatchpoint wp) : wp(wp) {}

void SetCondition() override;
void SetHitCondition() override;
void CreateJsonObject(llvm::json::Object &object) override;
};
} // namespace lldb_dap

#endif
Loading
Loading