Merge 5917554324
into 11f14e8488
This commit is contained in:
commit
4950de5d32
|
@ -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")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>());
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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_
|
|
@ -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()
|
|
@ -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(); });
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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_; }
|
||||
|
||||
|
|
|
@ -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