From 2e9c9f403d01d48a093fbc0fad22ab81ba74612a Mon Sep 17 00:00:00 2001 From: emoose Date: Thu, 24 Oct 2024 00:27:59 +0100 Subject: [PATCH] [GDBStub] Forward `DbgPrint` & `DmSendNotificationString` messages TODO: see if there's any other debug-string-output functions that we can pass to debugger? Might be worth adding a custom GDB command for toggling these too, some games could be spammy --- src/xenia/cpu/debug_listener.h | 3 ++ src/xenia/debug/gdb/gdbstub.cc | 47 ++++++++++++++++++- src/xenia/debug/gdb/gdbstub.h | 5 +- src/xenia/debug/ui/debug_window.cc | 2 + src/xenia/debug/ui/debug_window.h | 1 + src/xenia/kernel/xbdm/xbdm_misc.cc | 10 +++- src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc | 5 ++ 7 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/xenia/cpu/debug_listener.h b/src/xenia/cpu/debug_listener.h index 6233e87fe..6a9f4864e 100644 --- a/src/xenia/cpu/debug_listener.h +++ b/src/xenia/cpu/debug_listener.h @@ -52,6 +52,9 @@ class DebugListener { // Breakpoints may be hit during stepping. virtual void OnBreakpointHit(Breakpoint* breakpoint, ThreadDebugInfo* thread_info) = 0; + + // Handles any debug messages from the guest + virtual void OnDebugPrint(const std::string_view message) = 0; }; } // namespace cpu diff --git a/src/xenia/debug/gdb/gdbstub.cc b/src/xenia/debug/gdb/gdbstub.cc index 469efcd31..407577a55 100644 --- a/src/xenia/debug/gdb/gdbstub.cc +++ b/src/xenia/debug/gdb/gdbstub.cc @@ -143,6 +143,7 @@ constexpr char kTargetXml[] = )"; +// TODO: move these to string_util.h? std::string to_hexbyte(uint8_t i) { std::string result = "00"; uint8_t i1 = i & 0xF; @@ -164,6 +165,23 @@ uint8_t from_hexchar(char c) { return 0; } +template +inline std::string to_hex_string(const T* data, size_t count) { + // Ensure that T is trivially copyable + static_assert(std::is_trivially_copyable::value, + "T must be a trivially copyable type"); + + static const char hex_chars[] = "0123456789ABCDEF"; + std::string result; + result.reserve(sizeof(T) * count * 2); + const auto* bytes = reinterpret_cast(data); + for (size_t i = 0; i < sizeof(T) * count; ++i) { + result += hex_chars[bytes[i] >> 4]; + result += hex_chars[bytes[i] & 0x0F]; + } + return result; +} + GDBStub::GDBStub(Emulator* emulator, int listen_port) : emulator_(emulator), processor_(emulator->processor()), @@ -211,7 +229,7 @@ void GDBStub::Listen(GDBClient& client) { break; } // No data available, can do other work or sleep - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Check if we need to notify client about anything { @@ -238,6 +256,13 @@ void GDBStub::Listen(GDBClient& client) { GetThreadStateReply(cache_.notify_thread_id, signal)); cache_.notify_thread_id = -1; cache_.notify_stopped = false; + } else if (cache_.notify_debug_prints.size()) { + // Send the oldest message in our queue, only send 1 per loop to + // reduce spamming the client & process any incoming packets + std::string& message = cache_.notify_debug_prints.front(); + SendPacket(client, + "O" + to_hex_string(message.c_str(), message.length())); + cache_.notify_debug_prints.pop(); } } } @@ -676,6 +701,7 @@ std::string GDBStub::ExecutionStep() { debugging::DebugPrint("GDBStub: ExecutionStep (thread {})\n", cache_.last_bp_thread_id); #endif + std::unique_lock lock(mtx_); if (cache_.last_bp_thread_id != -1) { processor_->StepGuestInstruction(cache_.last_bp_thread_id); @@ -783,6 +809,8 @@ std::string GDBStub::MemoryWrite(const std::string& data) { } std::string GDBStub::BuildThreadList() { + std::unique_lock lock(mtx_); + std::string buffer; buffer += "l"; buffer += ""; @@ -836,6 +864,7 @@ bool GDBStub::CreateCodeBreakpoint(uint64_t address) { #ifdef DEBUG debugging::DebugPrint("GDBStub: Adding breakpoint: {:X}\n", address); #endif + std::unique_lock lock(mtx_); auto* exe_addr = processor_->LookupFunction((uint32_t)address); if (!exe_addr) { @@ -888,6 +917,7 @@ void GDBStub::DeleteCodeBreakpoint(uint64_t address) { } void GDBStub::DeleteCodeBreakpoint(Breakpoint* breakpoint) { + std::unique_lock lock(mtx_); auto& state = cache_.breakpoints; for (size_t i = 0; i < state.all_breakpoints.size(); ++i) { if (state.all_breakpoints[i].get() != breakpoint) { @@ -918,6 +948,7 @@ void GDBStub::OnFocus() {} void GDBStub::OnDetached() { UpdateCache(); + std::unique_lock lock(mtx_); // Delete all breakpoints auto& state = cache_.breakpoints; @@ -969,10 +1000,13 @@ void GDBStub::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) { #ifdef DEBUG debugging::DebugPrint("GDBStub: OnStepCompleted\n"); #endif + std::unique_lock lock(mtx_); + // Some debuggers like IDA will remove the current breakpoint & step into next // instruction, only re-adding BP after it's told about the step cache_.notify_thread_id = thread_info->thread_id; cache_.last_bp_thread_id = thread_info->thread_id; + UpdateCache(); } @@ -983,12 +1017,20 @@ void GDBStub::OnBreakpointHit(Breakpoint* breakpoint, breakpoint->address(), thread_info->thread_id); #endif + std::unique_lock lock(mtx_); + cache_.notify_bp_guest_address = breakpoint->address(); cache_.notify_thread_id = thread_info->thread_id; cache_.last_bp_thread_id = thread_info->thread_id; + UpdateCache(); } +void GDBStub::OnDebugPrint(const std::string_view message) { + std::unique_lock lock(mtx_); + cache_.notify_debug_prints.push(std::string(message)); +} + std::string GDBStub::HandleGDBCommand(GDBClient& client, const GDBCommand& command) { static const std::unordered_map lock(mtx_); auto* thread = cache_.cur_thread_info(); if (!thread) { return std::string(kGdbReplyError); @@ -1064,6 +1107,7 @@ std::string GDBStub::HandleGDBCommand(GDBClient& client, // Set current debugger thread ID {"H", [&](const GDBCommand& cmd) { + std::unique_lock lock(mtx_); int threadId = std::stol(cmd.data.substr(1), 0, 16); if (!threadId) { @@ -1131,6 +1175,7 @@ std::string GDBStub::HandleGDBCommand(GDBClient& client, // Thread list (IDA requests this but ignores in favor of qXfer?) {"qfThreadInfo", [&](const GDBCommand& cmd) { + std::unique_lock lock(mtx_); std::string result; for (auto& thread : cache_.thread_debug_infos) { if (!result.empty()) result += ","; diff --git a/src/xenia/debug/gdb/gdbstub.h b/src/xenia/debug/gdb/gdbstub.h index 39c80cb5b..e4ed4c16c 100644 --- a/src/xenia/debug/gdb/gdbstub.h +++ b/src/xenia/debug/gdb/gdbstub.h @@ -11,6 +11,7 @@ #define XENIA_DEBUG_GDB_GDBSTUB_H_ #include +#include #include #include "xenia/base/host_thread_context.h" @@ -64,6 +65,7 @@ class GDBStub : public cpu::DebugListener { void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override; void OnBreakpointHit(cpu::Breakpoint* breakpoint, cpu::ThreadDebugInfo* thread_info) override; + void OnDebugPrint(const std::string_view message) override; private: struct GDBCommand { @@ -119,7 +121,6 @@ class GDBStub : public cpu::DebugListener { std::unique_ptr socket_; std::mutex mtx_; - std::condition_variable cv_; bool stop_thread_ = false; xe::global_critical_region global_critical_region_; @@ -135,6 +136,8 @@ class GDBStub : public cpu::DebugListener { notify_exception_access_violation; std::optional notify_exception_code; + std::queue notify_debug_prints; + bool is_stopped = false; std::vector> modules; std::vector thread_debug_infos; diff --git a/src/xenia/debug/ui/debug_window.cc b/src/xenia/debug/ui/debug_window.cc index 83b076c46..3e7dc0f05 100644 --- a/src/xenia/debug/ui/debug_window.cc +++ b/src/xenia/debug/ui/debug_window.cc @@ -1606,6 +1606,8 @@ void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint, Focus(); } +void DebugWindow::OnDebugPrint(const std::string_view message) {} + void DebugWindow::Focus() const { app_context_.CallInUIThread([this]() { window_->Focus(); }); } diff --git a/src/xenia/debug/ui/debug_window.h b/src/xenia/debug/ui/debug_window.h index c58a3d448..7d6039440 100644 --- a/src/xenia/debug/ui/debug_window.h +++ b/src/xenia/debug/ui/debug_window.h @@ -51,6 +51,7 @@ class DebugWindow : public cpu::DebugListener { void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override; void OnBreakpointHit(cpu::Breakpoint* breakpoint, cpu::ThreadDebugInfo* thread_info) override; + void OnDebugPrint(const std::string_view message) override; private: class DebugDialog final : public xe::ui::ImGuiDialog { diff --git a/src/xenia/kernel/xbdm/xbdm_misc.cc b/src/xenia/kernel/xbdm/xbdm_misc.cc index 7adc3072e..b31fc2d68 100644 --- a/src/xenia/kernel/xbdm/xbdm_misc.cc +++ b/src/xenia/kernel/xbdm/xbdm_misc.cc @@ -8,6 +8,7 @@ */ #include "xenia/base/logging.h" +#include "xenia/cpu/processor.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xbdm/xbdm_private.h" @@ -69,7 +70,14 @@ DECLARE_XBDM_EXPORT1(DmGetXboxName, kDebug, kImplemented) dword_result_t DmIsDebuggerPresent_entry() { return 0; } DECLARE_XBDM_EXPORT1(DmIsDebuggerPresent, kDebug, kStub); -void DmSendNotificationString_entry(lpdword_t unk0_ptr) {} +void DmSendNotificationString_entry(lpstring_t message) { + XELOGI("(DmSendNotificationString) {}", message.value()); + + if (cpu::DebugListener* listener = + kernel_state()->processor()->debug_listener()) { + listener->OnDebugPrint(message.value()); + } +} DECLARE_XBDM_EXPORT1(DmSendNotificationString, kDebug, kStub); dword_result_t DmRegisterCommandProcessor_entry(lpdword_t name_ptr, diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc index 6da1b5380..3ec34cee9 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc @@ -13,6 +13,7 @@ #include #include "xenia/base/logging.h" +#include "xenia/cpu/processor.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/user_module.h" #include "xenia/kernel/util/shim_utils.h" @@ -850,6 +851,10 @@ SHIM_CALL DbgPrint_entry(PPCContext* ppc_context) { XELOGI("(DbgPrint) {}", str); + if (cpu::DebugListener* listener = ppc_context->processor->debug_listener()) { + listener->OnDebugPrint(str); + } + SHIM_SET_RETURN_32(X_STATUS_SUCCESS); }