Skip to content

Commit a5b4f6e

Browse files
committed
[lldb-dap] Support inspecting memory
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.
1 parent 82ee31f commit a5b4f6e

File tree

7 files changed

+365
-3
lines changed

7 files changed

+365
-3
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_read_memory(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: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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_read_memory(self):
16+
"""
17+
Tests the 'read_memory' request
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+
rawptr_ref = locals["rawptr"]["memoryReference"]
31+
32+
# We can read the complete string
33+
mem = self.dap_server.request_read_memory(rawptr_ref, 0, 5)["body"]
34+
self.assertEqual(mem["unreadableBytes"], 0)
35+
self.assertEqual(b64decode(mem["data"]), b"dead\0")
36+
37+
# Use an offset
38+
mem = self.dap_server.request_read_memory(rawptr_ref, 2, 3)["body"]
39+
self.assertEqual(b64decode(mem["data"]), b"ad\0")
40+
41+
# Use a negative offset
42+
mem = self.dap_server.request_read_memory(rawptr_ref, -1, 6)["body"]
43+
self.assertEqual(b64decode(mem["data"])[1:], b"dead\0")
44+
45+
# Reads of size 0 are successful
46+
# VS-Code sends those in order to check if a `memoryReference` can actually be dereferenced.
47+
mem = self.dap_server.request_read_memory(rawptr_ref, 0, 0)
48+
self.assertEqual(mem["success"], True)
49+
self.assertEqual(mem["body"]["data"], "")
50+
51+
# Reads at offset 0x0 fail
52+
mem = self.dap_server.request_read_memory("0x0", 0, 6)
53+
self.assertEqual(mem["success"], False)
54+
self.assertTrue(mem["message"].startswith("Unable to read memory: "))
55+
56+
def test_memory_refs_variables(self):
57+
"""
58+
Tests memory references for evaluate
59+
"""
60+
program = self.getBuildArtifact("a.out")
61+
self.build_and_launch(program)
62+
source = "main.cpp"
63+
self.source_path = os.path.join(os.getcwd(), source)
64+
self.set_source_breakpoints(
65+
source,
66+
[line_number(source, "// Breakpoint")],
67+
)
68+
self.continue_to_next_stop()
69+
70+
locals = {l["name"]: l for l in self.dap_server.get_local_variables()}
71+
72+
# Pointers should have memory-references
73+
self.assertIn("memoryReference", locals["rawptr"].keys())
74+
# Smart pointers also have memory-references
75+
self.assertIn(
76+
"memoryReference",
77+
self.dap_server.get_local_variable_child("smartptr", "pointer").keys(),
78+
)
79+
# Non-pointers should not have memory-references
80+
self.assertNotIn("memoryReference", locals["not_a_ptr"].keys())
81+
82+
def test_memory_refs_evaluate(self):
83+
"""
84+
Tests memory references for evaluate
85+
"""
86+
program = self.getBuildArtifact("a.out")
87+
self.build_and_launch(program)
88+
source = "main.cpp"
89+
self.source_path = os.path.join(os.getcwd(), source)
90+
self.set_source_breakpoints(
91+
source,
92+
[line_number(source, "// Breakpoint")],
93+
)
94+
self.continue_to_next_stop()
95+
96+
# Pointers contain memory references
97+
self.assertIn(
98+
"memoryReference",
99+
self.dap_server.request_evaluate("rawptr + 1")["body"].keys(),
100+
)
101+
102+
# Non-pointer expressions don't include a memory reference
103+
self.assertNotIn(
104+
"memoryReference",
105+
self.dap_server.request_evaluate("1 + 3")["body"].keys(),
106+
)
107+
108+
def test_memory_refs_set_variable(self):
109+
"""
110+
Tests memory references for `setVariable`
111+
"""
112+
program = self.getBuildArtifact("a.out")
113+
self.build_and_launch(program)
114+
source = "main.cpp"
115+
self.source_path = os.path.join(os.getcwd(), source)
116+
self.set_source_breakpoints(
117+
source,
118+
[line_number(source, "// Breakpoint")],
119+
)
120+
self.continue_to_next_stop()
121+
122+
# Pointers contain memory references
123+
ptr_value = self.get_local_as_int("rawptr")
124+
self.assertIn(
125+
"memoryReference",
126+
self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
127+
"body"
128+
].keys(),
129+
)
130+
131+
# Non-pointer expressions don't include a memory reference
132+
self.assertNotIn(
133+
"memoryReference",
134+
self.dap_server.request_setVariable(1, "not_a_ptr", 42)["body"].keys(),
135+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <iostream>
2+
#include <memory>
3+
4+
int main() {
5+
int not_a_ptr = 666;
6+
const char *rawptr = "dead";
7+
std::unique_ptr<int> smartptr(new int(42));
8+
// Breakpoint
9+
return 0;
10+
}

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,19 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
10851085
return description.trim().str();
10861086
}
10871087

1088+
lldb::addr_t GetMemoryReference(lldb::SBValue v) {
1089+
if (!v.GetType().IsPointerType() && !v.GetType().IsArrayType()) {
1090+
return LLDB_INVALID_ADDRESS;
1091+
}
1092+
1093+
lldb::SBValue deref = v.Dereference();
1094+
if (!deref.IsValid()) {
1095+
return LLDB_INVALID_ADDRESS;
1096+
}
1097+
1098+
return deref.GetLoadAddress();
1099+
}
1100+
10881101
// "Variable": {
10891102
// "type": "object",
10901103
// "description": "A Variable is a name/value pair. Optionally a variable
@@ -1144,8 +1157,16 @@ std::string VariableDescription::GetResult(llvm::StringRef context) {
11441157
// can use this optional information to present the
11451158
// children in a paged UI and fetch them in chunks."
11461159
// }
1147-
//
1148-
//
1160+
// "memoryReference": {
1161+
// "type": "string",
1162+
// "description": "A memory reference associated with this variable.
1163+
// For pointer type variables, this is generally a
1164+
// reference to the memory address contained in the
1165+
// pointer. For executable data, this reference may later
1166+
// be used in a `disassemble` request. This attribute may
1167+
// be returned by a debug adapter if corresponding
1168+
// capability `supportsMemoryReferences` is true."
1169+
// },
11491170
// "$__lldb_extensions": {
11501171
// "description": "Unofficial extensions to the protocol",
11511172
// "properties": {
@@ -1253,6 +1274,10 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
12531274
else
12541275
object.try_emplace("variablesReference", (int64_t)0);
12551276

1277+
if (lldb::addr_t addr = GetMemoryReference(v); addr != LLDB_INVALID_ADDRESS) {
1278+
object.try_emplace("memoryReference", "0x" + llvm::utohexstr(addr));
1279+
}
1280+
12561281
object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON());
12571282
return llvm::json::Value(std::move(object));
12581283
}

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,12 @@ struct VariableDescription {
411411
std::string GetResult(llvm::StringRef context);
412412
};
413413

414+
/// Get the corresponding `memoryReference` for a value.
415+
///
416+
/// According to the DAP documentation, the `memoryReference` should
417+
/// refer to the pointee, not to the address of the pointer itself.
418+
lldb::addr_t GetMemoryReference(lldb::SBValue v);
419+
414420
/// Create a "Variable" object for a LLDB thread object.
415421
///
416422
/// This function will fill in the following keys in the returned

0 commit comments

Comments
 (0)