Skip to content

[lldb-dap] Support inspecting memory #104317

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,19 @@ def request_disassemble(
for inst in instructions:
self.disassembled_instructions[inst["address"]] = inst

def request_readMemory(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:
Expand Down
3 changes: 3 additions & 0 deletions lldb/test/API/tools/lldb-dap/memory/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp

include Makefile.rules
116 changes: 116 additions & 0 deletions lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""
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_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())
# Non-pointers should also have memory-references
self.assertIn("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()

self.assertIn(
"memoryReference",
self.dap_server.request_evaluate("rawptr")["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()

ptr_value = self.get_local_as_int("rawptr")
self.assertIn(
"memoryReference",
self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
"body"
].keys(),
)

def test_readMemory(self):
"""
Tests the 'readMemory' 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()

ptr_deref = self.dap_server.request_evaluate("*rawptr")["body"]
memref = ptr_deref["memoryReference"]

# We can read the complete string
mem = self.dap_server.request_readMemory(memref, 0, 5)["body"]
self.assertEqual(mem["unreadableBytes"], 0)
self.assertEqual(b64decode(mem["data"]), b"dead\0")

# Use an offset
mem = self.dap_server.request_readMemory(memref, 2, 3)["body"]
self.assertEqual(b64decode(mem["data"]), b"ad\0")

# Use a negative offset
mem = self.dap_server.request_readMemory(memref, -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_readMemory(memref, 0, 0)
self.assertEqual(mem["success"], True)
self.assertEqual(mem["body"]["data"], "")

# Reads at offset 0x0 fail
mem = self.dap_server.request_readMemory("0x0", 0, 6)
self.assertEqual(mem["success"], False)
self.assertEqual(mem["message"], "Memory region is not readable")
9 changes: 9 additions & 0 deletions lldb/test/API/tools/lldb-dap/memory/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <iostream>
#include <memory>

int main() {
int not_a_ptr = 666;
const char *rawptr = "dead";
// Breakpoint
return 0;
}
34 changes: 30 additions & 4 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) {
return obj.find(key) != obj.end();
}

std::string EncodeMemoryReference(lldb::addr_t addr) {
return "0x" + llvm::utohexstr(addr);
}

std::optional<lldb::addr_t>
DecodeMemoryReference(llvm::StringRef memoryReference) {
if (!memoryReference.starts_with("0x"))
return std::nullopt;

lldb::addr_t addr;
if (memoryReference.consumeInteger(0, addr))
return std::nullopt;

return addr;
}

std::vector<std::string> GetStrings(const llvm::json::Object *obj,
llvm::StringRef key) {
std::vector<std::string> strs;
Expand Down Expand Up @@ -690,8 +706,7 @@ std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) {
// "instructionPointerReference": {
// "type": "string",
// "description": "A memory reference for the current instruction
// pointer
// in this frame."
// pointer in this frame."
// },
// "moduleId": {
// "type": ["integer", "string"],
Expand Down Expand Up @@ -1239,8 +1254,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": {
Expand Down Expand Up @@ -1348,6 +1371,9 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t variablesReference,
else
object.try_emplace("variablesReference", (int64_t)0);

if (lldb::addr_t addr = v.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
object.try_emplace("memoryReference", EncodeMemoryReference(addr));

object.try_emplace("$__lldb_extensions", desc.GetVariableExtensionsJSON());
return llvm::json::Value(std::move(object));
}
Expand Down
7 changes: 7 additions & 0 deletions lldb/tools/lldb-dap/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key,
/// \b True if the key exists in the \a obj, \b False otherwise.
bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key);

/// Encodes a memory reference
std::string EncodeMemoryReference(lldb::addr_t addr);

/// Decodes a memory reference
std::optional<lldb::addr_t>
DecodeMemoryReference(llvm::StringRef memoryReference);

/// Extract an array of strings for the specified key from an object.
///
/// String values in the array will be extracted without any quotes
Expand Down
Loading