Skip to content

Commit

Permalink
Re-land [lldb-dap] Add support for data breakpoint. (#81909)
Browse files Browse the repository at this point in the history
This implements functionality to handle DataBreakpointInfo request and
SetDataBreakpoints request.

Previous commit
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
#81680.
  • Loading branch information
ZequanWu committed Feb 22, 2024
1 parent 7f71fa9 commit df6f756
Show file tree
Hide file tree
Showing 9 changed files with 590 additions and 34 deletions.
47 changes: 47 additions & 0 deletions lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
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
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
@@ -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
@@ -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
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
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
@@ -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
@@ -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

0 comments on commit df6f756

Please sign in to comment.