From cb9357dc2ec288360308d04837865af13bdd776b Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Wed, 8 Oct 2025 15:37:48 -0700 Subject: [PATCH 1/3] [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 --- .../macosx/debugserver-multimemread/Makefile | 3 + .../TestDebugserverMultiMemRead.py | 72 +++++++++ .../macosx/debugserver-multimemread/main.c | 10 ++ lldb/tools/debugserver/source/RNBRemote.cpp | 140 ++++++++++++++++++ lldb/tools/debugserver/source/RNBRemote.h | 2 + 5 files changed, 227 insertions(+) create mode 100644 lldb/test/API/macosx/debugserver-multimemread/Makefile create mode 100644 lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py create mode 100644 lldb/test/API/macosx/debugserver-multimemread/main.c diff --git a/lldb/test/API/macosx/debugserver-multimemread/Makefile b/lldb/test/API/macosx/debugserver-multimemread/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/macosx/debugserver-multimemread/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py b/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py new file mode 100644 index 0000000000000..5c0c7a34e18bd --- /dev/null +++ b/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py @@ -0,0 +1,72 @@ +""" +Tests the exit code/description coming from the debugserver. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +@skipUnlessDarwin +@skipIfOutOfTreeDebugserver +class TestCase(TestBase): + def send_process_packet(self, packet_str): + self.runCmd(f"proc plugin packet send {packet_str}", check=False) + # The output is of the form: + # packet: + # response: + reply = self.res.GetOutput().split("\n") + reply[0] = reply[0].strip() + reply[1] = reply[1].strip() + + self.assertTrue(reply[0].startswith("packet: "), reply[0]) + self.assertTrue(reply[1].startswith("response: ")) + return reply[1][len("response: ") :] + + def test_packets(self): + self.build() + source_file = lldb.SBFileSpec("main.c") + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "break here", source_file + ) + + reply = self.send_process_packet("qSupported") + self.assertIn("MultiMemRead+", reply) + + mem_address_var = thread.frames[0].FindVariable("memory") + self.assertTrue(mem_address_var) + mem_address = int(mem_address_var.GetValue(), 16) + + reply = self.send_process_packet("MultiMemRead:") + self.assertEqual(reply, "E03") + reply = self.send_process_packet("MultiMemRead:ranges:") + self.assertEqual(reply, "E03") + reply = self.send_process_packet("MultiMemRead:ranges:10") + self.assertEqual(reply, "E03") + reply = self.send_process_packet("MultiMemRead:ranges:10,") + self.assertEqual(reply, "E03") + reply = self.send_process_packet("MultiMemRead:ranges:10,2") + self.assertEqual(reply, "E03") + reply = self.send_process_packet("MultiMemRead:ranges:10,2,") + self.assertEqual(reply, "E03") + reply = self.send_process_packet("MultiMemRead:ranges:10,2,;") + self.assertEqual(reply, "0;") # Debugserver is permissive with trailing commas. + reply = self.send_process_packet("MultiMemRead:ranges:10,2;") + self.assertEqual(reply, "0;") + reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},0;") + self.assertEqual(reply, "0;") + reply = self.send_process_packet( + f"MultiMemRead:ranges:{mem_address:x},0;options:;" + ) + self.assertEqual(reply, "0;") + reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2;") + self.assertEqual(reply, "2;ab") + reply = self.send_process_packet( + f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4;" + ) + self.assertEqual(reply, "2,4;abcdef") + reply = self.send_process_packet( + f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},8;" + ) + self.assertEqual(reply, "2,4,8;abcdefghijklmn") diff --git a/lldb/test/API/macosx/debugserver-multimemread/main.c b/lldb/test/API/macosx/debugserver-multimemread/main.c new file mode 100644 index 0000000000000..1a756a3c4cbac --- /dev/null +++ b/lldb/test/API/macosx/debugserver-multimemread/main.c @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char **argv) { + char *memory = malloc(1024); + memset(memory, '-', 1024); + for (int i = 0; i < 50; i++) + memory[i] = 'a' + i; + return 0; // break here +} diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index b06c6bf6735c6..777ea450a9a18 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -270,6 +270,11 @@ void RNBRemote::CreatePacketTable() { "Read memory")); t.push_back(Packet(read_register, &RNBRemote::HandlePacket_p, NULL, "p", "Read one register")); + // Careful: this *must* come before the `M` packet, as debugserver matches + // packet prefixes against known packet names. Inverting the order would match + // `MultiMemRead` as an `M` packet. + t.push_back(Packet(multi_mem_read, &RNBRemote::HandlePacket_MultiMemRead, + NULL, "MultiMemRead", "Read multiple memory addresses")); t.push_back(Packet(write_memory, &RNBRemote::HandlePacket_M, NULL, "M", "Write memory")); t.push_back(Packet(write_register, &RNBRemote::HandlePacket_P, NULL, "P", @@ -3150,6 +3155,140 @@ rnb_err_t RNBRemote::HandlePacket_m(const char *p) { return SendPacket(ostrm.str()); } +/// Returns true if `str` starts with `prefix`. +static bool starts_with(std::string_view str, std::string_view prefix) { + return str.size() >= prefix.size() && + str.compare(0, prefix.size(), prefix) == 0; +} + +/// Attempts to parse a prefix of `number_str` as a number of type `T`. If +/// successful, the number is returned and the prefix is dropped from +/// `number_str`. +template +static std::optional extract_number(std::string_view &number_str) { + static_assert(std::is_integral()); + char *str_end = nullptr; + errno = 0; + nub_addr_t number = strtoull(number_str.data(), &str_end, 16); + if (errno != 0) + return std::nullopt; + assert(str_end); + number_str.remove_prefix(str_end - number_str.data()); + return number; +} + +/// Splits `list_str` into multiple string_views separated by `,`. +static std::vector +parse_comma_separated_list(std::string_view list_str) { + std::vector list; + while (!list_str.empty()) { + auto pos = list_str.find(','); + list.push_back(list_str.substr(0, pos)); + if (pos == list_str.npos) + break; + list_str.remove_prefix(pos + 1); + } + return list; +} + +rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) { + const std::string_view packet_name("MultiMemRead:"); + std::string_view packet(p); + + if (!starts_with(packet, packet_name)) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid MultiMemRead packet prefix"); + + packet.remove_prefix(packet_name.size()); + + const std::string_view ranges_prefix("ranges:"); + if (!starts_with(packet, ranges_prefix)) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "Missing 'ranges' in MultiMemRead packet"); + packet.remove_prefix(ranges_prefix.size()); + + std::vector> ranges; + std::size_t total_length = 0; + + // Ranges should have the form: ,[,,]*; + auto end_of_ranges_pos = packet.find(';'); + if (end_of_ranges_pos == packet.npos) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "MultiMemRead missing end of ranges marker"); + + std::vector numbers_list = + parse_comma_separated_list(packet.substr(0, end_of_ranges_pos)); + packet.remove_prefix(end_of_ranges_pos + 1); + + // Ranges are pairs, so the number of elements must be even. + if (numbers_list.size() % 2 == 1) + return HandlePacket_ILLFORMED( + __FILE__, __LINE__, p, + "MultiMemRead has an odd number of numbers for the ranges"); + + for (unsigned idx = 0; idx < numbers_list.size(); idx += 2) { + std::optional maybe_addr = + extract_number(numbers_list[idx]); + std::optional maybe_length = + extract_number(numbers_list[idx + 1]); + if (!maybe_addr || !maybe_length) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "Invalid MultiMemRead range"); + // A sanity check that the packet requested is not too large or a negative + // number. + if (*maybe_length > 4 * 1024 * 1024) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "MultiMemRead length is too large"); + + ranges.emplace_back(*maybe_addr, *maybe_length); + total_length += *maybe_length; + } + + if (ranges.empty()) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "MultiMemRead has an empty range list"); + + // If 'options:' is present, ensure it's empty. + const std::string_view options_prefix("options:"); + if (starts_with(packet, options_prefix)) { + packet.remove_prefix(options_prefix.size()); + if (packet.empty()) + return HandlePacket_ILLFORMED( + __FILE__, __LINE__, packet.data(), + "Too short 'options' in MultiMemRead packet"); + if (packet[0] != ';') + return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), + "Unimplemented 'options' in MultiMemRead"); + packet.remove_prefix(1); + } + + if (!packet.empty()) + return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, + "Invalid MultiMemRead address_range"); + + std::vector> buffers; + buffers.reserve(ranges.size()); + for (auto [base_addr, length] : ranges) { + buffers.emplace_back(length, '\0'); + nub_size_t bytes_read = DNBProcessMemoryRead(m_ctx.ProcessID(), base_addr, + length, buffers.back().data()); + buffers.back().resize(bytes_read); + } + + std::ostringstream reply_stream; + bool first = true; + for (const std::vector &buffer : buffers) { + reply_stream << (first ? "" : ",") << std::hex << buffer.size(); + first = false; + } + reply_stream << ';'; + + for (const std::vector &buffer : buffers) + binary_encode_data_vector(reply_stream, buffer); + + return SendPacket(reply_stream.str()); +} + // Read memory, sent it up as binary data. // Usage: xADDR,LEN // ADDR and LEN are both base 16. @@ -3503,6 +3642,7 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) { if (supports_memory_tagging()) reply << "memory-tagging+;"; + reply << "MultiMemRead+;"; return SendPacket(reply.str().c_str()); } diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h index cf1c978afcd23..b32c00adc8b23 100644 --- a/lldb/tools/debugserver/source/RNBRemote.h +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -136,6 +136,7 @@ class RNBRemote { query_transfer, // 'qXfer:' json_query_dyld_process_state, // 'jGetDyldProcessState' enable_error_strings, // 'QEnableErrorStrings' + multi_mem_read, // 'MultiMemRead' unknown_type }; // clang-format on @@ -216,6 +217,7 @@ class RNBRemote { rnb_err_t HandlePacket_last_signal(const char *p); rnb_err_t HandlePacket_m(const char *p); rnb_err_t HandlePacket_M(const char *p); + rnb_err_t HandlePacket_MultiMemRead(const char *p); rnb_err_t HandlePacket_x(const char *p); rnb_err_t HandlePacket_X(const char *p); rnb_err_t HandlePacket_z(const char *p); From 55c8f39d25c2f55847e3c1007210a488d88bb024 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Sun, 12 Oct 2025 08:08:58 -0700 Subject: [PATCH 2/3] fixup! Address review comments --- .../TestDebugserverMultiMemRead.py | 74 ++++++++++----- .../macosx/debugserver-multimemread/main.c | 8 +- lldb/tools/debugserver/source/RNBRemote.cpp | 95 ++++++++----------- 3 files changed, 94 insertions(+), 83 deletions(-) diff --git a/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py b/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py index 5c0c7a34e18bd..a624172b3a0ab 100644 --- a/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py +++ b/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py @@ -1,5 +1,5 @@ """ -Tests the exit code/description coming from the debugserver. +Tests debugserver support for MultiMemRead. """ import lldb @@ -17,12 +17,16 @@ def send_process_packet(self, packet_str): # packet: # response: reply = self.res.GetOutput().split("\n") - reply[0] = reply[0].strip() - reply[1] = reply[1].strip() + packet = reply[0].strip() + response = reply[1].strip() - self.assertTrue(reply[0].startswith("packet: "), reply[0]) - self.assertTrue(reply[1].startswith("response: ")) - return reply[1][len("response: ") :] + self.assertTrue(packet.startswith("packet: ")) + self.assertTrue(response.startswith("response: ")) + return response[len("response: ") :] + + def check_invalid_packet(self, packet_str): + reply = self.send_process_packet("packet_str") + self.assertEqual(reply, "E03") def test_packets(self): self.build() @@ -36,30 +40,41 @@ def test_packets(self): mem_address_var = thread.frames[0].FindVariable("memory") self.assertTrue(mem_address_var) - mem_address = int(mem_address_var.GetValue(), 16) + mem_address = mem_address_var.GetValueAsUnsigned() + 42 - reply = self.send_process_packet("MultiMemRead:") - self.assertEqual(reply, "E03") - reply = self.send_process_packet("MultiMemRead:ranges:") - self.assertEqual(reply, "E03") - reply = self.send_process_packet("MultiMemRead:ranges:10") - self.assertEqual(reply, "E03") - reply = self.send_process_packet("MultiMemRead:ranges:10,") - self.assertEqual(reply, "E03") - reply = self.send_process_packet("MultiMemRead:ranges:10,2") - self.assertEqual(reply, "E03") - reply = self.send_process_packet("MultiMemRead:ranges:10,2,") - self.assertEqual(reply, "E03") + # no ":" + self.check_invalid_packet("MultiMemRead") + # missing ranges + self.check_invalid_packet("MultiMemRead:") + # needs at least one range + self.check_invalid_packet("MultiMemRead:ranges:") + # needs at least one range + self.check_invalid_packet("MultiMemRead:ranges:,") + # a range is a pair of numbers + self.check_invalid_packet("MultiMemRead:ranges:10") + # a range is a pair of numbers + self.check_invalid_packet("MultiMemRead:ranges:10,") + # range list must end with ; + self.check_invalid_packet("MultiMemRead:ranges:10,2") + self.check_invalid_packet("MultiMemRead:ranges:10,2,") + self.check_invalid_packet("MultiMemRead:ranges:10,2,3") + # ranges are pairs of numbers. + self.check_invalid_packet("MultiMemRead:ranges:10,2,3;") + # unrecognized field + self.check_invalid_packet("MultiMemRead:ranges:10,2;blah:;") + # unrecognized field + self.check_invalid_packet("MultiMemRead:blah:;ranges:10,2;") + + # This is a valid way of testing for MultiMemRead support + reply = self.send_process_packet("MultiMemRead:ranges:0,0;") + self.assertEqual(reply, "0;") + # Debugserver is permissive with trailing commas. reply = self.send_process_packet("MultiMemRead:ranges:10,2,;") - self.assertEqual(reply, "0;") # Debugserver is permissive with trailing commas. + self.assertEqual(reply, "0;") reply = self.send_process_packet("MultiMemRead:ranges:10,2;") self.assertEqual(reply, "0;") reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},0;") self.assertEqual(reply, "0;") - reply = self.send_process_packet( - f"MultiMemRead:ranges:{mem_address:x},0;options:;" - ) - self.assertEqual(reply, "0;") reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2;") self.assertEqual(reply, "2;ab") reply = self.send_process_packet( @@ -70,3 +85,14 @@ def test_packets(self): f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},8;" ) self.assertEqual(reply, "2,4,8;abcdefghijklmn") + + # Test zero length in the middle. + reply = self.send_process_packet( + f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},0,{mem_address+6:x},8;" + ) + self.assertEqual(reply, "2,0,8;abghijklmn") + # Test zero length in the end. + reply = self.send_process_packet( + f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},0;" + ) + self.assertEqual(reply, "2,4,0;abcdef") diff --git a/lldb/test/API/macosx/debugserver-multimemread/main.c b/lldb/test/API/macosx/debugserver-multimemread/main.c index 1a756a3c4cbac..44cdd475b5505 100644 --- a/lldb/test/API/macosx/debugserver-multimemread/main.c +++ b/lldb/test/API/macosx/debugserver-multimemread/main.c @@ -4,7 +4,11 @@ int main(int argc, char **argv) { char *memory = malloc(1024); memset(memory, '-', 1024); - for (int i = 0; i < 50; i++) - memory[i] = 'a' + i; + // Write "interesting" characters at an offset from the memory filled with + // `-`. This way, if we read outside the range in either direction, we should + // find `-`s`. + int offset = 42; + for (int i = offset; i < offset + 14; i++) + memory[i] = 'a' + (i - offset); return 0; // break here } diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index 777ea450a9a18..b2d79377f1eec 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -170,6 +170,20 @@ static uint64_t decode_uint64(const char *p, int base, char **end = nullptr, return addr; } +/// Attempts to parse a prefix of `number_str` as a uint64_t. If +/// successful, the number is returned and the prefix is dropped from +/// `number_str`. +static std::optional extract_u64(std::string_view &number_str) { + char *str_end = nullptr; + errno = 0; + uint64_t number = strtoull(number_str.data(), &str_end, 16); + if (errno != 0) + return std::nullopt; + assert(str_end); + number_str.remove_prefix(str_end - number_str.data()); + return number; +} + static void append_hex_value(std::ostream &ostrm, const void *buf, size_t buf_size, bool swap) { int i; @@ -204,6 +218,25 @@ static void append_hexified_string(std::ostream &ostrm, } } +/// Returns true if `str` starts with `prefix`. +static bool starts_with(std::string_view str, std::string_view prefix) { + return str.substr(0, prefix.size()) == prefix; +} + +/// Splits `list_str` into multiple string_views separated by `,`. +static std::vector +parse_comma_separated_list(std::string_view list_str) { + std::vector list; + while (!list_str.empty()) { + auto pos = list_str.find(','); + list.push_back(list_str.substr(0, pos)); + if (pos == list_str.npos) + break; + list_str.remove_prefix(pos + 1); + } + return list; +} + // from System.framework/Versions/B/PrivateHeaders/sys/codesign.h extern "C" { #define CS_OPS_STATUS 0 /* return status */ @@ -3155,42 +3188,6 @@ rnb_err_t RNBRemote::HandlePacket_m(const char *p) { return SendPacket(ostrm.str()); } -/// Returns true if `str` starts with `prefix`. -static bool starts_with(std::string_view str, std::string_view prefix) { - return str.size() >= prefix.size() && - str.compare(0, prefix.size(), prefix) == 0; -} - -/// Attempts to parse a prefix of `number_str` as a number of type `T`. If -/// successful, the number is returned and the prefix is dropped from -/// `number_str`. -template -static std::optional extract_number(std::string_view &number_str) { - static_assert(std::is_integral()); - char *str_end = nullptr; - errno = 0; - nub_addr_t number = strtoull(number_str.data(), &str_end, 16); - if (errno != 0) - return std::nullopt; - assert(str_end); - number_str.remove_prefix(str_end - number_str.data()); - return number; -} - -/// Splits `list_str` into multiple string_views separated by `,`. -static std::vector -parse_comma_separated_list(std::string_view list_str) { - std::vector list; - while (!list_str.empty()) { - auto pos = list_str.find(','); - list.push_back(list_str.substr(0, pos)); - if (pos == list_str.npos) - break; - list_str.remove_prefix(pos + 1); - } - return list; -} - rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) { const std::string_view packet_name("MultiMemRead:"); std::string_view packet(p); @@ -3227,10 +3224,8 @@ rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) { "MultiMemRead has an odd number of numbers for the ranges"); for (unsigned idx = 0; idx < numbers_list.size(); idx += 2) { - std::optional maybe_addr = - extract_number(numbers_list[idx]); - std::optional maybe_length = - extract_number(numbers_list[idx + 1]); + std::optional maybe_addr = extract_u64(numbers_list[idx]); + std::optional maybe_length = extract_u64(numbers_list[idx + 1]); if (!maybe_addr || !maybe_length) return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), "Invalid MultiMemRead range"); @@ -3248,28 +3243,14 @@ rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) { return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, "MultiMemRead has an empty range list"); - // If 'options:' is present, ensure it's empty. - const std::string_view options_prefix("options:"); - if (starts_with(packet, options_prefix)) { - packet.remove_prefix(options_prefix.size()); - if (packet.empty()) - return HandlePacket_ILLFORMED( - __FILE__, __LINE__, packet.data(), - "Too short 'options' in MultiMemRead packet"); - if (packet[0] != ';') - return HandlePacket_ILLFORMED(__FILE__, __LINE__, packet.data(), - "Unimplemented 'options' in MultiMemRead"); - packet.remove_prefix(1); - } - if (!packet.empty()) - return HandlePacket_ILLFORMED(__FILE__, __LINE__, p, - "Invalid MultiMemRead address_range"); + return HandlePacket_ILLFORMED( + __FILE__, __LINE__, p, "MultiMemRead packet has unrecognized fields"); std::vector> buffers; buffers.reserve(ranges.size()); for (auto [base_addr, length] : ranges) { - buffers.emplace_back(length, '\0'); + buffers.emplace_back(length, 0); nub_size_t bytes_read = DNBProcessMemoryRead(m_ctx.ProcessID(), base_addr, length, buffers.back().data()); buffers.back().resize(bytes_read); From 0f3c342cfb73016722e793fd4a4c3b3965ac9d6b Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan Date: Wed, 15 Oct 2025 08:00:33 -0700 Subject: [PATCH 3/3] fixup! Add trailing comma test --- .../debugserver-multimemread/TestDebugserverMultiMemRead.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py b/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py index a624172b3a0ab..6caa5f85c2848 100644 --- a/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py +++ b/lldb/test/API/macosx/debugserver-multimemread/TestDebugserverMultiMemRead.py @@ -65,12 +65,16 @@ def test_packets(self): # unrecognized field self.check_invalid_packet("MultiMemRead:blah:;ranges:10,2;") - # This is a valid way of testing for MultiMemRead support + # Zero-length reads are ok. reply = self.send_process_packet("MultiMemRead:ranges:0,0;") self.assertEqual(reply, "0;") + # Debugserver is permissive with trailing commas. reply = self.send_process_packet("MultiMemRead:ranges:10,2,;") self.assertEqual(reply, "0;") + reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2,;") + self.assertEqual(reply, "2;ab") + reply = self.send_process_packet("MultiMemRead:ranges:10,2;") self.assertEqual(reply, "0;") reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},0;")