Merge pull request #8394 from liamwhite/debugger
core/debugger: Implement new GDB stub debugger
This commit is contained in:
commit
de2f2e5140
27 changed files with 1500 additions and 42 deletions
|
@ -70,6 +70,7 @@ void LogSettings() {
|
||||||
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
|
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
|
||||||
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
|
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
|
||||||
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
|
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
|
||||||
|
log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue());
|
||||||
log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
|
log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
|
||||||
log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
|
log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
|
||||||
log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
|
log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
|
||||||
|
|
|
@ -601,7 +601,7 @@ struct Values {
|
||||||
// Debugging
|
// Debugging
|
||||||
bool record_frame_times;
|
bool record_frame_times;
|
||||||
BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
|
BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
|
||||||
BasicSetting<u16> gdbstub_port{0, "gdbstub_port"};
|
BasicSetting<u16> gdbstub_port{6543, "gdbstub_port"};
|
||||||
BasicSetting<std::string> program_args{std::string(), "program_args"};
|
BasicSetting<std::string> program_args{std::string(), "program_args"};
|
||||||
BasicSetting<bool> dump_exefs{false, "dump_exefs"};
|
BasicSetting<bool> dump_exefs{false, "dump_exefs"};
|
||||||
BasicSetting<bool> dump_nso{false, "dump_nso"};
|
BasicSetting<bool> dump_nso{false, "dump_nso"};
|
||||||
|
|
|
@ -36,6 +36,13 @@ add_library(core STATIC
|
||||||
crypto/ctr_encryption_layer.h
|
crypto/ctr_encryption_layer.h
|
||||||
crypto/xts_encryption_layer.cpp
|
crypto/xts_encryption_layer.cpp
|
||||||
crypto/xts_encryption_layer.h
|
crypto/xts_encryption_layer.h
|
||||||
|
debugger/debugger_interface.h
|
||||||
|
debugger/debugger.cpp
|
||||||
|
debugger/debugger.h
|
||||||
|
debugger/gdbstub_arch.cpp
|
||||||
|
debugger/gdbstub_arch.h
|
||||||
|
debugger/gdbstub.cpp
|
||||||
|
debugger/gdbstub.h
|
||||||
device_memory.cpp
|
device_memory.cpp
|
||||||
device_memory.h
|
device_memory.h
|
||||||
file_sys/bis_factory.cpp
|
file_sys/bis_factory.cpp
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/arm/symbols.h"
|
#include "core/arm/symbols.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/debugger/debugger.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
@ -88,4 +89,8 @@ void ARM_Interface::LogBacktrace() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ARM_Interface::ShouldStep() const {
|
||||||
|
return system.DebuggerEnabled() && system.GetDebugger().IsStepping();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -66,9 +66,6 @@ public:
|
||||||
/// Runs the CPU until an event happens
|
/// Runs the CPU until an event happens
|
||||||
virtual void Run() = 0;
|
virtual void Run() = 0;
|
||||||
|
|
||||||
/// Step CPU by one instruction
|
|
||||||
virtual void Step() = 0;
|
|
||||||
|
|
||||||
/// Clear all instruction cache
|
/// Clear all instruction cache
|
||||||
virtual void ClearInstructionCache() = 0;
|
virtual void ClearInstructionCache() = 0;
|
||||||
|
|
||||||
|
@ -194,6 +191,8 @@ public:
|
||||||
|
|
||||||
void LogBacktrace() const;
|
void LogBacktrace() const;
|
||||||
|
|
||||||
|
bool ShouldStep() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// System context that this ARM interface is running under.
|
/// System context that this ARM interface is running under.
|
||||||
System& system;
|
System& system;
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/debugger/debugger.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/svc.h"
|
#include "core/hle/kernel/svc.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
@ -26,6 +28,7 @@ using namespace Common::Literals;
|
||||||
|
|
||||||
constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
||||||
constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
||||||
|
constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
||||||
|
|
||||||
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
|
@ -78,11 +81,16 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
||||||
|
if (parent.system.DebuggerEnabled()) {
|
||||||
|
parent.breakpoint_pc = pc;
|
||||||
|
parent.jit.load()->HaltExecution(breakpoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
parent.LogBacktrace();
|
parent.LogBacktrace();
|
||||||
LOG_CRITICAL(Core_ARM,
|
LOG_CRITICAL(Core_ARM,
|
||||||
"ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
|
"ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
|
||||||
exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
|
exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
|
||||||
UNIMPLEMENTED();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallSVC(u32 swi) override {
|
void CallSVC(u32 swi) override {
|
||||||
|
@ -234,20 +242,35 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||||
|
|
||||||
void ARM_Dynarmic_32::Run() {
|
void ARM_Dynarmic_32::Run() {
|
||||||
while (true) {
|
while (true) {
|
||||||
const auto hr = jit.load()->Run();
|
const auto hr = ShouldStep() ? jit.load()->Step() : jit.load()->Run();
|
||||||
if (Has(hr, svc_call)) {
|
if (Has(hr, svc_call)) {
|
||||||
Kernel::Svc::Call(system, svc_swi);
|
Kernel::Svc::Call(system, svc_swi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check to see if breakpoint is triggered.
|
||||||
|
// Recheck step condition in case stop is no longer desired.
|
||||||
|
Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
|
||||||
|
if (Has(hr, breakpoint)) {
|
||||||
|
jit.load()->Regs()[15] = breakpoint_pc;
|
||||||
|
|
||||||
|
if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
|
||||||
|
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ShouldStep()) {
|
||||||
|
// When stepping, this should be the only thread running.
|
||||||
|
ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
|
||||||
|
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (Has(hr, break_loop) || !uses_wall_clock) {
|
if (Has(hr, break_loop) || !uses_wall_clock) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic_32::Step() {
|
|
||||||
jit.load()->Step();
|
|
||||||
}
|
|
||||||
|
|
||||||
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
|
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
|
||||||
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
||||||
std::size_t core_index_)
|
std::size_t core_index_)
|
||||||
|
|
|
@ -42,7 +42,6 @@ public:
|
||||||
u32 GetPSTATE() const override;
|
u32 GetPSTATE() const override;
|
||||||
void SetPSTATE(u32 pstate) override;
|
void SetPSTATE(u32 pstate) override;
|
||||||
void Run() override;
|
void Run() override;
|
||||||
void Step() override;
|
|
||||||
VAddr GetTlsAddress() const override;
|
VAddr GetTlsAddress() const override;
|
||||||
void SetTlsAddress(VAddr address) override;
|
void SetTlsAddress(VAddr address) override;
|
||||||
void SetTPIDR_EL0(u64 value) override;
|
void SetTPIDR_EL0(u64 value) override;
|
||||||
|
@ -95,6 +94,9 @@ private:
|
||||||
|
|
||||||
// SVC callback
|
// SVC callback
|
||||||
u32 svc_swi{};
|
u32 svc_swi{};
|
||||||
|
|
||||||
|
// Debug restart address
|
||||||
|
u32 breakpoint_pc{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/debugger/debugger.h"
|
||||||
#include "core/hardware_properties.h"
|
#include "core/hardware_properties.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
#include "core/hle/kernel/svc.h"
|
#include "core/hle/kernel/svc.h"
|
||||||
|
@ -27,6 +28,7 @@ using namespace Common::Literals;
|
||||||
|
|
||||||
constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
||||||
constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
||||||
|
constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
||||||
|
|
||||||
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
|
@ -119,8 +121,13 @@ public:
|
||||||
case Dynarmic::A64::Exception::SendEventLocal:
|
case Dynarmic::A64::Exception::SendEventLocal:
|
||||||
case Dynarmic::A64::Exception::Yield:
|
case Dynarmic::A64::Exception::Yield:
|
||||||
return;
|
return;
|
||||||
case Dynarmic::A64::Exception::Breakpoint:
|
|
||||||
default:
|
default:
|
||||||
|
if (parent.system.DebuggerEnabled()) {
|
||||||
|
parent.breakpoint_pc = pc;
|
||||||
|
parent.jit.load()->HaltExecution(breakpoint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
parent.LogBacktrace();
|
parent.LogBacktrace();
|
||||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
|
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
|
||||||
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
|
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
|
||||||
|
@ -299,16 +306,31 @@ void ARM_Dynarmic_64::Run() {
|
||||||
if (Has(hr, svc_call)) {
|
if (Has(hr, svc_call)) {
|
||||||
Kernel::Svc::Call(system, svc_swi);
|
Kernel::Svc::Call(system, svc_swi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check to see if breakpoint is triggered.
|
||||||
|
// Recheck step condition in case stop is no longer desired.
|
||||||
|
Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
|
||||||
|
if (Has(hr, breakpoint)) {
|
||||||
|
jit.load()->SetPC(breakpoint_pc);
|
||||||
|
|
||||||
|
if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
|
||||||
|
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ShouldStep()) {
|
||||||
|
// When stepping, this should be the only thread running.
|
||||||
|
ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
|
||||||
|
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (Has(hr, break_loop) || !uses_wall_clock) {
|
if (Has(hr, break_loop) || !uses_wall_clock) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic_64::Step() {
|
|
||||||
jit.load()->Step();
|
|
||||||
}
|
|
||||||
|
|
||||||
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
|
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
|
||||||
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
|
||||||
std::size_t core_index_)
|
std::size_t core_index_)
|
||||||
|
|
|
@ -40,7 +40,6 @@ public:
|
||||||
u32 GetPSTATE() const override;
|
u32 GetPSTATE() const override;
|
||||||
void SetPSTATE(u32 pstate) override;
|
void SetPSTATE(u32 pstate) override;
|
||||||
void Run() override;
|
void Run() override;
|
||||||
void Step() override;
|
|
||||||
VAddr GetTlsAddress() const override;
|
VAddr GetTlsAddress() const override;
|
||||||
void SetTlsAddress(VAddr address) override;
|
void SetTlsAddress(VAddr address) override;
|
||||||
void SetTPIDR_EL0(u64 value) override;
|
void SetTPIDR_EL0(u64 value) override;
|
||||||
|
@ -88,6 +87,9 @@ private:
|
||||||
|
|
||||||
// SVC callback
|
// SVC callback
|
||||||
u32 svc_swi{};
|
u32 svc_swi{};
|
||||||
|
|
||||||
|
// Debug restart address
|
||||||
|
u64 breakpoint_pc{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/cpu_manager.h"
|
#include "core/cpu_manager.h"
|
||||||
|
#include "core/debugger/debugger.h"
|
||||||
#include "core/device_memory.h"
|
#include "core/device_memory.h"
|
||||||
#include "core/file_sys/bis_factory.h"
|
#include "core/file_sys/bis_factory.h"
|
||||||
#include "core/file_sys/mode.h"
|
#include "core/file_sys/mode.h"
|
||||||
|
@ -171,6 +172,10 @@ struct System::Impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InitializeDebugger(System& system, u16 port) {
|
||||||
|
debugger = std::make_unique<Debugger>(system, port);
|
||||||
|
}
|
||||||
|
|
||||||
SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
|
SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
|
||||||
LOG_DEBUG(Core, "initialized OK");
|
LOG_DEBUG(Core, "initialized OK");
|
||||||
|
|
||||||
|
@ -329,6 +334,7 @@ struct System::Impl {
|
||||||
gpu_core->NotifyShutdown();
|
gpu_core->NotifyShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugger.reset();
|
||||||
services.reset();
|
services.reset();
|
||||||
service_manager.reset();
|
service_manager.reset();
|
||||||
cheat_engine.reset();
|
cheat_engine.reset();
|
||||||
|
@ -436,6 +442,9 @@ struct System::Impl {
|
||||||
/// Network instance
|
/// Network instance
|
||||||
Network::NetworkInstance network_instance;
|
Network::NetworkInstance network_instance;
|
||||||
|
|
||||||
|
/// Debugger
|
||||||
|
std::unique_ptr<Core::Debugger> debugger;
|
||||||
|
|
||||||
SystemResultStatus status = SystemResultStatus::Success;
|
SystemResultStatus status = SystemResultStatus::Success;
|
||||||
std::string status_details = "";
|
std::string status_details = "";
|
||||||
|
|
||||||
|
@ -472,10 +481,6 @@ SystemResultStatus System::Pause() {
|
||||||
return impl->Pause();
|
return impl->Pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemResultStatus System::SingleStep() {
|
|
||||||
return SystemResultStatus::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::InvalidateCpuInstructionCaches() {
|
void System::InvalidateCpuInstructionCaches() {
|
||||||
impl->kernel.InvalidateAllInstructionCaches();
|
impl->kernel.InvalidateAllInstructionCaches();
|
||||||
}
|
}
|
||||||
|
@ -496,6 +501,10 @@ void System::UnstallCPU() {
|
||||||
impl->UnstallCPU();
|
impl->UnstallCPU();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void System::InitializeDebugger() {
|
||||||
|
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
||||||
u64 program_id, std::size_t program_index) {
|
u64 program_id, std::size_t program_index) {
|
||||||
return impl->Load(*this, emu_window, filepath, program_id, program_index);
|
return impl->Load(*this, emu_window, filepath, program_id, program_index);
|
||||||
|
@ -809,6 +818,18 @@ bool System::IsMulticore() const {
|
||||||
return impl->is_multicore;
|
return impl->is_multicore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool System::DebuggerEnabled() const {
|
||||||
|
return Settings::values.use_gdbstub.GetValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::Debugger& System::GetDebugger() {
|
||||||
|
return *impl->debugger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Core::Debugger& System::GetDebugger() const {
|
||||||
|
return *impl->debugger;
|
||||||
|
}
|
||||||
|
|
||||||
void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
|
void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
|
||||||
impl->execute_program_callback = std::move(callback);
|
impl->execute_program_callback = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@ namespace Core {
|
||||||
|
|
||||||
class ARM_Interface;
|
class ARM_Interface;
|
||||||
class CpuManager;
|
class CpuManager;
|
||||||
|
class Debugger;
|
||||||
class DeviceMemory;
|
class DeviceMemory;
|
||||||
class ExclusiveMonitor;
|
class ExclusiveMonitor;
|
||||||
class SpeedLimiter;
|
class SpeedLimiter;
|
||||||
|
@ -147,12 +148,6 @@ public:
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] SystemResultStatus Pause();
|
[[nodiscard]] SystemResultStatus Pause();
|
||||||
|
|
||||||
/**
|
|
||||||
* Step the CPU one instruction
|
|
||||||
* @return Result status, indicating whether or not the operation succeeded.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] SystemResultStatus SingleStep();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate the CPU instruction caches
|
* Invalidate the CPU instruction caches
|
||||||
* This function should only be used by GDB Stub to support breakpoints, memory updates and
|
* This function should only be used by GDB Stub to support breakpoints, memory updates and
|
||||||
|
@ -168,6 +163,11 @@ public:
|
||||||
std::unique_lock<std::mutex> StallCPU();
|
std::unique_lock<std::mutex> StallCPU();
|
||||||
void UnstallCPU();
|
void UnstallCPU();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the debugger.
|
||||||
|
*/
|
||||||
|
void InitializeDebugger();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an executable application.
|
* Load an executable application.
|
||||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||||
|
@ -354,6 +354,9 @@ public:
|
||||||
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
|
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
|
||||||
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
|
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
|
||||||
|
|
||||||
|
[[nodiscard]] Core::Debugger& GetDebugger();
|
||||||
|
[[nodiscard]] const Core::Debugger& GetDebugger() const;
|
||||||
|
|
||||||
void SetExitLock(bool locked);
|
void SetExitLock(bool locked);
|
||||||
[[nodiscard]] bool GetExitLock() const;
|
[[nodiscard]] bool GetExitLock() const;
|
||||||
|
|
||||||
|
@ -375,6 +378,9 @@ public:
|
||||||
/// Tells if system is running on multicore.
|
/// Tells if system is running on multicore.
|
||||||
[[nodiscard]] bool IsMulticore() const;
|
[[nodiscard]] bool IsMulticore() const;
|
||||||
|
|
||||||
|
/// Tells if the system debugger is enabled.
|
||||||
|
[[nodiscard]] bool DebuggerEnabled() const;
|
||||||
|
|
||||||
/// Type used for the frontend to designate a callback for System to re-launch the application
|
/// Type used for the frontend to designate a callback for System to re-launch the application
|
||||||
/// using a specified program index.
|
/// using a specified program index.
|
||||||
using ExecuteProgramCallback = std::function<void(std::size_t)>;
|
using ExecuteProgramCallback = std::function<void(std::size_t)>;
|
||||||
|
|
259
src/core/debugger/debugger.cpp
Normal file
259
src/core/debugger/debugger.cpp
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/process/async_pipe.hpp>
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/debugger/debugger.h"
|
||||||
|
#include "core/debugger/debugger_interface.h"
|
||||||
|
#include "core/debugger/gdbstub.h"
|
||||||
|
#include "core/hle/kernel/global_scheduler_context.h"
|
||||||
|
|
||||||
|
template <typename Readable, typename Buffer, typename Callback>
|
||||||
|
static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
|
||||||
|
static_assert(std::is_trivial_v<Buffer>);
|
||||||
|
auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
|
||||||
|
r.async_read_some(boost_buffer, [&](const boost::system::error_code& error, size_t bytes_read) {
|
||||||
|
if (!error.failed()) {
|
||||||
|
const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
|
||||||
|
std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
|
||||||
|
c(received_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncReceiveInto(r, buffer, c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Readable, typename Buffer>
|
||||||
|
static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
|
||||||
|
static_assert(std::is_trivial_v<Buffer>);
|
||||||
|
auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
|
||||||
|
size_t bytes_read = r.read_some(boost_buffer);
|
||||||
|
const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
|
||||||
|
std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
|
||||||
|
return received_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
class DebuggerImpl : public DebuggerBackend {
|
||||||
|
public:
|
||||||
|
explicit DebuggerImpl(Core::System& system_, u16 port)
|
||||||
|
: system{system_}, signal_pipe{io_context}, client_socket{io_context} {
|
||||||
|
frontend = std::make_unique<GDBStub>(*this, system);
|
||||||
|
InitializeServer(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
~DebuggerImpl() {
|
||||||
|
ShutdownServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NotifyThreadStopped(Kernel::KThread* thread) {
|
||||||
|
std::scoped_lock lk{connection_lock};
|
||||||
|
|
||||||
|
if (stopped) {
|
||||||
|
// Do not notify the debugger about another event.
|
||||||
|
// It should be ignored.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stopped = true;
|
||||||
|
|
||||||
|
signal_pipe.write_some(boost::asio::buffer(&thread, sizeof(thread)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::span<const u8> ReadFromClient() override {
|
||||||
|
return ReceiveInto(client_socket, client_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteToClient(std::span<const u8> data) override {
|
||||||
|
client_socket.write_some(boost::asio::buffer(data.data(), data.size_bytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetActiveThread(Kernel::KThread* thread) override {
|
||||||
|
active_thread = thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
Kernel::KThread* GetActiveThread() override {
|
||||||
|
return active_thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsStepping() const {
|
||||||
|
return stepping;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitializeServer(u16 port) {
|
||||||
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
|
LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
|
||||||
|
|
||||||
|
// Initialize the listening socket and accept a new client.
|
||||||
|
tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
|
||||||
|
tcp::acceptor acceptor{io_context, endpoint};
|
||||||
|
client_socket = acceptor.accept();
|
||||||
|
|
||||||
|
// Run the connection thread.
|
||||||
|
connection_thread = std::jthread([&](std::stop_token stop_token) {
|
||||||
|
try {
|
||||||
|
ThreadLoop(stop_token);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
client_socket.shutdown(client_socket.shutdown_both);
|
||||||
|
client_socket.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownServer() {
|
||||||
|
connection_thread.request_stop();
|
||||||
|
io_context.stop();
|
||||||
|
connection_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadLoop(std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("yuzu:Debugger");
|
||||||
|
|
||||||
|
// Set up the client signals for new data.
|
||||||
|
AsyncReceiveInto(signal_pipe, active_thread, [&](auto d) { PipeData(d); });
|
||||||
|
AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
|
||||||
|
|
||||||
|
// Stop the emulated CPU.
|
||||||
|
AllCoreStop();
|
||||||
|
|
||||||
|
// Set the active thread.
|
||||||
|
active_thread = ThreadList()[0];
|
||||||
|
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
|
||||||
|
// Set up the frontend.
|
||||||
|
frontend->Connected();
|
||||||
|
|
||||||
|
// Main event loop.
|
||||||
|
while (!stop_token.stop_requested() && io_context.run()) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipeData(std::span<const u8> data) {
|
||||||
|
AllCoreStop();
|
||||||
|
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
frontend->Stopped(active_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientData(std::span<const u8> data) {
|
||||||
|
const auto actions{frontend->ClientData(data)};
|
||||||
|
for (const auto action : actions) {
|
||||||
|
switch (action) {
|
||||||
|
case DebuggerAction::Interrupt: {
|
||||||
|
{
|
||||||
|
std::scoped_lock lk{connection_lock};
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
AllCoreStop();
|
||||||
|
active_thread = ThreadList()[0];
|
||||||
|
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
frontend->Stopped(active_thread);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DebuggerAction::Continue:
|
||||||
|
stepping = false;
|
||||||
|
ResumeInactiveThreads();
|
||||||
|
AllCoreResume();
|
||||||
|
break;
|
||||||
|
case DebuggerAction::StepThread:
|
||||||
|
stepping = true;
|
||||||
|
SuspendInactiveThreads();
|
||||||
|
AllCoreResume();
|
||||||
|
break;
|
||||||
|
case DebuggerAction::ShutdownEmulation: {
|
||||||
|
// Suspend all threads and release any locks held
|
||||||
|
active_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
SuspendInactiveThreads();
|
||||||
|
AllCoreResume();
|
||||||
|
|
||||||
|
// Spawn another thread that will exit after shutdown,
|
||||||
|
// to avoid a deadlock
|
||||||
|
Core::System* system_ref{&system};
|
||||||
|
std::thread t([system_ref] { system_ref->Exit(); });
|
||||||
|
t.detach();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AllCoreStop() {
|
||||||
|
if (!suspend) {
|
||||||
|
suspend = system.StallCPU();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AllCoreResume() {
|
||||||
|
stopped = false;
|
||||||
|
system.UnstallCPU();
|
||||||
|
suspend.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuspendInactiveThreads() {
|
||||||
|
for (auto* thread : ThreadList()) {
|
||||||
|
if (thread != active_thread) {
|
||||||
|
thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResumeInactiveThreads() {
|
||||||
|
for (auto* thread : ThreadList()) {
|
||||||
|
if (thread != active_thread) {
|
||||||
|
thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Kernel::KThread*>& ThreadList() {
|
||||||
|
return system.GlobalSchedulerContext().GetThreadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
System& system;
|
||||||
|
std::unique_ptr<DebuggerFrontend> frontend;
|
||||||
|
|
||||||
|
std::jthread connection_thread;
|
||||||
|
std::mutex connection_lock;
|
||||||
|
boost::asio::io_context io_context;
|
||||||
|
boost::process::async_pipe signal_pipe;
|
||||||
|
boost::asio::ip::tcp::socket client_socket;
|
||||||
|
std::optional<std::unique_lock<std::mutex>> suspend;
|
||||||
|
|
||||||
|
Kernel::KThread* active_thread;
|
||||||
|
bool stopped;
|
||||||
|
bool stepping;
|
||||||
|
|
||||||
|
std::array<u8, 4096> client_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
Debugger::Debugger(Core::System& system, u16 port) {
|
||||||
|
try {
|
||||||
|
impl = std::make_unique<DebuggerImpl>(system, port);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debugger::~Debugger() = default;
|
||||||
|
|
||||||
|
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
|
||||||
|
return impl && impl->NotifyThreadStopped(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Debugger::IsStepping() const {
|
||||||
|
return impl && impl->IsStepping();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
46
src/core/debugger/debugger.h
Normal file
46
src/core/debugger/debugger.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
class KThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
|
||||||
|
class DebuggerImpl;
|
||||||
|
|
||||||
|
class Debugger {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Blocks and waits for a connection on localhost, port `server_port`.
|
||||||
|
* Does not create the debugger if the port is already in use.
|
||||||
|
*/
|
||||||
|
explicit Debugger(Core::System& system, u16 server_port);
|
||||||
|
~Debugger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the debugger that the given thread is stopped
|
||||||
|
* (due to a breakpoint, or due to stopping after a successful step).
|
||||||
|
*
|
||||||
|
* The debugger will asynchronously halt emulation after the notification has
|
||||||
|
* occurred. If another thread attempts to notify before emulation has stopped,
|
||||||
|
* it is ignored and this method will return false. Otherwise it will return true.
|
||||||
|
*/
|
||||||
|
bool NotifyThreadStopped(Kernel::KThread* thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a step is in progress.
|
||||||
|
*/
|
||||||
|
bool IsStepping() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<DebuggerImpl> impl;
|
||||||
|
};
|
||||||
|
} // namespace Core
|
74
src/core/debugger/debugger_interface.h
Normal file
74
src/core/debugger/debugger_interface.h
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
class KThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
enum class DebuggerAction {
|
||||||
|
Interrupt, // Stop emulation as soon as possible.
|
||||||
|
Continue, // Resume emulation.
|
||||||
|
StepThread, // Step the currently-active thread.
|
||||||
|
ShutdownEmulation, // Shut down the emulator.
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebuggerBackend {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Can be invoked from a callback to synchronously wait for more data.
|
||||||
|
* Will return as soon as least one byte is received. Reads up to 4096 bytes.
|
||||||
|
*/
|
||||||
|
virtual std::span<const u8> ReadFromClient() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be invoked from a callback to write data to the client.
|
||||||
|
* Returns immediately after the data is sent.
|
||||||
|
*/
|
||||||
|
virtual void WriteToClient(std::span<const u8> data) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the currently active thread when the debugger is stopped.
|
||||||
|
*/
|
||||||
|
virtual Kernel::KThread* GetActiveThread() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the currently active thread when the debugger is stopped.
|
||||||
|
*/
|
||||||
|
virtual void SetActiveThread(Kernel::KThread* thread) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebuggerFrontend {
|
||||||
|
public:
|
||||||
|
explicit DebuggerFrontend(DebuggerBackend& backend_) : backend{backend_} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the client has successfully connected to the port.
|
||||||
|
*/
|
||||||
|
virtual void Connected() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when emulation has stopped.
|
||||||
|
*/
|
||||||
|
virtual void Stopped(Kernel::KThread* thread) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when new data is asynchronously received on the client socket.
|
||||||
|
* A list of actions to perform is returned.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::vector<DebuggerAction> ClientData(std::span<const u8> data) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
DebuggerBackend& backend;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
382
src/core/debugger/gdbstub.cpp
Normal file
382
src/core/debugger/gdbstub.cpp
Normal file
|
@ -0,0 +1,382 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/process/async_pipe.hpp>
|
||||||
|
|
||||||
|
#include "common/hex_util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "core/arm/arm_interface.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/debugger/gdbstub.h"
|
||||||
|
#include "core/debugger/gdbstub_arch.h"
|
||||||
|
#include "core/hle/kernel/k_page_table.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
constexpr char GDB_STUB_START = '$';
|
||||||
|
constexpr char GDB_STUB_END = '#';
|
||||||
|
constexpr char GDB_STUB_ACK = '+';
|
||||||
|
constexpr char GDB_STUB_NACK = '-';
|
||||||
|
constexpr char GDB_STUB_INT3 = 0x03;
|
||||||
|
constexpr int GDB_STUB_SIGTRAP = 5;
|
||||||
|
|
||||||
|
constexpr char GDB_STUB_REPLY_ERR[] = "E01";
|
||||||
|
constexpr char GDB_STUB_REPLY_OK[] = "OK";
|
||||||
|
constexpr char GDB_STUB_REPLY_EMPTY[] = "";
|
||||||
|
|
||||||
|
GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
|
||||||
|
: DebuggerFrontend(backend_), system{system_} {
|
||||||
|
if (system.CurrentProcess()->Is64BitProcess()) {
|
||||||
|
arch = std::make_unique<GDBStubA64>();
|
||||||
|
} else {
|
||||||
|
arch = std::make_unique<GDBStubA32>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GDBStub::~GDBStub() = default;
|
||||||
|
|
||||||
|
void GDBStub::Connected() {}
|
||||||
|
|
||||||
|
void GDBStub::Stopped(Kernel::KThread* thread) {
|
||||||
|
SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
|
||||||
|
std::vector<DebuggerAction> actions;
|
||||||
|
current_command.insert(current_command.end(), data.begin(), data.end());
|
||||||
|
|
||||||
|
while (current_command.size() != 0) {
|
||||||
|
ProcessData(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
|
||||||
|
const char c{current_command[0]};
|
||||||
|
|
||||||
|
// Acknowledgement
|
||||||
|
if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
|
||||||
|
current_command.erase(current_command.begin());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt
|
||||||
|
if (c == GDB_STUB_INT3) {
|
||||||
|
LOG_INFO(Debug_GDBStub, "Received interrupt");
|
||||||
|
current_command.erase(current_command.begin());
|
||||||
|
actions.push_back(DebuggerAction::Interrupt);
|
||||||
|
SendStatus(GDB_STUB_ACK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, require the data to be the start of a command
|
||||||
|
if (c != GDB_STUB_START) {
|
||||||
|
LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
|
||||||
|
current_command.clear();
|
||||||
|
SendStatus(GDB_STUB_NACK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue reading until command is complete
|
||||||
|
while (CommandEnd() == current_command.end()) {
|
||||||
|
const auto new_data{backend.ReadFromClient()};
|
||||||
|
current_command.insert(current_command.end(), new_data.begin(), new_data.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute and respond to GDB
|
||||||
|
const auto command{DetachCommand()};
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
SendStatus(GDB_STUB_ACK);
|
||||||
|
ExecuteCommand(*command, actions);
|
||||||
|
} else {
|
||||||
|
SendStatus(GDB_STUB_NACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
|
||||||
|
LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
|
||||||
|
|
||||||
|
if (packet.length() == 0) {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view command{packet.substr(1, packet.size())};
|
||||||
|
|
||||||
|
switch (packet[0]) {
|
||||||
|
case 'H': {
|
||||||
|
Kernel::KThread* thread{nullptr};
|
||||||
|
s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
|
||||||
|
if (thread_id >= 1) {
|
||||||
|
thread = GetThreadByID(thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread) {
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
backend.SetActiveThread(thread);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'T': {
|
||||||
|
s64 thread_id{strtoll(command.data(), nullptr, 16)};
|
||||||
|
if (GetThreadByID(thread_id)) {
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'q':
|
||||||
|
HandleQuery(command);
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
|
||||||
|
break;
|
||||||
|
case 'k':
|
||||||
|
LOG_INFO(Debug_GDBStub, "Shutting down emulation");
|
||||||
|
actions.push_back(DebuggerAction::ShutdownEmulation);
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
SendReply(arch->ReadRegisters(backend.GetActiveThread()));
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
arch->WriteRegisters(backend.GetActiveThread(), command);
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
break;
|
||||||
|
case 'p': {
|
||||||
|
const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
|
||||||
|
SendReply(arch->RegRead(backend.GetActiveThread(), reg));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'P': {
|
||||||
|
const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
|
||||||
|
const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
|
||||||
|
arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'm': {
|
||||||
|
const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
||||||
|
const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
|
||||||
|
const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
|
||||||
|
|
||||||
|
if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
|
||||||
|
std::vector<u8> mem(size);
|
||||||
|
system.Memory().ReadBlock(addr, mem.data(), size);
|
||||||
|
|
||||||
|
SendReply(Common::HexToString(mem));
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'M': {
|
||||||
|
const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
||||||
|
const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
|
||||||
|
|
||||||
|
const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
|
||||||
|
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
|
||||||
|
|
||||||
|
const auto mem_substr{std::string_view(command).substr(mem_sep)};
|
||||||
|
const auto mem{Common::HexStringToVector(mem_substr, false)};
|
||||||
|
|
||||||
|
if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
|
||||||
|
system.Memory().WriteBlock(addr, mem.data(), size);
|
||||||
|
system.InvalidateCpuInstructionCacheRange(addr, size);
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
actions.push_back(DebuggerAction::StepThread);
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
case 'c':
|
||||||
|
actions.push_back(DebuggerAction::Continue);
|
||||||
|
break;
|
||||||
|
case 'Z': {
|
||||||
|
const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
||||||
|
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
|
||||||
|
|
||||||
|
if (system.Memory().IsValidVirtualAddress(addr)) {
|
||||||
|
replaced_instructions[addr] = system.Memory().Read32(addr);
|
||||||
|
system.Memory().Write32(addr, arch->BreakpointInstruction());
|
||||||
|
system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
|
||||||
|
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'z': {
|
||||||
|
const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
|
||||||
|
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
|
||||||
|
|
||||||
|
const auto orig_insn{replaced_instructions.find(addr)};
|
||||||
|
if (system.Memory().IsValidVirtualAddress(addr) &&
|
||||||
|
orig_insn != replaced_instructions.end()) {
|
||||||
|
system.Memory().Write32(addr, orig_insn->second);
|
||||||
|
system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
|
||||||
|
replaced_instructions.erase(addr);
|
||||||
|
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStub::HandleQuery(std::string_view command) {
|
||||||
|
if (command.starts_with("TStatus")) {
|
||||||
|
// no tracepoint support
|
||||||
|
SendReply("T0");
|
||||||
|
} else if (command.starts_with("Supported")) {
|
||||||
|
SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+");
|
||||||
|
} else if (command.starts_with("Xfer:features:read:target.xml:")) {
|
||||||
|
const auto offset{command.substr(30)};
|
||||||
|
const auto amount{command.substr(command.find(',') + 1)};
|
||||||
|
|
||||||
|
const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
|
||||||
|
const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
|
||||||
|
const auto target_xml{arch->GetTargetXML()};
|
||||||
|
|
||||||
|
if (offset_val + amount_val > target_xml.size()) {
|
||||||
|
SendReply("l" + target_xml.substr(offset_val));
|
||||||
|
} else {
|
||||||
|
SendReply("m" + target_xml.substr(offset_val, amount_val));
|
||||||
|
}
|
||||||
|
} else if (command.starts_with("Offsets")) {
|
||||||
|
Loader::AppLoader::Modules modules;
|
||||||
|
system.GetAppLoader().ReadNSOModules(modules);
|
||||||
|
|
||||||
|
const auto main = std::find_if(modules.begin(), modules.end(),
|
||||||
|
[](const auto& key) { return key.second == "main"; });
|
||||||
|
if (main != modules.end()) {
|
||||||
|
SendReply(fmt::format("TextSeg={:x}", main->first));
|
||||||
|
} else {
|
||||||
|
SendReply(fmt::format("TextSeg={:x}",
|
||||||
|
system.CurrentProcess()->PageTable().GetCodeRegionStart()));
|
||||||
|
}
|
||||||
|
} else if (command.starts_with("fThreadInfo")) {
|
||||||
|
// beginning of list
|
||||||
|
const auto& threads = system.GlobalSchedulerContext().GetThreadList();
|
||||||
|
std::vector<std::string> thread_ids;
|
||||||
|
for (const auto& thread : threads) {
|
||||||
|
thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
|
||||||
|
}
|
||||||
|
SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
|
||||||
|
} else if (command.starts_with("sThreadInfo")) {
|
||||||
|
// end of list
|
||||||
|
SendReply("l");
|
||||||
|
} else if (command.starts_with("Xfer:threads:read")) {
|
||||||
|
std::string buffer;
|
||||||
|
buffer += R"(l<?xml version="1.0"?>)";
|
||||||
|
buffer += "<threads>";
|
||||||
|
|
||||||
|
const auto& threads = system.GlobalSchedulerContext().GetThreadList();
|
||||||
|
for (const auto& thread : threads) {
|
||||||
|
buffer +=
|
||||||
|
fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)",
|
||||||
|
thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID());
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer += "</threads>";
|
||||||
|
SendReply(buffer);
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
|
||||||
|
const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
|
||||||
|
for (auto* thread : threads) {
|
||||||
|
if (thread->GetThreadID() == thread_id) {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char>::const_iterator GDBStub::CommandEnd() const {
|
||||||
|
// Find the end marker
|
||||||
|
const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
|
||||||
|
|
||||||
|
// Require the checksum to be present
|
||||||
|
return std::min(end + 2, current_command.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> GDBStub::DetachCommand() {
|
||||||
|
// Slice the string part from the beginning to the end marker
|
||||||
|
const auto end{CommandEnd()};
|
||||||
|
|
||||||
|
// Extract possible command data
|
||||||
|
std::string data(current_command.data(), end - current_command.begin() + 1);
|
||||||
|
|
||||||
|
// Shift over the remaining contents
|
||||||
|
current_command.erase(current_command.begin(), end + 1);
|
||||||
|
|
||||||
|
// Validate received command
|
||||||
|
if (data[0] != GDB_STUB_START) {
|
||||||
|
LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
|
||||||
|
u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
|
||||||
|
|
||||||
|
// Verify checksum
|
||||||
|
if (calculated != received) {
|
||||||
|
LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
|
||||||
|
calculated, received);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.substr(1, data.size() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 GDBStub::CalculateChecksum(std::string_view data) {
|
||||||
|
return static_cast<u8>(
|
||||||
|
std::accumulate(data.begin(), data.end(), u8{0}, [](u8 lhs, u8 rhs) { return lhs + rhs; }));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStub::SendReply(std::string_view data) {
|
||||||
|
const auto output{
|
||||||
|
fmt::format("{}{}{}{:02x}", GDB_STUB_START, data, GDB_STUB_END, CalculateChecksum(data))};
|
||||||
|
LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
|
||||||
|
|
||||||
|
// C++ string support is complete rubbish
|
||||||
|
const u8* output_begin = reinterpret_cast<const u8*>(output.data());
|
||||||
|
const u8* output_end = output_begin + output.size();
|
||||||
|
backend.WriteToClient(std::span<const u8>(output_begin, output_end));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStub::SendStatus(char status) {
|
||||||
|
std::array<u8, 1> buf = {static_cast<u8>(status)};
|
||||||
|
LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
|
||||||
|
backend.WriteToClient(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
47
src/core/debugger/gdbstub.h
Normal file
47
src/core/debugger/gdbstub.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/debugger/debugger_interface.h"
|
||||||
|
#include "core/debugger/gdbstub_arch.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
class System;
|
||||||
|
|
||||||
|
class GDBStub : public DebuggerFrontend {
|
||||||
|
public:
|
||||||
|
explicit GDBStub(DebuggerBackend& backend, Core::System& system);
|
||||||
|
~GDBStub();
|
||||||
|
|
||||||
|
void Connected() override;
|
||||||
|
void Stopped(Kernel::KThread* thread) override;
|
||||||
|
std::vector<DebuggerAction> ClientData(std::span<const u8> data) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ProcessData(std::vector<DebuggerAction>& actions);
|
||||||
|
void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
|
||||||
|
void HandleQuery(std::string_view command);
|
||||||
|
std::vector<char>::const_iterator CommandEnd() const;
|
||||||
|
std::optional<std::string> DetachCommand();
|
||||||
|
Kernel::KThread* GetThreadByID(u64 thread_id);
|
||||||
|
|
||||||
|
static u8 CalculateChecksum(std::string_view data);
|
||||||
|
void SendReply(std::string_view data);
|
||||||
|
void SendStatus(char status);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core::System& system;
|
||||||
|
std::unique_ptr<GDBStubArch> arch;
|
||||||
|
std::vector<char> current_command;
|
||||||
|
std::map<VAddr, u32> replaced_instructions;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
406
src/core/debugger/gdbstub_arch.cpp
Normal file
406
src/core/debugger/gdbstub_arch.cpp
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/hex_util.h"
|
||||||
|
#include "core/debugger/gdbstub_arch.h"
|
||||||
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T HexToValue(std::string_view hex) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
T value{};
|
||||||
|
const auto mem{Common::HexStringToVector(hex, false)};
|
||||||
|
std::memcpy(&value, mem.data(), std::min(mem.size(), sizeof(T)));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static std::string ValueToHex(const T value) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
std::array<u8, sizeof(T)> mem{};
|
||||||
|
std::memcpy(mem.data(), &value, sizeof(T));
|
||||||
|
return Common::HexToString(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T GetSIMDRegister(const std::array<u32, 64>& simd_regs, size_t offset) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
T value{};
|
||||||
|
std::memcpy(&value, reinterpret_cast<const u8*>(simd_regs.data()) + sizeof(T) * offset,
|
||||||
|
sizeof(T));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void PutSIMDRegister(std::array<u32, 64>& simd_regs, size_t offset, const T value) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
std::memcpy(reinterpret_cast<u8*>(simd_regs.data()) + sizeof(T) * offset, &value, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
// For sample XML files see the GDB source /gdb/features
|
||||||
|
// This XML defines what the registers are for this specific ARM device
|
||||||
|
std::string GDBStubA64::GetTargetXML() const {
|
||||||
|
constexpr const char* target_xml =
|
||||||
|
R"(<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||||
|
<target version="1.0">
|
||||||
|
<feature name="org.gnu.gdb.aarch64.core">
|
||||||
|
<reg name="x0" bitsize="64"/>
|
||||||
|
<reg name="x1" bitsize="64"/>
|
||||||
|
<reg name="x2" bitsize="64"/>
|
||||||
|
<reg name="x3" bitsize="64"/>
|
||||||
|
<reg name="x4" bitsize="64"/>
|
||||||
|
<reg name="x5" bitsize="64"/>
|
||||||
|
<reg name="x6" bitsize="64"/>
|
||||||
|
<reg name="x7" bitsize="64"/>
|
||||||
|
<reg name="x8" bitsize="64"/>
|
||||||
|
<reg name="x9" bitsize="64"/>
|
||||||
|
<reg name="x10" bitsize="64"/>
|
||||||
|
<reg name="x11" bitsize="64"/>
|
||||||
|
<reg name="x12" bitsize="64"/>
|
||||||
|
<reg name="x13" bitsize="64"/>
|
||||||
|
<reg name="x14" bitsize="64"/>
|
||||||
|
<reg name="x15" bitsize="64"/>
|
||||||
|
<reg name="x16" bitsize="64"/>
|
||||||
|
<reg name="x17" bitsize="64"/>
|
||||||
|
<reg name="x18" bitsize="64"/>
|
||||||
|
<reg name="x19" bitsize="64"/>
|
||||||
|
<reg name="x20" bitsize="64"/>
|
||||||
|
<reg name="x21" bitsize="64"/>
|
||||||
|
<reg name="x22" bitsize="64"/>
|
||||||
|
<reg name="x23" bitsize="64"/>
|
||||||
|
<reg name="x24" bitsize="64"/>
|
||||||
|
<reg name="x25" bitsize="64"/>
|
||||||
|
<reg name="x26" bitsize="64"/>
|
||||||
|
<reg name="x27" bitsize="64"/>
|
||||||
|
<reg name="x28" bitsize="64"/>
|
||||||
|
<reg name="x29" bitsize="64"/>
|
||||||
|
<reg name="x30" bitsize="64"/>
|
||||||
|
<reg name="sp" bitsize="64" type="data_ptr"/>
|
||||||
|
<reg name="pc" bitsize="64" type="code_ptr"/>
|
||||||
|
<flags id="pstate_flags" size="4">
|
||||||
|
<field name="SP" start="0" end="0"/>
|
||||||
|
<field name="" start="1" end="1"/>
|
||||||
|
<field name="EL" start="2" end="3"/>
|
||||||
|
<field name="nRW" start="4" end="4"/>
|
||||||
|
<field name="" start="5" end="5"/>
|
||||||
|
<field name="F" start="6" end="6"/>
|
||||||
|
<field name="I" start="7" end="7"/>
|
||||||
|
<field name="A" start="8" end="8"/>
|
||||||
|
<field name="D" start="9" end="9"/>
|
||||||
|
<field name="IL" start="20" end="20"/>
|
||||||
|
<field name="SS" start="21" end="21"/>
|
||||||
|
<field name="V" start="28" end="28"/>
|
||||||
|
<field name="C" start="29" end="29"/>
|
||||||
|
<field name="Z" start="30" end="30"/>
|
||||||
|
<field name="N" start="31" end="31"/>
|
||||||
|
</flags>
|
||||||
|
<reg name="pstate" bitsize="32" type="pstate_flags"/>
|
||||||
|
</feature>
|
||||||
|
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||||
|
</feature>
|
||||||
|
</target>)";
|
||||||
|
|
||||||
|
return target_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const {
|
||||||
|
if (!thread) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& context{thread->GetContext64()};
|
||||||
|
const auto& gprs{context.cpu_registers};
|
||||||
|
const auto& fprs{context.vector_registers};
|
||||||
|
|
||||||
|
if (id <= SP_REGISTER) {
|
||||||
|
return ValueToHex(gprs[id]);
|
||||||
|
} else if (id == PC_REGISTER) {
|
||||||
|
return ValueToHex(context.pc);
|
||||||
|
} else if (id == PSTATE_REGISTER) {
|
||||||
|
return ValueToHex(context.pstate);
|
||||||
|
} else if (id >= Q0_REGISTER && id < FPCR_REGISTER) {
|
||||||
|
return ValueToHex(fprs[id - Q0_REGISTER]);
|
||||||
|
} else if (id == FPCR_REGISTER) {
|
||||||
|
return ValueToHex(context.fpcr);
|
||||||
|
} else if (id == FPSR_REGISTER) {
|
||||||
|
return ValueToHex(context.fpsr);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
|
||||||
|
if (!thread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& context{thread->GetContext64()};
|
||||||
|
|
||||||
|
if (id <= SP_REGISTER) {
|
||||||
|
context.cpu_registers[id] = HexToValue<u64>(value);
|
||||||
|
} else if (id == PC_REGISTER) {
|
||||||
|
context.pc = HexToValue<u64>(value);
|
||||||
|
} else if (id == PSTATE_REGISTER) {
|
||||||
|
context.pstate = HexToValue<u32>(value);
|
||||||
|
} else if (id >= Q0_REGISTER && id < FPCR_REGISTER) {
|
||||||
|
context.vector_registers[id - Q0_REGISTER] = HexToValue<u128>(value);
|
||||||
|
} else if (id == FPCR_REGISTER) {
|
||||||
|
context.fpcr = HexToValue<u32>(value);
|
||||||
|
} else if (id == FPSR_REGISTER) {
|
||||||
|
context.fpsr = HexToValue<u32>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA64::ReadRegisters(const Kernel::KThread* thread) const {
|
||||||
|
std::string output;
|
||||||
|
|
||||||
|
for (size_t reg = 0; reg <= FPCR_REGISTER; reg++) {
|
||||||
|
output += RegRead(thread, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStubA64::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
|
||||||
|
for (size_t i = 0, reg = 0; reg <= FPCR_REGISTER; reg++) {
|
||||||
|
if (reg <= SP_REGISTER || reg == PC_REGISTER) {
|
||||||
|
RegWrite(thread, reg, register_data.substr(i, 16));
|
||||||
|
i += 16;
|
||||||
|
} else if (reg == PSTATE_REGISTER || reg == FPCR_REGISTER || reg == FPSR_REGISTER) {
|
||||||
|
RegWrite(thread, reg, register_data.substr(i, 8));
|
||||||
|
i += 8;
|
||||||
|
} else if (reg >= Q0_REGISTER && reg < FPCR_REGISTER) {
|
||||||
|
RegWrite(thread, reg, register_data.substr(i, 32));
|
||||||
|
i += 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA64::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
|
||||||
|
return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
|
||||||
|
RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
|
||||||
|
LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GDBStubA64::BreakpointInstruction() const {
|
||||||
|
// A64: brk #0
|
||||||
|
return 0xd4200000;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA32::GetTargetXML() const {
|
||||||
|
constexpr const char* target_xml =
|
||||||
|
R"(<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||||
|
<target version="1.0">
|
||||||
|
<feature name="org.gnu.gdb.arm.core">
|
||||||
|
<reg name="r0" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r1" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r2" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r3" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r4" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r5" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r6" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r7" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r8" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r9" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r10" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r11" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="r12" bitsize="32" type="uint32"/>
|
||||||
|
<reg name="sp" bitsize="32" type="data_ptr"/>
|
||||||
|
<reg name="lr" bitsize="32" type="code_ptr"/>
|
||||||
|
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||||
|
<!-- The CPSR is register 25, rather than register 16, because
|
||||||
|
the FPA registers historically were placed between the PC
|
||||||
|
and the CPSR in the "g" packet. -->
|
||||||
|
<reg name="cpsr" bitsize="32" regnum="25"/>
|
||||||
|
</feature>
|
||||||
|
<feature name="org.gnu.gdb.arm.vfp">
|
||||||
|
<vector id="neon_uint8x8" type="uint8" count="8"/>
|
||||||
|
<vector id="neon_uint16x4" type="uint16" count="4"/>
|
||||||
|
<vector id="neon_uint32x2" type="uint32" count="2"/>
|
||||||
|
<vector id="neon_float32x2" type="ieee_single" count="2"/>
|
||||||
|
<union id="neon_d">
|
||||||
|
<field name="u8" type="neon_uint8x8"/>
|
||||||
|
<field name="u16" type="neon_uint16x4"/>
|
||||||
|
<field name="u32" type="neon_uint32x2"/>
|
||||||
|
<field name="u64" type="uint64"/>
|
||||||
|
<field name="f32" type="neon_float32x2"/>
|
||||||
|
<field name="f64" type="ieee_double"/>
|
||||||
|
</union>
|
||||||
|
<vector id="neon_uint8x16" type="uint8" count="16"/>
|
||||||
|
<vector id="neon_uint16x8" type="uint16" count="8"/>
|
||||||
|
<vector id="neon_uint32x4" type="uint32" count="4"/>
|
||||||
|
<vector id="neon_uint64x2" type="uint64" count="2"/>
|
||||||
|
<vector id="neon_float32x4" type="ieee_single" count="4"/>
|
||||||
|
<vector id="neon_float64x2" type="ieee_double" count="2"/>
|
||||||
|
<union id="neon_q">
|
||||||
|
<field name="u8" type="neon_uint8x16"/>
|
||||||
|
<field name="u16" type="neon_uint16x8"/>
|
||||||
|
<field name="u32" type="neon_uint32x4"/>
|
||||||
|
<field name="u64" type="neon_uint64x2"/>
|
||||||
|
<field name="f32" type="neon_float32x4"/>
|
||||||
|
<field name="f64" type="neon_float64x2"/>
|
||||||
|
</union>
|
||||||
|
<reg name="d0" bitsize="64" type="neon_d" regnum="32"/>
|
||||||
|
<reg name="d1" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d2" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d3" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d4" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d5" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d6" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d7" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d8" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d9" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d10" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d11" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d12" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d13" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d14" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d15" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d16" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d17" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d18" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d19" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d20" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d21" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d22" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d23" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d24" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d25" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d26" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d27" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d28" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d29" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d30" bitsize="64" type="neon_d"/>
|
||||||
|
<reg name="d31" bitsize="64" type="neon_d"/>
|
||||||
|
|
||||||
|
<reg name="q0" bitsize="128" type="neon_q" regnum="64"/>
|
||||||
|
<reg name="q1" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q2" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q3" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q4" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q5" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q6" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q7" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q8" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q9" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q10" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q10" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q12" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q13" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q14" bitsize="128" type="neon_q"/>
|
||||||
|
<reg name="q15" bitsize="128" type="neon_q"/>
|
||||||
|
|
||||||
|
<reg name="fpscr" bitsize="32" type="int" group="float" regnum="80"/>
|
||||||
|
</feature>
|
||||||
|
</target>)";
|
||||||
|
|
||||||
|
return target_xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const {
|
||||||
|
if (!thread) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& context{thread->GetContext32()};
|
||||||
|
const auto& gprs{context.cpu_registers};
|
||||||
|
const auto& fprs{context.extension_registers};
|
||||||
|
|
||||||
|
if (id <= PC_REGISTER) {
|
||||||
|
return ValueToHex(gprs[id]);
|
||||||
|
} else if (id == CPSR_REGISTER) {
|
||||||
|
return ValueToHex(context.cpsr);
|
||||||
|
} else if (id >= D0_REGISTER && id < Q0_REGISTER) {
|
||||||
|
const u64 dN{GetSIMDRegister<u64>(fprs, id - D0_REGISTER)};
|
||||||
|
return ValueToHex(dN);
|
||||||
|
} else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
|
||||||
|
const u128 qN{GetSIMDRegister<u128>(fprs, id - Q0_REGISTER)};
|
||||||
|
return ValueToHex(qN);
|
||||||
|
} else if (id == FPSCR_REGISTER) {
|
||||||
|
return ValueToHex(context.fpscr);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
|
||||||
|
if (!thread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& context{thread->GetContext32()};
|
||||||
|
auto& fprs{context.extension_registers};
|
||||||
|
|
||||||
|
if (id <= PC_REGISTER) {
|
||||||
|
context.cpu_registers[id] = HexToValue<u32>(value);
|
||||||
|
} else if (id == CPSR_REGISTER) {
|
||||||
|
context.cpsr = HexToValue<u32>(value);
|
||||||
|
} else if (id >= D0_REGISTER && id < Q0_REGISTER) {
|
||||||
|
PutSIMDRegister(fprs, id - D0_REGISTER, HexToValue<u64>(value));
|
||||||
|
} else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
|
||||||
|
PutSIMDRegister(fprs, id - Q0_REGISTER, HexToValue<u128>(value));
|
||||||
|
} else if (id == FPSCR_REGISTER) {
|
||||||
|
context.fpscr = HexToValue<u32>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA32::ReadRegisters(const Kernel::KThread* thread) const {
|
||||||
|
std::string output;
|
||||||
|
|
||||||
|
for (size_t reg = 0; reg <= FPSCR_REGISTER; reg++) {
|
||||||
|
const bool gpr{reg <= PC_REGISTER};
|
||||||
|
const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
|
||||||
|
const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
|
||||||
|
|
||||||
|
if (!(gpr || dfpr || qfpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
output += RegRead(thread, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GDBStubA32::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
|
||||||
|
for (size_t i = 0, reg = 0; reg <= FPSCR_REGISTER; reg++) {
|
||||||
|
const bool gpr{reg <= PC_REGISTER};
|
||||||
|
const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
|
||||||
|
const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
|
||||||
|
|
||||||
|
if (gpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER) {
|
||||||
|
RegWrite(thread, reg, register_data.substr(i, 8));
|
||||||
|
i += 8;
|
||||||
|
} else if (dfpr) {
|
||||||
|
RegWrite(thread, reg, register_data.substr(i, 16));
|
||||||
|
i += 16;
|
||||||
|
} else if (qfpr) {
|
||||||
|
RegWrite(thread, reg, register_data.substr(i, 32));
|
||||||
|
i += 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reg == PC_REGISTER) {
|
||||||
|
reg = CPSR_REGISTER - 1;
|
||||||
|
} else if (reg == CPSR_REGISTER) {
|
||||||
|
reg = D0_REGISTER - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GDBStubA32::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
|
||||||
|
return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
|
||||||
|
RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
|
||||||
|
LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GDBStubA32::BreakpointInstruction() const {
|
||||||
|
// A32: trap
|
||||||
|
// T32: trap + b #4
|
||||||
|
return 0xe7ffdefe;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
67
src/core/debugger/gdbstub_arch.h
Normal file
67
src/core/debugger/gdbstub_arch.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
class KThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
class GDBStubArch {
|
||||||
|
public:
|
||||||
|
virtual std::string GetTargetXML() const = 0;
|
||||||
|
virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0;
|
||||||
|
virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0;
|
||||||
|
virtual std::string ReadRegisters(const Kernel::KThread* thread) const = 0;
|
||||||
|
virtual void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const = 0;
|
||||||
|
virtual std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const = 0;
|
||||||
|
virtual u32 BreakpointInstruction() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GDBStubA64 final : public GDBStubArch {
|
||||||
|
public:
|
||||||
|
std::string GetTargetXML() const override;
|
||||||
|
std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
|
||||||
|
void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
|
||||||
|
std::string ReadRegisters(const Kernel::KThread* thread) const override;
|
||||||
|
void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
|
||||||
|
std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
|
||||||
|
u32 BreakpointInstruction() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u32 LR_REGISTER = 30;
|
||||||
|
static constexpr u32 SP_REGISTER = 31;
|
||||||
|
static constexpr u32 PC_REGISTER = 32;
|
||||||
|
static constexpr u32 PSTATE_REGISTER = 33;
|
||||||
|
static constexpr u32 Q0_REGISTER = 34;
|
||||||
|
static constexpr u32 FPCR_REGISTER = 66;
|
||||||
|
static constexpr u32 FPSR_REGISTER = 67;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GDBStubA32 final : public GDBStubArch {
|
||||||
|
public:
|
||||||
|
std::string GetTargetXML() const override;
|
||||||
|
std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
|
||||||
|
void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
|
||||||
|
std::string ReadRegisters(const Kernel::KThread* thread) const override;
|
||||||
|
void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
|
||||||
|
std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
|
||||||
|
u32 BreakpointInstruction() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u32 SP_REGISTER = 13;
|
||||||
|
static constexpr u32 LR_REGISTER = 14;
|
||||||
|
static constexpr u32 PC_REGISTER = 15;
|
||||||
|
static constexpr u32 CPSR_REGISTER = 25;
|
||||||
|
static constexpr u32 D0_REGISTER = 32;
|
||||||
|
static constexpr u32 Q0_REGISTER = 64;
|
||||||
|
static constexpr u32 FPSCR_REGISTER = 80;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
|
@ -64,6 +64,10 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority
|
||||||
{
|
{
|
||||||
KScopedSchedulerLock lock{kernel};
|
KScopedSchedulerLock lock{kernel};
|
||||||
thread->SetState(ThreadState::Runnable);
|
thread->SetState(ThreadState::Runnable);
|
||||||
|
|
||||||
|
if (system.DebuggerEnabled()) {
|
||||||
|
thread->RequestSuspend(SuspendType::Debug);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
|
@ -594,6 +594,19 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
|
||||||
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
|
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
|
||||||
|
VAddr end = base + size;
|
||||||
|
VAddr page = Common::AlignDown(base, PAGE_SIZE);
|
||||||
|
|
||||||
|
for (; page < end; page += PAGE_SIZE) {
|
||||||
|
if (!IsValidVirtualAddress(page)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
u8* Memory::GetPointer(VAddr vaddr) {
|
u8* Memory::GetPointer(VAddr vaddr) {
|
||||||
return impl->GetPointer(vaddr);
|
return impl->GetPointer(vaddr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,17 @@ public:
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const;
|
[[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether or not the supplied range of addresses are all valid
|
||||||
|
* virtual addresses for the current process.
|
||||||
|
*
|
||||||
|
* @param base The address to begin checking.
|
||||||
|
* @param size The amount of bytes to check.
|
||||||
|
*
|
||||||
|
* @returns True if all bytes in the given range are valid, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool IsValidVirtualAddressRange(VAddr base, u64 size) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a pointer to the given address.
|
* Gets a pointer to the given address.
|
||||||
*
|
*
|
||||||
|
|
|
@ -50,6 +50,7 @@ void EmuThread::run() {
|
||||||
|
|
||||||
auto& gpu = system.GPU();
|
auto& gpu = system.GPU();
|
||||||
auto stop_token = stop_source.get_token();
|
auto stop_token = stop_source.get_token();
|
||||||
|
bool debugger_should_start = system.DebuggerEnabled();
|
||||||
|
|
||||||
system.RegisterHostThread();
|
system.RegisterHostThread();
|
||||||
|
|
||||||
|
@ -89,6 +90,12 @@ void EmuThread::run() {
|
||||||
this->SetRunning(false);
|
this->SetRunning(false);
|
||||||
emit ErrorThrown(result, system.GetStatusDetails());
|
emit ErrorThrown(result, system.GetStatusDetails());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (debugger_should_start) {
|
||||||
|
system.InitializeDebugger();
|
||||||
|
debugger_should_start = false;
|
||||||
|
}
|
||||||
|
|
||||||
running_wait.Wait();
|
running_wait.Wait();
|
||||||
result = system.Pause();
|
result = system.Pause();
|
||||||
if (result != Core::SystemResultStatus::Success) {
|
if (result != Core::SystemResultStatus::Success) {
|
||||||
|
@ -102,11 +109,9 @@ void EmuThread::run() {
|
||||||
was_active = true;
|
was_active = true;
|
||||||
emit DebugModeEntered();
|
emit DebugModeEntered();
|
||||||
}
|
}
|
||||||
} else if (exec_step) {
|
|
||||||
UNIMPLEMENTED();
|
|
||||||
} else {
|
} else {
|
||||||
std::unique_lock lock{running_mutex};
|
std::unique_lock lock{running_mutex};
|
||||||
running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; });
|
running_cv.wait(lock, stop_token, [this] { return IsRunning(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,15 +54,6 @@ public:
|
||||||
*/
|
*/
|
||||||
void run() override;
|
void run() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* Steps the emulation thread by a single CPU instruction (if the CPU is not already running)
|
|
||||||
* @note This function is thread-safe
|
|
||||||
*/
|
|
||||||
void ExecStep() {
|
|
||||||
exec_step = true;
|
|
||||||
running_cv.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether the emulation thread is running or not
|
* Sets whether the emulation thread is running or not
|
||||||
* @param running Boolean value, set the emulation thread to running if true
|
* @param running Boolean value, set the emulation thread to running if true
|
||||||
|
@ -99,7 +90,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool exec_step = false;
|
|
||||||
bool running = false;
|
bool running = false;
|
||||||
std::stop_source stop_source;
|
std::stop_source stop_source;
|
||||||
std::mutex running_mutex;
|
std::mutex running_mutex;
|
||||||
|
|
|
@ -525,6 +525,9 @@ void Config::ReadDebuggingValues() {
|
||||||
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
||||||
Settings::values.record_frame_times =
|
Settings::values.record_frame_times =
|
||||||
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
||||||
|
|
||||||
|
ReadBasicSetting(Settings::values.use_gdbstub);
|
||||||
|
ReadBasicSetting(Settings::values.gdbstub_port);
|
||||||
ReadBasicSetting(Settings::values.program_args);
|
ReadBasicSetting(Settings::values.program_args);
|
||||||
ReadBasicSetting(Settings::values.dump_exefs);
|
ReadBasicSetting(Settings::values.dump_exefs);
|
||||||
ReadBasicSetting(Settings::values.dump_nso);
|
ReadBasicSetting(Settings::values.dump_nso);
|
||||||
|
@ -1095,6 +1098,8 @@ void Config::SaveDebuggingValues() {
|
||||||
|
|
||||||
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
// Intentionally not using the QT default setting as this is intended to be changed in the ini
|
||||||
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
||||||
|
WriteBasicSetting(Settings::values.use_gdbstub);
|
||||||
|
WriteBasicSetting(Settings::values.gdbstub_port);
|
||||||
WriteBasicSetting(Settings::values.program_args);
|
WriteBasicSetting(Settings::values.program_args);
|
||||||
WriteBasicSetting(Settings::values.dump_exefs);
|
WriteBasicSetting(Settings::values.dump_exefs);
|
||||||
WriteBasicSetting(Settings::values.dump_nso);
|
WriteBasicSetting(Settings::values.dump_nso);
|
||||||
|
|
|
@ -24,13 +24,18 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
|
||||||
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir));
|
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir));
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
|
||||||
|
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureDebug::~ConfigureDebug() = default;
|
ConfigureDebug::~ConfigureDebug() = default;
|
||||||
|
|
||||||
void ConfigureDebug::SetConfiguration() {
|
void ConfigureDebug::SetConfiguration() {
|
||||||
const bool runtime_lock = !system.IsPoweredOn();
|
const bool runtime_lock = !system.IsPoweredOn();
|
||||||
|
ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
|
||||||
|
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
|
||||||
|
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
|
||||||
ui->toggle_console->setEnabled(runtime_lock);
|
ui->toggle_console->setEnabled(runtime_lock);
|
||||||
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
|
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
|
||||||
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
|
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
|
||||||
|
@ -71,6 +76,8 @@ void ConfigureDebug::SetConfiguration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureDebug::ApplyConfiguration() {
|
void ConfigureDebug::ApplyConfiguration() {
|
||||||
|
Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
|
||||||
|
Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
|
||||||
UISettings::values.show_console = ui->toggle_console->isChecked();
|
UISettings::values.show_console = ui->toggle_console->isChecked();
|
||||||
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
|
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
|
||||||
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
|
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
|
||||||
|
|
|
@ -3,6 +3,60 @@
|
||||||
<class>ConfigureDebug</class>
|
<class>ConfigureDebug</class>
|
||||||
<widget class="QWidget" name="ConfigureDebug">
|
<widget class="QWidget" name="ConfigureDebug">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Debugger</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_gdbstub">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable GDB Stub</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="text">
|
||||||
|
<string>Port:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="gdbport_spinbox">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1024</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>65535</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
|
|
@ -3152,7 +3152,7 @@ void GMainWindow::OnTasStateChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::UpdateStatusBar() {
|
void GMainWindow::UpdateStatusBar() {
|
||||||
if (emu_thread == nullptr) {
|
if (emu_thread == nullptr || !system->IsPoweredOn()) {
|
||||||
status_bar_update_timer.stop();
|
status_bar_update_timer.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue