Skip to content

Commit 2930a70

Browse files
committed
[lldb][lldb-dap] Added support for "WriteMemory" request.
Followed non-legacy approach to implement "writeMemory" request.
1 parent d9fccd7 commit 2930a70

File tree

7 files changed

+103
-137
lines changed

7 files changed

+103
-137
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,15 @@ def test_writeMemory(self):
184184
mem_response = self.writeMemory(memref, 50, 0, False)
185185
self.assertEqual(mem_response["success"], False)
186186
self.assertRegex(
187-
mem_response["message"],
187+
mem_response["body"]["error"]["format"],
188188
r"Memory " + memref + " region is not writable",
189189
)
190190

191191
# Trying to write empty value; data=""
192192
mem_response = self.writeMemory(memref)
193193
self.assertEqual(mem_response["success"], False)
194194
self.assertRegex(
195-
mem_response["message"],
195+
mem_response["body"]["error"]["format"],
196196
r"Data cannot be empty value. Provide valid data",
197197
)
198198

@@ -204,6 +204,6 @@ def test_writeMemory(self):
204204
)
205205
self.assertEqual(mem_response["success"], False)
206206
self.assertRegex(
207-
mem_response["message"],
207+
mem_response["body"]["error"]["format"],
208208
r"Memory " + memref + " region is not writable",
209-
)
209+
)

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,10 +1563,4 @@ void DAP::RegisterRequests() {
15631563
RegisterRequest<TestGetTargetBreakpointsRequestHandler>();
15641564
}
15651565

1566-
void DAP::SendErrorResponse(llvm::json::Object &response,
1567-
llvm::StringRef message) {
1568-
response["success"] = false;
1569-
EmplaceSafeString(response, "message", message);
1570-
SendJSON(llvm::json::Value(std::move(response)));
1571-
}
15721566
} // namespace lldb_dap

lldb/tools/lldb-dap/DAP.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,14 +418,6 @@ struct DAP {
418418
const std::optional<std::vector<protocol::SourceBreakpoint>>
419419
&breakpoints);
420420

421-
/// Sends an error response in DAP with success=false and an error message.
422-
///
423-
/// \param[out] response
424-
/// The response object to update.
425-
/// \param[in] message
426-
/// The error message.
427-
void SendErrorResponse(llvm::json::Object &response, llvm::StringRef message);
428-
429421
private:
430422
std::vector<protocol::Breakpoint> SetSourceBreakpoints(
431423
const protocol::Source &source,

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,14 +601,17 @@ class TestGetTargetBreakpointsRequestHandler : public LegacyRequestHandler {
601601
void operator()(const llvm::json::Object &request) const override;
602602
};
603603

604-
class WriteMemoryRequestHandler : public LegacyRequestHandler {
604+
class WriteMemoryRequestHandler final
605+
: public RequestHandler<protocol::WriteMemoryArguments,
606+
llvm::Expected<protocol::WriteMemoryResponseBody>> {
605607
public:
606-
using LegacyRequestHandler::LegacyRequestHandler;
608+
using RequestHandler::RequestHandler;
607609
static llvm::StringLiteral GetCommand() { return "writeMemory"; }
608610
FeatureSet GetSupportedFeatures() const override {
609611
return {protocol::eAdapterFeatureWriteMemoryRequest};
610612
}
611-
void operator()(const llvm::json::Object &request) const override;
613+
llvm::Expected<protocol::WriteMemoryResponseBody>
614+
Run(const protocol::WriteMemoryArguments &args) const override;
612615
};
613616

614617
} // namespace lldb_dap

lldb/tools/lldb-dap/Handler/WriteMemoryRequestHandler.cpp

Lines changed: 28 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -15,119 +15,36 @@
1515

1616
namespace lldb_dap {
1717

18-
// "WriteMemoryRequest": {
19-
// "allOf": [ { "$ref": "#/definitions/Request" }, {
20-
// "type": "object",
21-
// "description": "Writes bytes to memory at the provided location.\n
22-
// Clients should only call this request if the corresponding
23-
// capability `supportsWriteMemoryRequest` is true.",
24-
// "properties": {
25-
// "command": {
26-
// "type": "string",
27-
// "enum": [ "writeMemory" ]
28-
// },
29-
// "arguments": {
30-
// "$ref": "#/definitions/WriteMemoryArguments"
31-
// }
32-
// },
33-
// "required": [ "command", "arguments" ]
34-
// }]
35-
// },
36-
// "WriteMemoryArguments": {
37-
// "type": "object",
38-
// "description": "Arguments for `writeMemory` request.",
39-
// "properties": {
40-
// "memoryReference": {
41-
// "type": "string",
42-
// "description": "Memory reference to the base location to which
43-
// data should be written."
44-
// },
45-
// "offset": {
46-
// "type": "integer",
47-
// "description": "Offset (in bytes) to be applied to the reference
48-
// location before writing data. Can be negative."
49-
// },
50-
// "allowPartial": {
51-
// "type": "boolean",
52-
// "description": "Property to control partial writes. If true, the
53-
// debug adapter should attempt to write memory even if the entire
54-
// memory region is not writable. In such a case the debug adapter
55-
// should stop after hitting the first byte of memory that cannot be
56-
// written and return the number of bytes written in the response
57-
// via the `offset` and `bytesWritten` properties.\nIf false or
58-
// missing, a debug adapter should attempt to verify the region is
59-
// writable before writing, and fail the response if it is not."
60-
// },
61-
// "data": {
62-
// "type": "string",
63-
// "description": "Bytes to write, encoded using base64."
64-
// }
65-
// },
66-
// "required": [ "memoryReference", "data" ]
67-
// },
68-
// "WriteMemoryResponse": {
69-
// "allOf": [ { "$ref": "#/definitions/Response" }, {
70-
// "type": "object",
71-
// "description": "Response to `writeMemory` request.",
72-
// "properties": {
73-
// "body": {
74-
// "type": "object",
75-
// "properties": {
76-
// "offset": {
77-
// "type": "integer",
78-
// "description": "Property that should be returned when
79-
// `allowPartial` is true to indicate the offset of the first
80-
// byte of data successfully written. Can be negative."
81-
// },
82-
// "bytesWritten": {
83-
// "type": "integer",
84-
// "description": "Property that should be returned when
85-
// `allowPartial` is true to indicate the number of bytes
86-
// starting from address that were successfully written."
87-
// }
88-
// }
89-
// }
90-
// }
91-
// }]
92-
// },
93-
void WriteMemoryRequestHandler::operator()(
94-
const llvm::json::Object &request) const {
95-
llvm::json::Object response;
96-
FillResponse(request, response);
97-
98-
auto arguments = request.getObject("arguments");
99-
llvm::StringRef memory_reference =
100-
GetString(arguments, "memoryReference").value_or("");
101-
102-
auto addr_opt = DecodeMemoryReference(memory_reference);
103-
if (!addr_opt.has_value()) {
104-
dap.SendErrorResponse(response, "Malformed memory reference: " +
105-
memory_reference.str());
106-
return;
107-
}
108-
lldb::addr_t address =
109-
*addr_opt + GetInteger<uint64_t>(arguments, "offset").value_or(0);
110-
111-
llvm::StringRef data64 = GetString(arguments, "data").value_or("");
112-
if (data64.empty()) {
113-
dap.SendErrorResponse(response,
114-
"Data cannot be empty value. Provide valid data");
115-
return;
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");
11635
}
11736

11837
// The VSCode IDE or other DAP clients send memory data as a Base64 string.
11938
// This function decodes it into raw binary before writing it to the target
12039
// process memory.
12140
std::vector<char> output;
122-
auto decode_error = llvm::decodeBase64(data64, output);
41+
auto decode_error = llvm::decodeBase64(args.data, output);
12342

12443
if (decode_error) {
125-
dap.SendErrorResponse(response,
126-
llvm::toString(std::move(decode_error)).c_str());
127-
return;
44+
return llvm::make_error<DAPError>(
45+
llvm::toString(std::move(decode_error)).c_str());
12846
}
12947

130-
bool allow_partial = GetBoolean(arguments, "allowPartial").value_or(true);
13148
lldb::SBError write_error;
13249
uint64_t bytes_written = 0;
13350

@@ -137,7 +54,7 @@ void WriteMemoryRequestHandler::operator()(
13754
// If 'allowPartial' is false or missing, a debug adapter should attempt to
13855
// verify the region is writable before writing, and fail the response if it
13956
// is not.
140-
if (!allow_partial) {
57+
if (!args.allowPartial.value_or(false)) {
14158
// Start checking from the initial write address.
14259
lldb::addr_t start_address = address;
14360
// Compute the end of the write range.
@@ -153,10 +70,9 @@ void WriteMemoryRequestHandler::operator()(
15370
// Fail if the region info retrieval fails, is not writable, or the
15471
// range exceeds the region.
15572
if (!error.Success() || !region_info.IsWritable()) {
156-
dap.SendErrorResponse(response, "Memory 0x" +
157-
llvm::utohexstr(address) +
158-
" region is not writable");
159-
return;
73+
return llvm::make_error<DAPError>(
74+
"Memory 0x" + llvm::utohexstr(args.memoryReference) +
75+
" region is not writable");
16076
}
16177
// If the current region covers the full requested range, stop futher
16278
// iterations.
@@ -174,15 +90,11 @@ void WriteMemoryRequestHandler::operator()(
17490
}
17591

17692
if (bytes_written == 0) {
177-
dap.SendErrorResponse(response, write_error.GetCString());
178-
return;
93+
return llvm::make_error<DAPError>(write_error.GetCString());
17994
}
180-
181-
llvm::json::Object body;
182-
body.try_emplace("bytesWritten", std::move(bytes_written));
183-
184-
response.try_emplace("body", std::move(body));
185-
dap.SendJSON(llvm::json::Value(std::move(response)));
95+
protocol::WriteMemoryResponseBody response;
96+
response.bytesWritten = bytes_written;
97+
return response;
18698
}
18799

188100
} // namespace lldb_dap

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,37 @@ json::Value toJSON(const ReadMemoryResponseBody &RMR) {
517517
return result;
518518
}
519519

520+
bool fromJSON(const json::Value &Params, WriteMemoryArguments &WMA,
521+
json::Path P) {
522+
json::ObjectMapper O(Params, P);
523+
524+
const json::Object *wma_obj = Params.getAsObject();
525+
constexpr llvm::StringRef ref_key = "memoryReference";
526+
const std::optional<llvm::StringRef> memory_ref = wma_obj->getString(ref_key);
527+
if (!memory_ref) {
528+
P.field(ref_key).report("missing value");
529+
return false;
530+
}
531+
532+
const std::optional<lldb::addr_t> addr_opt =
533+
DecodeMemoryReference(*memory_ref);
534+
if (!addr_opt) {
535+
P.field(ref_key).report("Malformed memory reference");
536+
return false;
537+
}
538+
539+
WMA.memoryReference = *addr_opt;
540+
541+
return O && O.mapOptional("allowPartial", WMA.allowPartial) &&
542+
O.mapOptional("offset", WMA.offset) && O.map("data", WMA.data);
543+
}
544+
545+
json::Value toJSON(const WriteMemoryResponseBody &WMR) {
546+
json::Object result;
547+
548+
if (WMR.bytesWritten != 0)
549+
result.insert({"bytesWritten", WMR.bytesWritten});
550+
return result;
551+
}
552+
520553
} // 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
@@ -875,6 +875,38 @@ struct ReadMemoryResponseBody {
875875
};
876876
llvm::json::Value toJSON(const ReadMemoryResponseBody &);
877877

878+
/// Arguments for `writeMemory` request.
879+
struct WriteMemoryArguments {
880+
/// Memory reference to the base location to which data should be written.
881+
lldb::addr_t memoryReference;
882+
883+
/// Offset (in bytes) to be applied to the reference location before writing
884+
/// data. Can be negative.
885+
std::optional<int64_t> offset;
886+
887+
/// Property to control partial writes. If true, the debug adapter should
888+
/// attempt to write memory even if the entire memory region is not writable.
889+
/// In such a case the debug adapter should stop after hitting the first byte
890+
/// of memory that cannot be written and return the number of bytes written in
891+
/// the response via the `offset` and `bytesWritten` properties.
892+
/// If false or missing, a debug adapter should attempt to verify the region
893+
/// is writable before writing, and fail the response if it is not.
894+
std::optional<bool> allowPartial;
895+
896+
/// Bytes to write, encoded using base64.
897+
std::string data;
898+
};
899+
bool fromJSON(const llvm::json::Value &, WriteMemoryArguments &,
900+
llvm::json::Path);
901+
902+
/// Response to writeMemory request.
903+
struct WriteMemoryResponseBody {
904+
/// Property that should be returned when `allowPartial` is true to indicate
905+
/// the number of bytes starting from address that were successfully written.
906+
uint64_t bytesWritten = 0;
907+
};
908+
llvm::json::Value toJSON(const WriteMemoryResponseBody &);
909+
878910
} // namespace lldb_dap::protocol
879911

880912
#endif

0 commit comments

Comments
 (0)