Author: Druzhkov Sergei Date: 2025-09-17T15:51:40-07:00 New Revision: c744f6168f6f817c7a2dcd493a8e29a0c1f8a3bb
URL: https://github.com/llvm/llvm-project/commit/c744f6168f6f817c7a2dcd493a8e29a0c1f8a3bb DIFF: https://github.com/llvm/llvm-project/commit/c744f6168f6f817c7a2dcd493a8e29a0c1f8a3bb.diff LOG: [lldb-dap] Add memory event (#158437) This patch adds support for the `memory` event from the [DAP](https://microsoft.github.io/debug-adapter-protocol/specification#Events_Memory). After this event is emitted, VS Code refetches the corresponding memory range and re-renders the memory view. I think this patch and [PR](https://github.com/llvm/llvm-project/pull/151884) can improve experience for users who use `setVariable` request. 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/tools/lldb-dap/EventHelper.cpp lldb/tools/lldb-dap/EventHelper.h lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp lldb/tools/lldb-dap/Protocol/ProtocolEvents.h lldb/unittests/DAP/ProtocolTypesTest.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 daa3e76df6d82..8eb64b4ab8b2b 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 @@ -216,6 +216,7 @@ def __init__( self.events: List[Event] = [] self.progress_events: List[Event] = [] self.invalidated_event: Optional[Event] = None + self.memory_event: Optional[Event] = None self.reverse_requests: List[Request] = [] self.module_events: List[Dict] = [] self.sequence: int = 1 @@ -443,6 +444,8 @@ def _handle_event(self, packet: Event) -> None: self.capabilities.update(body["capabilities"]) elif event == "invalidated": self.invalidated_event = packet + elif event == "memory": + self.memory_event = packet def _handle_reverse_request(self, request: Request) -> None: if request in self.reverse_requests: @@ -1018,6 +1021,7 @@ def request_initialize(self, sourceInitFile=False): "supportsStartDebuggingRequest": True, "supportsProgressReporting": True, "supportsInvalidatedEvent": True, + "supportsMemoryEvent": True, "$__lldb_sourceInitFile": sourceInitFile, }, } 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 a0a009ae6cc9a..882eec9971a73 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 @@ -248,6 +248,14 @@ def verify_invalidated_event(self, expected_areas): areas = event["body"].get("areas", []) self.assertEqual(set(expected_areas), set(areas)) + def verify_memory_event(self, memoryReference): + if memoryReference is None: + self.assertIsNone(self.dap_server.memory_event) + event = self.dap_server.memory_event + self.dap_server.memory_event = None + self.assertIsNotNone(event) + self.assertEqual(memoryReference, event["body"].get("memoryReference")) + def get_dict_value(self, d: dict, key_path: list[str]) -> Any: """Verify each key in the key_path array is in contained in each dictionary within "d". Assert if any key isn't in the @@ -364,6 +372,7 @@ def set_variable(self, varRef, name, value, id=None): response = self.dap_server.request_setVariable(varRef, name, str(value), id=id) if response["success"]: self.verify_invalidated_event(["variables"]) + self.verify_memory_event(response["body"].get("memoryReference")) return response def set_local(self, name, value, id=None): diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index 6eb468e76b16c..2b9ed229405a8 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -284,4 +284,15 @@ void SendInvalidatedEvent( dap.Send(protocol::Event{"invalidated", std::move(body)}); } +void SendMemoryEvent(DAP &dap, lldb::SBValue variable) { + if (!dap.clientFeatures.contains(protocol::eClientFeatureMemoryEvent)) + return; + protocol::MemoryEventBody body; + body.memoryReference = variable.GetLoadAddress(); + body.count = variable.GetByteSize(); + if (body.memoryReference == LLDB_INVALID_ADDRESS) + return; + dap.Send(protocol::Event{"memory", std::move(body)}); +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index 0c57afbaf1f33..48eb5af6bd0b9 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -37,6 +37,8 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process); void SendInvalidatedEvent( DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas); +void SendMemoryEvent(DAP &dap, lldb::SBValue variable); + } // namespace lldb_dap #endif diff --git a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp index 2a50dea0b4ada..b9ae28d6772ac 100644 --- a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp @@ -82,6 +82,9 @@ SetVariableRequestHandler::Run(const SetVariableArguments &args) const { // (e.g. references) can be changed. SendInvalidatedEvent(dap, {InvalidatedEventBody::eAreaVariables}); + // Also send memory event to signal client that variable memory was changed. + SendMemoryEvent(dap, variable); + return body; } diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp index 062b9494ec10f..b896eca817be6 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "Protocol/ProtocolEvents.h" +#include "JSONUtils.h" #include "llvm/Support/JSON.h" using namespace llvm; @@ -56,4 +57,11 @@ llvm::json::Value toJSON(const InvalidatedEventBody &IEB) { return Result; } +llvm::json::Value toJSON(const MemoryEventBody &MEB) { + return json::Object{ + {"memoryReference", EncodeMemoryReference(MEB.memoryReference)}, + {"offset", MEB.offset}, + {"count", MEB.count}}; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h index cb976d3395217..5cd5a843d284e 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h @@ -21,6 +21,7 @@ #define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H #include "Protocol/ProtocolTypes.h" +#include "lldb/lldb-defines.h" #include "lldb/lldb-types.h" #include "llvm/Support/JSON.h" #include <cstdint> @@ -88,6 +89,34 @@ struct InvalidatedEventBody { llvm::json::Value toJSON(const InvalidatedEventBody::Area &); llvm::json::Value toJSON(const InvalidatedEventBody &); +/// This event indicates that some memory range has been updated. It should only +/// be sent if the corresponding capability supportsMemoryEvent is true. +/// +/// Clients typically react to the event by re-issuing a readMemory request if +/// they show the memory identified by the memoryReference and if the updated +/// memory range overlaps the displayed range. Clients should not make +/// assumptions how individual memory references relate to each other, so they +/// should not assume that they are part of a single continuous address range +/// and might overlap. +/// +/// Debug adapters can use this event to indicate that the contents of a memory +/// range has changed due to some other request like setVariable or +/// setExpression. Debug adapters are not expected to emit this event for each +/// and every memory change of a running program, because that information is +/// typically not available from debuggers and it would flood clients with too +/// many events. +struct MemoryEventBody { + /// Memory reference of a memory range that has been updated. + lldb::addr_t memoryReference = LLDB_INVALID_ADDRESS; + + /// Starting offset in bytes where memory has been updated. Can be negative. + int64_t offset = 0; + + /// Number of bytes updated. + uint64_t count = 0; +}; +llvm::json::Value toJSON(const MemoryEventBody &); + } // end namespace lldb_dap::protocol #endif diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index 61d197a705e0e..0989a5becc8fe 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -1088,3 +1088,16 @@ TEST(ProtocolTypesTest, InvalidatedEventBody) { })"; EXPECT_EQ(json, pp(body)); } + +TEST(ProtocolTypesTest, MemoryEventBody) { + MemoryEventBody body; + body.memoryReference = 12345; + body.offset = 0; + body.count = 4; + StringRef json = R"({ + "count": 4, + "memoryReference": "0x3039", + "offset": 0 +})"; + EXPECT_EQ(json, pp(body)); +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits