[lldb-dap] Support inspecting memory by vogelsgesang · Pull Request #104317 · llvm/llvm-project (original) (raw)

@llvm/pr-subscribers-lldb

Author: Adrian Vogelsgesang (vogelsgesang)

Changes

Adds support for the readMemory request which allows VS-Code to inspect memory. Also, add memoryReference to variablesa and evaluate responses, such that the binary view can be opened from the variables view and from the "watch" pane.


Full diff: https://github.com/llvm/llvm-project/pull/104317.diff

7 Files Affected:

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 a324af57b61df3..35d792351e6bfc 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 @@ -691,6 +691,21 @@ def request_disassemble( for inst in instructions: self.disassembled_instructions[inst["address"]] = inst + def request_read_memory( + self, memoryReference, offset, count + ): + args_dict = { + "memoryReference": memoryReference, + "offset": offset, + "count": count, + } + command_dict = { + "command": "readMemory", + "type": "request", + "arguments": args_dict, + } + return self.send_recv(command_dict) + def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None): stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId) if stackFrame is None: diff --git a/lldb/test/API/tools/lldb-dap/memory/Makefile b/lldb/test/API/tools/lldb-dap/memory/Makefile new file mode 100644 index 00000000000000..99998b20bcb050 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/memory/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py new file mode 100644 index 00000000000000..9ff4dbd3138428 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py @@ -0,0 +1,121 @@ +""" +Test lldb-dap memory support +""" + + +from base64 import b64decode +import dap_server +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbdap_testcase +import os + + +class TestDAP_memory(lldbdap_testcase.DAPTestCaseBase): + def test_read_memory(self): + """ + Tests the 'read_memory' request + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.cpp" + self.source_path = os.path.join(os.getcwd(), source) + self.set_source_breakpoints( + source, + [ + line_number(source, "// Breakpoint"), + ], + ) + self.continue_to_next_stop() + + locals = {l['name']: l for l in self.dap_server.get_local_variables()} + rawptr_ref = locals['rawptr']['memoryReference'] + + # We can read the complete string + mem = self.dap_server.request_read_memory(rawptr_ref, 0, 5)["body"] + self.assertEqual(mem["unreadableBytes"], 0); + self.assertEqual(b64decode(mem["data"]), b"dead\0") + + # Use an offset + mem = self.dap_server.request_read_memory(rawptr_ref, 2, 3)["body"] + self.assertEqual(b64decode(mem["data"]), b"ad\0") + + # Use a negative offset + mem = self.dap_server.request_read_memory(rawptr_ref, -1, 6)["body"] + self.assertEqual(b64decode(mem["data"])[1:], b"dead\0") + + # Reads of size 0 are successful + # VS-Code sends those in order to check if a memoryReference can actually be dereferenced. + mem = self.dap_server.request_read_memory(rawptr_ref, 0, 0) + self.assertEqual(mem["success"], True) + self.assertEqual(mem["body"]["data"], "") + + # Reads at offset 0x0 fail + mem = self.dap_server.request_read_memory("0x0", 0, 6) + self.assertEqual(mem["success"], False) + self.assertTrue(mem["message"].startswith("Unable to read memory: ")) + + + def test_memory_refs_variables(self): + """ + Tests memory references for evaluate + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.cpp" + self.source_path = os.path.join(os.getcwd(), source) + self.set_source_breakpoints( + source, [line_number(source, "// Breakpoint")], + ) + self.continue_to_next_stop() + + locals = {l['name']: l for l in self.dap_server.get_local_variables()} + + # Pointers should have memory-references + self.assertIn("memoryReference", locals['rawptr'].keys()) + # Smart-pointers also have memory-references + self.assertIn("memoryReference", self.dap_server.get_local_variable_child("smartptr", "pointer").keys()) + # Non-pointers should not have memory-references + self.assertNotIn("memoryReference", locals['not_a_ptr'].keys()) + + + def test_memory_refs_evaluate(self): + """ + Tests memory references for evaluate + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.cpp" + self.source_path = os.path.join(os.getcwd(), source) + self.set_source_breakpoints( + source, [line_number(source, "// Breakpoint")], + ) + self.continue_to_next_stop() + + # Pointers contain memory references + self.assertIn("memoryReference", self.dap_server.request_evaluate("rawptr + 1")["body"].keys()) + + # Non-pointer expressions don't include a memory reference + self.assertNotIn("memoryReference", self.dap_server.request_evaluate("1 + 3")["body"].keys()) + + + def test_memory_refs_set_variable(self): + """ + Tests memory references for setVariable + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.cpp" + self.source_path = os.path.join(os.getcwd(), source) + self.set_source_breakpoints( + source, [line_number(source, "// Breakpoint")], + ) + self.continue_to_next_stop() + + # Pointers contain memory references + ptr_value = self.get_local_as_int("rawptr") + self.assertIn("memoryReference", self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)["body"].keys()) + + # Non-pointer expressions don't include a memory reference + self.assertNotIn("memoryReference", self.dap_server.request_setVariable(1, "not_a_ptr", 42)["body"].keys()) diff --git a/lldb/test/API/tools/lldb-dap/memory/main.cpp b/lldb/test/API/tools/lldb-dap/memory/main.cpp new file mode 100644 index 00000000000000..95ecde82ea2798 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/memory/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main() { + int not_a_ptr = 666; + const char* rawptr = "dead"; + std::unique_ptr smartptr(new int(42)); + // Breakpoint + return 0; +} diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index a8b85f55939e17..dc8932d5f3181d 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -1085,6 +1085,19 @@ std::string VariableDescription::GetResult(llvm::StringRef context) { return description.trim().str(); } +lldb::addr_t GetMemoryReference(lldb::SBValue v) { + if (!v.GetType().IsPointerType() && !v.GetType().IsArrayType()) { + return LLDB_INVALID_ADDRESS; + } + + lldb::SBValue deref = v.Dereference(); + if (!deref.IsValid()) { + return LLDB_INVALID_ADDRESS; + } + + return deref.GetLoadAddress(); +} + // "Variable": { // "type": "object", // "description": "A Variable is a name/value pair. Optionally a variable @@ -1144,8 +1157,16 @@ std::string VariableDescription::GetResult(llvm::StringRef context) { // can use this optional information to present the // children in a paged UI and fetch them in chunks." // } -// -// +// "memoryReference": { +// "type": "string", +// "description": "A memory reference associated with this variable. +// For pointer type variables, this is generally a +// reference to the memory address contained in the +// pointer. For executable data, this reference may later +// be used in a disassemble request. This attribute may +// be returned by a debug adapter if corresponding +// capability supportsMemoryReferences is true." +// }, // "$__lldb_extensions": { // "description": "Unofficial extensions to the protocol", // "properties": { @@ -1253,6 +1274,11 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference, else object.try_emplace("variablesReference", (int64_t)0); +
+ if (lldb::addr_t addr = GetMemoryReference(v); addr != LLDB_INVALID_ADDRESS) { + object.try_emplace("memoryReference", "0x" + llvm::utohexstr(addr)); + } + object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON()); return llvm::json::Value(std::move(object)); } diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 1515f5ba2e5f4d..c619712c2c682b 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -411,6 +411,12 @@ struct VariableDescription { std::string GetResult(llvm::StringRef context); }; +/// Get the corresponding memoryReference for a value. +/// +/// According to the DAP documentation, the memoryReference should +/// refer to the pointee, not to the address of the pointer itself. +lldb::addr_t GetMemoryReference(lldb::SBValue v); + /// Create a "Variable" object for a LLDB thread object. /// /// This function will fill in the following keys in the returned diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index ea84f31aec3a6c..d138a79d4488e5 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -9,6 +9,7 @@ #include "DAP.h" #include "Watchpoint.h" #include "lldb/API/SBMemoryRegionInfo.h" +#include "llvm/Support/Base64.h" #include #include @@ -1346,6 +1347,16 @@ void request_completions(const llvm::json::Object &request) { // present the variables in a paged UI and fetch // them in chunks." // } +// "memoryReference": { +// "type": "string", +// "description": "A memory reference to a location appropriate +// for this result. For pointer type eval +// results, this is generally a reference to the +// memory address contained in the pointer. This +// attribute may be returned by a debug adapter +// if corresponding capability +// supportsMemoryReferences is true." +// }, // }, // "required": [ "result", "variablesReference" ] // } @@ -1411,6 +1422,10 @@ void request_evaluate(const llvm::json::Object &request) { } else { body.try_emplace("variablesReference", (int64_t)0); } + if (lldb::addr_t addr = GetMemoryReference(value); + addr != LLDB_INVALID_ADDRESS) { + body.try_emplace("memoryReference", "0x" + llvm::utohexstr(addr)); + } } } response.try_emplace("body", std::move(body)); @@ -1718,6 +1733,8 @@ void request_initialize(const llvm::json::Object &request) { body.try_emplace("supportsLogPoints", true); // The debug adapter supports data watchpoints. body.try_emplace("supportsDataBreakpoints", true); + // The debug adapter supports the readMemory request. + body.try_emplace("supportsReadMemoryRequest", true); // Put in non-DAP specification lldb specific information. llvm::json::Object lldb_json; @@ -3600,8 +3617,12 @@ void request_setVariable(const llvm::json::Object &request) { if (variable.MightHaveChildren()) newVariablesReference = g_dap.variables.InsertExpandableVariable( variable, /is_permanent=/false);

   body.try_emplace("variablesReference", newVariablesReference);