https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/141122
>From a839dde6f9be0d3cbec73b9c001144b6cdbdc732 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <yerimy...@gmail.com> Date: Wed, 21 May 2025 23:26:14 +0100 Subject: [PATCH 1/4] [lldb][lldb-dap] support DataBreakpointBytes capability --- .../DataBreakpointInfoRequestHandler.cpp | 48 ++++++++++++++++++- lldb/tools/lldb-dap/Handler/RequestHandler.h | 3 ++ .../lldb-dap/Protocol/ProtocolRequests.h | 2 +- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp index 8cb25d0603449..9b969560d7973 100644 --- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "EventHelper.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" #include "lldb/API/SBMemoryRegionInfo.h" @@ -16,12 +15,59 @@ namespace lldb_dap { +static llvm::Expected<protocol::DataBreakpointInfoResponseBody> +HandleDataBreakpointBytes(DAP &dap, + const protocol::DataBreakpointInfoArguments &args) { + llvm::StringRef address = args.name; + + unsigned long long load_addr = LLDB_INVALID_ADDRESS; + if (llvm::getAsUnsignedInteger(address, 0, load_addr)) { + return llvm::make_error<DAPError>(llvm::formatv("invalid address"), + llvm::inconvertibleErrorCode(), false); + } + + lldb::SBAddress sb_addr(load_addr, dap.target); + if (!sb_addr.IsValid()) { + return llvm::make_error<DAPError>( + llvm::formatv("address {:x} does not exist in the debuggee", load_addr), + llvm::inconvertibleErrorCode(), false); + } + + const uint32_t byte_size = + args.bytes.value_or(dap.target.GetAddressByteSize()); + + protocol::DataBreakpointInfoResponseBody response; + response.dataId = llvm::formatv("{:x}/{}", load_addr, byte_size); + + 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() && !(region.IsReadable() || region.IsWritable())) { + response.description = llvm::formatv( + "memory region for address {} has no read or write permissions", + load_addr); + } else { + response.description = llvm::formatv("{} bytes at {:x}", load_addr); + response.accessTypes = {protocol::eDataBreakpointAccessTypeRead, + protocol::eDataBreakpointAccessTypeWrite, + protocol::eDataBreakpointAccessTypeReadWrite}; + } + + return response; +} + /// 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. llvm::Expected<protocol::DataBreakpointInfoResponseBody> DataBreakpointInfoRequestHandler::Run( const protocol::DataBreakpointInfoArguments &args) const { + + if (args.asAddress.value_or(false)) + return HandleDataBreakpointBytes(dap, args); + protocol::DataBreakpointInfoResponseBody response; lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId.value_or(UINT64_MAX)); lldb::SBValue variable = dap.variables.FindVariable( diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 3a965bcc87a5e..dec68683fee65 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -420,6 +420,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/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 7c774e50d6e56..cde441351fc88 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -668,7 +668,7 @@ struct DataBreakpointInfoArguments { /// pause on data access anywhere within that range. /// Clients may set this property only if the `supportsDataBreakpointBytes` /// capability is true. - std::optional<int64_t> bytes; + std::optional<uint64_t> bytes; /// If `true`, the `name` is a memory address and the debugger should /// interpret it as a decimal value, or hex value if it is prefixed with `0x`. >From 7abbc1f90272587e225ed6ba4a978661c9449cab Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <yerimy...@gmail.com> Date: Thu, 22 May 2025 15:18:57 +0100 Subject: [PATCH 2/4] [lldb][lldb-dap] support DataBreakpointBytes capability --- .../Handler/DataBreakpointInfoRequestHandler.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp index 9b969560d7973..2e429c045cdc0 100644 --- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp @@ -37,7 +37,7 @@ HandleDataBreakpointBytes(DAP &dap, args.bytes.value_or(dap.target.GetAddressByteSize()); protocol::DataBreakpointInfoResponseBody response; - response.dataId = llvm::formatv("{:x}/{}", load_addr, byte_size); + response.dataId = llvm::formatv("{:x-}/{}", load_addr, byte_size); lldb::SBMemoryRegionInfo region; lldb::SBError err = @@ -48,8 +48,10 @@ HandleDataBreakpointBytes(DAP &dap, response.description = llvm::formatv( "memory region for address {} has no read or write permissions", load_addr); + } else { - response.description = llvm::formatv("{} bytes at {:x}", load_addr); + response.description = + llvm::formatv("{} bytes at {:x}", byte_size, load_addr); response.accessTypes = {protocol::eDataBreakpointAccessTypeRead, protocol::eDataBreakpointAccessTypeWrite, protocol::eDataBreakpointAccessTypeReadWrite}; @@ -86,7 +88,7 @@ DataBreakpointInfoRequestHandler::Run( is_data_ok = false; response.description = "variable size is 0"; } else { - addr = llvm::utohexstr(load_addr); + addr = llvm::utohexstr(load_addr, /*lowerCase=*/true); size = llvm::utostr(byte_size); } } else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) { @@ -103,7 +105,7 @@ DataBreakpointInfoRequestHandler::Run( lldb::SBData data = value.GetPointeeData(); if (data.IsValid()) { size = llvm::utostr(data.GetByteSize()); - addr = llvm::utohexstr(load_addr); + addr = llvm::utohexstr(load_addr, /*lowerCase=*/true); lldb::SBMemoryRegionInfo region; lldb::SBError err = dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); @@ -132,7 +134,7 @@ DataBreakpointInfoRequestHandler::Run( response.accessTypes = {protocol::eDataBreakpointAccessTypeRead, protocol::eDataBreakpointAccessTypeWrite, protocol::eDataBreakpointAccessTypeReadWrite}; - response.description = size + " bytes at " + addr + " " + args.name; + response.description = size + " bytes at 0x" + addr + " " + args.name; } return response; >From e11eeee4cba135590a5f39f4ecdd170cf3a18132 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <yerimy...@gmail.com> Date: Thu, 22 May 2025 19:19:07 +0100 Subject: [PATCH 3/4] [lldb][lldb-dap] add DataBreakpointBytes test case --- .../test/tools/lldb-dap/dap_server.py | 33 ++++++++---- .../test/tools/lldb-dap/lldbdap_testcase.py | 19 ++++--- .../TestDAP_setDataBreakpoints.py | 53 ++++++++++++++++--- .../tools/lldb-dap/databreakpoint/main.cpp | 3 ++ 4 files changed, 87 insertions(+), 21 deletions(-) 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 a028381a0a4f9..2468ca7e37f14 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 @@ -1042,16 +1042,31 @@ 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 + self, + name: str, + variablesReference: int = None, + frameIndex: int = 0, + bytes_: int = None, + asAddress: bool = 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 = {} + if asAddress is not None: + args_dict = { + "name": name, + "asAddress": asAddress, + "bytes": bytes_, + } + else: + stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=None) + if stackFrame is None: + return [] + args_dict = { + "variablesReference": variablesReference, + "name": name, + "frameId": stackFrame["id"], + } + 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 91ae55977046b..996c85ea69622 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 @@ -104,7 +104,9 @@ def waitUntil(self, condition_callback): time.sleep(0.5) return False - def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT): + def verify_breakpoint_hit( + self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, is_watchpoint=False + ): """Wait for the process we are debugging to stop, and verify we hit any breakpoint location in the "breakpoint_ids" array. "breakpoint_ids" should be a list of breakpoint ID strings @@ -131,9 +133,10 @@ def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT): # So when looking at the description we just want to make sure # the right breakpoint matches and not worry about the actual # location. + type_name = "watchpoint" if is_watchpoint else "breakpoint" description = body["description"] for breakpoint_id in breakpoint_ids: - match_desc = f"breakpoint {breakpoint_id}." + match_desc = f"{type_name} {breakpoint_id}" if match_desc in description: return self.assertTrue(False, f"breakpoint not hit, stopped_events={stopped_events}") @@ -329,12 +332,16 @@ def continue_to_next_stop(self, timeout=DEFAULT_TIMEOUT): self.do_continue() return self.dap_server.wait_for_stopped(timeout) - def continue_to_breakpoint(self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT): - self.continue_to_breakpoints((breakpoint_id), timeout) + def continue_to_breakpoint( + self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT, is_watchpoint=False + ): + self.continue_to_breakpoints([breakpoint_id], timeout, is_watchpoint) - def continue_to_breakpoints(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT): + def continue_to_breakpoints( + self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, is_watchpoint=False + ): self.do_continue() - self.verify_breakpoint_hit(breakpoint_ids, timeout) + self.verify_breakpoint_hit(breakpoint_ids, timeout, is_watchpoint) def continue_to_exception_breakpoint(self, filter_label, timeout=DEFAULT_TIMEOUT): self.do_continue() 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..62392f2c49afd 100644 --- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py @@ -23,8 +23,8 @@ def test_duplicate_start_addresses(self): 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") + response_x = self.dap_server.request_dataBreakpointInfo("&x", 0) + response_arr_2 = self.dap_server.request_dataBreakpointInfo("arr+2", 0) # Test response from dataBreakpointInfo request. self.assertEqual(response_x["body"]["dataId"].split("/")[1], "4") self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes) @@ -56,6 +56,47 @@ def test_duplicate_start_addresses(self): self.assertEqual(arr_2["value"], "42") self.assertEqual(i_val, "2") + @skipIfWindows + def test_breakpoint_info_bytes(self): + """Test supportBreakpointInfoBytes + Set the watchpoint on `var` variable address + 6 characters. + """ + 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() + + # get the address of `var` variable + eval_response = self.dap_server.request_evaluate("&var", context="watch") + self.assertTrue(eval_response["success"]) + var_address = eval_response["body"]["result"] + + var_byte_watch_size = 5 + bp_resp = self.dap_server.request_dataBreakpointInfo( + var_address, asAddress=True, bytes_=var_byte_watch_size + ) + resp_data_id = bp_resp["body"]["dataId"] + self.assertTrue( + bp_resp["success"], f"dataBreakpointInfo request failed: {bp_resp}" + ) + self.assertEqual(resp_data_id.split("/")[1], str(var_byte_watch_size)) + + data_breakpoints = [{"dataId": resp_data_id, "accessType": "write"}] + self.dap_server.request_setDataBreakpoint(data_breakpoints) + + self.continue_to_breakpoint(breakpoint_id=1, is_watchpoint=True) + eval_response = self.dap_server.request_evaluate("var", context="watch") + self.assertTrue(eval_response["success"]) + var_value = eval_response["body"]["result"] + self.assertEqual(var_value, '"HALLO"') + + # Remove the watchpoint because once it leaves this function scope, the address can be + # be used by another variable or register. + self.dap_server.request_setDataBreakpoint([]) + self.continue_to_exit() + @skipIfWindows def test_expression(self): """Tests setting data breakpoints on expression.""" @@ -67,8 +108,8 @@ def test_expression(self): 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") + response_x = self.dap_server.request_dataBreakpointInfo("&x", 0) + response_arr_2 = self.dap_server.request_dataBreakpointInfo("arr+2", 0) # Test response from dataBreakpointInfo request. self.assertEqual(response_x["body"]["dataId"].split("/")[1], "4") self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes) @@ -107,10 +148,10 @@ def test_functionality(self): 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") + response_x = self.dap_server.request_dataBreakpointInfo("x", 1) arr = self.dap_server.get_local_variable("arr") response_arr_2 = self.dap_server.request_dataBreakpointInfo( - arr["variablesReference"], "[2]" + "[2]", arr["variablesReference"] ) # Test response from dataBreakpointInfo request. diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp b/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp index bef09c203845e..e4007980d27a8 100644 --- a/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp +++ b/lldb/test/API/tools/lldb-dap/databreakpoint/main.cpp @@ -1,5 +1,6 @@ int main(int argc, char const *argv[]) { // Test for data breakpoint + char var[6] = "HELLO"; int x = 0; int arr[4] = {1, 2, 3, 4}; for (int i = 0; i < 5; ++i) { // first loop breakpoint @@ -10,6 +11,8 @@ int main(int argc, char const *argv[]) { } } + var[1] = 'A'; + x = 1; for (int i = 0; i < 10; ++i) { // second loop breakpoint ++x; >From bcfea6b18adfe82c82f8825ca42518d15471614a Mon Sep 17 00:00:00 2001 From: Ebuka Ezike <yerimy...@gmail.com> Date: Thu, 22 May 2025 20:57:27 +0100 Subject: [PATCH 4/4] [lldb] format files. --- .../lldbsuite/test/tools/lldb-dap/dap_server.py | 12 ++++++------ .../test/tools/lldb-dap/lldbdap_testcase.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) 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 2468ca7e37f14..a287979492528 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 @@ -1042,12 +1042,12 @@ def request_setFunctionBreakpoints(self, names, condition=None, hitCondition=Non return self.send_recv(command_dict) def request_dataBreakpointInfo( - self, - name: str, - variablesReference: int = None, - frameIndex: int = 0, - bytes_: int = None, - asAddress: bool = None, + self, + name: str, + variablesReference: int = None, + frameIndex: int = 0, + bytes_: int = None, + asAddress: bool = None, ): args_dict = {} 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 996c85ea69622..dadaf0fc53145 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 @@ -105,7 +105,7 @@ def waitUntil(self, condition_callback): return False def verify_breakpoint_hit( - self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, is_watchpoint=False + self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, is_watchpoint=False ): """Wait for the process we are debugging to stop, and verify we hit any breakpoint location in the "breakpoint_ids" array. @@ -333,12 +333,12 @@ def continue_to_next_stop(self, timeout=DEFAULT_TIMEOUT): return self.dap_server.wait_for_stopped(timeout) def continue_to_breakpoint( - self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT, is_watchpoint=False + self, breakpoint_id: str, timeout=DEFAULT_TIMEOUT, is_watchpoint=False ): self.continue_to_breakpoints([breakpoint_id], timeout, is_watchpoint) def continue_to_breakpoints( - self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, is_watchpoint=False + self, breakpoint_ids, timeout=DEFAULT_TIMEOUT, is_watchpoint=False ): self.do_continue() self.verify_breakpoint_hit(breakpoint_ids, timeout, is_watchpoint) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits