Skip to content

Commit 76b1dcf

Browse files
santhoshe447Santhosh Kumar EllendulaSanthosh Kumar Ellendula
authored
[lldb][lldb-dap] Added support for "WriteMemory" request. (#131820)
Added debug adapter support for write memory. --------- Co-authored-by: Santhosh Kumar Ellendula <sellendu@hu-sellendu-hyd.qualcomm.com> Co-authored-by: Santhosh Kumar Ellendula <sellendu@hu-sellendu-lv.qualcomm.com>
1 parent d193a58 commit 76b1dcf

File tree

10 files changed

+297
-1
lines changed

10 files changed

+297
-1
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,20 @@ def request_readMemory(self, memoryReference, offset, count):
831831
}
832832
return self.send_recv(command_dict)
833833

834+
def request_writeMemory(self, memoryReference, data, offset=0, allowPartial=True):
835+
args_dict = {
836+
"memoryReference": memoryReference,
837+
"offset": offset,
838+
"allowPartial": allowPartial,
839+
"data": data,
840+
}
841+
command_dict = {
842+
"command": "writeMemory",
843+
"type": "request",
844+
"arguments": args_dict,
845+
}
846+
return self.send_recv(command_dict)
847+
834848
def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None):
835849
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
836850
if stackFrame is None:

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from lldbsuite.test.lldbtest import *
99
from lldbsuite.test import lldbplatformutil
1010
import lldbgdbserverutils
11+
import base64
1112

1213

1314
class DAPTestCaseBase(TestBase):
@@ -506,3 +507,20 @@ def getBuiltinDebugServerTool(self):
506507
self.dap_server.request_disconnect(terminateDebuggee=True)
507508
self.assertIsNotNone(server_tool, "debugserver not found.")
508509
return server_tool
510+
511+
def writeMemory(self, memoryReference, data=None, offset=None, allowPartial=None):
512+
# This function accepts data in decimal and hexadecimal format,
513+
# converts it to a Base64 string, and send it to the DAP,
514+
# which expects Base64 encoded data.
515+
encodedData = (
516+
""
517+
if data is None
518+
else base64.b64encode(
519+
# (bit_length + 7 (rounding up to nearest byte) ) //8 = converts to bytes.
520+
data.to_bytes((data.bit_length() + 7) // 8, "little")
521+
).decode()
522+
)
523+
response = self.dap_server.request_writeMemory(
524+
memoryReference, encodedData, offset=offset, allowPartial=allowPartial
525+
)
526+
return response

lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,85 @@ def test_readMemory(self):
125125
self.assertFalse(mem["success"], "expect fail on reading memory.")
126126

127127
self.continue_to_exit()
128+
129+
def test_writeMemory(self):
130+
"""
131+
Tests the 'writeMemory' request
132+
"""
133+
program = self.getBuildArtifact("a.out")
134+
self.build_and_launch(program)
135+
source = "main.cpp"
136+
self.source_path = os.path.join(os.getcwd(), source)
137+
self.set_source_breakpoints(
138+
source,
139+
[line_number(source, "// Breakpoint")],
140+
)
141+
self.continue_to_next_stop()
142+
143+
# Get the 'not_a_ptr' writable variable reference address.
144+
ptr_deref = self.dap_server.request_evaluate("not_a_ptr")["body"]
145+
memref = ptr_deref["memoryReference"]
146+
147+
# Write the decimal value 50 (0x32 in hexadecimal) to memory.
148+
# This corresponds to the ASCII character '2' and encodes to Base64 as "Mg==".
149+
mem_response = self.writeMemory(memref, 50, 0, True)
150+
self.assertEqual(mem_response["success"], True)
151+
self.assertEqual(mem_response["body"]["bytesWritten"], 1)
152+
153+
# Read back the modified memory and verify that the written data matches
154+
# the expected result.
155+
mem_response = self.dap_server.request_readMemory(memref, 0, 1)
156+
self.assertEqual(mem_response["success"], True)
157+
self.assertEqual(mem_response["body"]["data"], "Mg==")
158+
159+
# Write the decimal value 100 (0x64 in hexadecimal) to memory.
160+
# This corresponds to the ASCII character 'd' and encodes to Base64 as "ZA==".
161+
# allowPartial=False
162+
mem_response = self.writeMemory(memref, 100, 0, False)
163+
self.assertEqual(mem_response["success"], True)
164+
self.assertEqual(mem_response["body"]["bytesWritten"], 1)
165+
166+
# Read back the modified memory and verify that the written data matches
167+
# the expected result.
168+
mem_response = self.dap_server.request_readMemory(memref, 0, 1)
169+
self.assertEqual(mem_response["success"], True)
170+
self.assertEqual(mem_response["body"]["data"], "ZA==")
171+
172+
# Memory write failed for 0x0.
173+
mem_response = self.writeMemory("0x0", 50, 0, True)
174+
self.assertEqual(mem_response["success"], False)
175+
176+
# Malformed memory reference.
177+
mem_response = self.writeMemory("12345", 50, 0, True)
178+
self.assertEqual(mem_response["success"], False)
179+
180+
ptr_deref = self.dap_server.request_evaluate("nonWritable")["body"]
181+
memref = ptr_deref["memoryReference"]
182+
183+
# Writing to non-writable region should return an appropriate error.
184+
mem_response = self.writeMemory(memref, 50, 0, False)
185+
self.assertEqual(mem_response["success"], False)
186+
self.assertRegex(
187+
mem_response["body"]["error"]["format"],
188+
r"Memory " + memref + " region is not writable",
189+
)
190+
191+
# Trying to write empty value; data=""
192+
mem_response = self.writeMemory(memref)
193+
self.assertEqual(mem_response["success"], False)
194+
self.assertRegex(
195+
mem_response["body"]["error"]["format"],
196+
r"Data cannot be empty value. Provide valid data",
197+
)
198+
199+
# Verify that large memory writes fail if the range spans non-writable
200+
# or non -contiguous regions.
201+
data = bytes([0xFF] * 8192)
202+
mem_response = self.writeMemory(
203+
memref, int.from_bytes(data, byteorder="little"), 0, False
204+
)
205+
self.assertEqual(mem_response["success"], False)
206+
self.assertRegex(
207+
mem_response["body"]["error"]["format"],
208+
r"Memory " + memref + " region is not writable",
209+
)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
int main() {
22
int not_a_ptr = 666;
33
const char *rawptr = "dead";
4+
// Immutable variable, .rodata region.
5+
static const int nonWritable = 100;
46
// Breakpoint
57
return 0;
68
}

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ add_lldb_library(lldbDAP
6565
Handler/TestGetTargetBreakpointsRequestHandler.cpp
6666
Handler/ThreadsRequestHandler.cpp
6767
Handler/VariablesRequestHandler.cpp
68-
68+
Handler/WriteMemoryRequestHandler.cpp
69+
6970
Protocol/ProtocolBase.cpp
7071
Protocol/ProtocolEvents.cpp
7172
Protocol/ProtocolTypes.cpp

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,7 @@ void DAP::RegisterRequests() {
15541554
RegisterRequest<StepOutRequestHandler>();
15551555
RegisterRequest<ThreadsRequestHandler>();
15561556
RegisterRequest<VariablesRequestHandler>();
1557+
RegisterRequest<WriteMemoryRequestHandler>();
15571558

15581559
// Custom requests
15591560
RegisterRequest<CompileUnitsRequestHandler>();

lldb/tools/lldb-dap/Handler/RequestHandler.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,19 @@ class TestGetTargetBreakpointsRequestHandler : public LegacyRequestHandler {
604604
void operator()(const llvm::json::Object &request) const override;
605605
};
606606

607+
class WriteMemoryRequestHandler final
608+
: public RequestHandler<protocol::WriteMemoryArguments,
609+
llvm::Expected<protocol::WriteMemoryResponseBody>> {
610+
public:
611+
using RequestHandler::RequestHandler;
612+
static llvm::StringLiteral GetCommand() { return "writeMemory"; }
613+
FeatureSet GetSupportedFeatures() const override {
614+
return {protocol::eAdapterFeatureWriteMemoryRequest};
615+
}
616+
llvm::Expected<protocol::WriteMemoryResponseBody>
617+
Run(const protocol::WriteMemoryArguments &args) const override;
618+
};
619+
607620
} // namespace lldb_dap
608621

609622
#endif
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//===-- WriteMemoryRequestHandler.cpp -------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "DAP.h"
10+
#include "JSONUtils.h"
11+
#include "RequestHandler.h"
12+
#include "lldb/API/SBMemoryRegionInfo.h"
13+
#include "llvm/ADT/StringExtras.h"
14+
#include "llvm/Support/Base64.h"
15+
16+
namespace lldb_dap {
17+
18+
// Writes bytes to memory at the provided location.
19+
//
20+
// Clients should only call this request if the corresponding capability
21+
// supportsWriteMemoryRequest is true.
22+
llvm::Expected<protocol::WriteMemoryResponseBody>
23+
WriteMemoryRequestHandler::Run(
24+
const protocol::WriteMemoryArguments &args) const {
25+
const lldb::addr_t address = args.memoryReference + args.offset.value_or(0);
26+
;
27+
28+
lldb::SBProcess process = dap.target.GetProcess();
29+
if (!lldb::SBDebugger::StateIsStoppedState(process.GetState()))
30+
return llvm::make_error<NotStoppedError>();
31+
32+
if (args.data.empty()) {
33+
return llvm::make_error<DAPError>(
34+
"Data cannot be empty value. Provide valid data");
35+
}
36+
37+
// The VSCode IDE or other DAP clients send memory data as a Base64 string.
38+
// This function decodes it into raw binary before writing it to the target
39+
// process memory.
40+
std::vector<char> output;
41+
auto decode_error = llvm::decodeBase64(args.data, output);
42+
43+
if (decode_error) {
44+
return llvm::make_error<DAPError>(
45+
llvm::toString(std::move(decode_error)).c_str());
46+
}
47+
48+
lldb::SBError write_error;
49+
uint64_t bytes_written = 0;
50+
51+
// Write the memory.
52+
if (!output.empty()) {
53+
lldb::SBProcess process = dap.target.GetProcess();
54+
// If 'allowPartial' is false or missing, a debug adapter should attempt to
55+
// verify the region is writable before writing, and fail the response if it
56+
// is not.
57+
if (!args.allowPartial.value_or(false)) {
58+
// Start checking from the initial write address.
59+
lldb::addr_t start_address = address;
60+
// Compute the end of the write range.
61+
lldb::addr_t end_address = start_address + output.size() - 1;
62+
63+
while (start_address <= end_address) {
64+
// Get memory region info for the given address.
65+
// This provides the region's base, end, and permissions
66+
// (read/write/executable).
67+
lldb::SBMemoryRegionInfo region_info;
68+
lldb::SBError error =
69+
process.GetMemoryRegionInfo(start_address, region_info);
70+
// Fail if the region info retrieval fails, is not writable, or the
71+
// range exceeds the region.
72+
if (!error.Success() || !region_info.IsWritable()) {
73+
return llvm::make_error<DAPError>(
74+
"Memory 0x" + llvm::utohexstr(args.memoryReference) +
75+
" region is not writable");
76+
}
77+
// If the current region covers the full requested range, stop futher
78+
// iterations.
79+
if (end_address <= region_info.GetRegionEnd()) {
80+
break;
81+
}
82+
// Move to the start of the next memory region.
83+
start_address = region_info.GetRegionEnd() + 1;
84+
}
85+
}
86+
87+
bytes_written =
88+
process.WriteMemory(address, static_cast<void *>(output.data()),
89+
output.size(), write_error);
90+
}
91+
92+
if (bytes_written == 0) {
93+
return llvm::make_error<DAPError>(write_error.GetCString());
94+
}
95+
protocol::WriteMemoryResponseBody response;
96+
response.bytesWritten = bytes_written;
97+
return response;
98+
}
99+
100+
} // namespace lldb_dap

lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,4 +531,37 @@ json::Value toJSON(const ModulesResponseBody &MR) {
531531
return result;
532532
}
533533

534+
bool fromJSON(const json::Value &Params, WriteMemoryArguments &WMA,
535+
json::Path P) {
536+
json::ObjectMapper O(Params, P);
537+
538+
const json::Object *wma_obj = Params.getAsObject();
539+
constexpr llvm::StringRef ref_key = "memoryReference";
540+
const std::optional<llvm::StringRef> memory_ref = wma_obj->getString(ref_key);
541+
if (!memory_ref) {
542+
P.field(ref_key).report("missing value");
543+
return false;
544+
}
545+
546+
const std::optional<lldb::addr_t> addr_opt =
547+
DecodeMemoryReference(*memory_ref);
548+
if (!addr_opt) {
549+
P.field(ref_key).report("Malformed memory reference");
550+
return false;
551+
}
552+
553+
WMA.memoryReference = *addr_opt;
554+
555+
return O && O.mapOptional("allowPartial", WMA.allowPartial) &&
556+
O.mapOptional("offset", WMA.offset) && O.map("data", WMA.data);
557+
}
558+
559+
json::Value toJSON(const WriteMemoryResponseBody &WMR) {
560+
json::Object result;
561+
562+
if (WMR.bytesWritten != 0)
563+
result.insert({"bytesWritten", WMR.bytesWritten});
564+
return result;
565+
}
566+
534567
} // namespace lldb_dap::protocol

lldb/tools/lldb-dap/Protocol/ProtocolRequests.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,38 @@ struct ModulesResponseBody {
896896
};
897897
llvm::json::Value toJSON(const ModulesResponseBody &);
898898

899+
/// Arguments for `writeMemory` request.
900+
struct WriteMemoryArguments {
901+
/// Memory reference to the base location to which data should be written.
902+
lldb::addr_t memoryReference;
903+
904+
/// Offset (in bytes) to be applied to the reference location before writing
905+
/// data. Can be negative.
906+
std::optional<int64_t> offset;
907+
908+
/// Property to control partial writes. If true, the debug adapter should
909+
/// attempt to write memory even if the entire memory region is not writable.
910+
/// In such a case the debug adapter should stop after hitting the first byte
911+
/// of memory that cannot be written and return the number of bytes written in
912+
/// the response via the `offset` and `bytesWritten` properties.
913+
/// If false or missing, a debug adapter should attempt to verify the region
914+
/// is writable before writing, and fail the response if it is not.
915+
std::optional<bool> allowPartial;
916+
917+
/// Bytes to write, encoded using base64.
918+
std::string data;
919+
};
920+
bool fromJSON(const llvm::json::Value &, WriteMemoryArguments &,
921+
llvm::json::Path);
922+
923+
/// Response to writeMemory request.
924+
struct WriteMemoryResponseBody {
925+
/// Property that should be returned when `allowPartial` is true to indicate
926+
/// the number of bytes starting from address that were successfully written.
927+
uint64_t bytesWritten = 0;
928+
};
929+
llvm::json::Value toJSON(const WriteMemoryResponseBody &);
930+
899931
} // namespace lldb_dap::protocol
900932

901933
#endif

0 commit comments

Comments
 (0)