From 6f23ee43ae812a57c62b81efd3993b3f4823ae35 Mon Sep 17 00:00:00 2001
From: Ian Chamberlain <ian.h.chamberlain@gmail.com>
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, &current_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, &current_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::FunctionDef, 180> 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"},