[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.
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

View File

@ -143,6 +143,7 @@ constexpr char kTargetXml[] =
</target>
)";
// 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 <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)
: 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<std::mutex> 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<std::mutex> lock(mtx_);
std::string buffer;
buffer += "l<?xml version=\"1.0\"?>";
buffer += "<threads>";
@ -836,6 +864,7 @@ bool GDBStub::CreateCodeBreakpoint(uint64_t address) {
#ifdef DEBUG
debugging::DebugPrint("GDBStub: Adding breakpoint: {:X}\n", address);
#endif
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mtx_);
cache_.notify_debug_prints.push(std::string(message));
}
std::string GDBStub::HandleGDBCommand(GDBClient& client,
const GDBCommand& command) {
static const std::unordered_map<std::string,
@ -1055,6 +1097,7 @@ std::string GDBStub::HandleGDBCommand(GDBClient& client,
// Get current debugger thread ID
{"qC",
[&](const GDBCommand& cmd) {
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> lock(mtx_);
std::string result;
for (auto& thread : cache_.thread_debug_infos) {
if (!result.empty()) result += ",";

View File

@ -11,6 +11,7 @@
#define XENIA_DEBUG_GDB_GDBSTUB_H_
#include <memory>
#include <queue>
#include <vector>
#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<xe::SocketServer> 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<Exception::Code> notify_exception_code;
std::queue<std::string> notify_debug_prints;
bool is_stopped = false;
std::vector<kernel::object_ref<kernel::XModule>> modules;
std::vector<cpu::ThreadDebugInfo*> thread_debug_infos;

View File

@ -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(); });
}

View File

@ -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 {

View File

@ -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,

View File

@ -13,6 +13,7 @@
#include <string>
#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);
}