From 6f23ee43ae812a57c62b81efd3993b3f4823ae35 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Fri, 2 Dec 2022 19:31:31 -0600 Subject: [PATCH] Initial port of luma3ds' gdb_hio to Citra --- src/core/CMakeLists.txt | 2 + src/core/gdbstub/gdbstub.cpp | 19 +++++ src/core/gdbstub/hio.cpp | 150 +++++++++++++++++++++++++++++++++++ src/core/gdbstub/hio.h | 32 ++++++++ src/core/hle/kernel/svc.cpp | 11 ++- 5 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 src/core/gdbstub/hio.cpp create mode 100644 src/core/gdbstub/hio.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8aadcad81..c97062ed7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -115,6 +115,8 @@ add_library(core STATIC frontend/mic.h gdbstub/gdbstub.cpp gdbstub/gdbstub.h + gdbstub/hio.cpp + gdbstub/hio.h hle/applets/applet.cpp hle/applets/applet.h hle/applets/erreula.cpp diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 932d1f06d..8e73898b8 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -35,6 +35,7 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/gdbstub/gdbstub.h" +#include "core/gdbstub/hio.h" #include "core/hle/kernel/process.h" #include "core/loader/loader.h" #include "core/memory.h" @@ -1061,6 +1062,19 @@ void HandlePacket() { LOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer[0]); + // HACK: instead of polling DebugEvents properly via SVC, just check for + // whether there's a pending a request, and send it if so. + switch (command_buffer[0]) { + case 'c': + case 'C': + case 's': + if (HasHioRequest()) { + const auto reply = BuildHioReply(); + SendReply(reply.data()); + return; + } + } + switch (command_buffer[0]) { case 'q': HandleQuery(); @@ -1075,6 +1089,11 @@ void HandlePacket() { Shutdown(); LOG_INFO(Debug_GDBStub, "killed by gdb"); return; + case 'F': + if (HandleHioRequest(command_buffer, command_length)) { + Continue(); + }; + break; case 'g': ReadRegisters(); break; diff --git a/src/core/gdbstub/hio.cpp b/src/core/gdbstub/hio.cpp new file mode 100644 index 000000000..da120f6c3 --- /dev/null +++ b/src/core/gdbstub/hio.cpp @@ -0,0 +1,150 @@ +#include "core/core.h" +#include "core/gdbstub/gdbstub.h" +#include "core/gdbstub/hio.h" + +namespace GDBStub { + +namespace { + +static VAddr current_hio_request_addr; +static PackedGdbHioRequest current_hio_request; + +} // namespace + +void SetHioRequest(const VAddr addr) { + if (!IsServerEnabled()) { + LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running"); + return; + } + + if (current_hio_request_addr != 0) { + LOG_WARNING(Debug_GDBStub, "HIO requested while already in progress!"); + return; + } + + auto& memory = Core::System::GetInstance().Memory(); + if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(), + addr)) { + LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request"); + return; + } + + memory.ReadBlock(addr, ¤t_hio_request, sizeof(PackedGdbHioRequest)); + current_hio_request_addr = addr; + + LOG_DEBUG(Debug_GDBStub, "HIO request initiated"); +} + +bool HandleHioRequest(const u8* const command_buffer, const u32 command_length) { + if (!HasHioRequest()) { + // TODO send error reply packet? + return false; + } + + u64 retval{0}; + + auto* command_pos = command_buffer; + ++command_pos; + + // TODO: not totally sure what's going on here... + if (*command_pos == 0 || *command_pos == ',') { + // return GDB_ReplyErrno(ctx, EILSEQ); + return false; + } else if (*command_pos == '-') { + command_pos++; + current_hio_request.retval = -1; + } else if (*command_pos == '+') { + command_pos++; + current_hio_request.retval = 1; + } else { + current_hio_request.retval = 1; + } + + // TODO: + // pos = GDB_ParseHexIntegerList64(&retval, pos, 1, ','); + + if (command_pos == nullptr) { + // return GDB_ReplyErrno(ctx, EILSEQ); + return false; + } + + current_hio_request.retval *= retval; + current_hio_request.gdb_errno = 0; + current_hio_request.ctrl_c = 0; + + if (*command_pos != 0) { + u32 errno_; + // GDB protocol technically allows errno to have a +/- prefix but this will never happen. + // TODO: + // pos = GDB_ParseHexIntegerList(&errno_, ++pos, 1, ','); + current_hio_request.gdb_errno = (int)errno_; + if (command_pos == nullptr) { + return false; + // return GDB_ReplyErrno(ctx, EILSEQ); + } + + if (*command_pos != 0) { + if (*command_pos != 'C') { + return false; + // return GDB_ReplyErrno(ctx, EILSEQ); + } + + current_hio_request.ctrl_c = true; + } + } + + std::fill(std::begin(current_hio_request.param_format), + std::end(current_hio_request.param_format), 0); + + auto& memory = Core::System::GetInstance().Memory(); + // should have been checked when we first initialized the request: + assert(memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(), + current_hio_request_addr)); + + memory.WriteBlock(current_hio_request_addr, ¤t_hio_request, sizeof(PackedGdbHioRequest)); + + current_hio_request = PackedGdbHioRequest{}; + current_hio_request_addr = 0; + + return true; +} + +bool HasHioRequest() { + return current_hio_request_addr != 0; +} + +std::string BuildHioReply() { + char buf[256 + 1]; + char tmp[32 + 1]; + u32 nStr = 0; + + // TODO: c++ify this and use the IntToGdbHex funcs instead of snprintf + + snprintf(buf, 256, "F%s", current_hio_request.function_name); + + for (u32 i = 0; i < 8 && current_hio_request.param_format[i] != 0; i++) { + switch (current_hio_request.param_format[i]) { + case 'i': + case 'I': + case 'p': + snprintf(tmp, 32, ",%x", (u32)current_hio_request.parameters[i]); + break; + case 'l': + case 'L': + snprintf(tmp, 32, ",%llx", current_hio_request.parameters[i]); + break; + case 's': + snprintf(tmp, 32, ",%x/%zx", (u32)current_hio_request.parameters[i], + current_hio_request.string_lengths[nStr++]); + break; + default: + tmp[0] = 0; + break; + } + strcat(buf, tmp); + } + + return std::string{buf, strlen(buf)}; +} + +} // namespace GDBStub diff --git a/src/core/gdbstub/hio.h b/src/core/gdbstub/hio.h new file mode 100644 index 000000000..5b4bce8a6 --- /dev/null +++ b/src/core/gdbstub/hio.h @@ -0,0 +1,32 @@ +#pragma once + +#include "common/common_types.h" + +namespace GDBStub { + +struct PackedGdbHioRequest { + char magic[4]; // "GDB\x00" + u32 version; + + // Request + char function_name[16 + 1]; + char param_format[8 + 1]; + + u64 parameters[8]; + size_t string_lengths[8]; + + // Return + s64 retval; + int gdb_errno; + bool ctrl_c; +}; + +void SetHioRequest(const VAddr address); + +bool HandleHioRequest(const u8* const command_buffer, const u32 command_length); + +bool HasHioRequest(); + +std::string BuildHioReply(); + +} // namespace GDBStub diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 2603bd03f..8157d92ea 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -13,6 +13,7 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/gdbstub/hio.h" #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" @@ -1140,8 +1141,14 @@ void SVC::Break(u8 break_reason) { system.SetStatus(Core::System::ResultStatus::ErrorUnknown); } -/// Used to output a message on a debug hardware unit - does nothing on a retail unit +/// Used to output a message on a debug hardware unit, or for the GDB HIO +// protocol - does nothing on a retail unit. void SVC::OutputDebugString(VAddr address, s32 len) { + if (len == 0) { + GDBStub::SetHioRequest(address); + return; + } + if (len <= 0) { return; } @@ -2212,7 +2219,7 @@ const std::array SVC::SVC_Table{{ {0x60, nullptr, "DebugActiveProcess"}, {0x61, nullptr, "BreakDebugProcess"}, {0x62, nullptr, "TerminateDebugProcess"}, - {0x63, nullptr, "GetProcessDebugEvent"}, + {0x63, nullptr, "GetProcessDebugEvent"}, // TODO: do we need this for HIO to work? {0x64, nullptr, "ContinueDebugEvent"}, {0x65, &SVC::Wrap<&SVC::GetProcessList>, "GetProcessList"}, {0x66, nullptr, "GetThreadList"},