[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:
parent
220324b463
commit
2e9c9f403d
|
@ -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
|
||||||
|
|
|
@ -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 += ",";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(); });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue