Skip to content

Commit 86c55d2

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 01f4510 commit 86c55d2

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
@@ -246,6 +246,11 @@ void RNBRemote::CreatePacketTable() {
246246
"Read memory"));
247247
t.push_back(Packet(read_register, &RNBRemote::HandlePacket_p, NULL, "p",
248248
"Read one register"));
249+
// Careful: this *must* come before the `M` packet, as debugserver matches
250+
// packet prefixes against known packet names. Inverting the order would match
251+
// `MultiMemRead` as an `M` packet.
252+
t.push_back(Packet(multi_mem_read, &RNBRemote::HandlePacket_MultiMemRead,
253+
NULL, "MultiMemRead", "Read multiple memory addresses"));
249254
t.push_back(Packet(write_memory, &RNBRemote::HandlePacket_M, NULL, "M",
250255
"Write memory"));
251256
t.push_back(Packet(write_register, &RNBRemote::HandlePacket_P, NULL, "P",
@@ -3160,6 +3165,140 @@ rnb_err_t RNBRemote::HandlePacket_m(const char *p) {
31603165
return SendPacket(ostrm.str());
31613166
}
31623167

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

3667+
reply << "MultiMemRead+;";
35283668
return SendPacket(reply.str().c_str());
35293669
}
35303670

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)