Skip to content

Commit e01ea18

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 e01ea18

File tree

7 files changed

+354
-3
lines changed

7 files changed

+354
-3
lines changed

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

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

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

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

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)