Skip to content

Commit cb9357d

Browse files
[debugserver] Implement MultiMemRead packet
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 1fde14b commit cb9357d

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-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: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Tests the exit code/description coming from the debugserver.
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+
reply[0] = reply[0].strip()
21+
reply[1] = reply[1].strip()
22+
23+
self.assertTrue(reply[0].startswith("packet: "), reply[0])
24+
self.assertTrue(reply[1].startswith("response: "))
25+
return reply[1][len("response: ") :]
26+
27+
def test_packets(self):
28+
self.build()
29+
source_file = lldb.SBFileSpec("main.c")
30+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
31+
self, "break here", source_file
32+
)
33+
34+
reply = self.send_process_packet("qSupported")
35+
self.assertIn("MultiMemRead+", reply)
36+
37+
mem_address_var = thread.frames[0].FindVariable("memory")
38+
self.assertTrue(mem_address_var)
39+
mem_address = int(mem_address_var.GetValue(), 16)
40+
41+
reply = self.send_process_packet("MultiMemRead:")
42+
self.assertEqual(reply, "E03")
43+
reply = self.send_process_packet("MultiMemRead:ranges:")
44+
self.assertEqual(reply, "E03")
45+
reply = self.send_process_packet("MultiMemRead:ranges:10")
46+
self.assertEqual(reply, "E03")
47+
reply = self.send_process_packet("MultiMemRead:ranges:10,")
48+
self.assertEqual(reply, "E03")
49+
reply = self.send_process_packet("MultiMemRead:ranges:10,2")
50+
self.assertEqual(reply, "E03")
51+
reply = self.send_process_packet("MultiMemRead:ranges:10,2,")
52+
self.assertEqual(reply, "E03")
53+
reply = self.send_process_packet("MultiMemRead:ranges:10,2,;")
54+
self.assertEqual(reply, "0;") # Debugserver is permissive with trailing commas.
55+
reply = self.send_process_packet("MultiMemRead:ranges:10,2;")
56+
self.assertEqual(reply, "0;")
57+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},0;")
58+
self.assertEqual(reply, "0;")
59+
reply = self.send_process_packet(
60+
f"MultiMemRead:ranges:{mem_address:x},0;options:;"
61+
)
62+
self.assertEqual(reply, "0;")
63+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2;")
64+
self.assertEqual(reply, "2;ab")
65+
reply = self.send_process_packet(
66+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4;"
67+
)
68+
self.assertEqual(reply, "2,4;abcdef")
69+
reply = self.send_process_packet(
70+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},8;"
71+
)
72+
self.assertEqual(reply, "2,4,8;abcdefghijklmn")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
for (int i = 0; i < 50; i++)
8+
memory[i] = 'a' + i;
9+
return 0; // break here
10+
}

lldb/tools/debugserver/source/RNBRemote.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ void RNBRemote::CreatePacketTable() {
270270
"Read memory"));
271271
t.push_back(Packet(read_register, &RNBRemote::HandlePacket_p, NULL, "p",
272272
"Read one register"));
273+
// Careful: this *must* come before the `M` packet, as debugserver matches
274+
// packet prefixes against known packet names. Inverting the order would match
275+
// `MultiMemRead` as an `M` packet.
276+
t.push_back(Packet(multi_mem_read, &RNBRemote::HandlePacket_MultiMemRead,
277+
NULL, "MultiMemRead", "Read multiple memory addresses"));
273278
t.push_back(Packet(write_memory, &RNBRemote::HandlePacket_M, NULL, "M",
274279
"Write memory"));
275280
t.push_back(Packet(write_register, &RNBRemote::HandlePacket_P, NULL, "P",
@@ -3150,6 +3155,140 @@ rnb_err_t RNBRemote::HandlePacket_m(const char *p) {
31503155
return SendPacket(ostrm.str());
31513156
}
31523157

3158+
/// Returns true if `str` starts with `prefix`.
3159+
static bool starts_with(std::string_view str, std::string_view prefix) {
3160+
return str.size() >= prefix.size() &&
3161+
str.compare(0, prefix.size(), prefix) == 0;
3162+
}
3163+
3164+
/// Attempts to parse a prefix of `number_str` as a number of type `T`. If
3165+
/// successful, the number is returned and the prefix is dropped from
3166+
/// `number_str`.
3167+
template <typename T>
3168+
static std::optional<T> extract_number(std::string_view &number_str) {
3169+
static_assert(std::is_integral<T>());
3170+
char *str_end = nullptr;
3171+
errno = 0;
3172+
nub_addr_t number = strtoull(number_str.data(), &str_end, 16);
3173+
if (errno != 0)
3174+
return std::nullopt;
3175+
assert(str_end);
3176+
number_str.remove_prefix(str_end - number_str.data());
3177+
return number;
3178+
}
3179+
3180+
/// Splits `list_str` into multiple string_views separated by `,`.
3181+
static std::vector<std::string_view>
3182+
parse_comma_separated_list(std::string_view list_str) {
3183+
std::vector<std::string_view> list;
3184+
while (!list_str.empty()) {
3185+
auto pos = list_str.find(',');
3186+
list.push_back(list_str.substr(0, pos));
3187+
if (pos == list_str.npos)
3188+
break;
3189+
list_str.remove_prefix(pos + 1);
3190+
}
3191+
return list;
3192+
}
3193+
3194+
rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) {
3195+
const std::string_view packet_name("MultiMemRead:");
3196+
std::string_view packet(p);
3197+
3198+
if (!starts_with(packet, packet_name))
3199+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
3200+
"Invalid MultiMemRead packet prefix");
3201+
3202+
packet.remove_prefix(packet_name.size());
3203+
3204+
const std::string_view ranges_prefix("ranges:");
3205+
if (!starts_with(packet, ranges_prefix))
3206+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3207+
"Missing 'ranges' in MultiMemRead packet");
3208+
packet.remove_prefix(ranges_prefix.size());
3209+
3210+
std::vector<std::pair<nub_addr_t, std::size_t>> ranges;
3211+
std::size_t total_length = 0;
3212+
3213+
// Ranges should have the form: <addr>,<size>[,<addr>,<size>]*;
3214+
auto end_of_ranges_pos = packet.find(';');
3215+
if (end_of_ranges_pos == packet.npos)
3216+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3217+
"MultiMemRead missing end of ranges marker");
3218+
3219+
std::vector<std::string_view> numbers_list =
3220+
parse_comma_separated_list(packet.substr(0, end_of_ranges_pos));
3221+
packet.remove_prefix(end_of_ranges_pos + 1);
3222+
3223+
// Ranges are pairs, so the number of elements must be even.
3224+
if (numbers_list.size() % 2 == 1)
3225+
return HandlePacket_ILLFORMED(
3226+
__FILE__, __LINE__, p,
3227+
"MultiMemRead has an odd number of numbers for the ranges");
3228+
3229+
for (unsigned idx = 0; idx < numbers_list.size(); idx += 2) {
3230+
std::optional<nub_addr_t> maybe_addr =
3231+
extract_number<nub_addr_t>(numbers_list[idx]);
3232+
std::optional<size_t> maybe_length =
3233+
extract_number<size_t>(numbers_list[idx + 1]);
3234+
if (!maybe_addr || !maybe_length)
3235+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3236+
"Invalid MultiMemRead range");
3237+
// A sanity check that the packet requested is not too large or a negative
3238+
// number.
3239+
if (*maybe_length > 4 * 1024 * 1024)
3240+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3241+
"MultiMemRead length is too large");
3242+
3243+
ranges.emplace_back(*maybe_addr, *maybe_length);
3244+
total_length += *maybe_length;
3245+
}
3246+
3247+
if (ranges.empty())
3248+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
3249+
"MultiMemRead has an empty range list");
3250+
3251+
// If 'options:' is present, ensure it's empty.
3252+
const std::string_view options_prefix("options:");
3253+
if (starts_with(packet, options_prefix)) {
3254+
packet.remove_prefix(options_prefix.size());
3255+
if (packet.empty())
3256+
return HandlePacket_ILLFORMED(
3257+
__FILE__, __LINE__, packet.data(),
3258+
"Too short 'options' in MultiMemRead packet");
3259+
if (packet[0] != ';')
3260+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(),
3261+
"Unimplemented 'options' in MultiMemRead");
3262+
packet.remove_prefix(1);
3263+
}
3264+
3265+
if (!packet.empty())
3266+
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
3267+
"Invalid MultiMemRead address_range");
3268+
3269+
std::vector<std::vector<uint8_t>> buffers;
3270+
buffers.reserve(ranges.size());
3271+
for (auto [base_addr, length] : ranges) {
3272+
buffers.emplace_back(length, '\0');
3273+
nub_size_t bytes_read = DNBProcessMemoryRead(m_ctx.ProcessID(), base_addr,
3274+
length, buffers.back().data());
3275+
buffers.back().resize(bytes_read);
3276+
}
3277+
3278+
std::ostringstream reply_stream;
3279+
bool first = true;
3280+
for (const std::vector<uint8_t> &buffer : buffers) {
3281+
reply_stream << (first ? "" : ",") << std::hex << buffer.size();
3282+
first = false;
3283+
}
3284+
reply_stream << ';';
3285+
3286+
for (const std::vector<uint8_t> &buffer : buffers)
3287+
binary_encode_data_vector(reply_stream, buffer);
3288+
3289+
return SendPacket(reply_stream.str());
3290+
}
3291+
31533292
// Read memory, sent it up as binary data.
31543293
// Usage: xADDR,LEN
31553294
// ADDR and LEN are both base 16.
@@ -3503,6 +3642,7 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) {
35033642
if (supports_memory_tagging())
35043643
reply << "memory-tagging+;";
35053644

3645+
reply << "MultiMemRead+;";
35063646
return SendPacket(reply.str().c_str());
35073647
}
35083648

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)