This commit is contained in:
emoose 2024-12-21 22:03:59 +01:00 committed by GitHub
commit 4950de5d32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1491 additions and 22 deletions

View File

@ -295,6 +295,7 @@ workspace("xenia")
include("src/xenia/base")
include("src/xenia/cpu")
include("src/xenia/cpu/backend/x64")
include("src/xenia/debug/gdb")
include("src/xenia/debug/ui")
include("src/xenia/gpu")
include("src/xenia/gpu/null")

View File

@ -92,6 +92,7 @@ project("xenia-app")
"xenia-app-discord",
"xenia-apu-sdl",
-- TODO(Triang3l): CPU debugger on Android.
"xenia-debug-gdb",
"xenia-debug-ui",
"xenia-helper-sdl",
"xenia-hid-sdl",

View File

@ -25,6 +25,7 @@
#include "xenia/base/profiling.h"
#include "xenia/base/threading.h"
#include "xenia/config.h"
#include "xenia/debug/gdb/gdbstub.h"
#include "xenia/debug/ui/debug_window.h"
#include "xenia/emulator.h"
#include "xenia/kernel/xam/xam_module.h"
@ -100,6 +101,10 @@ DEFINE_transient_bool(portable, true,
"General");
DECLARE_bool(debug);
DEFINE_int32(
gdbport, 0,
"Port for GDBStub debugger to listen on, requires --debug (0 = disable)",
"General");
DEFINE_bool(discord, true, "Enable Discord rich presence", "General");
@ -228,6 +233,7 @@ class EmulatorApp final : public xe::ui::WindowedApp {
// Created on demand, used by the emulator.
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window_;
std::unique_ptr<xe::debug::gdb::GDBStub> debug_gdbstub_;
// Refreshing the emulator - placed after its dependencies.
std::atomic<bool> emulator_thread_quit_requested_;
@ -566,20 +572,33 @@ void EmulatorApp::EmulatorThread() {
// Set a debug handler.
// This will respond to debugging requests so we can open the debug UI.
if (cvars::debug) {
emulator_->processor()->set_debug_listener_request_handler(
[this](xe::cpu::Processor* processor) {
if (debug_window_) {
return debug_window_.get();
}
app_context().CallInUIThreadSynchronous([this]() {
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
app_context());
debug_window_->window()->AddListener(
&debug_window_closed_listener_);
if (cvars::gdbport > 0) {
emulator_->processor()->set_debug_listener_request_handler(
[this](xe::cpu::Processor* processor) {
if (debug_gdbstub_) {
return debug_gdbstub_.get();
}
debug_gdbstub_ = xe::debug::gdb::GDBStub::Create(emulator_.get(),
cvars::gdbport);
return debug_gdbstub_.get();
});
// If failed to enqueue the UI thread call, this will just be null.
return debug_window_.get();
});
emulator_->processor()->ShowDebugger();
} else {
emulator_->processor()->set_debug_listener_request_handler(
[this](xe::cpu::Processor* processor) {
if (debug_window_) {
return debug_window_.get();
}
app_context().CallInUIThreadSynchronous([this]() {
debug_window_ = xe::debug::ui::DebugWindow::Create(
emulator_.get(), app_context());
debug_window_->window()->AddListener(
&debug_window_closed_listener_);
});
// If failed to enqueue the UI thread call, this will just be null.
return debug_window_.get();
});
}
}
emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) {

View File

@ -46,6 +46,9 @@ class Socket {
// Returns true if the client is connected and can send/receive data.
virtual bool is_connected() = 0;
// Sets socket non-blocking mode
virtual void set_nonblocking(bool nonblocking) = 0;
// Closes the socket.
// This will signal the wait handle.
virtual void Close() = 0;

View File

@ -113,6 +113,15 @@ class Win32Socket : public Socket {
return socket_ != INVALID_SOCKET;
}
void set_nonblocking(bool nonblocking) override {
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (socket_ == INVALID_SOCKET) {
return;
}
u_long val = nonblocking ? 1 : 0;
ioctlsocket(socket_, FIONBIO, &val);
}
void Close() override {
std::lock_guard<std::recursive_mutex> lock(mutex_);

View File

@ -30,6 +30,9 @@ class DebugListener {
// end.
virtual void OnDetached() = 0;
// Handles exception info (will be followed by OnExecutionPaused)
virtual void OnUnhandledException(Exception* ex) = 0;
// Handles execution being interrupted and transitioning to
// ExceutionState::kPaused.
virtual void OnExecutionPaused() = 0;
@ -49,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

@ -33,6 +33,8 @@ DEFINE_bool(disable_context_promotion, false,
"some sports games, but will reduce performance.",
"CPU");
DECLARE_bool(debug);
namespace xe {
namespace cpu {
namespace ppc {
@ -59,7 +61,10 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
// Passes are executed in the order they are added. Multiple of the same
// pass type may be used.
if (!cvars::disable_context_promotion) {
// Disable context promotion for debug, otherwise register changes won't apply
// correctly
if (!cvars::disable_context_promotion && !cvars::debug) {
if (validate) {
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
}

View File

@ -644,7 +644,8 @@ bool Processor::OnThreadBreakpointHit(Exception* ex) {
if ((scan_breakpoint->address_type() == Breakpoint::AddressType::kGuest &&
scan_breakpoint->guest_address() == frame.guest_pc) ||
(scan_breakpoint->address_type() == Breakpoint::AddressType::kHost &&
scan_breakpoint->host_address() == frame.host_pc)) {
scan_breakpoint->host_address() == frame.host_pc) ||
scan_breakpoint->ContainsHostAddress(frame.host_pc)) {
breakpoint = scan_breakpoint;
break;
}
@ -670,18 +671,19 @@ bool Processor::OnThreadBreakpointHit(Exception* ex) {
}
ResumeAllThreads();
thread_info->thread->thread()->Suspend();
// Apply thread context changes.
// TODO(benvanik): apply to all threads?
#if XE_ARCH_AMD64
ex->set_resume_pc(thread_info->host_context.rip + 2);
ex->set_resume_pc(thread_info->host_context.rip);
#elif XE_ARCH_ARM64
ex->set_resume_pc(thread_info->host_context.pc + 2);
ex->set_resume_pc(thread_info->host_context.pc);
#else
#error Instruction pointer not specified for the target CPU architecture.
#endif // XE_ARCH
thread_info->thread->thread()->Suspend();
// Resume execution.
return true;
}
@ -722,7 +724,7 @@ bool Processor::OnUnhandledException(Exception* ex) {
execution_state_ = ExecutionState::kPaused;
// Notify debugger that exceution stopped.
// debug_listener_->OnException(info);
debug_listener_->OnUnhandledException(ex);
debug_listener_->OnExecutionPaused();
// Suspend self.
@ -936,7 +938,10 @@ void Processor::StepHostInstruction(uint32_t thread_id) {
thread_info->step_breakpoint.reset();
OnStepCompleted(thread_info);
}));
AddBreakpoint(thread_info->step_breakpoint.get());
// Add to front of breakpoints map, so this should get evaluated first
breakpoints_.insert(breakpoints_.begin(), thread_info->step_breakpoint.get());
thread_info->step_breakpoint->Resume();
// ResumeAllBreakpoints();
@ -969,7 +974,10 @@ void Processor::StepGuestInstruction(uint32_t thread_id) {
thread_info->step_breakpoint.reset();
OnStepCompleted(thread_info);
}));
AddBreakpoint(thread_info->step_breakpoint.get());
// Add to front of breakpoints map, so this should get evaluated first
breakpoints_.insert(breakpoints_.begin(), thread_info->step_breakpoint.get());
thread_info->step_breakpoint->Resume();
// ResumeAllBreakpoints();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,169 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_GDB_GDBSTUB_H_
#define XENIA_DEBUG_GDB_GDBSTUB_H_
#include <memory>
#include <queue>
#include <vector>
#include "xenia/base/host_thread_context.h"
#include "xenia/base/socket.h"
#include "xenia/cpu/breakpoint.h"
#include "xenia/cpu/debug_listener.h"
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
#include "xenia/xbox.h"
namespace xe {
namespace debug {
namespace gdb {
class GDBStub : public cpu::DebugListener {
enum class ControlCode : char {
Ack = '+',
Nack = '-',
PacketStart = '$',
PacketEnd = '#',
Interrupt = '\03',
};
enum class SignalCode : uint8_t { SIGILL = 4, SIGTRAP = 5, SIGSEGV = 11 };
enum class RegisterIndex : int {
GPR0 = 0,
FPR0 = 32,
PC = 64,
MSR = 65,
CR = 66,
LR = 67,
CTR = 68,
XER = 69,
FPSCR = 70
};
public:
virtual ~GDBStub();
static std::unique_ptr<GDBStub> Create(Emulator* emulator, int listen_port);
Emulator* emulator() const { return emulator_; }
void OnFocus() override;
void OnDetached() override;
void OnUnhandledException(Exception* ex) override;
void OnExecutionPaused() override;
void OnExecutionContinued() override;
void OnExecutionEnded() override;
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 {
std::string cmd{};
std::string data{};
uint8_t checksum{};
};
struct GDBClient {
std::unique_ptr<Socket> socket;
bool no_ack_mode = false;
std::string receive_buffer;
};
explicit GDBStub(Emulator* emulator, int listen_port);
bool Initialize();
void Listen(GDBClient& client);
void SendPacket(GDBClient& client, const std::string& data);
bool ProcessIncomingData(GDBClient& client);
bool ParsePacket(const std::string& packet, GDBCommand& out_cmd);
std::string HandleGDBCommand(GDBClient& client, const GDBCommand& command);
void UpdateCache();
std::string DebuggerDetached();
std::string RegisterRead(xe::cpu::ThreadDebugInfo* thread, uint32_t rid);
std::string RegisterWrite(xe::cpu::ThreadDebugInfo* thread, uint32_t rid,
const std::string_view value);
std::string RegisterRead(const std::string& data);
std::string RegisterWrite(const std::string& data);
std::string RegisterReadAll();
std::string RegisterWriteAll(const std::string& data);
std::string ExecutionPause();
std::string ExecutionContinue();
std::string ExecutionStep();
std::string MemoryRead(const std::string& data);
std::string MemoryWrite(const std::string& data);
std::string BuildThreadList();
std::string QueryPacket(GDBClient& client, const std::string& data);
std::string GetThreadStateReply(uint32_t thread_id, SignalCode signal);
bool CreateCodeBreakpoint(uint64_t address);
void DeleteCodeBreakpoint(uint64_t address);
void DeleteCodeBreakpoint(cpu::Breakpoint* breakpoint);
cpu::Breakpoint* LookupBreakpointAtAddress(uint64_t address);
Emulator* emulator_ = nullptr;
cpu::Processor* processor_ = nullptr;
int listen_port_;
std::unique_ptr<xe::SocketServer> socket_;
std::mutex mtx_;
bool stop_thread_ = false;
xe::global_critical_region global_critical_region_;
struct EmulatorStateCache {
uint32_t cur_thread_id = -1;
uint32_t last_bp_thread_id = -1;
uint64_t notify_bp_guest_address = -1;
uint32_t notify_thread_id = -1;
bool notify_stopped = false;
std::optional<Exception::AccessViolationOperation>
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;
struct {
std::vector<std::unique_ptr<cpu::Breakpoint>> all_breakpoints;
std::unordered_map<uint32_t, cpu::Breakpoint*>
code_breakpoints_by_guest_address;
} breakpoints;
cpu::ThreadDebugInfo* thread_info(int threadId) {
for (auto& thread : thread_debug_infos) {
if (thread->thread_id == threadId) {
return thread;
}
}
return nullptr;
}
cpu::ThreadDebugInfo* cur_thread_info() {
return thread_info(cur_thread_id);
}
} cache_;
};
} // namespace gdb
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_DEBUG_WINDOW_H_

View File

@ -0,0 +1,25 @@
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-debug-gdb")
uuid("9193a274-f4c2-4746-bd85-93fcfc5c3e39")
kind("StaticLib")
language("C++")
links({
"imgui",
"xenia-base",
"xenia-cpu",
"xenia-ui",
})
filter({"configurations:Release", "platforms:Windows"})
buildoptions({
"/Os",
"/O1"
})
filter{}
defines({
})
includedirs({
})
local_platform_files()

View File

@ -1576,6 +1576,8 @@ void DebugWindow::OnDetached() {
}
}
void DebugWindow::OnUnhandledException(Exception* ex) {}
void DebugWindow::OnExecutionPaused() {
UpdateCache();
Focus();
@ -1604,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

@ -44,12 +44,14 @@ class DebugWindow : public cpu::DebugListener {
void OnFocus() override;
void OnDetached() override;
void OnUnhandledException(Exception* ex) override;
void OnExecutionPaused() override;
void OnExecutionContinued() override;
void OnExecutionEnded() override;
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

@ -189,6 +189,10 @@ Emulator::~Emulator() {
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
}
uint32_t Emulator::main_thread_id() {
return main_thread_ ? main_thread_->thread_id() : 0;
}
X_STATUS Emulator::Setup(
ui::Window* display_window, ui::ImGuiDrawer* imgui_drawer,
bool require_cpu_backend,

View File

@ -125,6 +125,8 @@ class Emulator {
// Are we currently running a title?
bool is_title_open() const { return title_id_.has_value(); }
uint32_t main_thread_id();
// Window used for displaying graphical output. Can be null.
ui::Window* display_window() const { return display_window_; }

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