From 8c1a2022e4e10dca6cf4f4a773fe0e75e3c29bc6 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 9 Aug 2026 15:31:32 +1000 Subject: [PATCH] GDBServer: Expose target memory map and registers Ugh, XML. --- src/core/gdb_server.cpp | 179 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/src/core/gdb_server.cpp b/src/core/gdb_server.cpp index 3aeb6357b..c175cc206 100644 --- a/src/core/gdb_server.cpp +++ b/src/core/gdb_server.cpp @@ -35,6 +35,121 @@ static constexpr u32 MAX_FRAMED_PACKET_SIZE = MAX_PACKET_SIZE + 4; static constexpr u32 MAX_MEMORY_READ_SIZE = MAX_PACKET_SIZE / 2; static constexpr u32 MAX_WATCHPOINT_LENGTH = 256; +static constexpr std::string_view TARGET_XML = R"( + + + mips:3000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)"; + +static constexpr std::string_view MEMORY_MAP_XML = R"( + + + + + + + + + + + + + + + + + + + + + + + +)"; + namespace { enum class GDBBreakpointType : u8 @@ -122,10 +237,14 @@ template static bool Cmd$z(ClientSocket* client, std::string_view data); static bool Cmd$vMustReplyEmpty(ClientSocket* client, std::string_view data); static bool Cmd$qSupported(ClientSocket* client, std::string_view data); +static bool Cmd$qXferFeaturesRead(ClientSocket* client, std::string_view data); +static bool Cmd$qXferMemoryMapRead(ClientSocket* client, std::string_view data); static bool ParseOptionalAddress(std::string_view data, std::optional* address); static bool ParseSignalAndOptionalAddress(std::string_view data, u8* signal, std::optional* address); +static bool SendXferRead(ClientSocket* client, std::string_view data, std::string_view expected_annex, + std::string_view contents); static void InvalidateMemoryWrite(VirtualMemoryAddress address, u32 length); static CPU::BreakpointCallbackAction OnBreakpointHit(CPU::BreakpointType type, VirtualMemoryAddress pc, VirtualMemoryAddress address); @@ -200,6 +319,8 @@ static constexpr std::pair}, {"Z", Cmd$z}, {"vMustReplyEmpty", Cmd$vMustReplyEmpty}, + {"qXfer:features:read:", Cmd$qXferFeaturesRead}, + {"qXfer:memory-map:read:", Cmd$qXferMemoryMapRead}, {"qSupported", Cmd$qSupported}, }; @@ -552,7 +673,63 @@ bool GDBServer::Cmd$vMustReplyEmpty(ClientSocket* client, std::string_view data) bool GDBServer::Cmd$qSupported(ClientSocket* client, std::string_view data) { - client->SendReplyWithAck(TinyString::from_format("PacketSize={:x}", MAX_PACKET_SIZE)); + client->SendReplyWithAck( + TinyString::from_format("PacketSize={:x};qXfer:features:read+;qXfer:memory-map:read+", MAX_PACKET_SIZE)); + return true; +} + +bool GDBServer::Cmd$qXferFeaturesRead(ClientSocket* client, std::string_view data) +{ + return SendXferRead(client, data, "target.xml", TARGET_XML); +} + +bool GDBServer::Cmd$qXferMemoryMapRead(ClientSocket* client, std::string_view data) +{ + return SendXferRead(client, data, {}, MEMORY_MAP_XML); +} + +bool GDBServer::SendXferRead(ClientSocket* client, std::string_view data, std::string_view expected_annex, + std::string_view contents) +{ + const size_t annex_end = data.find(':'); + if (annex_end == std::string_view::npos) + { + ERROR_LOG("Invalid qXfer read packet: {}", data); + client->SendReplyWithAck("E01"); + return true; + } + + if (data.substr(0, annex_end) != expected_annex) + { + client->SendReplyWithAck(); + return true; + } + + std::string_view caret = data.substr(annex_end + 1); + std::optional offset; + std::optional length; + if (!(offset = StringUtil::FromChars(caret, 16, &caret)).has_value() || caret.empty() || caret[0] != ',' || + !(length = StringUtil::FromChars(caret.substr(1), 16, &caret)).has_value() || !caret.empty() || + length.value() == 0) + { + ERROR_LOG("Invalid qXfer read packet: {}", data); + client->SendReplyWithAck("E01"); + return true; + } + + if (offset.value() >= contents.size()) + { + client->SendReplyWithAck("l"); + return true; + } + + const size_t remaining = contents.size() - static_cast(offset.value()); + const size_t transfer_size = std::min({length.value(), MAX_PACKET_SIZE - 1, remaining}); + + SmallString reply; + reply.push_back((transfer_size < remaining) ? 'm' : 'l'); + reply.append(contents.substr(static_cast(offset.value()), transfer_size)); + client->SendReplyWithAck(reply); return true; }