[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
This commit is contained in:
emoose 2024-10-24 00:27:59 +01:00
parent 220324b463
commit 2e9c9f403d
7 changed files with 70 additions and 3 deletions

View File

@ -52,6 +52,9 @@ class DebugListener {
// Breakpoints may be hit during stepping. // Breakpoints may be hit during stepping.
virtual void OnBreakpointHit(Breakpoint* breakpoint, virtual void OnBreakpointHit(Breakpoint* breakpoint,
ThreadDebugInfo* thread_info) = 0; ThreadDebugInfo* thread_info) = 0;
// Handles any debug messages from the guest
virtual void OnDebugPrint(const std::string_view message) = 0;
}; };
} // namespace cpu } // namespace cpu

View File

@ -143,6 +143,7 @@ constexpr char kTargetXml[] =
</target> </target>
)"; )";
// TODO: move these to string_util.h?
std::string to_hexbyte(uint8_t i) { std::string to_hexbyte(uint8_t i) {
std::string result = "00"; std::string result = "00";
uint8_t i1 = i & 0xF; uint8_t i1 = i & 0xF;
@ -164,6 +165,23 @@ uint8_t from_hexchar(char c) {
return 0; return 0;
} }
template <typename T>
inline std::string to_hex_string(const T* data, size_t count) {
// Ensure that T is trivially copyable
static_assert(std::is_trivially_copyable<T>::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<const std::uint8_t*>(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) GDBStub::GDBStub(Emulator* emulator, int listen_port)
: emulator_(emulator), : emulator_(emulator),
processor_(emulator->processor()), processor_(emulator->processor()),
@ -211,7 +229,7 @@ void GDBStub::Listen(GDBClient& client) {
break; break;
} }
// No data available, can do other work or sleep // 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 // Check if we need to notify client about anything
{ {
@ -238,6 +256,13 @@ void GDBStub::Listen(GDBClient& client) {
GetThreadStateReply(cache_.notify_thread_id, signal)); GetThreadStateReply(cache_.notify_thread_id, signal));
cache_.notify_thread_id = -1; cache_.notify_thread_id = -1;
cache_.notify_stopped = false; 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", debugging::DebugPrint("GDBStub: ExecutionStep (thread {})\n",
cache_.last_bp_thread_id); cache_.last_bp_thread_id);
#endif #endif
std::unique_lock<std::mutex> lock(mtx_);
if (cache_.last_bp_thread_id != -1) { if (cache_.last_bp_thread_id != -1) {
processor_->StepGuestInstruction(cache_.last_bp_thread_id); processor_->StepGuestInstruction(cache_.last_bp_thread_id);
@ -783,6 +809,8 @@ std::string GDBStub::MemoryWrite(const std::string& data) {
} }
std::string GDBStub::BuildThreadList() { std::string GDBStub::BuildThreadList() {
std::unique_lock<std::mutex> lock(mtx_);
std::string buffer; std::string buffer;
buffer += "l<?xml version=\"1.0\"?>"; buffer += "l<?xml version=\"1.0\"?>";
buffer += "<threads>"; buffer += "<threads>";
@ -836,6 +864,7 @@ bool GDBStub::CreateCodeBreakpoint(uint64_t address) {
#ifdef DEBUG #ifdef DEBUG
debugging::DebugPrint("GDBStub: Adding breakpoint: {:X}\n", address); debugging::DebugPrint("GDBStub: Adding breakpoint: {:X}\n", address);
#endif #endif
std::unique_lock<std::mutex> lock(mtx_);
auto* exe_addr = processor_->LookupFunction((uint32_t)address); auto* exe_addr = processor_->LookupFunction((uint32_t)address);
if (!exe_addr) { if (!exe_addr) {
@ -888,6 +917,7 @@ void GDBStub::DeleteCodeBreakpoint(uint64_t address) {
} }
void GDBStub::DeleteCodeBreakpoint(Breakpoint* breakpoint) { void GDBStub::DeleteCodeBreakpoint(Breakpoint* breakpoint) {
std::unique_lock<std::mutex> lock(mtx_);
auto& state = cache_.breakpoints; auto& state = cache_.breakpoints;
for (size_t i = 0; i < state.all_breakpoints.size(); ++i) { for (size_t i = 0; i < state.all_breakpoints.size(); ++i) {
if (state.all_breakpoints[i].get() != breakpoint) { if (state.all_breakpoints[i].get() != breakpoint) {
@ -918,6 +948,7 @@ void GDBStub::OnFocus() {}
void GDBStub::OnDetached() { void GDBStub::OnDetached() {
UpdateCache(); UpdateCache();
std::unique_lock<std::mutex> lock(mtx_);
// Delete all breakpoints // Delete all breakpoints
auto& state = cache_.breakpoints; auto& state = cache_.breakpoints;
@ -969,10 +1000,13 @@ void GDBStub::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) {
#ifdef DEBUG #ifdef DEBUG
debugging::DebugPrint("GDBStub: OnStepCompleted\n"); debugging::DebugPrint("GDBStub: OnStepCompleted\n");
#endif #endif
std::unique_lock<std::mutex> lock(mtx_);
// Some debuggers like IDA will remove the current breakpoint & step into next // Some debuggers like IDA will remove the current breakpoint & step into next
// instruction, only re-adding BP after it's told about the step // instruction, only re-adding BP after it's told about the step
cache_.notify_thread_id = thread_info->thread_id; cache_.notify_thread_id = thread_info->thread_id;
cache_.last_bp_thread_id = thread_info->thread_id; cache_.last_bp_thread_id = thread_info->thread_id;
UpdateCache(); UpdateCache();
} }
@ -983,12 +1017,20 @@ void GDBStub::OnBreakpointHit(Breakpoint* breakpoint,
breakpoint->address(), thread_info->thread_id); breakpoint->address(), thread_info->thread_id);
#endif #endif
std::unique_lock<std::mutex> lock(mtx_);
cache_.notify_bp_guest_address = breakpoint->address(); cache_.notify_bp_guest_address = breakpoint->address();
cache_.notify_thread_id = thread_info->thread_id; cache_.notify_thread_id = thread_info->thread_id;
cache_.last_bp_thread_id = thread_info->thread_id; cache_.last_bp_thread_id = thread_info->thread_id;
UpdateCache(); UpdateCache();
} }
void GDBStub::OnDebugPrint(const std::string_view message) {
std::unique_lock<std::mutex> lock(mtx_);
cache_.notify_debug_prints.push(std::string(message));
}
std::string GDBStub::HandleGDBCommand(GDBClient& client, std::string GDBStub::HandleGDBCommand(GDBClient& client,
const GDBCommand& command) { const GDBCommand& command) {
static const std::unordered_map<std::string, static const std::unordered_map<std::string,
@ -1055,6 +1097,7 @@ std::string GDBStub::HandleGDBCommand(GDBClient& client,
// Get current debugger thread ID // Get current debugger thread ID
{"qC", {"qC",
[&](const GDBCommand& cmd) { [&](const GDBCommand& cmd) {
std::unique_lock<std::mutex> lock(mtx_);
auto* thread = cache_.cur_thread_info(); auto* thread = cache_.cur_thread_info();
if (!thread) { if (!thread) {
return std::string(kGdbReplyError); return std::string(kGdbReplyError);
@ -1064,6 +1107,7 @@ std::string GDBStub::HandleGDBCommand(GDBClient& client,
// Set current debugger thread ID // Set current debugger thread ID
{"H", {"H",
[&](const GDBCommand& cmd) { [&](const GDBCommand& cmd) {
std::unique_lock<std::mutex> lock(mtx_);
int threadId = std::stol(cmd.data.substr(1), 0, 16); int threadId = std::stol(cmd.data.substr(1), 0, 16);
if (!threadId) { if (!threadId) {
@ -1131,6 +1175,7 @@ std::string GDBStub::HandleGDBCommand(GDBClient& client,
// Thread list (IDA requests this but ignores in favor of qXfer?) // Thread list (IDA requests this but ignores in favor of qXfer?)
{"qfThreadInfo", {"qfThreadInfo",
[&](const GDBCommand& cmd) { [&](const GDBCommand& cmd) {
std::unique_lock<std::mutex> lock(mtx_);
std::string result; std::string result;
for (auto& thread : cache_.thread_debug_infos) { for (auto& thread : cache_.thread_debug_infos) {
if (!result.empty()) result += ","; if (!result.empty()) result += ",";

View File

@ -11,6 +11,7 @@
#define XENIA_DEBUG_GDB_GDBSTUB_H_ #define XENIA_DEBUG_GDB_GDBSTUB_H_
#include <memory> #include <memory>
#include <queue>
#include <vector> #include <vector>
#include "xenia/base/host_thread_context.h" #include "xenia/base/host_thread_context.h"
@ -64,6 +65,7 @@ class GDBStub : public cpu::DebugListener {
void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override; void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override;
void OnBreakpointHit(cpu::Breakpoint* breakpoint, void OnBreakpointHit(cpu::Breakpoint* breakpoint,
cpu::ThreadDebugInfo* thread_info) override; cpu::ThreadDebugInfo* thread_info) override;
void OnDebugPrint(const std::string_view message) override;
private: private:
struct GDBCommand { struct GDBCommand {
@ -119,7 +121,6 @@ class GDBStub : public cpu::DebugListener {
std::unique_ptr<xe::SocketServer> socket_; std::unique_ptr<xe::SocketServer> socket_;
std::mutex mtx_; std::mutex mtx_;
std::condition_variable cv_;
bool stop_thread_ = false; bool stop_thread_ = false;
xe::global_critical_region global_critical_region_; xe::global_critical_region global_critical_region_;
@ -135,6 +136,8 @@ class GDBStub : public cpu::DebugListener {
notify_exception_access_violation; notify_exception_access_violation;
std::optional<Exception::Code> notify_exception_code; std::optional<Exception::Code> notify_exception_code;
std::queue<std::string> notify_debug_prints;
bool is_stopped = false; bool is_stopped = false;
std::vector<kernel::object_ref<kernel::XModule>> modules; std::vector<kernel::object_ref<kernel::XModule>> modules;
std::vector<cpu::ThreadDebugInfo*> thread_debug_infos; std::vector<cpu::ThreadDebugInfo*> thread_debug_infos;

View File

@ -1606,6 +1606,8 @@ void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
Focus(); Focus();
} }
void DebugWindow::OnDebugPrint(const std::string_view message) {}
void DebugWindow::Focus() const { void DebugWindow::Focus() const {
app_context_.CallInUIThread([this]() { window_->Focus(); }); app_context_.CallInUIThread([this]() { window_->Focus(); });
} }

View File

@ -51,6 +51,7 @@ class DebugWindow : public cpu::DebugListener {
void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override; void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override;
void OnBreakpointHit(cpu::Breakpoint* breakpoint, void OnBreakpointHit(cpu::Breakpoint* breakpoint,
cpu::ThreadDebugInfo* thread_info) override; cpu::ThreadDebugInfo* thread_info) override;
void OnDebugPrint(const std::string_view message) override;
private: private:
class DebugDialog final : public xe::ui::ImGuiDialog { class DebugDialog final : public xe::ui::ImGuiDialog {

View File

@ -8,6 +8,7 @@
*/ */
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/cpu/processor.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xbdm/xbdm_private.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; } dword_result_t DmIsDebuggerPresent_entry() { return 0; }
DECLARE_XBDM_EXPORT1(DmIsDebuggerPresent, kDebug, kStub); 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); DECLARE_XBDM_EXPORT1(DmSendNotificationString, kDebug, kStub);
dword_result_t DmRegisterCommandProcessor_entry(lpdword_t name_ptr, dword_result_t DmRegisterCommandProcessor_entry(lpdword_t name_ptr,

View File

@ -13,6 +13,7 @@
#include <string> #include <string>
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/cpu/processor.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/user_module.h" #include "xenia/kernel/user_module.h"
#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/util/shim_utils.h"
@ -850,6 +851,10 @@ SHIM_CALL DbgPrint_entry(PPCContext* ppc_context) {
XELOGI("(DbgPrint) {}", str); XELOGI("(DbgPrint) {}", str);
if (cpu::DebugListener* listener = ppc_context->processor->debug_listener()) {
listener->OnDebugPrint(str);
}
SHIM_SET_RETURN_32(X_STATUS_SUCCESS); SHIM_SET_RETURN_32(X_STATUS_SUCCESS);
} }