Author: Sergei Druzhkov
Date: 2025-11-28T18:20:20+03:00
New Revision: 207627f20f06771edfbb1e779f94dfdc2ff7df26

URL: 
https://github.com/llvm/llvm-project/commit/207627f20f06771edfbb1e779f94dfdc2ff7df26
DIFF: 
https://github.com/llvm/llvm-project/commit/207627f20f06771edfbb1e779f94dfdc2ff7df26.diff

LOG: [lldb-dap] Add data breakpoints for bytes (#167237)

This patch adds support for `dataBreakpointInfoBytes` capability from
DAP. You can test this feature in VSCode (`Add data breakpoint at
address` button in breakpoints tab).

Added: 
    

Modified: 
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
    lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
    lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
    lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
    lldb/tools/lldb-dap/Handler/RequestHandler.h
    lldb/tools/lldb-dap/JSONUtils.cpp
    lldb/tools/lldb-dap/Watchpoint.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py 
b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 35a4f8934e961..4a7ba78b63993 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -1265,16 +1265,18 @@ def request_setFunctionBreakpoints(self, names, 
condition=None, hitCondition=Non
         return response
 
     def request_dataBreakpointInfo(
-        self, variablesReference, name, frameIndex=0, threadId=None
+        self, variablesReference, name, size=None, 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"],
-        }
+        args_dict = {"name": name}
+        if size is None:
+            args_dict["variablesReference"] = variablesReference
+            args_dict["frameId"] = stackFrame["id"]
+        else:
+            args_dict["asAddress"] = True
+            args_dict["bytes"] = size
         command_dict = {
             "command": "dataBreakpointInfo",
             "type": "request",

diff  --git 
a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py 
b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index 71ca60ebe8d34..c7d302cc2dea2 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -169,6 +169,7 @@ def verify_breakpoint_hit(self, breakpoint_ids: 
List[Union[int, str]]):
                 if (
                     body["reason"] != "breakpoint"
                     and body["reason"] != "instruction breakpoint"
+                    and body["reason"] != "data breakpoint"
                 ):
                     continue
                 if "hitBreakpointIds" not in body:

diff  --git 
a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py 
b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
index a542a318050dd..df029ca16d667 100644
--- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
+++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py
@@ -39,18 +39,21 @@ def test_duplicate_start_addresses(self):
             {"dataId": response_x["body"]["dataId"], "accessType": "write"},
         ]
         set_response = 
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
-        self.assertEqual(
-            set_response["body"]["breakpoints"],
-            [{"verified": False}, {"verified": True}, {"verified": True}],
-        )
+        breakpoints = set_response["body"]["breakpoints"]
+        self.assertEqual(len(breakpoints), 3)
+        self.assertFalse(breakpoints[0]["verified"])
+        self.assertTrue(breakpoints[1]["verified"])
+        self.assertTrue(breakpoints[2]["verified"])
 
-        self.continue_to_next_stop()
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[2]["id"]])
         x_val = self.dap_server.get_local_variable_value("x")
         i_val = self.dap_server.get_local_variable_value("i")
         self.assertEqual(x_val, "2")
         self.assertEqual(i_val, "1")
 
-        self.continue_to_next_stop()
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[1]["id"]])
         arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
         i_val = self.dap_server.get_local_variable_value("i")
         self.assertEqual(arr_2["value"], "42")
@@ -79,18 +82,20 @@ def test_expression(self):
             {"dataId": response_arr_2["body"]["dataId"], "accessType": 
"write"},
         ]
         set_response = 
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
-        self.assertEqual(
-            set_response["body"]["breakpoints"],
-            [{"verified": True}, {"verified": True}],
-        )
+        breakpoints = set_response["body"]["breakpoints"]
+        self.assertEqual(len(breakpoints), 2)
+        self.assertTrue(breakpoints[0]["verified"])
+        self.assertTrue(breakpoints[1]["verified"])
 
-        self.continue_to_next_stop()
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[0]["id"]])
         x_val = self.dap_server.get_local_variable_value("x")
         i_val = self.dap_server.get_local_variable_value("i")
         self.assertEqual(x_val, "2")
         self.assertEqual(i_val, "1")
 
-        self.continue_to_next_stop()
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[1]["id"]])
         arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
         i_val = self.dap_server.get_local_variable_value("i")
         self.assertEqual(arr_2["value"], "42")
@@ -123,18 +128,20 @@ def test_functionality(self):
             {"dataId": response_arr_2["body"]["dataId"], "accessType": 
"write"},
         ]
         set_response = 
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
-        self.assertEqual(
-            set_response["body"]["breakpoints"],
-            [{"verified": True}, {"verified": True}],
-        )
+        breakpoints = set_response["body"]["breakpoints"]
+        self.assertEqual(len(breakpoints), 2)
+        self.assertTrue(breakpoints[0]["verified"])
+        self.assertTrue(breakpoints[1]["verified"])
 
-        self.continue_to_next_stop()
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[0]["id"]])
         x_val = self.dap_server.get_local_variable_value("x")
         i_val = self.dap_server.get_local_variable_value("i")
         self.assertEqual(x_val, "2")
         self.assertEqual(i_val, "1")
 
-        self.continue_to_next_stop()
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[1]["id"]])
         arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
         i_val = self.dap_server.get_local_variable_value("i")
         self.assertEqual(arr_2["value"], "42")
@@ -153,8 +160,11 @@ def test_functionality(self):
             }
         ]
         set_response = 
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
-        self.assertEqual(set_response["body"]["breakpoints"], [{"verified": 
True}])
-        self.continue_to_next_stop()
+        breakpoints = set_response["body"]["breakpoints"]
+        self.assertEqual(len(breakpoints), 1)
+        self.assertTrue(breakpoints[0]["verified"])
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[0]["id"]])
         x_val = self.dap_server.get_local_variable_value("x")
         self.assertEqual(x_val, "3")
 
@@ -167,7 +177,64 @@ def test_functionality(self):
             }
         ]
         set_response = 
self.dap_server.request_setDataBreakpoint(dataBreakpoints)
-        self.assertEqual(set_response["body"]["breakpoints"], [{"verified": 
True}])
-        self.continue_to_next_stop()
+        breakpoints = set_response["body"]["breakpoints"]
+        self.assertEqual(len(breakpoints), 1)
+        self.assertTrue(breakpoints[0]["verified"])
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[0]["id"]])
         x_val = self.dap_server.get_local_variable_value("x")
         self.assertEqual(x_val, "10")
+
+    @skipIfWindows
+    def test_bytes(self):
+        """Tests setting data breakpoints on memory range."""
+        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()
+        # Test write watchpoints on x, arr[2]
+        x = self.dap_server.get_local_variable("x")
+        response_x = self.dap_server.request_dataBreakpointInfo(
+            0, x["memoryReference"], 4
+        )
+        arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
+        response_arr_2 = self.dap_server.request_dataBreakpointInfo(
+            0, arr_2["memoryReference"], 4
+        )
+
+        # Test response from dataBreakpointInfo request.
+        self.assertEqual(
+            response_x["body"]["dataId"].split("/"), 
[x["memoryReference"][2:], "4"]
+        )
+        self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes)
+        self.assertEqual(
+            response_arr_2["body"]["dataId"].split("/"),
+            [arr_2["memoryReference"][2:], "4"],
+        )
+        self.assertEqual(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)
+        breakpoints = set_response["body"]["breakpoints"]
+        self.assertEqual(len(breakpoints), 2)
+        self.assertTrue(breakpoints[0]["verified"])
+        self.assertTrue(breakpoints[1]["verified"])
+
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[0]["id"]])
+        x_val = self.dap_server.get_local_variable_value("x")
+        i_val = self.dap_server.get_local_variable_value("i")
+        self.assertEqual(x_val, "2")
+        self.assertEqual(i_val, "1")
+
+        self.dap_server.request_continue()
+        self.verify_breakpoint_hit([breakpoints[1]["id"]])
+        arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
+        i_val = self.dap_server.get_local_variable_value("i")
+        self.assertEqual(arr_2["value"], "42")
+        self.assertEqual(i_val, "2")
+        self.dap_server.request_setDataBreakpoint([])

diff  --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp 
b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
index 87b93fc999ecd..245d92c18e59e 100644
--- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp
@@ -7,15 +7,33 @@
 
//===----------------------------------------------------------------------===//
 
 #include "DAP.h"
+#include "DAPError.h"
 #include "EventHelper.h"
 #include "Protocol/ProtocolTypes.h"
 #include "RequestHandler.h"
+#include "lldb/API/SBAddress.h"
 #include "lldb/API/SBMemoryRegionInfo.h"
 #include "llvm/ADT/StringExtras.h"
 #include <optional>
 
 namespace lldb_dap {
 
+static bool IsRW(DAP &dap, lldb::addr_t load_addr) {
+  if (!lldb::SBAddress(load_addr, dap.target).IsValid())
+    return false;
+  lldb::SBMemoryRegionInfo region;
+  lldb::SBError err =
+      dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
+  // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
+  // request if SBProcess::GetMemoryRegionInfo returns error.
+  if (err.Success()) {
+    if (!(region.IsReadable() || region.IsWritable())) {
+      return false;
+    }
+  }
+  return true;
+}
+
 /// Obtains information on a possible data breakpoint that could be set on an
 /// expression or variable. Clients should only call this request if the
 /// corresponding capability supportsDataBreakpoints is true.
@@ -23,7 +41,6 @@ llvm::Expected<protocol::DataBreakpointInfoResponseBody>
 DataBreakpointInfoRequestHandler::Run(
     const protocol::DataBreakpointInfoArguments &args) const {
   protocol::DataBreakpointInfoResponseBody response;
-  lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
   lldb::SBValue variable = dap.variables.FindVariable(
       args.variablesReference.value_or(0), args.name);
   std::string addr, size;
@@ -43,7 +60,8 @@ DataBreakpointInfoRequestHandler::Run(
       addr = llvm::utohexstr(load_addr);
       size = llvm::utostr(byte_size);
     }
-  } else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
+  } else if (lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId);
+             args.variablesReference.value_or(0) == 0 && frame.IsValid()) {
     lldb::SBValue value = frame.EvaluateExpression(args.name.c_str());
     if (value.GetError().Fail()) {
       lldb::SBError error = value.GetError();
@@ -58,17 +76,10 @@ DataBreakpointInfoRequestHandler::Run(
       if (data.IsValid()) {
         size = llvm::utostr(data.GetByteSize());
         addr = llvm::utohexstr(load_addr);
-        lldb::SBMemoryRegionInfo region;
-        lldb::SBError err =
-            dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
-        // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this
-        // request if SBProcess::GetMemoryRegionInfo returns error.
-        if (err.Success()) {
-          if (!(region.IsReadable() || region.IsWritable())) {
-            is_data_ok = false;
-            response.description = "memory region for address " + addr +
-                                   " has no read or write permissions";
-          }
+        if (!IsRW(dap, load_addr)) {
+          is_data_ok = false;
+          response.description = "memory region for address " + addr +
+                                 " has no read or write permissions";
         }
       } else {
         is_data_ok = false;
@@ -76,6 +87,17 @@ DataBreakpointInfoRequestHandler::Run(
             "unable to get byte size for expression: " + args.name;
       }
     }
+  } else if (args.asAddress) {
+    size = llvm::utostr(args.bytes.value_or(dap.target.GetAddressByteSize()));
+    lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
+    if (llvm::StringRef(args.name).getAsInteger<lldb::addr_t>(0, load_addr))
+      return llvm::make_error<DAPError>(args.name + " is not a valid address",
+                                        llvm::inconvertibleErrorCode(), false);
+    addr = llvm::utohexstr(load_addr);
+    if (!IsRW(dap, load_addr))
+      return llvm::make_error<DAPError>("memory region for address " + addr +
+                                            " has no read or write 
permissions",
+                                        llvm::inconvertibleErrorCode(), false);
   } else {
     is_data_ok = false;
     response.description = "variable not found: " + args.name;
@@ -86,7 +108,10 @@ DataBreakpointInfoRequestHandler::Run(
     response.accessTypes = {protocol::eDataBreakpointAccessTypeRead,
                             protocol::eDataBreakpointAccessTypeWrite,
                             protocol::eDataBreakpointAccessTypeReadWrite};
-    response.description = size + " bytes at " + addr + " " + args.name;
+    if (args.asAddress)
+      response.description = size + " bytes at " + addr;
+    else
+      response.description = size + " bytes at " + addr + " " + args.name;
   }
 
   return response;

diff  --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h 
b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 65a52075ebd79..5d235352b7738 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -435,6 +435,9 @@ class DataBreakpointInfoRequestHandler
 public:
   using RequestHandler::RequestHandler;
   static llvm::StringLiteral GetCommand() { return "dataBreakpointInfo"; }
+  FeatureSet GetSupportedFeatures() const override {
+    return {protocol::eAdapterFeatureDataBreakpointBytes};
+  }
   llvm::Expected<protocol::DataBreakpointInfoResponseBody>
   Run(const protocol::DataBreakpointInfoArguments &args) const override;
 };

diff  --git a/lldb/tools/lldb-dap/JSONUtils.cpp 
b/lldb/tools/lldb-dap/JSONUtils.cpp
index 81eadae03bb48..5c4afa3fd2f62 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -677,7 +677,14 @@ llvm::json::Value CreateThreadStopped(DAP &dap, 
lldb::SBThread &thread,
       EmplaceSafeString(body, "description", desc_str);
     }
   } break;
-  case lldb::eStopReasonWatchpoint:
+  case lldb::eStopReasonWatchpoint: {
+    body.try_emplace("reason", "data breakpoint");
+    lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);
+    body.try_emplace("hitBreakpointIds",
+                     llvm::json::Array{llvm::json::Value(bp_id)});
+    EmplaceSafeString(body, "description",
+                      llvm::formatv("data breakpoint {0}", bp_id).str());
+  } break;
   case lldb::eStopReasonInstrumentation:
     body.try_emplace("reason", "breakpoint");
     break;

diff  --git a/lldb/tools/lldb-dap/Watchpoint.cpp 
b/lldb/tools/lldb-dap/Watchpoint.cpp
index 0acc980890be8..e730e71c0dc31 100644
--- a/lldb/tools/lldb-dap/Watchpoint.cpp
+++ b/lldb/tools/lldb-dap/Watchpoint.cpp
@@ -45,6 +45,7 @@ protocol::Breakpoint Watchpoint::ToProtocolBreakpoint() {
       breakpoint.message = m_error.GetCString();
   } else {
     breakpoint.verified = true;
+    breakpoint.id = m_wp.GetID();
   }
 
   return breakpoint;


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to