summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Mai M2022-06-01 00:19:49 -0400
committerGravatar GitHub2022-06-01 00:19:49 -0400
commitde2f2e5140eb85311e0fd844c580d6726adf7e03 (patch)
tree10f0e96a0689b2edb823f5833ff388ac9c645229 /src
parentMerge pull request #8401 from zhaobot/tx-update-20220601034505 (diff)
parentcore/debugger: Implement new GDB stub debugger (diff)
downloadyuzu-de2f2e5140eb85311e0fd844c580d6726adf7e03.tar.gz
yuzu-de2f2e5140eb85311e0fd844c580d6726adf7e03.tar.xz
yuzu-de2f2e5140eb85311e0fd844c580d6726adf7e03.zip
Merge pull request #8394 from liamwhite/debugger
core/debugger: Implement new GDB stub debugger
Diffstat (limited to '')
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h2
-rw-r--r--src/core/CMakeLists.txt7
-rw-r--r--src/core/arm/arm_interface.cpp5
-rw-r--r--src/core/arm/arm_interface.h5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp35
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp32
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h4
-rw-r--r--src/core/core.cpp29
-rw-r--r--src/core/core.h18
-rw-r--r--src/core/debugger/debugger.cpp259
-rw-r--r--src/core/debugger/debugger.h46
-rw-r--r--src/core/debugger/debugger_interface.h74
-rw-r--r--src/core/debugger/gdbstub.cpp382
-rw-r--r--src/core/debugger/gdbstub.h47
-rw-r--r--src/core/debugger/gdbstub_arch.cpp406
-rw-r--r--src/core/debugger/gdbstub_arch.h67
-rw-r--r--src/core/hle/kernel/k_process.cpp4
-rw-r--r--src/core/memory.cpp13
-rw-r--r--src/core/memory.h11
-rw-r--r--src/yuzu/bootmanager.cpp11
-rw-r--r--src/yuzu/bootmanager.h10
-rw-r--r--src/yuzu/configuration/config.cpp5
-rw-r--r--src/yuzu/configuration/configure_debug.cpp9
-rw-r--r--src/yuzu/configuration/configure_debug.ui54
-rw-r--r--src/yuzu/main.cpp2
27 files changed, 1500 insertions, 42 deletions
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 9a9c74a70..6ffab63af 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -70,6 +70,7 @@ void LogSettings() {
70 log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir)); 70 log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
71 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir)); 71 log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
72 log_setting("Debugging_ProgramArgs", values.program_args.GetValue()); 72 log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
73 log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue());
73 log_setting("Input_EnableMotion", values.motion_enabled.GetValue()); 74 log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
74 log_setting("Input_EnableVibration", values.vibration_enabled.GetValue()); 75 log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
75 log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue()); 76 log_setting("Input_EnableRawInput", values.enable_raw_input.GetValue());
diff --git a/src/common/settings.h b/src/common/settings.h
index e61d9cd7f..a7bbfb0da 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -601,7 +601,7 @@ struct Values {
601 // Debugging 601 // Debugging
602 bool record_frame_times; 602 bool record_frame_times;
603 BasicSetting<bool> use_gdbstub{false, "use_gdbstub"}; 603 BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
604 BasicSetting<u16> gdbstub_port{0, "gdbstub_port"}; 604 BasicSetting<u16> gdbstub_port{6543, "gdbstub_port"};
605 BasicSetting<std::string> program_args{std::string(), "program_args"}; 605 BasicSetting<std::string> program_args{std::string(), "program_args"};
606 BasicSetting<bool> dump_exefs{false, "dump_exefs"}; 606 BasicSetting<bool> dump_exefs{false, "dump_exefs"};
607 BasicSetting<bool> dump_nso{false, "dump_nso"}; 607 BasicSetting<bool> dump_nso{false, "dump_nso"};
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 62230bae0..948cc318a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -36,6 +36,13 @@ add_library(core STATIC
36 crypto/ctr_encryption_layer.h 36 crypto/ctr_encryption_layer.h
37 crypto/xts_encryption_layer.cpp 37 crypto/xts_encryption_layer.cpp
38 crypto/xts_encryption_layer.h 38 crypto/xts_encryption_layer.h
39 debugger/debugger_interface.h
40 debugger/debugger.cpp
41 debugger/debugger.h
42 debugger/gdbstub_arch.cpp
43 debugger/gdbstub_arch.h
44 debugger/gdbstub.cpp
45 debugger/gdbstub.h
39 device_memory.cpp 46 device_memory.cpp
40 device_memory.h 47 device_memory.h
41 file_sys/bis_factory.cpp 48 file_sys/bis_factory.cpp
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index c347e7ea7..1310f72bf 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -9,6 +9,7 @@
9#include "core/arm/arm_interface.h" 9#include "core/arm/arm_interface.h"
10#include "core/arm/symbols.h" 10#include "core/arm/symbols.h"
11#include "core/core.h" 11#include "core/core.h"
12#include "core/debugger/debugger.h"
12#include "core/hle/kernel/k_process.h" 13#include "core/hle/kernel/k_process.h"
13#include "core/loader/loader.h" 14#include "core/loader/loader.h"
14#include "core/memory.h" 15#include "core/memory.h"
@@ -88,4 +89,8 @@ void ARM_Interface::LogBacktrace() const {
88 } 89 }
89} 90}
90 91
92bool ARM_Interface::ShouldStep() const {
93 return system.DebuggerEnabled() && system.GetDebugger().IsStepping();
94}
95
91} // namespace Core 96} // namespace Core
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 8ce973a77..7842c626b 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -66,9 +66,6 @@ public:
66 /// Runs the CPU until an event happens 66 /// Runs the CPU until an event happens
67 virtual void Run() = 0; 67 virtual void Run() = 0;
68 68
69 /// Step CPU by one instruction
70 virtual void Step() = 0;
71
72 /// Clear all instruction cache 69 /// Clear all instruction cache
73 virtual void ClearInstructionCache() = 0; 70 virtual void ClearInstructionCache() = 0;
74 71
@@ -194,6 +191,8 @@ public:
194 191
195 void LogBacktrace() const; 192 void LogBacktrace() const;
196 193
194 bool ShouldStep() const;
195
197protected: 196protected:
198 /// System context that this ARM interface is running under. 197 /// System context that this ARM interface is running under.
199 System& system; 198 System& system;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index 781a77f6f..894c1c527 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -17,6 +17,8 @@
17#include "core/arm/dynarmic/arm_exclusive_monitor.h" 17#include "core/arm/dynarmic/arm_exclusive_monitor.h"
18#include "core/core.h" 18#include "core/core.h"
19#include "core/core_timing.h" 19#include "core/core_timing.h"
20#include "core/debugger/debugger.h"
21#include "core/hle/kernel/k_process.h"
20#include "core/hle/kernel/svc.h" 22#include "core/hle/kernel/svc.h"
21#include "core/memory.h" 23#include "core/memory.h"
22 24
@@ -26,6 +28,7 @@ using namespace Common::Literals;
26 28
27constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; 29constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
28constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; 30constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
31constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
29 32
30class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { 33class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
31public: 34public:
@@ -78,11 +81,16 @@ public:
78 } 81 }
79 82
80 void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { 83 void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
84 if (parent.system.DebuggerEnabled()) {
85 parent.breakpoint_pc = pc;
86 parent.jit.load()->HaltExecution(breakpoint);
87 return;
88 }
89
81 parent.LogBacktrace(); 90 parent.LogBacktrace();
82 LOG_CRITICAL(Core_ARM, 91 LOG_CRITICAL(Core_ARM,
83 "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", 92 "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
84 exception, pc, MemoryReadCode(pc), parent.IsInThumbMode()); 93 exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
85 UNIMPLEMENTED();
86 } 94 }
87 95
88 void CallSVC(u32 swi) override { 96 void CallSVC(u32 swi) override {
@@ -234,20 +242,35 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
234 242
235void ARM_Dynarmic_32::Run() { 243void ARM_Dynarmic_32::Run() {
236 while (true) { 244 while (true) {
237 const auto hr = jit.load()->Run(); 245 const auto hr = ShouldStep() ? jit.load()->Step() : jit.load()->Run();
238 if (Has(hr, svc_call)) { 246 if (Has(hr, svc_call)) {
239 Kernel::Svc::Call(system, svc_swi); 247 Kernel::Svc::Call(system, svc_swi);
240 } 248 }
249
250 // Check to see if breakpoint is triggered.
251 // Recheck step condition in case stop is no longer desired.
252 Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
253 if (Has(hr, breakpoint)) {
254 jit.load()->Regs()[15] = breakpoint_pc;
255
256 if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
257 current_thread->RequestSuspend(Kernel::SuspendType::Debug);
258 }
259 break;
260 }
261 if (ShouldStep()) {
262 // When stepping, this should be the only thread running.
263 ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
264 current_thread->RequestSuspend(Kernel::SuspendType::Debug);
265 break;
266 }
267
241 if (Has(hr, break_loop) || !uses_wall_clock) { 268 if (Has(hr, break_loop) || !uses_wall_clock) {
242 break; 269 break;
243 } 270 }
244 } 271 }
245} 272}
246 273
247void ARM_Dynarmic_32::Step() {
248 jit.load()->Step();
249}
250
251ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, 274ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
252 bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, 275 bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
253 std::size_t core_index_) 276 std::size_t core_index_)
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index abfe76644..0557d5940 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -42,7 +42,6 @@ public:
42 u32 GetPSTATE() const override; 42 u32 GetPSTATE() const override;
43 void SetPSTATE(u32 pstate) override; 43 void SetPSTATE(u32 pstate) override;
44 void Run() override; 44 void Run() override;
45 void Step() override;
46 VAddr GetTlsAddress() const override; 45 VAddr GetTlsAddress() const override;
47 void SetTlsAddress(VAddr address) override; 46 void SetTlsAddress(VAddr address) override;
48 void SetTPIDR_EL0(u64 value) override; 47 void SetTPIDR_EL0(u64 value) override;
@@ -95,6 +94,9 @@ private:
95 94
96 // SVC callback 95 // SVC callback
97 u32 svc_swi{}; 96 u32 svc_swi{};
97
98 // Debug restart address
99 u32 breakpoint_pc{};
98}; 100};
99 101
100} // namespace Core 102} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 1b1334598..1f596cfef 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -15,6 +15,7 @@
15#include "core/arm/dynarmic/arm_exclusive_monitor.h" 15#include "core/arm/dynarmic/arm_exclusive_monitor.h"
16#include "core/core.h" 16#include "core/core.h"
17#include "core/core_timing.h" 17#include "core/core_timing.h"
18#include "core/debugger/debugger.h"
18#include "core/hardware_properties.h" 19#include "core/hardware_properties.h"
19#include "core/hle/kernel/k_process.h" 20#include "core/hle/kernel/k_process.h"
20#include "core/hle/kernel/svc.h" 21#include "core/hle/kernel/svc.h"
@@ -27,6 +28,7 @@ using namespace Common::Literals;
27 28
28constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2; 29constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
29constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3; 30constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
31constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
30 32
31class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { 33class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
32public: 34public:
@@ -119,8 +121,13 @@ public:
119 case Dynarmic::A64::Exception::SendEventLocal: 121 case Dynarmic::A64::Exception::SendEventLocal:
120 case Dynarmic::A64::Exception::Yield: 122 case Dynarmic::A64::Exception::Yield:
121 return; 123 return;
122 case Dynarmic::A64::Exception::Breakpoint:
123 default: 124 default:
125 if (parent.system.DebuggerEnabled()) {
126 parent.breakpoint_pc = pc;
127 parent.jit.load()->HaltExecution(breakpoint);
128 return;
129 }
130
124 parent.LogBacktrace(); 131 parent.LogBacktrace();
125 ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", 132 ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
126 static_cast<std::size_t>(exception), pc, MemoryReadCode(pc)); 133 static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
@@ -299,16 +306,31 @@ void ARM_Dynarmic_64::Run() {
299 if (Has(hr, svc_call)) { 306 if (Has(hr, svc_call)) {
300 Kernel::Svc::Call(system, svc_swi); 307 Kernel::Svc::Call(system, svc_swi);
301 } 308 }
309
310 // Check to see if breakpoint is triggered.
311 // Recheck step condition in case stop is no longer desired.
312 Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
313 if (Has(hr, breakpoint)) {
314 jit.load()->SetPC(breakpoint_pc);
315
316 if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
317 current_thread->RequestSuspend(Kernel::SuspendType::Debug);
318 }
319 break;
320 }
321 if (ShouldStep()) {
322 // When stepping, this should be the only thread running.
323 ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
324 current_thread->RequestSuspend(Kernel::SuspendType::Debug);
325 break;
326 }
327
302 if (Has(hr, break_loop) || !uses_wall_clock) { 328 if (Has(hr, break_loop) || !uses_wall_clock) {
303 break; 329 break;
304 } 330 }
305 } 331 }
306} 332}
307 333
308void ARM_Dynarmic_64::Step() {
309 jit.load()->Step();
310}
311
312ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, 334ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
313 bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_, 335 bool uses_wall_clock_, ExclusiveMonitor& exclusive_monitor_,
314 std::size_t core_index_) 336 std::size_t core_index_)
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index 01a7e4dad..aa7054e0c 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -40,7 +40,6 @@ public:
40 u32 GetPSTATE() const override; 40 u32 GetPSTATE() const override;
41 void SetPSTATE(u32 pstate) override; 41 void SetPSTATE(u32 pstate) override;
42 void Run() override; 42 void Run() override;
43 void Step() override;
44 VAddr GetTlsAddress() const override; 43 VAddr GetTlsAddress() const override;
45 void SetTlsAddress(VAddr address) override; 44 void SetTlsAddress(VAddr address) override;
46 void SetTPIDR_EL0(u64 value) override; 45 void SetTPIDR_EL0(u64 value) override;
@@ -88,6 +87,9 @@ private:
88 87
89 // SVC callback 88 // SVC callback
90 u32 svc_swi{}; 89 u32 svc_swi{};
90
91 // Debug restart address
92 u64 breakpoint_pc{};
91}; 93};
92 94
93} // namespace Core 95} // namespace Core
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 8a887904d..7d974ba65 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -17,6 +17,7 @@
17#include "core/core.h" 17#include "core/core.h"
18#include "core/core_timing.h" 18#include "core/core_timing.h"
19#include "core/cpu_manager.h" 19#include "core/cpu_manager.h"
20#include "core/debugger/debugger.h"
20#include "core/device_memory.h" 21#include "core/device_memory.h"
21#include "core/file_sys/bis_factory.h" 22#include "core/file_sys/bis_factory.h"
22#include "core/file_sys/mode.h" 23#include "core/file_sys/mode.h"
@@ -171,6 +172,10 @@ struct System::Impl {
171 } 172 }
172 } 173 }
173 174
175 void InitializeDebugger(System& system, u16 port) {
176 debugger = std::make_unique<Debugger>(system, port);
177 }
178
174 SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { 179 SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
175 LOG_DEBUG(Core, "initialized OK"); 180 LOG_DEBUG(Core, "initialized OK");
176 181
@@ -329,6 +334,7 @@ struct System::Impl {
329 gpu_core->NotifyShutdown(); 334 gpu_core->NotifyShutdown();
330 } 335 }
331 336
337 debugger.reset();
332 services.reset(); 338 services.reset();
333 service_manager.reset(); 339 service_manager.reset();
334 cheat_engine.reset(); 340 cheat_engine.reset();
@@ -436,6 +442,9 @@ struct System::Impl {
436 /// Network instance 442 /// Network instance
437 Network::NetworkInstance network_instance; 443 Network::NetworkInstance network_instance;
438 444
445 /// Debugger
446 std::unique_ptr<Core::Debugger> debugger;
447
439 SystemResultStatus status = SystemResultStatus::Success; 448 SystemResultStatus status = SystemResultStatus::Success;
440 std::string status_details = ""; 449 std::string status_details = "";
441 450
@@ -472,10 +481,6 @@ SystemResultStatus System::Pause() {
472 return impl->Pause(); 481 return impl->Pause();
473} 482}
474 483
475SystemResultStatus System::SingleStep() {
476 return SystemResultStatus::Success;
477}
478
479void System::InvalidateCpuInstructionCaches() { 484void System::InvalidateCpuInstructionCaches() {
480 impl->kernel.InvalidateAllInstructionCaches(); 485 impl->kernel.InvalidateAllInstructionCaches();
481} 486}
@@ -496,6 +501,10 @@ void System::UnstallCPU() {
496 impl->UnstallCPU(); 501 impl->UnstallCPU();
497} 502}
498 503
504void System::InitializeDebugger() {
505 impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
506}
507
499SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, 508SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
500 u64 program_id, std::size_t program_index) { 509 u64 program_id, std::size_t program_index) {
501 return impl->Load(*this, emu_window, filepath, program_id, program_index); 510 return impl->Load(*this, emu_window, filepath, program_id, program_index);
@@ -809,6 +818,18 @@ bool System::IsMulticore() const {
809 return impl->is_multicore; 818 return impl->is_multicore;
810} 819}
811 820
821bool System::DebuggerEnabled() const {
822 return Settings::values.use_gdbstub.GetValue();
823}
824
825Core::Debugger& System::GetDebugger() {
826 return *impl->debugger;
827}
828
829const Core::Debugger& System::GetDebugger() const {
830 return *impl->debugger;
831}
832
812void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) { 833void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
813 impl->execute_program_callback = std::move(callback); 834 impl->execute_program_callback = std::move(callback);
814} 835}
diff --git a/src/core/core.h b/src/core/core.h
index 4a0c7dc84..94477206e 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -97,6 +97,7 @@ namespace Core {
97 97
98class ARM_Interface; 98class ARM_Interface;
99class CpuManager; 99class CpuManager;
100class Debugger;
100class DeviceMemory; 101class DeviceMemory;
101class ExclusiveMonitor; 102class ExclusiveMonitor;
102class SpeedLimiter; 103class SpeedLimiter;
@@ -148,12 +149,6 @@ public:
148 [[nodiscard]] SystemResultStatus Pause(); 149 [[nodiscard]] SystemResultStatus Pause();
149 150
150 /** 151 /**
151 * Step the CPU one instruction
152 * @return Result status, indicating whether or not the operation succeeded.
153 */
154 [[nodiscard]] SystemResultStatus SingleStep();
155
156 /**
157 * Invalidate the CPU instruction caches 152 * Invalidate the CPU instruction caches
158 * This function should only be used by GDB Stub to support breakpoints, memory updates and 153 * This function should only be used by GDB Stub to support breakpoints, memory updates and
159 * step/continue commands. 154 * step/continue commands.
@@ -169,6 +164,11 @@ public:
169 void UnstallCPU(); 164 void UnstallCPU();
170 165
171 /** 166 /**
167 * Initialize the debugger.
168 */
169 void InitializeDebugger();
170
171 /**
172 * Load an executable application. 172 * Load an executable application.
173 * @param emu_window Reference to the host-system window used for video output and keyboard 173 * @param emu_window Reference to the host-system window used for video output and keyboard
174 * input. 174 * input.
@@ -354,6 +354,9 @@ public:
354 [[nodiscard]] Service::Time::TimeManager& GetTimeManager(); 354 [[nodiscard]] Service::Time::TimeManager& GetTimeManager();
355 [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const; 355 [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
356 356
357 [[nodiscard]] Core::Debugger& GetDebugger();
358 [[nodiscard]] const Core::Debugger& GetDebugger() const;
359
357 void SetExitLock(bool locked); 360 void SetExitLock(bool locked);
358 [[nodiscard]] bool GetExitLock() const; 361 [[nodiscard]] bool GetExitLock() const;
359 362
@@ -375,6 +378,9 @@ public:
375 /// Tells if system is running on multicore. 378 /// Tells if system is running on multicore.
376 [[nodiscard]] bool IsMulticore() const; 379 [[nodiscard]] bool IsMulticore() const;
377 380
381 /// Tells if the system debugger is enabled.
382 [[nodiscard]] bool DebuggerEnabled() const;
383
378 /// Type used for the frontend to designate a callback for System to re-launch the application 384 /// Type used for the frontend to designate a callback for System to re-launch the application
379 /// using a specified program index. 385 /// using a specified program index.
380 using ExecuteProgramCallback = std::function<void(std::size_t)>; 386 using ExecuteProgramCallback = std::function<void(std::size_t)>;
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
new file mode 100644
index 000000000..7a2012d3c
--- /dev/null
+++ b/src/core/debugger/debugger.cpp
@@ -0,0 +1,259 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <mutex>
5#include <thread>
6
7#include <boost/asio.hpp>
8#include <boost/process/async_pipe.hpp>
9
10#include "common/logging/log.h"
11#include "common/thread.h"
12#include "core/core.h"
13#include "core/debugger/debugger.h"
14#include "core/debugger/debugger_interface.h"
15#include "core/debugger/gdbstub.h"
16#include "core/hle/kernel/global_scheduler_context.h"
17
18template <typename Readable, typename Buffer, typename Callback>
19static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
20 static_assert(std::is_trivial_v<Buffer>);
21 auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
22 r.async_read_some(boost_buffer, [&](const boost::system::error_code& error, size_t bytes_read) {
23 if (!error.failed()) {
24 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
25 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
26 c(received_data);
27 }
28
29 AsyncReceiveInto(r, buffer, c);
30 });
31}
32
33template <typename Readable, typename Buffer>
34static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
35 static_assert(std::is_trivial_v<Buffer>);
36 auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
37 size_t bytes_read = r.read_some(boost_buffer);
38 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
39 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
40 return received_data;
41}
42
43namespace Core {
44
45class DebuggerImpl : public DebuggerBackend {
46public:
47 explicit DebuggerImpl(Core::System& system_, u16 port)
48 : system{system_}, signal_pipe{io_context}, client_socket{io_context} {
49 frontend = std::make_unique<GDBStub>(*this, system);
50 InitializeServer(port);
51 }
52
53 ~DebuggerImpl() {
54 ShutdownServer();
55 }
56
57 bool NotifyThreadStopped(Kernel::KThread* thread) {
58 std::scoped_lock lk{connection_lock};
59
60 if (stopped) {
61 // Do not notify the debugger about another event.
62 // It should be ignored.
63 return false;
64 }
65 stopped = true;
66
67 signal_pipe.write_some(boost::asio::buffer(&thread, sizeof(thread)));
68 return true;
69 }
70
71 std::span<const u8> ReadFromClient() override {
72 return ReceiveInto(client_socket, client_data);
73 }
74
75 void WriteToClient(std::span<const u8> data) override {
76 client_socket.write_some(boost::asio::buffer(data.data(), data.size_bytes()));
77 }
78
79 void SetActiveThread(Kernel::KThread* thread) override {
80 active_thread = thread;
81 }
82
83 Kernel::KThread* GetActiveThread() override {
84 return active_thread;
85 }
86
87 bool IsStepping() const {
88 return stepping;
89 }
90
91private:
92 void InitializeServer(u16 port) {
93 using boost::asio::ip::tcp;
94
95 LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
96
97 // Initialize the listening socket and accept a new client.
98 tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
99 tcp::acceptor acceptor{io_context, endpoint};
100 client_socket = acceptor.accept();
101
102 // Run the connection thread.
103 connection_thread = std::jthread([&](std::stop_token stop_token) {
104 try {
105 ThreadLoop(stop_token);
106 } catch (const std::exception& ex) {
107 LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
108 }
109
110 client_socket.shutdown(client_socket.shutdown_both);
111 client_socket.close();
112 });
113 }
114
115 void ShutdownServer() {
116 connection_thread.request_stop();
117 io_context.stop();
118 connection_thread.join();
119 }
120
121 void ThreadLoop(std::stop_token stop_token) {
122 Common::SetCurrentThreadName("yuzu:Debugger");
123
124 // Set up the client signals for new data.
125 AsyncReceiveInto(signal_pipe, active_thread, [&](auto d) { PipeData(d); });
126 AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
127
128 // Stop the emulated CPU.
129 AllCoreStop();
130
131 // Set the active thread.
132 active_thread = ThreadList()[0];
133 active_thread->Resume(Kernel::SuspendType::Debug);
134
135 // Set up the frontend.
136 frontend->Connected();
137
138 // Main event loop.
139 while (!stop_token.stop_requested() && io_context.run()) {
140 }
141 }
142
143 void PipeData(std::span<const u8> data) {
144 AllCoreStop();
145 active_thread->Resume(Kernel::SuspendType::Debug);
146 frontend->Stopped(active_thread);
147 }
148
149 void ClientData(std::span<const u8> data) {
150 const auto actions{frontend->ClientData(data)};
151 for (const auto action : actions) {
152 switch (action) {
153 case DebuggerAction::Interrupt: {
154 {
155 std::scoped_lock lk{connection_lock};
156 stopped = true;
157 }
158 AllCoreStop();
159 active_thread = ThreadList()[0];
160 active_thread->Resume(Kernel::SuspendType::Debug);
161 frontend->Stopped(active_thread);
162 break;
163 }
164 case DebuggerAction::Continue:
165 stepping = false;
166 ResumeInactiveThreads();
167 AllCoreResume();
168 break;
169 case DebuggerAction::StepThread:
170 stepping = true;
171 SuspendInactiveThreads();
172 AllCoreResume();
173 break;
174 case DebuggerAction::ShutdownEmulation: {
175 // Suspend all threads and release any locks held
176 active_thread->RequestSuspend(Kernel::SuspendType::Debug);
177 SuspendInactiveThreads();
178 AllCoreResume();
179
180 // Spawn another thread that will exit after shutdown,
181 // to avoid a deadlock
182 Core::System* system_ref{&system};
183 std::thread t([system_ref] { system_ref->Exit(); });
184 t.detach();
185 break;
186 }
187 }
188 }
189 }
190
191 void AllCoreStop() {
192 if (!suspend) {
193 suspend = system.StallCPU();
194 }
195 }
196
197 void AllCoreResume() {
198 stopped = false;
199 system.UnstallCPU();
200 suspend.reset();
201 }
202
203 void SuspendInactiveThreads() {
204 for (auto* thread : ThreadList()) {
205 if (thread != active_thread) {
206 thread->RequestSuspend(Kernel::SuspendType::Debug);
207 }
208 }
209 }
210
211 void ResumeInactiveThreads() {
212 for (auto* thread : ThreadList()) {
213 if (thread != active_thread) {
214 thread->Resume(Kernel::SuspendType::Debug);
215 }
216 }
217 }
218
219 const std::vector<Kernel::KThread*>& ThreadList() {
220 return system.GlobalSchedulerContext().GetThreadList();
221 }
222
223private:
224 System& system;
225 std::unique_ptr<DebuggerFrontend> frontend;
226
227 std::jthread connection_thread;
228 std::mutex connection_lock;
229 boost::asio::io_context io_context;
230 boost::process::async_pipe signal_pipe;
231 boost::asio::ip::tcp::socket client_socket;
232 std::optional<std::unique_lock<std::mutex>> suspend;
233
234 Kernel::KThread* active_thread;
235 bool stopped;
236 bool stepping;
237
238 std::array<u8, 4096> client_data;
239};
240
241Debugger::Debugger(Core::System& system, u16 port) {
242 try {
243 impl = std::make_unique<DebuggerImpl>(system, port);
244 } catch (const std::exception& ex) {
245 LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what());
246 }
247}
248
249Debugger::~Debugger() = default;
250
251bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
252 return impl && impl->NotifyThreadStopped(thread);
253}
254
255bool Debugger::IsStepping() const {
256 return impl && impl->IsStepping();
257}
258
259} // namespace Core
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h
new file mode 100644
index 000000000..7acd11815
--- /dev/null
+++ b/src/core/debugger/debugger.h
@@ -0,0 +1,46 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "common/common_types.h"
9
10namespace Kernel {
11class KThread;
12}
13
14namespace Core {
15class System;
16
17class DebuggerImpl;
18
19class Debugger {
20public:
21 /**
22 * Blocks and waits for a connection on localhost, port `server_port`.
23 * Does not create the debugger if the port is already in use.
24 */
25 explicit Debugger(Core::System& system, u16 server_port);
26 ~Debugger();
27
28 /**
29 * Notify the debugger that the given thread is stopped
30 * (due to a breakpoint, or due to stopping after a successful step).
31 *
32 * The debugger will asynchronously halt emulation after the notification has
33 * occurred. If another thread attempts to notify before emulation has stopped,
34 * it is ignored and this method will return false. Otherwise it will return true.
35 */
36 bool NotifyThreadStopped(Kernel::KThread* thread);
37
38 /**
39 * Returns whether a step is in progress.
40 */
41 bool IsStepping() const;
42
43private:
44 std::unique_ptr<DebuggerImpl> impl;
45};
46} // namespace Core
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h
new file mode 100644
index 000000000..0b357fcb5
--- /dev/null
+++ b/src/core/debugger/debugger_interface.h
@@ -0,0 +1,74 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <span>
8#include <vector>
9
10#include "common/common_types.h"
11
12namespace Kernel {
13class KThread;
14}
15
16namespace Core {
17
18enum class DebuggerAction {
19 Interrupt, // Stop emulation as soon as possible.
20 Continue, // Resume emulation.
21 StepThread, // Step the currently-active thread.
22 ShutdownEmulation, // Shut down the emulator.
23};
24
25class DebuggerBackend {
26public:
27 /**
28 * Can be invoked from a callback to synchronously wait for more data.
29 * Will return as soon as least one byte is received. Reads up to 4096 bytes.
30 */
31 virtual std::span<const u8> ReadFromClient() = 0;
32
33 /**
34 * Can be invoked from a callback to write data to the client.
35 * Returns immediately after the data is sent.
36 */
37 virtual void WriteToClient(std::span<const u8> data) = 0;
38
39 /**
40 * Gets the currently active thread when the debugger is stopped.
41 */
42 virtual Kernel::KThread* GetActiveThread() = 0;
43
44 /**
45 * Sets the currently active thread when the debugger is stopped.
46 */
47 virtual void SetActiveThread(Kernel::KThread* thread) = 0;
48};
49
50class DebuggerFrontend {
51public:
52 explicit DebuggerFrontend(DebuggerBackend& backend_) : backend{backend_} {}
53
54 /**
55 * Called after the client has successfully connected to the port.
56 */
57 virtual void Connected() = 0;
58
59 /**
60 * Called when emulation has stopped.
61 */
62 virtual void Stopped(Kernel::KThread* thread) = 0;
63
64 /**
65 * Called when new data is asynchronously received on the client socket.
66 * A list of actions to perform is returned.
67 */
68 [[nodiscard]] virtual std::vector<DebuggerAction> ClientData(std::span<const u8> data) = 0;
69
70protected:
71 DebuggerBackend& backend;
72};
73
74} // namespace Core
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
new file mode 100644
index 000000000..718c45952
--- /dev/null
+++ b/src/core/debugger/gdbstub.cpp
@@ -0,0 +1,382 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <atomic>
5#include <numeric>
6#include <optional>
7#include <thread>
8
9#include <boost/asio.hpp>
10#include <boost/process/async_pipe.hpp>
11
12#include "common/hex_util.h"
13#include "common/logging/log.h"
14#include "common/scope_exit.h"
15#include "core/arm/arm_interface.h"
16#include "core/core.h"
17#include "core/debugger/gdbstub.h"
18#include "core/debugger/gdbstub_arch.h"
19#include "core/hle/kernel/k_page_table.h"
20#include "core/hle/kernel/k_process.h"
21#include "core/hle/kernel/k_thread.h"
22#include "core/loader/loader.h"
23#include "core/memory.h"
24
25namespace Core {
26
27constexpr char GDB_STUB_START = '$';
28constexpr char GDB_STUB_END = '#';
29constexpr char GDB_STUB_ACK = '+';
30constexpr char GDB_STUB_NACK = '-';
31constexpr char GDB_STUB_INT3 = 0x03;
32constexpr int GDB_STUB_SIGTRAP = 5;
33
34constexpr char GDB_STUB_REPLY_ERR[] = "E01";
35constexpr char GDB_STUB_REPLY_OK[] = "OK";
36constexpr char GDB_STUB_REPLY_EMPTY[] = "";
37
38GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
39 : DebuggerFrontend(backend_), system{system_} {
40 if (system.CurrentProcess()->Is64BitProcess()) {
41 arch = std::make_unique<GDBStubA64>();
42 } else {
43 arch = std::make_unique<GDBStubA32>();
44 }
45}
46
47GDBStub::~GDBStub() = default;
48
49void GDBStub::Connected() {}
50
51void GDBStub::Stopped(Kernel::KThread* thread) {
52 SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
53}
54
55std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
56 std::vector<DebuggerAction> actions;
57 current_command.insert(current_command.end(), data.begin(), data.end());
58
59 while (current_command.size() != 0) {
60 ProcessData(actions);
61 }
62
63 return actions;
64}
65
66void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
67 const char c{current_command[0]};
68
69 // Acknowledgement
70 if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
71 current_command.erase(current_command.begin());
72 return;
73 }
74
75 // Interrupt
76 if (c == GDB_STUB_INT3) {
77 LOG_INFO(Debug_GDBStub, "Received interrupt");
78 current_command.erase(current_command.begin());
79 actions.push_back(DebuggerAction::Interrupt);
80 SendStatus(GDB_STUB_ACK);
81 return;
82 }
83
84 // Otherwise, require the data to be the start of a command
85 if (c != GDB_STUB_START) {
86 LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
87 current_command.clear();
88 SendStatus(GDB_STUB_NACK);
89 return;
90 }
91
92 // Continue reading until command is complete
93 while (CommandEnd() == current_command.end()) {
94 const auto new_data{backend.ReadFromClient()};
95 current_command.insert(current_command.end(), new_data.begin(), new_data.end());
96 }
97
98 // Execute and respond to GDB
99 const auto command{DetachCommand()};
100
101 if (command) {
102 SendStatus(GDB_STUB_ACK);
103 ExecuteCommand(*command, actions);
104 } else {
105 SendStatus(GDB_STUB_NACK);
106 }
107}
108
109void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
110 LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
111
112 if (packet.length() == 0) {
113 SendReply(GDB_STUB_REPLY_ERR);
114 return;
115 }
116
117 std::string_view command{packet.substr(1, packet.size())};
118
119 switch (packet[0]) {
120 case 'H': {
121 Kernel::KThread* thread{nullptr};
122 s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
123 if (thread_id >= 1) {
124 thread = GetThreadByID(thread_id);
125 }
126
127 if (thread) {
128 SendReply(GDB_STUB_REPLY_OK);
129 backend.SetActiveThread(thread);
130 } else {
131 SendReply(GDB_STUB_REPLY_ERR);
132 }
133 break;
134 }
135 case 'T': {
136 s64 thread_id{strtoll(command.data(), nullptr, 16)};
137 if (GetThreadByID(thread_id)) {
138 SendReply(GDB_STUB_REPLY_OK);
139 } else {
140 SendReply(GDB_STUB_REPLY_ERR);
141 }
142 break;
143 }
144 case 'q':
145 HandleQuery(command);
146 break;
147 case '?':
148 SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
149 break;
150 case 'k':
151 LOG_INFO(Debug_GDBStub, "Shutting down emulation");
152 actions.push_back(DebuggerAction::ShutdownEmulation);
153 break;
154 case 'g':
155 SendReply(arch->ReadRegisters(backend.GetActiveThread()));
156 break;
157 case 'G':
158 arch->WriteRegisters(backend.GetActiveThread(), command);
159 SendReply(GDB_STUB_REPLY_OK);
160 break;
161 case 'p': {
162 const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
163 SendReply(arch->RegRead(backend.GetActiveThread(), reg));
164 break;
165 }
166 case 'P': {
167 const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
168 const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
169 arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
170 break;
171 }
172 case 'm': {
173 const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
174 const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
175 const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
176
177 if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
178 std::vector<u8> mem(size);
179 system.Memory().ReadBlock(addr, mem.data(), size);
180
181 SendReply(Common::HexToString(mem));
182 } else {
183 SendReply(GDB_STUB_REPLY_ERR);
184 }
185 break;
186 }
187 case 'M': {
188 const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
189 const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
190
191 const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
192 const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
193
194 const auto mem_substr{std::string_view(command).substr(mem_sep)};
195 const auto mem{Common::HexStringToVector(mem_substr, false)};
196
197 if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
198 system.Memory().WriteBlock(addr, mem.data(), size);
199 system.InvalidateCpuInstructionCacheRange(addr, size);
200 SendReply(GDB_STUB_REPLY_OK);
201 } else {
202 SendReply(GDB_STUB_REPLY_ERR);
203 }
204 break;
205 }
206 case 's':
207 actions.push_back(DebuggerAction::StepThread);
208 break;
209 case 'C':
210 case 'c':
211 actions.push_back(DebuggerAction::Continue);
212 break;
213 case 'Z': {
214 const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
215 const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
216
217 if (system.Memory().IsValidVirtualAddress(addr)) {
218 replaced_instructions[addr] = system.Memory().Read32(addr);
219 system.Memory().Write32(addr, arch->BreakpointInstruction());
220 system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
221
222 SendReply(GDB_STUB_REPLY_OK);
223 } else {
224 SendReply(GDB_STUB_REPLY_ERR);
225 }
226 break;
227 }
228 case 'z': {
229 const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
230 const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
231
232 const auto orig_insn{replaced_instructions.find(addr)};
233 if (system.Memory().IsValidVirtualAddress(addr) &&
234 orig_insn != replaced_instructions.end()) {
235 system.Memory().Write32(addr, orig_insn->second);
236 system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
237 replaced_instructions.erase(addr);
238
239 SendReply(GDB_STUB_REPLY_OK);
240 } else {
241 SendReply(GDB_STUB_REPLY_ERR);
242 }
243 break;
244 }
245 default:
246 SendReply(GDB_STUB_REPLY_EMPTY);
247 break;
248 }
249}
250
251void GDBStub::HandleQuery(std::string_view command) {
252 if (command.starts_with("TStatus")) {
253 // no tracepoint support
254 SendReply("T0");
255 } else if (command.starts_with("Supported")) {
256 SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+");
257 } else if (command.starts_with("Xfer:features:read:target.xml:")) {
258 const auto offset{command.substr(30)};
259 const auto amount{command.substr(command.find(',') + 1)};
260
261 const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
262 const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
263 const auto target_xml{arch->GetTargetXML()};
264
265 if (offset_val + amount_val > target_xml.size()) {
266 SendReply("l" + target_xml.substr(offset_val));
267 } else {
268 SendReply("m" + target_xml.substr(offset_val, amount_val));
269 }
270 } else if (command.starts_with("Offsets")) {
271 Loader::AppLoader::Modules modules;
272 system.GetAppLoader().ReadNSOModules(modules);
273
274 const auto main = std::find_if(modules.begin(), modules.end(),
275 [](const auto& key) { return key.second == "main"; });
276 if (main != modules.end()) {
277 SendReply(fmt::format("TextSeg={:x}", main->first));
278 } else {
279 SendReply(fmt::format("TextSeg={:x}",
280 system.CurrentProcess()->PageTable().GetCodeRegionStart()));
281 }
282 } else if (command.starts_with("fThreadInfo")) {
283 // beginning of list
284 const auto& threads = system.GlobalSchedulerContext().GetThreadList();
285 std::vector<std::string> thread_ids;
286 for (const auto& thread : threads) {
287 thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
288 }
289 SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
290 } else if (command.starts_with("sThreadInfo")) {
291 // end of list
292 SendReply("l");
293 } else if (command.starts_with("Xfer:threads:read")) {
294 std::string buffer;
295 buffer += R"(l<?xml version="1.0"?>)";
296 buffer += "<threads>";
297
298 const auto& threads = system.GlobalSchedulerContext().GetThreadList();
299 for (const auto& thread : threads) {
300 buffer +=
301 fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)",
302 thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID());
303 }
304
305 buffer += "</threads>";
306 SendReply(buffer);
307 } else {
308 SendReply(GDB_STUB_REPLY_EMPTY);
309 }
310}
311
312Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
313 const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
314 for (auto* thread : threads) {
315 if (thread->GetThreadID() == thread_id) {
316 return thread;
317 }
318 }
319
320 return nullptr;
321}
322
323std::vector<char>::const_iterator GDBStub::CommandEnd() const {
324 // Find the end marker
325 const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
326
327 // Require the checksum to be present
328 return std::min(end + 2, current_command.end());
329}
330
331std::optional<std::string> GDBStub::DetachCommand() {
332 // Slice the string part from the beginning to the end marker
333 const auto end{CommandEnd()};
334
335 // Extract possible command data
336 std::string data(current_command.data(), end - current_command.begin() + 1);
337
338 // Shift over the remaining contents
339 current_command.erase(current_command.begin(), end + 1);
340
341 // Validate received command
342 if (data[0] != GDB_STUB_START) {
343 LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
344 return std::nullopt;
345 }
346
347 u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
348 u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
349
350 // Verify checksum
351 if (calculated != received) {
352 LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
353 calculated, received);
354 return std::nullopt;
355 }
356
357 return data.substr(1, data.size() - 4);
358}
359
360u8 GDBStub::CalculateChecksum(std::string_view data) {
361 return static_cast<u8>(
362 std::accumulate(data.begin(), data.end(), u8{0}, [](u8 lhs, u8 rhs) { return lhs + rhs; }));
363}
364
365void GDBStub::SendReply(std::string_view data) {
366 const auto output{
367 fmt::format("{}{}{}{:02x}", GDB_STUB_START, data, GDB_STUB_END, CalculateChecksum(data))};
368 LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
369
370 // C++ string support is complete rubbish
371 const u8* output_begin = reinterpret_cast<const u8*>(output.data());
372 const u8* output_end = output_begin + output.size();
373 backend.WriteToClient(std::span<const u8>(output_begin, output_end));
374}
375
376void GDBStub::SendStatus(char status) {
377 std::array<u8, 1> buf = {static_cast<u8>(status)};
378 LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
379 backend.WriteToClient(buf);
380}
381
382} // namespace Core
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
new file mode 100644
index 000000000..b93a3a511
--- /dev/null
+++ b/src/core/debugger/gdbstub.h
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <map>
7#include <memory>
8#include <optional>
9#include <string_view>
10#include <vector>
11
12#include "core/debugger/debugger_interface.h"
13#include "core/debugger/gdbstub_arch.h"
14
15namespace Core {
16
17class System;
18
19class GDBStub : public DebuggerFrontend {
20public:
21 explicit GDBStub(DebuggerBackend& backend, Core::System& system);
22 ~GDBStub();
23
24 void Connected() override;
25 void Stopped(Kernel::KThread* thread) override;
26 std::vector<DebuggerAction> ClientData(std::span<const u8> data) override;
27
28private:
29 void ProcessData(std::vector<DebuggerAction>& actions);
30 void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
31 void HandleQuery(std::string_view command);
32 std::vector<char>::const_iterator CommandEnd() const;
33 std::optional<std::string> DetachCommand();
34 Kernel::KThread* GetThreadByID(u64 thread_id);
35
36 static u8 CalculateChecksum(std::string_view data);
37 void SendReply(std::string_view data);
38 void SendStatus(char status);
39
40private:
41 Core::System& system;
42 std::unique_ptr<GDBStubArch> arch;
43 std::vector<char> current_command;
44 std::map<VAddr, u32> replaced_instructions;
45};
46
47} // namespace Core
diff --git a/src/core/debugger/gdbstub_arch.cpp b/src/core/debugger/gdbstub_arch.cpp
new file mode 100644
index 000000000..99e3893a9
--- /dev/null
+++ b/src/core/debugger/gdbstub_arch.cpp
@@ -0,0 +1,406 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/hex_util.h"
5#include "core/debugger/gdbstub_arch.h"
6#include "core/hle/kernel/k_thread.h"
7
8namespace Core {
9
10template <typename T>
11static T HexToValue(std::string_view hex) {
12 static_assert(std::is_trivially_copyable_v<T>);
13 T value{};
14 const auto mem{Common::HexStringToVector(hex, false)};
15 std::memcpy(&value, mem.data(), std::min(mem.size(), sizeof(T)));
16 return value;
17}
18
19template <typename T>
20static std::string ValueToHex(const T value) {
21 static_assert(std::is_trivially_copyable_v<T>);
22 std::array<u8, sizeof(T)> mem{};
23 std::memcpy(mem.data(), &value, sizeof(T));
24 return Common::HexToString(mem);
25}
26
27template <typename T>
28static T GetSIMDRegister(const std::array<u32, 64>& simd_regs, size_t offset) {
29 static_assert(std::is_trivially_copyable_v<T>);
30 T value{};
31 std::memcpy(&value, reinterpret_cast<const u8*>(simd_regs.data()) + sizeof(T) * offset,
32 sizeof(T));
33 return value;
34}
35
36template <typename T>
37static void PutSIMDRegister(std::array<u32, 64>& simd_regs, size_t offset, const T value) {
38 static_assert(std::is_trivially_copyable_v<T>);
39 std::memcpy(reinterpret_cast<u8*>(simd_regs.data()) + sizeof(T) * offset, &value, sizeof(T));
40}
41
42// For sample XML files see the GDB source /gdb/features
43// This XML defines what the registers are for this specific ARM device
44std::string GDBStubA64::GetTargetXML() const {
45 constexpr const char* target_xml =
46 R"(<?xml version="1.0"?>
47<!DOCTYPE target SYSTEM "gdb-target.dtd">
48<target version="1.0">
49 <feature name="org.gnu.gdb.aarch64.core">
50 <reg name="x0" bitsize="64"/>
51 <reg name="x1" bitsize="64"/>
52 <reg name="x2" bitsize="64"/>
53 <reg name="x3" bitsize="64"/>
54 <reg name="x4" bitsize="64"/>
55 <reg name="x5" bitsize="64"/>
56 <reg name="x6" bitsize="64"/>
57 <reg name="x7" bitsize="64"/>
58 <reg name="x8" bitsize="64"/>
59 <reg name="x9" bitsize="64"/>
60 <reg name="x10" bitsize="64"/>
61 <reg name="x11" bitsize="64"/>
62 <reg name="x12" bitsize="64"/>
63 <reg name="x13" bitsize="64"/>
64 <reg name="x14" bitsize="64"/>
65 <reg name="x15" bitsize="64"/>
66 <reg name="x16" bitsize="64"/>
67 <reg name="x17" bitsize="64"/>
68 <reg name="x18" bitsize="64"/>
69 <reg name="x19" bitsize="64"/>
70 <reg name="x20" bitsize="64"/>
71 <reg name="x21" bitsize="64"/>
72 <reg name="x22" bitsize="64"/>
73 <reg name="x23" bitsize="64"/>
74 <reg name="x24" bitsize="64"/>
75 <reg name="x25" bitsize="64"/>
76 <reg name="x26" bitsize="64"/>
77 <reg name="x27" bitsize="64"/>
78 <reg name="x28" bitsize="64"/>
79 <reg name="x29" bitsize="64"/>
80 <reg name="x30" bitsize="64"/>
81 <reg name="sp" bitsize="64" type="data_ptr"/>
82 <reg name="pc" bitsize="64" type="code_ptr"/>
83 <flags id="pstate_flags" size="4">
84 <field name="SP" start="0" end="0"/>
85 <field name="" start="1" end="1"/>
86 <field name="EL" start="2" end="3"/>
87 <field name="nRW" start="4" end="4"/>
88 <field name="" start="5" end="5"/>
89 <field name="F" start="6" end="6"/>
90 <field name="I" start="7" end="7"/>
91 <field name="A" start="8" end="8"/>
92 <field name="D" start="9" end="9"/>
93 <field name="IL" start="20" end="20"/>
94 <field name="SS" start="21" end="21"/>
95 <field name="V" start="28" end="28"/>
96 <field name="C" start="29" end="29"/>
97 <field name="Z" start="30" end="30"/>
98 <field name="N" start="31" end="31"/>
99 </flags>
100 <reg name="pstate" bitsize="32" type="pstate_flags"/>
101 </feature>
102 <feature name="org.gnu.gdb.aarch64.fpu">
103 </feature>
104</target>)";
105
106 return target_xml;
107}
108
109std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const {
110 if (!thread) {
111 return "";
112 }
113
114 const auto& context{thread->GetContext64()};
115 const auto& gprs{context.cpu_registers};
116 const auto& fprs{context.vector_registers};
117
118 if (id <= SP_REGISTER) {
119 return ValueToHex(gprs[id]);
120 } else if (id == PC_REGISTER) {
121 return ValueToHex(context.pc);
122 } else if (id == PSTATE_REGISTER) {
123 return ValueToHex(context.pstate);
124 } else if (id >= Q0_REGISTER && id < FPCR_REGISTER) {
125 return ValueToHex(fprs[id - Q0_REGISTER]);
126 } else if (id == FPCR_REGISTER) {
127 return ValueToHex(context.fpcr);
128 } else if (id == FPSR_REGISTER) {
129 return ValueToHex(context.fpsr);
130 } else {
131 return "";
132 }
133}
134
135void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
136 if (!thread) {
137 return;
138 }
139
140 auto& context{thread->GetContext64()};
141
142 if (id <= SP_REGISTER) {
143 context.cpu_registers[id] = HexToValue<u64>(value);
144 } else if (id == PC_REGISTER) {
145 context.pc = HexToValue<u64>(value);
146 } else if (id == PSTATE_REGISTER) {
147 context.pstate = HexToValue<u32>(value);
148 } else if (id >= Q0_REGISTER && id < FPCR_REGISTER) {
149 context.vector_registers[id - Q0_REGISTER] = HexToValue<u128>(value);
150 } else if (id == FPCR_REGISTER) {
151 context.fpcr = HexToValue<u32>(value);
152 } else if (id == FPSR_REGISTER) {
153 context.fpsr = HexToValue<u32>(value);
154 }
155}
156
157std::string GDBStubA64::ReadRegisters(const Kernel::KThread* thread) const {
158 std::string output;
159
160 for (size_t reg = 0; reg <= FPCR_REGISTER; reg++) {
161 output += RegRead(thread, reg);
162 }
163
164 return output;
165}
166
167void GDBStubA64::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
168 for (size_t i = 0, reg = 0; reg <= FPCR_REGISTER; reg++) {
169 if (reg <= SP_REGISTER || reg == PC_REGISTER) {
170 RegWrite(thread, reg, register_data.substr(i, 16));
171 i += 16;
172 } else if (reg == PSTATE_REGISTER || reg == FPCR_REGISTER || reg == FPSR_REGISTER) {
173 RegWrite(thread, reg, register_data.substr(i, 8));
174 i += 8;
175 } else if (reg >= Q0_REGISTER && reg < FPCR_REGISTER) {
176 RegWrite(thread, reg, register_data.substr(i, 32));
177 i += 32;
178 }
179 }
180}
181
182std::string GDBStubA64::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
183 return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
184 RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
185 LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
186}
187
188u32 GDBStubA64::BreakpointInstruction() const {
189 // A64: brk #0
190 return 0xd4200000;
191}
192
193std::string GDBStubA32::GetTargetXML() const {
194 constexpr const char* target_xml =
195 R"(<?xml version="1.0"?>
196<!DOCTYPE target SYSTEM "gdb-target.dtd">
197<target version="1.0">
198 <feature name="org.gnu.gdb.arm.core">
199 <reg name="r0" bitsize="32" type="uint32"/>
200 <reg name="r1" bitsize="32" type="uint32"/>
201 <reg name="r2" bitsize="32" type="uint32"/>
202 <reg name="r3" bitsize="32" type="uint32"/>
203 <reg name="r4" bitsize="32" type="uint32"/>
204 <reg name="r5" bitsize="32" type="uint32"/>
205 <reg name="r6" bitsize="32" type="uint32"/>
206 <reg name="r7" bitsize="32" type="uint32"/>
207 <reg name="r8" bitsize="32" type="uint32"/>
208 <reg name="r9" bitsize="32" type="uint32"/>
209 <reg name="r10" bitsize="32" type="uint32"/>
210 <reg name="r11" bitsize="32" type="uint32"/>
211 <reg name="r12" bitsize="32" type="uint32"/>
212 <reg name="sp" bitsize="32" type="data_ptr"/>
213 <reg name="lr" bitsize="32" type="code_ptr"/>
214 <reg name="pc" bitsize="32" type="code_ptr"/>
215 <!-- The CPSR is register 25, rather than register 16, because
216 the FPA registers historically were placed between the PC
217 and the CPSR in the "g" packet. -->
218 <reg name="cpsr" bitsize="32" regnum="25"/>
219 </feature>
220 <feature name="org.gnu.gdb.arm.vfp">
221 <vector id="neon_uint8x8" type="uint8" count="8"/>
222 <vector id="neon_uint16x4" type="uint16" count="4"/>
223 <vector id="neon_uint32x2" type="uint32" count="2"/>
224 <vector id="neon_float32x2" type="ieee_single" count="2"/>
225 <union id="neon_d">
226 <field name="u8" type="neon_uint8x8"/>
227 <field name="u16" type="neon_uint16x4"/>
228 <field name="u32" type="neon_uint32x2"/>
229 <field name="u64" type="uint64"/>
230 <field name="f32" type="neon_float32x2"/>
231 <field name="f64" type="ieee_double"/>
232 </union>
233 <vector id="neon_uint8x16" type="uint8" count="16"/>
234 <vector id="neon_uint16x8" type="uint16" count="8"/>
235 <vector id="neon_uint32x4" type="uint32" count="4"/>
236 <vector id="neon_uint64x2" type="uint64" count="2"/>
237 <vector id="neon_float32x4" type="ieee_single" count="4"/>
238 <vector id="neon_float64x2" type="ieee_double" count="2"/>
239 <union id="neon_q">
240 <field name="u8" type="neon_uint8x16"/>
241 <field name="u16" type="neon_uint16x8"/>
242 <field name="u32" type="neon_uint32x4"/>
243 <field name="u64" type="neon_uint64x2"/>
244 <field name="f32" type="neon_float32x4"/>
245 <field name="f64" type="neon_float64x2"/>
246 </union>
247 <reg name="d0" bitsize="64" type="neon_d" regnum="32"/>
248 <reg name="d1" bitsize="64" type="neon_d"/>
249 <reg name="d2" bitsize="64" type="neon_d"/>
250 <reg name="d3" bitsize="64" type="neon_d"/>
251 <reg name="d4" bitsize="64" type="neon_d"/>
252 <reg name="d5" bitsize="64" type="neon_d"/>
253 <reg name="d6" bitsize="64" type="neon_d"/>
254 <reg name="d7" bitsize="64" type="neon_d"/>
255 <reg name="d8" bitsize="64" type="neon_d"/>
256 <reg name="d9" bitsize="64" type="neon_d"/>
257 <reg name="d10" bitsize="64" type="neon_d"/>
258 <reg name="d11" bitsize="64" type="neon_d"/>
259 <reg name="d12" bitsize="64" type="neon_d"/>
260 <reg name="d13" bitsize="64" type="neon_d"/>
261 <reg name="d14" bitsize="64" type="neon_d"/>
262 <reg name="d15" bitsize="64" type="neon_d"/>
263 <reg name="d16" bitsize="64" type="neon_d"/>
264 <reg name="d17" bitsize="64" type="neon_d"/>
265 <reg name="d18" bitsize="64" type="neon_d"/>
266 <reg name="d19" bitsize="64" type="neon_d"/>
267 <reg name="d20" bitsize="64" type="neon_d"/>
268 <reg name="d21" bitsize="64" type="neon_d"/>
269 <reg name="d22" bitsize="64" type="neon_d"/>
270 <reg name="d23" bitsize="64" type="neon_d"/>
271 <reg name="d24" bitsize="64" type="neon_d"/>
272 <reg name="d25" bitsize="64" type="neon_d"/>
273 <reg name="d26" bitsize="64" type="neon_d"/>
274 <reg name="d27" bitsize="64" type="neon_d"/>
275 <reg name="d28" bitsize="64" type="neon_d"/>
276 <reg name="d29" bitsize="64" type="neon_d"/>
277 <reg name="d30" bitsize="64" type="neon_d"/>
278 <reg name="d31" bitsize="64" type="neon_d"/>
279
280 <reg name="q0" bitsize="128" type="neon_q" regnum="64"/>
281 <reg name="q1" bitsize="128" type="neon_q"/>
282 <reg name="q2" bitsize="128" type="neon_q"/>
283 <reg name="q3" bitsize="128" type="neon_q"/>
284 <reg name="q4" bitsize="128" type="neon_q"/>
285 <reg name="q5" bitsize="128" type="neon_q"/>
286 <reg name="q6" bitsize="128" type="neon_q"/>
287 <reg name="q7" bitsize="128" type="neon_q"/>
288 <reg name="q8" bitsize="128" type="neon_q"/>
289 <reg name="q9" bitsize="128" type="neon_q"/>
290 <reg name="q10" bitsize="128" type="neon_q"/>
291 <reg name="q10" bitsize="128" type="neon_q"/>
292 <reg name="q12" bitsize="128" type="neon_q"/>
293 <reg name="q13" bitsize="128" type="neon_q"/>
294 <reg name="q14" bitsize="128" type="neon_q"/>
295 <reg name="q15" bitsize="128" type="neon_q"/>
296
297 <reg name="fpscr" bitsize="32" type="int" group="float" regnum="80"/>
298 </feature>
299</target>)";
300
301 return target_xml;
302}
303
304std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const {
305 if (!thread) {
306 return "";
307 }
308
309 const auto& context{thread->GetContext32()};
310 const auto& gprs{context.cpu_registers};
311 const auto& fprs{context.extension_registers};
312
313 if (id <= PC_REGISTER) {
314 return ValueToHex(gprs[id]);
315 } else if (id == CPSR_REGISTER) {
316 return ValueToHex(context.cpsr);
317 } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
318 const u64 dN{GetSIMDRegister<u64>(fprs, id - D0_REGISTER)};
319 return ValueToHex(dN);
320 } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
321 const u128 qN{GetSIMDRegister<u128>(fprs, id - Q0_REGISTER)};
322 return ValueToHex(qN);
323 } else if (id == FPSCR_REGISTER) {
324 return ValueToHex(context.fpscr);
325 } else {
326 return "";
327 }
328}
329
330void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
331 if (!thread) {
332 return;
333 }
334
335 auto& context{thread->GetContext32()};
336 auto& fprs{context.extension_registers};
337
338 if (id <= PC_REGISTER) {
339 context.cpu_registers[id] = HexToValue<u32>(value);
340 } else if (id == CPSR_REGISTER) {
341 context.cpsr = HexToValue<u32>(value);
342 } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
343 PutSIMDRegister(fprs, id - D0_REGISTER, HexToValue<u64>(value));
344 } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
345 PutSIMDRegister(fprs, id - Q0_REGISTER, HexToValue<u128>(value));
346 } else if (id == FPSCR_REGISTER) {
347 context.fpscr = HexToValue<u32>(value);
348 }
349}
350
351std::string GDBStubA32::ReadRegisters(const Kernel::KThread* thread) const {
352 std::string output;
353
354 for (size_t reg = 0; reg <= FPSCR_REGISTER; reg++) {
355 const bool gpr{reg <= PC_REGISTER};
356 const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
357 const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
358
359 if (!(gpr || dfpr || qfpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER)) {
360 continue;
361 }
362
363 output += RegRead(thread, reg);
364 }
365
366 return output;
367}
368
369void GDBStubA32::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
370 for (size_t i = 0, reg = 0; reg <= FPSCR_REGISTER; reg++) {
371 const bool gpr{reg <= PC_REGISTER};
372 const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
373 const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
374
375 if (gpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER) {
376 RegWrite(thread, reg, register_data.substr(i, 8));
377 i += 8;
378 } else if (dfpr) {
379 RegWrite(thread, reg, register_data.substr(i, 16));
380 i += 16;
381 } else if (qfpr) {
382 RegWrite(thread, reg, register_data.substr(i, 32));
383 i += 32;
384 }
385
386 if (reg == PC_REGISTER) {
387 reg = CPSR_REGISTER - 1;
388 } else if (reg == CPSR_REGISTER) {
389 reg = D0_REGISTER - 1;
390 }
391 }
392}
393
394std::string GDBStubA32::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
395 return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
396 RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
397 LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
398}
399
400u32 GDBStubA32::BreakpointInstruction() const {
401 // A32: trap
402 // T32: trap + b #4
403 return 0xe7ffdefe;
404}
405
406} // namespace Core
diff --git a/src/core/debugger/gdbstub_arch.h b/src/core/debugger/gdbstub_arch.h
new file mode 100644
index 000000000..e943848e5
--- /dev/null
+++ b/src/core/debugger/gdbstub_arch.h
@@ -0,0 +1,67 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "common/common_types.h"
9
10namespace Kernel {
11class KThread;
12}
13
14namespace Core {
15
16class GDBStubArch {
17public:
18 virtual std::string GetTargetXML() const = 0;
19 virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0;
20 virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0;
21 virtual std::string ReadRegisters(const Kernel::KThread* thread) const = 0;
22 virtual void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const = 0;
23 virtual std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const = 0;
24 virtual u32 BreakpointInstruction() const = 0;
25};
26
27class GDBStubA64 final : public GDBStubArch {
28public:
29 std::string GetTargetXML() const override;
30 std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
31 void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
32 std::string ReadRegisters(const Kernel::KThread* thread) const override;
33 void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
34 std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
35 u32 BreakpointInstruction() const override;
36
37private:
38 static constexpr u32 LR_REGISTER = 30;
39 static constexpr u32 SP_REGISTER = 31;
40 static constexpr u32 PC_REGISTER = 32;
41 static constexpr u32 PSTATE_REGISTER = 33;
42 static constexpr u32 Q0_REGISTER = 34;
43 static constexpr u32 FPCR_REGISTER = 66;
44 static constexpr u32 FPSR_REGISTER = 67;
45};
46
47class GDBStubA32 final : public GDBStubArch {
48public:
49 std::string GetTargetXML() const override;
50 std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
51 void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
52 std::string ReadRegisters(const Kernel::KThread* thread) const override;
53 void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
54 std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
55 u32 BreakpointInstruction() const override;
56
57private:
58 static constexpr u32 SP_REGISTER = 13;
59 static constexpr u32 LR_REGISTER = 14;
60 static constexpr u32 PC_REGISTER = 15;
61 static constexpr u32 CPSR_REGISTER = 25;
62 static constexpr u32 D0_REGISTER = 32;
63 static constexpr u32 Q0_REGISTER = 64;
64 static constexpr u32 FPSCR_REGISTER = 80;
65};
66
67} // namespace Core
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 490e31fc7..dcfeacccd 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -64,6 +64,10 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority
64 { 64 {
65 KScopedSchedulerLock lock{kernel}; 65 KScopedSchedulerLock lock{kernel};
66 thread->SetState(ThreadState::Runnable); 66 thread->SetState(ThreadState::Runnable);
67
68 if (system.DebuggerEnabled()) {
69 thread->RequestSuspend(SuspendType::Debug);
70 }
67 } 71 }
68} 72}
69} // Anonymous namespace 73} // Anonymous namespace
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 28d30eee2..7534de01e 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -594,6 +594,19 @@ bool Memory::IsValidVirtualAddress(const VAddr vaddr) const {
594 return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory; 594 return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
595} 595}
596 596
597bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
598 VAddr end = base + size;
599 VAddr page = Common::AlignDown(base, PAGE_SIZE);
600
601 for (; page < end; page += PAGE_SIZE) {
602 if (!IsValidVirtualAddress(page)) {
603 return false;
604 }
605 }
606
607 return true;
608}
609
597u8* Memory::GetPointer(VAddr vaddr) { 610u8* Memory::GetPointer(VAddr vaddr) {
598 return impl->GetPointer(vaddr); 611 return impl->GetPointer(vaddr);
599} 612}
diff --git a/src/core/memory.h b/src/core/memory.h
index b5721b740..58cc27b29 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -96,6 +96,17 @@ public:
96 [[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const; 96 [[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const;
97 97
98 /** 98 /**
99 * Checks whether or not the supplied range of addresses are all valid
100 * virtual addresses for the current process.
101 *
102 * @param base The address to begin checking.
103 * @param size The amount of bytes to check.
104 *
105 * @returns True if all bytes in the given range are valid, false otherwise.
106 */
107 [[nodiscard]] bool IsValidVirtualAddressRange(VAddr base, u64 size) const;
108
109 /**
99 * Gets a pointer to the given address. 110 * Gets a pointer to the given address.
100 * 111 *
101 * @param vaddr Virtual address to retrieve a pointer to. 112 * @param vaddr Virtual address to retrieve a pointer to.
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 8f0a6bbb8..aae2de2f8 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -50,6 +50,7 @@ void EmuThread::run() {
50 50
51 auto& gpu = system.GPU(); 51 auto& gpu = system.GPU();
52 auto stop_token = stop_source.get_token(); 52 auto stop_token = stop_source.get_token();
53 bool debugger_should_start = system.DebuggerEnabled();
53 54
54 system.RegisterHostThread(); 55 system.RegisterHostThread();
55 56
@@ -89,6 +90,12 @@ void EmuThread::run() {
89 this->SetRunning(false); 90 this->SetRunning(false);
90 emit ErrorThrown(result, system.GetStatusDetails()); 91 emit ErrorThrown(result, system.GetStatusDetails());
91 } 92 }
93
94 if (debugger_should_start) {
95 system.InitializeDebugger();
96 debugger_should_start = false;
97 }
98
92 running_wait.Wait(); 99 running_wait.Wait();
93 result = system.Pause(); 100 result = system.Pause();
94 if (result != Core::SystemResultStatus::Success) { 101 if (result != Core::SystemResultStatus::Success) {
@@ -102,11 +109,9 @@ void EmuThread::run() {
102 was_active = true; 109 was_active = true;
103 emit DebugModeEntered(); 110 emit DebugModeEntered();
104 } 111 }
105 } else if (exec_step) {
106 UNIMPLEMENTED();
107 } else { 112 } else {
108 std::unique_lock lock{running_mutex}; 113 std::unique_lock lock{running_mutex};
109 running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; }); 114 running_cv.wait(lock, stop_token, [this] { return IsRunning(); });
110 } 115 }
111 } 116 }
112 117
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 841816564..87c559e7a 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -55,15 +55,6 @@ public:
55 void run() override; 55 void run() override;
56 56
57 /** 57 /**
58 * Steps the emulation thread by a single CPU instruction (if the CPU is not already running)
59 * @note This function is thread-safe
60 */
61 void ExecStep() {
62 exec_step = true;
63 running_cv.notify_all();
64 }
65
66 /**
67 * Sets whether the emulation thread is running or not 58 * Sets whether the emulation thread is running or not
68 * @param running Boolean value, set the emulation thread to running if true 59 * @param running Boolean value, set the emulation thread to running if true
69 * @note This function is thread-safe 60 * @note This function is thread-safe
@@ -99,7 +90,6 @@ public:
99 } 90 }
100 91
101private: 92private:
102 bool exec_step = false;
103 bool running = false; 93 bool running = false;
104 std::stop_source stop_source; 94 std::stop_source stop_source;
105 std::mutex running_mutex; 95 std::mutex running_mutex;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index ac26b885b..583e9df24 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -525,6 +525,9 @@ void Config::ReadDebuggingValues() {
525 // Intentionally not using the QT default setting as this is intended to be changed in the ini 525 // Intentionally not using the QT default setting as this is intended to be changed in the ini
526 Settings::values.record_frame_times = 526 Settings::values.record_frame_times =
527 qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); 527 qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
528
529 ReadBasicSetting(Settings::values.use_gdbstub);
530 ReadBasicSetting(Settings::values.gdbstub_port);
528 ReadBasicSetting(Settings::values.program_args); 531 ReadBasicSetting(Settings::values.program_args);
529 ReadBasicSetting(Settings::values.dump_exefs); 532 ReadBasicSetting(Settings::values.dump_exefs);
530 ReadBasicSetting(Settings::values.dump_nso); 533 ReadBasicSetting(Settings::values.dump_nso);
@@ -1095,6 +1098,8 @@ void Config::SaveDebuggingValues() {
1095 1098
1096 // Intentionally not using the QT default setting as this is intended to be changed in the ini 1099 // Intentionally not using the QT default setting as this is intended to be changed in the ini
1097 qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); 1100 qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
1101 WriteBasicSetting(Settings::values.use_gdbstub);
1102 WriteBasicSetting(Settings::values.gdbstub_port);
1098 WriteBasicSetting(Settings::values.program_args); 1103 WriteBasicSetting(Settings::values.program_args);
1099 WriteBasicSetting(Settings::values.dump_exefs); 1104 WriteBasicSetting(Settings::values.dump_exefs);
1100 WriteBasicSetting(Settings::values.dump_nso); 1105 WriteBasicSetting(Settings::values.dump_nso);
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index d6e8b5ead..343d2aee1 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -24,13 +24,18 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
24 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir)); 24 QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir));
25 QDesktopServices::openUrl(QUrl::fromLocalFile(path)); 25 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
26 }); 26 });
27
28 connect(ui->toggle_gdbstub, &QCheckBox::toggled,
29 [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
27} 30}
28 31
29ConfigureDebug::~ConfigureDebug() = default; 32ConfigureDebug::~ConfigureDebug() = default;
30 33
31void ConfigureDebug::SetConfiguration() { 34void ConfigureDebug::SetConfiguration() {
32 const bool runtime_lock = !system.IsPoweredOn(); 35 const bool runtime_lock = !system.IsPoweredOn();
33 36 ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
37 ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
38 ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
34 ui->toggle_console->setEnabled(runtime_lock); 39 ui->toggle_console->setEnabled(runtime_lock);
35 ui->toggle_console->setChecked(UISettings::values.show_console.GetValue()); 40 ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
36 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue())); 41 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
@@ -71,6 +76,8 @@ void ConfigureDebug::SetConfiguration() {
71} 76}
72 77
73void ConfigureDebug::ApplyConfiguration() { 78void ConfigureDebug::ApplyConfiguration() {
79 Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
80 Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
74 UISettings::values.show_console = ui->toggle_console->isChecked(); 81 UISettings::values.show_console = ui->toggle_console->isChecked();
75 Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); 82 Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
76 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); 83 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 863a3fd57..1152fa6c6 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -3,6 +3,60 @@
3 <class>ConfigureDebug</class> 3 <class>ConfigureDebug</class>
4 <widget class="QWidget" name="ConfigureDebug"> 4 <widget class="QWidget" name="ConfigureDebug">
5 <layout class="QVBoxLayout" name="verticalLayout_1"> 5 <layout class="QVBoxLayout" name="verticalLayout_1">
6 <item>
7 <layout class="QVBoxLayout" name="verticalLayout_2">
8 <item>
9 <widget class="QGroupBox" name="groupBox">
10 <property name="title">
11 <string>Debugger</string>
12 </property>
13 <layout class="QVBoxLayout" name="verticalLayout_3">
14 <item>
15 <layout class="QHBoxLayout" name="horizontalLayout_11">
16 <item>
17 <widget class="QCheckBox" name="toggle_gdbstub">
18 <property name="text">
19 <string>Enable GDB Stub</string>
20 </property>
21 </widget>
22 </item>
23 <item>
24 <spacer name="horizontalSpacer">
25 <property name="orientation">
26 <enum>Qt::Horizontal</enum>
27 </property>
28 <property name="sizeHint" stdset="0">
29 <size>
30 <width>40</width>
31 <height>20</height>
32 </size>
33 </property>
34 </spacer>
35 </item>
36 <item>
37 <widget class="QLabel" name="label_11">
38 <property name="text">
39 <string>Port:</string>
40 </property>
41 </widget>
42 </item>
43 <item>
44 <widget class="QSpinBox" name="gdbport_spinbox">
45 <property name="minimum">
46 <number>1024</number>
47 </property>
48 <property name="maximum">
49 <number>65535</number>
50 </property>
51 </widget>
52 </item>
53 </layout>
54 </item>
55 </layout>
56 </widget>
57 </item>
58 </layout>
59 </item>
6 <item> 60 <item>
7 <widget class="QGroupBox" name="groupBox_2"> 61 <widget class="QGroupBox" name="groupBox_2">
8 <property name="title"> 62 <property name="title">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f4a9a7171..e14e8333c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -3152,7 +3152,7 @@ void GMainWindow::OnTasStateChanged() {
3152} 3152}
3153 3153
3154void GMainWindow::UpdateStatusBar() { 3154void GMainWindow::UpdateStatusBar() {
3155 if (emu_thread == nullptr) { 3155 if (emu_thread == nullptr || !system->IsPoweredOn()) {
3156 status_bar_update_timer.stop(); 3156 status_bar_update_timer.stop();
3157 return; 3157 return;
3158 } 3158 }