-
Notifications
You must be signed in to change notification settings - Fork 14.8k
[debugserver] Implement MultiMemRead packet #162670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[debugserver] Implement MultiMemRead packet #162670
Conversation
@llvm/pr-subscribers-lldb Author: Felipe de Azevedo Piovezan (felipepiovezan) ChangesThis commit implements, in debugserver, the packet as discussed in the RFC 1. Full diff: https://github.com/llvm/llvm-project/pull/162670.diff 5 Files Affected:
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: <packet_str>
+ # response: <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 <stdlib.h>
+#include <string.h>
+
+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 434e9cfa40fb4..dfb0443fdd3dd 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -246,6 +246,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",
@@ -3160,6 +3165,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 <typename T>
+static std::optional<T> extract_number(std::string_view &number_str) {
+ static_assert(std::is_integral<T>());
+ 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<std::string_view>
+parse_comma_separated_list(std::string_view list_str) {
+ std::vector<std::string_view> 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<std::pair<nub_addr_t, std::size_t>> ranges;
+ std::size_t total_length = 0;
+
+ // Ranges should have the form: <addr>,<size>[,<addr>,<size>]*;
+ 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<std::string_view> 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<nub_addr_t> maybe_addr =
+ extract_number<nub_addr_t>(numbers_list[idx]);
+ std::optional<size_t> maybe_length =
+ extract_number<size_t>(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<std::vector<uint8_t>> 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<uint8_t> &buffer : buffers) {
+ reply_stream << (first ? "" : ",") << std::hex << buffer.size();
+ first = false;
+ }
+ reply_stream << ';';
+
+ for (const std::vector<uint8_t> &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.
@@ -3525,6 +3664,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);
|
return str.size() >= prefix.size() && | ||
str.compare(0, prefix.size(), prefix) == 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return str.size() >= prefix.size() && | |
str.compare(0, prefix.size(), prefix) == 0; | |
return str.substr(0, prefix.size()) == prefix; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will throw if prefix.size() > str.size()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I like the idea of using ==
to simplify it, I will make some tweaks to the suggested code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will throw if
prefix.size() > str.size()
oh from my read of the docs it doesn't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, my suggested code has pos = 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhhh you're totally right. My bad!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk if you can use StringRef here but it's very stringview like and it has a starts_with.
Maybe not because you don't want to link llvm libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debugserver doesn't link to llvm or lldb, today. it does include a socket header from the lldb sources, but everything in there is defined in the header. it does allow us to build debugserver entirely separately from the rest of llvm/lldb, handy when cross compiling to a different target quickly, via its Xcode project file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah... it hurts a lot knowing all the StringRef(ArrayRef) methods that I can't use! :((
static_assert(std::is_integral<T>()); | ||
char *str_end = nullptr; | ||
errno = 0; | ||
nub_addr_t number = strtoull(number_str.data(), &str_end, 16); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nub_addr_t number = strtoull(number_str.data(), &str_end, 16); | |
T number = strtoull(number_str.data(), &str_end, 16); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am tempted to get rid of the template and make this a uint64_t function only, as this is using that strtoull
function.
/// successful, the number is returned and the prefix is dropped from | ||
/// `number_str`. | ||
template <typename T> | ||
static std::optional<T> extract_number(std::string_view &number_str) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you want to put this next to decode_uint64()
in the source file, in case some future person is inspired to update the caller to that old function to use this one. The other ~37 strtoull's in this file parsing base16 strings do them all separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, also moved the other helper functions to the top of this file, in case we want to re-use them later.
packet.remove_prefix(packet_name.size()); | ||
|
||
const std::string_view ranges_prefix("ranges:"); | ||
if (!starts_with(packet, ranges_prefix)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're doing key-value pairs, I wouldn't hardcode that this key name must be first in the packet. It's an extra thing to handle in the future when we have additional keys "old debugservers need this key to come first".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed by removing the options field.
|
||
// If 'options:' is present, ensure it's empty. | ||
const std::string_view options_prefix("options:"); | ||
if (starts_with(packet, options_prefix)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hardcodes that options
key must come after ranges
key. I'd find
these from the start of the original packet string. Why Look for an options
key and require that it be empty? This means a future lldb can't use this key for anything.
std::vector<std::vector<uint8_t>> buffers; | ||
buffers.reserve(ranges.size()); | ||
for (auto [base_addr, length] : ranges) { | ||
buffers.emplace_back(length, '\0'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's such a minor style nit I hesitate to say it, but '\0' is the NUL character and is the marker of the end of a c-string. This is a vector<uint8_t> and I'd use a 0 here; there's no string stuff involved.
Looks very good, I had a couple of small suggestions but thumbs-up from me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great that you added tests.
The parsing is a bit "my kingdom for a StringRef" but I assume you have your reasons to avoid it in debugserver.
reply[0] = reply[0].strip() | ||
reply[1] = reply[1].strip() | ||
|
||
self.assertTrue(reply[0].startswith("packet: "), reply[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this one have reply[0] in it, for the assert failure message? Add a "expected bla got: " if that's the case, on the response as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a bit of an oopsie on my part. Will remove from both.
reply[0] = reply[0].strip() | ||
reply[1] = reply[1].strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could name these packet
and response
. Signals what you expect to find in there.
|
||
mem_address_var = thread.frames[0].FindVariable("memory") | ||
self.assertTrue(mem_address_var) | ||
mem_address = int(mem_address_var.GetValue(), 16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we have a GetAsWhatever method on SBValue use that. Same thing but seems more "proper" to use that.
self.assertTrue(mem_address_var) | ||
mem_address = int(mem_address_var.GetValue(), 16) | ||
|
||
reply = self.send_process_packet("MultiMemRead:") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add one without the colon, just "MultiMemRead". Might hit a generic "oh no no ;" handler that already existed but that's fine to check anyway.
self.assertEqual(reply, "E03") | ||
reply = self.send_process_packet("MultiMemRead:ranges:10") | ||
self.assertEqual(reply, "E03") | ||
reply = self.send_process_packet("MultiMemRead:ranges:10,") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comments to say why each one is invalid.
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;") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a test for 3 numbers. address, size, address - no size. It's an error I assume.
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:;" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we keep the options, if, reverse the order of keys as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed options!
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add tests for zero length reply in the middle and end, you have one for the start earlier.
Also check a zero length returns 0 bytes for a mapped and unmapped location.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added zero length reads at the middle end using mapped addresses.
The unmapped addresses were already covered, I believe, but I also added a ranges:0,0;
to simulate what a client may do when checking for MultiMemRead support.
memset(memory, '-', 1024); | ||
for (int i = 0; i < 50; i++) | ||
memory[i] = 'a' + i; | ||
return 0; // break here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea here is the a-z gives you a pattern to look for, and if we ever see the dashes, we know it didn't read correctly?
(and you didn't calloc it because all 0s could be the result of us forgetting to actually read anything)
In which case would it be a bit more rigorous to offset the a-z a bit from the start of the region? Now if you have an overread, we see dashes, but if we underread, then we get random bytes. The test would still fail but it would be random what wrong bytes we saw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points, added some ---
s at the start of the memory region as well
return str.size() >= prefix.size() && | ||
str.compare(0, prefix.size(), prefix) == 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk if you can use StringRef here but it's very stringview like and it has a starts_with.
Maybe not because you don't want to link llvm libraries.
@@ -0,0 +1,72 @@ | |||
""" | |||
Tests the exit code/description coming from the debugserver. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"...when handling MultiMemRead packets."
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
dee9744
to
55c8f39
Compare
Addressed review comments, also had to rebase due to a conflict |
This commit implements, in debugserver, the packet as discussed in the RFC 1.