Skip to content

Commit 3acb1ea

Browse files
authored
[lldb-dap] Support inspecting memory (llvm#104317)
Add support for the `readMemory` request which allows VS-Code to inspect memory. Also, add `memoryReference` to variables and `evaluate` responses, such that the binary view can be opened from the variables view and from the "watch" pane.
1 parent 9a1d074 commit 3acb1ea

File tree

7 files changed

+361
-12
lines changed

7 files changed

+361
-12
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,19 @@ def request_disassemble(
691691
for inst in instructions:
692692
self.disassembled_instructions[inst["address"]] = inst
693693

694+
def request_readMemory(self, memoryReference, offset, count):
695+
args_dict = {
696+
"memoryReference": memoryReference,
697+
"offset": offset,
698+
"count": count,
699+
}
700+
command_dict = {
701+
"command": "readMemory",
702+
"type": "request",
703+
"arguments": args_dict,
704+
}
705+
return self.send_recv(command_dict)
706+
694707
def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None):
695708
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
696709
if stackFrame is None:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
Test lldb-dap memory support
3+
"""
4+
5+
from base64 import b64decode
6+
import dap_server
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
import lldbdap_testcase
11+
import os
12+
13+
14+
class TestDAP_memory(lldbdap_testcase.DAPTestCaseBase):
15+
def test_memory_refs_variables(self):
16+
"""
17+
Tests memory references for evaluate
18+
"""
19+
program = self.getBuildArtifact("a.out")
20+
self.build_and_launch(program)
21+
source = "main.cpp"
22+
self.source_path = os.path.join(os.getcwd(), source)
23+
self.set_source_breakpoints(
24+
source,
25+
[line_number(source, "// Breakpoint")],
26+
)
27+
self.continue_to_next_stop()
28+
29+
locals = {l["name"]: l for l in self.dap_server.get_local_variables()}
30+
31+
# Pointers should have memory-references
32+
self.assertIn("memoryReference", locals["rawptr"].keys())
33+
# Non-pointers should also have memory-references
34+
self.assertIn("memoryReference", locals["not_a_ptr"].keys())
35+
36+
def test_memory_refs_evaluate(self):
37+
"""
38+
Tests memory references for evaluate
39+
"""
40+
program = self.getBuildArtifact("a.out")
41+
self.build_and_launch(program)
42+
source = "main.cpp"
43+
self.source_path = os.path.join(os.getcwd(), source)
44+
self.set_source_breakpoints(
45+
source,
46+
[line_number(source, "// Breakpoint")],
47+
)
48+
self.continue_to_next_stop()
49+
50+
self.assertIn(
51+
"memoryReference",
52+
self.dap_server.request_evaluate("rawptr")["body"].keys(),
53+
)
54+
55+
def test_memory_refs_set_variable(self):
56+
"""
57+
Tests memory references for `setVariable`
58+
"""
59+
program = self.getBuildArtifact("a.out")
60+
self.build_and_launch(program)
61+
source = "main.cpp"
62+
self.source_path = os.path.join(os.getcwd(), source)
63+
self.set_source_breakpoints(
64+
source,
65+
[line_number(source, "// Breakpoint")],
66+
)
67+
self.continue_to_next_stop()
68+
69+
ptr_value = self.get_local_as_int("rawptr")
70+
self.assertIn(
71+
"memoryReference",
72+
self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
73+
"body"
74+
].keys(),
75+
)
76+
77+
def test_readMemory(self):
78+
"""
79+
Tests the 'readMemory' request
80+
"""
81+
program = self.getBuildArtifact("a.out")
82+
self.build_and_launch(program)
83+
source = "main.cpp"
84+
self.source_path = os.path.join(os.getcwd(), source)
85+
self.set_source_breakpoints(
86+
source,
87+
[line_number(source, "// Breakpoint")],
88+
)
89+
self.continue_to_next_stop()
90+
91+
ptr_deref = self.dap_server.request_evaluate("*rawptr")["body"]
92+
memref = ptr_deref["memoryReference"]
93+
94+
# We can read the complete string
95+
mem = self.dap_server.request_readMemory(memref, 0, 5)["body"]
96+
self.assertEqual(mem["unreadableBytes"], 0)
97+
self.assertEqual(b64decode(mem["data"]), b"dead\0")
98+
99+
# Use an offset
100+
mem = self.dap_server.request_readMemory(memref, 2, 3)["body"]
101+
self.assertEqual(b64decode(mem["data"]), b"ad\0")
102+
103+
# Use a negative offset
104+
mem = self.dap_server.request_readMemory(memref, -1, 6)["body"]
105+
self.assertEqual(b64decode(mem["data"])[1:], b"dead\0")
106+
107+
# Reads of size 0 are successful
108+
# VS-Code sends those in order to check if a `memoryReference` can actually be dereferenced.
109+
mem = self.dap_server.request_readMemory(memref, 0, 0)
110+
self.assertEqual(mem["success"], True)
111+
self.assertEqual(mem["body"]["data"], "")
112+
113+
# Reads at offset 0x0 fail
114+
mem = self.dap_server.request_readMemory("0x0", 0, 6)
115+
self.assertEqual(mem["success"], False)
116+
self.assertEqual(mem["message"], "Memory region is not readable")
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <iostream>
2+
#include <memory>
3+
4+
int main() {
5+
int not_a_ptr = 666;
6+
const char *rawptr = "dead";
7+
// Breakpoint
8+
return 0;
9+
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {
112112
return obj.find(key) != obj.end();
113113
}
114114

115+
std::string EncodeMemoryReference(lldb::addr_t addr) {
116+
return "0x" + llvm::utohexstr(addr);
117+
}
118+
119+
std::optional<lldb::addr_t>
120+
DecodeMemoryReference(llvm::StringRef memoryReference) {
121+
if (!memoryReference.starts_with("0x"))
122+
return std::nullopt;
123+
124+
lldb::addr_t addr;
125+
if (memoryReference.consumeInteger(0, addr))
126+
return std::nullopt;
127+
128+
return addr;
129+
}
130+
115131
std::vector<std::string> GetStrings(const llvm::json::Object *obj,
116132
llvm::StringRef key) {
117133
std::vector<std::string> strs;
@@ -690,8 +706,7 @@ std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
690706
// "instructionPointerReference": {
691707
// "type": "string",
692708
// "description": "A memory reference for the current instruction
693-
// pointer
694-
// in this frame."
709+
// pointer in this frame."
695710
// },
696711
// "moduleId": {
697712
// "type": ["integer", "string"],
@@ -1239,8 +1254,16 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
12391254
// can use this optional information to present the
12401255
// children in a paged UI and fetch them in chunks."
12411256
// }
1242-
//
1243-
//
1257+
// "memoryReference": {
1258+
// "type": "string",
1259+
// "description": "A memory reference associated with this variable.
1260+
// For pointer type variables, this is generally a
1261+
// reference to the memory address contained in the
1262+
// pointer. For executable data, this reference may later
1263+
// be used in a `disassemble` request. This attribute may
1264+
// be returned by a debug adapter if corresponding
1265+
// capability `supportsMemoryReferences` is true."
1266+
// },
12441267
// "$__lldb_extensions": {
12451268
// "description": "Unofficial extensions to the protocol",
12461269
// "properties": {
@@ -1348,6 +1371,9 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
13481371
else
13491372
object.try_emplace("variablesReference", (int64_t)0);
13501373

1374+
if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
1375+
object.try_emplace("memoryReference", EncodeMemoryReference(addr));
1376+
13511377
object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON());
13521378
return llvm::json::Value(std::move(object));
13531379
}

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key,
131131
/// \b True if the key exists in the \a obj, \b False otherwise.
132132
bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key);
133133

134+
/// Encodes a memory reference
135+
std::string EncodeMemoryReference(lldb::addr_t addr);
136+
137+
/// Decodes a memory reference
138+
std::optional<lldb::addr_t>
139+
DecodeMemoryReference(llvm::StringRef memoryReference);
140+
134141
/// Extract an array of strings for the specified key from an object.
135142
///
136143
/// String values in the array will be extracted without any quotes

0 commit comments

Comments
 (0)