[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.
|
||||
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
|
||||
|
|
|
@ -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 += ",";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); });
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue