Skip to content

Commit 5e668fe

Browse files
[debugserver] Implement MultiMemRead packet (#162670)
This commit implements, in debugserver, the packet as discussed in the RFC [1]. [1]: https://discourse.llvm.org/t/rfc-a-new-vectorized-memory-read-packet/88441
1 parent bea77ed commit 5e668fe

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Tests debugserver support for MultiMemRead.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
@skipUnlessDarwin
12+
@skipIfOutOfTreeDebugserver
13+
class TestCase(TestBase):
14+
def send_process_packet(self, packet_str):
15+
self.runCmd(f"proc plugin packet send {packet_str}", check=False)
16+
# The output is of the form:
17+
# packet: <packet_str>
18+
# response: <response>
19+
reply = self.res.GetOutput().split("\n")
20+
packet = reply[0].strip()
21+
response = reply[1].strip()
22+
23+
self.assertTrue(packet.startswith("packet: "))
24+
self.assertTrue(response.startswith("response: "))
25+
return response[len("response: ") :]
26+
27+
def check_invalid_packet(self, packet_str):
28+
reply = self.send_process_packet("packet_str")
29+
self.assertEqual(reply, "E03")
30+
31+
def test_packets(self):
32+
self.build()
33+
source_file = lldb.SBFileSpec("main.c")
34+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
35+
self, "break here", source_file
36+
)
37+
38+
reply = self.send_process_packet("qSupported")
39+
self.assertIn("MultiMemRead+", reply)
40+
41+
mem_address_var = thread.frames[0].FindVariable("memory")
42+
self.assertTrue(mem_address_var)
43+
mem_address = mem_address_var.GetValueAsUnsigned() + 42
44+
45+
# no ":"
46+
self.check_invalid_packet("MultiMemRead")
47+
# missing ranges
48+
self.check_invalid_packet("MultiMemRead:")
49+
# needs at least one range
50+
self.check_invalid_packet("MultiMemRead:ranges:")
51+
# needs at least one range
52+
self.check_invalid_packet("MultiMemRead:ranges:,")
53+
# a range is a pair of numbers
54+
self.check_invalid_packet("MultiMemRead:ranges:10")
55+
# a range is a pair of numbers
56+
self.check_invalid_packet("MultiMemRead:ranges:10,")
57+
# range list must end with ;
58+
self.check_invalid_packet("MultiMemRead:ranges:10,2")
59+
self.check_invalid_packet("MultiMemRead:ranges:10,2,")
60+
self.check_invalid_packet("MultiMemRead:ranges:10,2,3")
61+
# ranges are pairs of numbers.
62+
self.check_invalid_packet("MultiMemRead:ranges:10,2,3;")
63+
# unrecognized field
64+
self.check_invalid_packet("MultiMemRead:ranges:10,2;blah:;")
65+
# unrecognized field
66+
self.check_invalid_packet("MultiMemRead:blah:;ranges:10,2;")
67+
68+
# Zero-length reads are ok.
69+
reply = self.send_process_packet("MultiMemRead:ranges:0,0;")
70+
self.assertEqual(reply, "0;")
71+
72+
# Debugserver is permissive with trailing commas.
73+
reply = self.send_process_packet("MultiMemRead:ranges:10,2,;")
74+
self.assertEqual(reply, "0;")
75+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2,;")
76+
self.assertEqual(reply, "2;ab")
77+
78+
reply = self.send_process_packet("MultiMemRead:ranges:10,2;")
79+
self.assertEqual(reply, "0;")
80+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},0;")
81+
self.assertEqual(reply, "0;")
82+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2;")
83+
self.assertEqual(reply, "2;ab")
84+
reply = self.send_process_packet(
85+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4;"
86+
)
87+
self.assertEqual(reply, "2,4;abcdef")
88+
reply = self.send_process_packet(
89+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},8;"
90+
)
91+
self.assertEqual(reply, "2,4,8;abcdefghijklmn")
92+
93+
# Test zero length in the middle.
94+
reply = self.send_process_packet(
95+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},0,{mem_address+6:x},8;"
96+
)
97+
self.assertEqual(reply, "2,0,8;abghijklmn")
98+
# Test zero length in the end.
99+
reply = self.send_process_packet(
100+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},0;"
101+
)
102+
self.assertEqual(reply, "2,4,0;abcdef")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <stdlib.h>
2+
#include <string.h>
3+
4+
int main(int argc, char **argv) {
5+
char *memory = malloc(1024);
6+
memset(memory, '-', 1024);
7+
// Write "interesting" characters at an offset from the memory filled with
8+
// `-`. This way, if we read outside the range in either direction, we should
9+
// find `-`s`.
10+
int offset = 42;
11+
for (int i = offset; i < offset + 14; i++)
12+
memory[i] = 'a' + (i - offset);
13+
return 0; // break here
14+
}

lldb/tools/debugserver/source/RNBRemote.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,20 @@ static uint64_t decode_uint64(const char *p, int base, char **end = nullptr,
170170
return addr;
171171
}
172172

173+
/// Attempts to parse a prefix of `number_str` as a uint64_t. If
174+
/// successful, the number is returned and the prefix is dropped from
175+
/// `number_str`.
176+
static std::optional<uint64_t> extract_u64(std::string_view &number_str) {
177+
char *str_end = nullptr;
178+
errno = 0;
179+
uint64_t number = strtoull(number_str.data(), &str_end, 16);
180+
if (errno != 0)
181+
return std::nullopt;
182+
assert(str_end);
183+
number_str.remove_prefix(str_end - number_str.data());
184+
return number;
185+
}
186+
173187
static void append_hex_value(std::ostream &ostrm, const void *buf,
174188
size_t buf_size, bool swap) {
175189
int i;
@@ -204,6 +218,25 @@ static void append_hexified_string(std::ostream &ostrm,
204218
}
205219
}
206220

221+
/// Returns true if `str` starts with `prefix`.
222+
static bool starts_with(std::string_view str, std::string_view prefix) {
223+
return str.substr(0, prefix.size()) == prefix;
224+
}
225+
226+
/// Splits `list_str` into multiple string_views separated by `,`.
227+
static std::vector<std::string_view>
228+
parse_comma_separated_list(std::string_view list_str) {
229+
std::vector<std::string_view> list;
230+
while (!list_str.empty()) {
231+
auto pos = list_str.find(',');
232+
list.push_back(list_str.substr(0, pos));
233+
if (pos == list_str.npos)
234+
break;
235+
list_str.remove_prefix(pos + 1);
236+
}
237+
return list;
238+
}
239+
207240
// from System.framework/Versions/B/PrivateHeaders/sys/codesign.h
208241
extern "C" {
209242
#define CS_OPS_STATUS 0 /* return status */
@@ -270,6 +303,11 @@ void RNBRemote::CreatePacketTable() {
270303
"Read memory"));
271304
t.push_back(Packet(read_register, &RNBRemote::HandlePacket_p, NULL, "p",
272305
"Read one register"));
306+
// Careful: this *must* come before the `M` packet, as debugserver matches
307+
// packet prefixes against known packet names. Inverting the order would match
308+
// `MultiMemRead` as an `M` packet.
309+
t.push_back(Packet(multi_mem_read, &RNBRemote::HandlePacket_MultiMemRead,
310+
NULL, "MultiMemRead", "Read multiple memory addresses"));
273311
t.push_back(Packet(write_memory, &RNBRemote::HandlePacket_M, NULL, "M",
274312
"Write memory"));
275313
t.push_back(Packet(write_register, &RNBRemote::HandlePacket_P, NULL, "P",
@@ -3150,6 +3188,88 @@ rnb_err_t RNBRemote::HandlePacket_m(const char *p) {
31503188
return SendPacket(ostrm.str());
31513189
}
31523190

3191+
rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) {
3192+
const std::string_view packet_name("MultiMemRead:");
3193+
std::string_view packet(p);
3194+
3195+
if (!starts_with(packet, packet_name))
3196+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
3197+
"Invalid MultiMemRead packet prefix");
3198+
3199+
packet.remove_prefix(packet_name.size());
3200+
3201+
const std::string_view ranges_prefix("ranges:");
3202+
if (!starts_with(packet, ranges_prefix))
3203+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3204+
"Missing 'ranges' in MultiMemRead packet");
3205+
packet.remove_prefix(ranges_prefix.size());
3206+
3207+
std::vector<std::pair<nub_addr_t, std::size_t>> ranges;
3208+
std::size_t total_length = 0;
3209+
3210+
// Ranges should have the form: <addr>,<size>[,<addr>,<size>]*;
3211+
auto end_of_ranges_pos = packet.find(';');
3212+
if (end_of_ranges_pos == packet.npos)
3213+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3214+
"MultiMemRead missing end of ranges marker");
3215+
3216+
std::vector<std::string_view> numbers_list =
3217+
parse_comma_separated_list(packet.substr(0, end_of_ranges_pos));
3218+
packet.remove_prefix(end_of_ranges_pos + 1);
3219+
3220+
// Ranges are pairs, so the number of elements must be even.
3221+
if (numbers_list.size() % 2 == 1)
3222+
return HandlePacket_ILLFORMED(
3223+
__FILE__, __LINE__, p,
3224+
"MultiMemRead has an odd number of numbers for the ranges");
3225+
3226+
for (unsigned idx = 0; idx < numbers_list.size(); idx += 2) {
3227+
std::optional<uint64_t> maybe_addr = extract_u64(numbers_list[idx]);
3228+
std::optional<uint64_t> maybe_length = extract_u64(numbers_list[idx + 1]);
3229+
if (!maybe_addr || !maybe_length)
3230+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3231+
"Invalid MultiMemRead range");
3232+
// A sanity check that the packet requested is not too large or a negative
3233+
// number.
3234+
if (*maybe_length > 4 * 1024 * 1024)
3235+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3236+
"MultiMemRead length is too large");
3237+
3238+
ranges.emplace_back(*maybe_addr, *maybe_length);
3239+
total_length += *maybe_length;
3240+
}
3241+
3242+
if (ranges.empty())
3243+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
3244+
"MultiMemRead has an empty range list");
3245+
3246+
if (!packet.empty())
3247+
return HandlePacket_ILLFORMED(
3248+
__FILE__, __LINE__, p, "MultiMemRead packet has unrecognized fields");
3249+
3250+
std::vector<std::vector<uint8_t>> buffers;
3251+
buffers.reserve(ranges.size());
3252+
for (auto [base_addr, length] : ranges) {
3253+
buffers.emplace_back(length, 0);
3254+
nub_size_t bytes_read = DNBProcessMemoryRead(m_ctx.ProcessID(), base_addr,
3255+
length, buffers.back().data());
3256+
buffers.back().resize(bytes_read);
3257+
}
3258+
3259+
std::ostringstream reply_stream;
3260+
bool first = true;
3261+
for (const std::vector<uint8_t> &buffer : buffers) {
3262+
reply_stream << (first ? "" : ",") << std::hex << buffer.size();
3263+
first = false;
3264+
}
3265+
reply_stream << ';';
3266+
3267+
for (const std::vector<uint8_t> &buffer : buffers)
3268+
binary_encode_data_vector(reply_stream, buffer);
3269+
3270+
return SendPacket(reply_stream.str());
3271+
}
3272+
31533273
// Read memory, sent it up as binary data.
31543274
// Usage: xADDR,LEN
31553275
// ADDR and LEN are both base 16.
@@ -3503,6 +3623,7 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) {
35033623
if (supports_memory_tagging())
35043624
reply << "memory-tagging+;";
35053625

3626+
reply << "MultiMemRead+;";
35063627
return SendPacket(reply.str().c_str());
35073628
}
35083629

lldb/tools/debugserver/source/RNBRemote.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class RNBRemote {
136136
query_transfer, // 'qXfer:'
137137
json_query_dyld_process_state, // 'jGetDyldProcessState'
138138
enable_error_strings, // 'QEnableErrorStrings'
139+
multi_mem_read, // 'MultiMemRead'
139140
unknown_type
140141
};
141142
// clang-format on
@@ -216,6 +217,7 @@ class RNBRemote {
216217
rnb_err_t HandlePacket_last_signal(const char *p);
217218
rnb_err_t HandlePacket_m(const char *p);
218219
rnb_err_t HandlePacket_M(const char *p);
220+
rnb_err_t HandlePacket_MultiMemRead(const char *p);
219221
rnb_err_t HandlePacket_x(const char *p);
220222
rnb_err_t HandlePacket_X(const char *p);
221223
rnb_err_t HandlePacket_z(const char *p);

0 commit comments

Comments
 (0)