summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h2
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/arm/arm_interface.cpp46
-rw-r--r--src/core/arm/arm_interface.h17
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp34
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h7
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp33
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h7
-rw-r--r--src/core/core.cpp29
-rw-r--r--src/core/core.h18
-rw-r--r--src/core/debugger/debugger.cpp268
-rw-r--r--src/core/debugger/debugger.h41
-rw-r--r--src/core/debugger/debugger_interface.h79
-rw-r--r--src/core/debugger/gdbstub.cpp612
-rw-r--r--src/core/debugger/gdbstub.h48
-rw-r--r--src/core/debugger/gdbstub_arch.cpp483
-rw-r--r--src/core/debugger/gdbstub_arch.h67
-rw-r--r--src/core/hle/kernel/k_process.cpp4
-rw-r--r--src/core/hle/kernel/k_thread.cpp4
-rw-r--r--src/core/hle/kernel/k_thread.h25
-rw-r--r--src/core/memory.cpp13
-rw-r--r--src/core/memory.h11
-rw-r--r--src/video_core/CMakeLists.txt4
-rw-r--r--src/video_core/engines/maxwell_3d.cpp4
-rw-r--r--src/video_core/engines/maxwell_3d.h2
-rw-r--r--src/yuzu/CMakeLists.txt4
-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.cpp6
33 files changed, 1897 insertions, 71 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..2bd720f08 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
@@ -761,6 +768,9 @@ create_target_directory_groups(core)
761 768
762target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 769target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
763target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) 770target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
771if (MINGW)
772 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
773endif()
764 774
765if (ENABLE_WEB_SERVICE) 775if (ENABLE_WEB_SERVICE)
766 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) 776 target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index c347e7ea7..9b5a5ca57 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -9,7 +9,9 @@
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"
14#include "core/hle/kernel/svc.h"
13#include "core/loader/loader.h" 15#include "core/loader/loader.h"
14#include "core/memory.h" 16#include "core/memory.h"
15 17
@@ -88,4 +90,48 @@ void ARM_Interface::LogBacktrace() const {
88 } 90 }
89} 91}
90 92
93void ARM_Interface::Run() {
94 using Kernel::StepState;
95 using Kernel::SuspendType;
96
97 while (true) {
98 Kernel::KThread* current_thread{system.Kernel().CurrentScheduler()->GetCurrentThread()};
99 Dynarmic::HaltReason hr{};
100
101 // Notify the debugger and go to sleep if a step was performed
102 // and this thread has been scheduled again.
103 if (current_thread->GetStepState() == StepState::StepPerformed) {
104 system.GetDebugger().NotifyThreadStopped(current_thread);
105 current_thread->RequestSuspend(SuspendType::Debug);
106 break;
107 }
108
109 // Otherwise, run the thread.
110 if (current_thread->GetStepState() == StepState::StepPending) {
111 hr = StepJit();
112
113 if (Has(hr, step_thread)) {
114 current_thread->SetStepState(StepState::StepPerformed);
115 }
116 } else {
117 hr = RunJit();
118 }
119
120 // Notify the debugger and go to sleep if a breakpoint was hit.
121 if (Has(hr, breakpoint)) {
122 system.GetDebugger().NotifyThreadStopped(current_thread);
123 current_thread->RequestSuspend(Kernel::SuspendType::Debug);
124 break;
125 }
126
127 // Handle syscalls and scheduling (this may change the current thread)
128 if (Has(hr, svc_call)) {
129 Kernel::Svc::Call(system, GetSvcNumber());
130 }
131 if (Has(hr, break_loop) || !uses_wall_clock) {
132 break;
133 }
134 }
135}
136
91} // namespace Core 137} // namespace Core
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 8ce973a77..66f6107e9 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -6,6 +6,9 @@
6 6
7#include <array> 7#include <array>
8#include <vector> 8#include <vector>
9
10#include <dynarmic/interface/halt_reason.h>
11
9#include "common/common_funcs.h" 12#include "common/common_funcs.h"
10#include "common/common_types.h" 13#include "common/common_types.h"
11#include "core/hardware_properties.h" 14#include "core/hardware_properties.h"
@@ -64,10 +67,7 @@ public:
64 static_assert(sizeof(ThreadContext64) == 0x320); 67 static_assert(sizeof(ThreadContext64) == 0x320);
65 68
66 /// Runs the CPU until an event happens 69 /// Runs the CPU until an event happens
67 virtual void Run() = 0; 70 void Run();
68
69 /// Step CPU by one instruction
70 virtual void Step() = 0;
71 71
72 /// Clear all instruction cache 72 /// Clear all instruction cache
73 virtual void ClearInstructionCache() = 0; 73 virtual void ClearInstructionCache() = 0;
@@ -194,6 +194,11 @@ public:
194 194
195 void LogBacktrace() const; 195 void LogBacktrace() const;
196 196
197 static constexpr Dynarmic::HaltReason step_thread = Dynarmic::HaltReason::Step;
198 static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
199 static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
200 static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
201
197protected: 202protected:
198 /// System context that this ARM interface is running under. 203 /// System context that this ARM interface is running under.
199 System& system; 204 System& system;
@@ -201,6 +206,10 @@ protected:
201 bool uses_wall_clock; 206 bool uses_wall_clock;
202 207
203 static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out); 208 static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
209
210 virtual Dynarmic::HaltReason RunJit() = 0;
211 virtual Dynarmic::HaltReason StepJit() = 0;
212 virtual u32 GetSvcNumber() const = 0;
204}; 213};
205 214
206} // namespace Core 215} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index 781a77f6f..7c82d0b96 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
@@ -24,9 +26,6 @@ namespace Core {
24 26
25using namespace Common::Literals; 27using namespace Common::Literals;
26 28
27constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
28constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
29
30class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { 29class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
31public: 30public:
32 explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) 31 explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
@@ -78,16 +77,21 @@ public:
78 } 77 }
79 78
80 void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { 79 void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
80 if (parent.system.DebuggerEnabled()) {
81 parent.jit.load()->Regs()[15] = pc;
82 parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
83 return;
84 }
85
81 parent.LogBacktrace(); 86 parent.LogBacktrace();
82 LOG_CRITICAL(Core_ARM, 87 LOG_CRITICAL(Core_ARM,
83 "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})", 88 "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
84 exception, pc, MemoryReadCode(pc), parent.IsInThumbMode()); 89 exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
85 UNIMPLEMENTED();
86 } 90 }
87 91
88 void CallSVC(u32 swi) override { 92 void CallSVC(u32 swi) override {
89 parent.svc_swi = swi; 93 parent.svc_swi = swi;
90 parent.jit.load()->HaltExecution(svc_call); 94 parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
91 } 95 }
92 96
93 void AddTicks(u64 ticks) override { 97 void AddTicks(u64 ticks) override {
@@ -232,20 +236,16 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
232 return std::make_unique<Dynarmic::A32::Jit>(config); 236 return std::make_unique<Dynarmic::A32::Jit>(config);
233} 237}
234 238
235void ARM_Dynarmic_32::Run() { 239Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() {
236 while (true) { 240 return jit.load()->Run();
237 const auto hr = jit.load()->Run(); 241}
238 if (Has(hr, svc_call)) { 242
239 Kernel::Svc::Call(system, svc_swi); 243Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() {
240 } 244 return jit.load()->Step();
241 if (Has(hr, break_loop) || !uses_wall_clock) {
242 break;
243 }
244 }
245} 245}
246 246
247void ARM_Dynarmic_32::Step() { 247u32 ARM_Dynarmic_32::GetSvcNumber() const {
248 jit.load()->Step(); 248 return svc_swi;
249} 249}
250 250
251ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, 251ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index abfe76644..5b1d60005 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -41,8 +41,6 @@ public:
41 void SetVectorReg(int index, u128 value) override; 41 void SetVectorReg(int index, u128 value) override;
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;
45 void Step() override;
46 VAddr GetTlsAddress() const override; 44 VAddr GetTlsAddress() const override;
47 void SetTlsAddress(VAddr address) override; 45 void SetTlsAddress(VAddr address) override;
48 void SetTPIDR_EL0(u64 value) override; 46 void SetTPIDR_EL0(u64 value) override;
@@ -70,6 +68,11 @@ public:
70 68
71 std::vector<BacktraceEntry> GetBacktrace() const override; 69 std::vector<BacktraceEntry> GetBacktrace() const override;
72 70
71protected:
72 Dynarmic::HaltReason RunJit() override;
73 Dynarmic::HaltReason StepJit() override;
74 u32 GetSvcNumber() const override;
75
73private: 76private:
74 std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const; 77 std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
75 78
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 1b1334598..d4c67eafd 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"
@@ -25,9 +26,6 @@ namespace Core {
25using Vector = Dynarmic::A64::Vector; 26using Vector = Dynarmic::A64::Vector;
26using namespace Common::Literals; 27using namespace Common::Literals;
27 28
28constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
29constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
30
31class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { 29class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
32public: 30public:
33 explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) 31 explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
@@ -119,8 +117,13 @@ public:
119 case Dynarmic::A64::Exception::SendEventLocal: 117 case Dynarmic::A64::Exception::SendEventLocal:
120 case Dynarmic::A64::Exception::Yield: 118 case Dynarmic::A64::Exception::Yield:
121 return; 119 return;
122 case Dynarmic::A64::Exception::Breakpoint:
123 default: 120 default:
121 if (parent.system.DebuggerEnabled()) {
122 parent.jit.load()->SetPC(pc);
123 parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
124 return;
125 }
126
124 parent.LogBacktrace(); 127 parent.LogBacktrace();
125 ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", 128 ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
126 static_cast<std::size_t>(exception), pc, MemoryReadCode(pc)); 129 static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
@@ -129,7 +132,7 @@ public:
129 132
130 void CallSVC(u32 swi) override { 133 void CallSVC(u32 swi) override {
131 parent.svc_swi = swi; 134 parent.svc_swi = swi;
132 parent.jit.load()->HaltExecution(svc_call); 135 parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
133 } 136 }
134 137
135 void AddTicks(u64 ticks) override { 138 void AddTicks(u64 ticks) override {
@@ -293,20 +296,16 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
293 return std::make_shared<Dynarmic::A64::Jit>(config); 296 return std::make_shared<Dynarmic::A64::Jit>(config);
294} 297}
295 298
296void ARM_Dynarmic_64::Run() { 299Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() {
297 while (true) { 300 return jit.load()->Run();
298 const auto hr = jit.load()->Run(); 301}
299 if (Has(hr, svc_call)) { 302
300 Kernel::Svc::Call(system, svc_swi); 303Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() {
301 } 304 return jit.load()->Step();
302 if (Has(hr, break_loop) || !uses_wall_clock) {
303 break;
304 }
305 }
306} 305}
307 306
308void ARM_Dynarmic_64::Step() { 307u32 ARM_Dynarmic_64::GetSvcNumber() const {
309 jit.load()->Step(); 308 return svc_swi;
310} 309}
311 310
312ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, 311ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index 01a7e4dad..abfbc3c3f 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -39,8 +39,6 @@ public:
39 void SetVectorReg(int index, u128 value) override; 39 void SetVectorReg(int index, u128 value) override;
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;
43 void Step() override;
44 VAddr GetTlsAddress() const override; 42 VAddr GetTlsAddress() const override;
45 void SetTlsAddress(VAddr address) override; 43 void SetTlsAddress(VAddr address) override;
46 void SetTPIDR_EL0(u64 value) override; 44 void SetTPIDR_EL0(u64 value) override;
@@ -64,6 +62,11 @@ public:
64 62
65 std::vector<BacktraceEntry> GetBacktrace() const override; 63 std::vector<BacktraceEntry> GetBacktrace() const override;
66 64
65protected:
66 Dynarmic::HaltReason RunJit() override;
67 Dynarmic::HaltReason StepJit() override;
68 u32 GetSvcNumber() const override;
69
67private: 70private:
68 std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, 71 std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
69 std::size_t address_space_bits) const; 72 std::size_t address_space_bits) const;
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..68ab33e46
--- /dev/null
+++ b/src/core/debugger/debugger.cpp
@@ -0,0 +1,268 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <mutex>
6#include <thread>
7
8#include <boost/asio.hpp>
9#include <boost/process/async_pipe.hpp>
10
11#include "common/logging/log.h"
12#include "common/thread.h"
13#include "core/core.h"
14#include "core/debugger/debugger.h"
15#include "core/debugger/debugger_interface.h"
16#include "core/debugger/gdbstub.h"
17#include "core/hle/kernel/global_scheduler_context.h"
18
19template <typename Readable, typename Buffer, typename Callback>
20static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
21 static_assert(std::is_trivial_v<Buffer>);
22 auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
23 r.async_read_some(boost_buffer, [&](const boost::system::error_code& error, size_t bytes_read) {
24 if (!error.failed()) {
25 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
26 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
27 c(received_data);
28 }
29
30 AsyncReceiveInto(r, buffer, c);
31 });
32}
33
34template <typename Readable, typename Buffer>
35static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
36 static_assert(std::is_trivial_v<Buffer>);
37 auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
38 size_t bytes_read = r.read_some(boost_buffer);
39 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
40 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
41 return received_data;
42}
43
44namespace Core {
45
46class DebuggerImpl : public DebuggerBackend {
47public:
48 explicit DebuggerImpl(Core::System& system_, u16 port)
49 : system{system_}, signal_pipe{io_context}, client_socket{io_context} {
50 frontend = std::make_unique<GDBStub>(*this, system);
51 InitializeServer(port);
52 }
53
54 ~DebuggerImpl() override {
55 ShutdownServer();
56 }
57
58 bool NotifyThreadStopped(Kernel::KThread* thread) {
59 std::scoped_lock lk{connection_lock};
60
61 if (stopped) {
62 // Do not notify the debugger about another event.
63 // It should be ignored.
64 return false;
65 }
66 stopped = true;
67
68 signal_pipe.write_some(boost::asio::buffer(&thread, sizeof(thread)));
69 return true;
70 }
71
72 std::span<const u8> ReadFromClient() override {
73 return ReceiveInto(client_socket, client_data);
74 }
75
76 void WriteToClient(std::span<const u8> data) override {
77 client_socket.write_some(boost::asio::buffer(data.data(), data.size_bytes()));
78 }
79
80 void SetActiveThread(Kernel::KThread* thread) override {
81 active_thread = thread;
82 }
83
84 Kernel::KThread* GetActiveThread() override {
85 return active_thread;
86 }
87
88private:
89 void InitializeServer(u16 port) {
90 using boost::asio::ip::tcp;
91
92 LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
93
94 // Run the connection thread.
95 connection_thread = std::jthread([&, port](std::stop_token stop_token) {
96 try {
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
101 acceptor.async_accept(client_socket, [](const auto&) {});
102 io_context.run_one();
103 io_context.restart();
104
105 if (stop_token.stop_requested()) {
106 return;
107 }
108
109 ThreadLoop(stop_token);
110 } catch (const std::exception& ex) {
111 LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
112 }
113 });
114 }
115
116 void ShutdownServer() {
117 connection_thread.request_stop();
118 io_context.stop();
119 connection_thread.join();
120 }
121
122 void ThreadLoop(std::stop_token stop_token) {
123 Common::SetCurrentThreadName("yuzu:Debugger");
124
125 // Set up the client signals for new data.
126 AsyncReceiveInto(signal_pipe, active_thread, [&](auto d) { PipeData(d); });
127 AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
128
129 // Stop the emulated CPU.
130 AllCoreStop();
131
132 // Set the active thread.
133 UpdateActiveThread();
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 UpdateActiveThread();
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 UpdateActiveThread();
160 frontend->Stopped(active_thread);
161 break;
162 }
163 case DebuggerAction::Continue:
164 active_thread->SetStepState(Kernel::StepState::NotStepping);
165 ResumeInactiveThreads();
166 AllCoreResume();
167 break;
168 case DebuggerAction::StepThreadUnlocked:
169 active_thread->SetStepState(Kernel::StepState::StepPending);
170 ResumeInactiveThreads();
171 AllCoreResume();
172 break;
173 case DebuggerAction::StepThreadLocked:
174 active_thread->SetStepState(Kernel::StepState::StepPending);
175 SuspendInactiveThreads();
176 AllCoreResume();
177 break;
178 case DebuggerAction::ShutdownEmulation: {
179 // Suspend all threads and release any locks held
180 active_thread->RequestSuspend(Kernel::SuspendType::Debug);
181 SuspendInactiveThreads();
182 AllCoreResume();
183
184 // Spawn another thread that will exit after shutdown,
185 // to avoid a deadlock
186 Core::System* system_ref{&system};
187 std::thread t([system_ref] { system_ref->Exit(); });
188 t.detach();
189 break;
190 }
191 }
192 }
193 }
194
195 void AllCoreStop() {
196 if (!suspend) {
197 suspend = system.StallCPU();
198 }
199 }
200
201 void AllCoreResume() {
202 stopped = false;
203 system.UnstallCPU();
204 suspend.reset();
205 }
206
207 void SuspendInactiveThreads() {
208 for (auto* thread : ThreadList()) {
209 if (thread != active_thread) {
210 thread->RequestSuspend(Kernel::SuspendType::Debug);
211 }
212 }
213 }
214
215 void ResumeInactiveThreads() {
216 for (auto* thread : ThreadList()) {
217 if (thread != active_thread) {
218 thread->Resume(Kernel::SuspendType::Debug);
219 thread->SetStepState(Kernel::StepState::NotStepping);
220 }
221 }
222 }
223
224 void UpdateActiveThread() {
225 const auto& threads{ThreadList()};
226 if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
227 active_thread = threads[0];
228 }
229 active_thread->Resume(Kernel::SuspendType::Debug);
230 active_thread->SetStepState(Kernel::StepState::NotStepping);
231 }
232
233 const std::vector<Kernel::KThread*>& ThreadList() {
234 return system.GlobalSchedulerContext().GetThreadList();
235 }
236
237private:
238 System& system;
239 std::unique_ptr<DebuggerFrontend> frontend;
240
241 std::jthread connection_thread;
242 std::mutex connection_lock;
243 boost::asio::io_context io_context;
244 boost::process::async_pipe signal_pipe;
245 boost::asio::ip::tcp::socket client_socket;
246 std::optional<std::unique_lock<std::mutex>> suspend;
247
248 Kernel::KThread* active_thread;
249 bool stopped;
250
251 std::array<u8, 4096> client_data;
252};
253
254Debugger::Debugger(Core::System& system, u16 port) {
255 try {
256 impl = std::make_unique<DebuggerImpl>(system, port);
257 } catch (const std::exception& ex) {
258 LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what());
259 }
260}
261
262Debugger::~Debugger() = default;
263
264bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
265 return impl && impl->NotifyThreadStopped(thread);
266}
267
268} // namespace Core
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h
new file mode 100644
index 000000000..ea36c6ab2
--- /dev/null
+++ b/src/core/debugger/debugger.h
@@ -0,0 +1,41 @@
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
38private:
39 std::unique_ptr<DebuggerImpl> impl;
40};
41} // namespace Core
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h
new file mode 100644
index 000000000..35ba0bc61
--- /dev/null
+++ b/src/core/debugger/debugger_interface.h
@@ -0,0 +1,79 @@
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 StepThreadLocked, ///< Step the currently-active thread without resuming others.
22 StepThreadUnlocked, ///< Step the currently-active thread and resume others.
23 ShutdownEmulation, ///< Shut down the emulator.
24};
25
26class DebuggerBackend {
27public:
28 virtual ~DebuggerBackend() = default;
29
30 /**
31 * Can be invoked from a callback to synchronously wait for more data.
32 * Will return as soon as least one byte is received. Reads up to 4096 bytes.
33 */
34 virtual std::span<const u8> ReadFromClient() = 0;
35
36 /**
37 * Can be invoked from a callback to write data to the client.
38 * Returns immediately after the data is sent.
39 */
40 virtual void WriteToClient(std::span<const u8> data) = 0;
41
42 /**
43 * Gets the currently active thread when the debugger is stopped.
44 */
45 virtual Kernel::KThread* GetActiveThread() = 0;
46
47 /**
48 * Sets the currently active thread when the debugger is stopped.
49 */
50 virtual void SetActiveThread(Kernel::KThread* thread) = 0;
51};
52
53class DebuggerFrontend {
54public:
55 explicit DebuggerFrontend(DebuggerBackend& backend_) : backend{backend_} {}
56
57 virtual ~DebuggerFrontend() = default;
58
59 /**
60 * Called after the client has successfully connected to the port.
61 */
62 virtual void Connected() = 0;
63
64 /**
65 * Called when emulation has stopped.
66 */
67 virtual void Stopped(Kernel::KThread* thread) = 0;
68
69 /**
70 * Called when new data is asynchronously received on the client socket.
71 * A list of actions to perform is returned.
72 */
73 [[nodiscard]] virtual std::vector<DebuggerAction> ClientData(std::span<const u8> data) = 0;
74
75protected:
76 DebuggerBackend& backend;
77};
78
79} // namespace Core
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
new file mode 100644
index 000000000..682651a86
--- /dev/null
+++ b/src/core/debugger/gdbstub.cpp
@@ -0,0 +1,612 @@
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/algorithm/string.hpp>
10
11#include "common/hex_util.h"
12#include "common/logging/log.h"
13#include "common/scope_exit.h"
14#include "core/arm/arm_interface.h"
15#include "core/core.h"
16#include "core/debugger/gdbstub.h"
17#include "core/debugger/gdbstub_arch.h"
18#include "core/hle/kernel/k_page_table.h"
19#include "core/hle/kernel/k_process.h"
20#include "core/hle/kernel/k_thread.h"
21#include "core/loader/loader.h"
22#include "core/memory.h"
23
24namespace Core {
25
26constexpr char GDB_STUB_START = '$';
27constexpr char GDB_STUB_END = '#';
28constexpr char GDB_STUB_ACK = '+';
29constexpr char GDB_STUB_NACK = '-';
30constexpr char GDB_STUB_INT3 = 0x03;
31constexpr int GDB_STUB_SIGTRAP = 5;
32
33constexpr char GDB_STUB_REPLY_ERR[] = "E01";
34constexpr char GDB_STUB_REPLY_OK[] = "OK";
35constexpr char GDB_STUB_REPLY_EMPTY[] = "";
36
37static u8 CalculateChecksum(std::string_view data) {
38 return std::accumulate(data.begin(), data.end(), u8{0},
39 [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); });
40}
41
42static std::string EscapeGDB(std::string_view data) {
43 std::string escaped;
44 escaped.reserve(data.size());
45
46 for (char c : data) {
47 switch (c) {
48 case '#':
49 escaped += "}\x03";
50 break;
51 case '$':
52 escaped += "}\x04";
53 break;
54 case '*':
55 escaped += "}\x0a";
56 break;
57 case '}':
58 escaped += "}\x5d";
59 break;
60 default:
61 escaped += c;
62 break;
63 }
64 }
65
66 return escaped;
67}
68
69static std::string EscapeXML(std::string_view data) {
70 std::string escaped;
71 escaped.reserve(data.size());
72
73 for (char c : data) {
74 switch (c) {
75 case '&':
76 escaped += "&amp;";
77 break;
78 case '"':
79 escaped += "&quot;";
80 break;
81 case '<':
82 escaped += "&lt;";
83 break;
84 case '>':
85 escaped += "&gt;";
86 break;
87 default:
88 escaped += c;
89 break;
90 }
91 }
92
93 return escaped;
94}
95
96GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
97 : DebuggerFrontend(backend_), system{system_} {
98 if (system.CurrentProcess()->Is64BitProcess()) {
99 arch = std::make_unique<GDBStubA64>();
100 } else {
101 arch = std::make_unique<GDBStubA32>();
102 }
103}
104
105GDBStub::~GDBStub() = default;
106
107void GDBStub::Connected() {}
108
109void GDBStub::Stopped(Kernel::KThread* thread) {
110 SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
111}
112
113std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
114 std::vector<DebuggerAction> actions;
115 current_command.insert(current_command.end(), data.begin(), data.end());
116
117 while (current_command.size() != 0) {
118 ProcessData(actions);
119 }
120
121 return actions;
122}
123
124void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
125 const char c{current_command[0]};
126
127 // Acknowledgement
128 if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
129 current_command.erase(current_command.begin());
130 return;
131 }
132
133 // Interrupt
134 if (c == GDB_STUB_INT3) {
135 LOG_INFO(Debug_GDBStub, "Received interrupt");
136 current_command.erase(current_command.begin());
137 actions.push_back(DebuggerAction::Interrupt);
138 SendStatus(GDB_STUB_ACK);
139 return;
140 }
141
142 // Otherwise, require the data to be the start of a command
143 if (c != GDB_STUB_START) {
144 LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
145 current_command.clear();
146 SendStatus(GDB_STUB_NACK);
147 return;
148 }
149
150 // Continue reading until command is complete
151 while (CommandEnd() == current_command.end()) {
152 const auto new_data{backend.ReadFromClient()};
153 current_command.insert(current_command.end(), new_data.begin(), new_data.end());
154 }
155
156 // Execute and respond to GDB
157 const auto command{DetachCommand()};
158
159 if (command) {
160 SendStatus(GDB_STUB_ACK);
161 ExecuteCommand(*command, actions);
162 } else {
163 SendStatus(GDB_STUB_NACK);
164 }
165}
166
167void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
168 LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
169
170 if (packet.length() == 0) {
171 SendReply(GDB_STUB_REPLY_ERR);
172 return;
173 }
174
175 if (packet.starts_with("vCont")) {
176 HandleVCont(packet.substr(5), actions);
177 return;
178 }
179
180 std::string_view command{packet.substr(1, packet.size())};
181
182 switch (packet[0]) {
183 case 'H': {
184 Kernel::KThread* thread{nullptr};
185 s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
186 if (thread_id >= 1) {
187 thread = GetThreadByID(thread_id);
188 } else {
189 thread = backend.GetActiveThread();
190 }
191
192 if (thread) {
193 SendReply(GDB_STUB_REPLY_OK);
194 backend.SetActiveThread(thread);
195 } else {
196 SendReply(GDB_STUB_REPLY_ERR);
197 }
198 break;
199 }
200 case 'T': {
201 s64 thread_id{strtoll(command.data(), nullptr, 16)};
202 if (GetThreadByID(thread_id)) {
203 SendReply(GDB_STUB_REPLY_OK);
204 } else {
205 SendReply(GDB_STUB_REPLY_ERR);
206 }
207 break;
208 }
209 case 'Q':
210 case 'q':
211 HandleQuery(command);
212 break;
213 case '?':
214 SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
215 break;
216 case 'k':
217 LOG_INFO(Debug_GDBStub, "Shutting down emulation");
218 actions.push_back(DebuggerAction::ShutdownEmulation);
219 break;
220 case 'g':
221 SendReply(arch->ReadRegisters(backend.GetActiveThread()));
222 break;
223 case 'G':
224 arch->WriteRegisters(backend.GetActiveThread(), command);
225 SendReply(GDB_STUB_REPLY_OK);
226 break;
227 case 'p': {
228 const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
229 SendReply(arch->RegRead(backend.GetActiveThread(), reg));
230 break;
231 }
232 case 'P': {
233 const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
234 const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
235 arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
236 break;
237 }
238 case 'm': {
239 const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
240 const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
241 const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
242
243 if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
244 std::vector<u8> mem(size);
245 system.Memory().ReadBlock(addr, mem.data(), size);
246
247 SendReply(Common::HexToString(mem));
248 } else {
249 SendReply(GDB_STUB_REPLY_ERR);
250 }
251 break;
252 }
253 case 'M': {
254 const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
255 const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
256
257 const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
258 const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
259
260 const auto mem_substr{std::string_view(command).substr(mem_sep)};
261 const auto mem{Common::HexStringToVector(mem_substr, false)};
262
263 if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
264 system.Memory().WriteBlock(addr, mem.data(), size);
265 system.InvalidateCpuInstructionCacheRange(addr, size);
266 SendReply(GDB_STUB_REPLY_OK);
267 } else {
268 SendReply(GDB_STUB_REPLY_ERR);
269 }
270 break;
271 }
272 case 's':
273 actions.push_back(DebuggerAction::StepThreadLocked);
274 break;
275 case 'C':
276 case 'c':
277 actions.push_back(DebuggerAction::Continue);
278 break;
279 case 'Z': {
280 const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
281 const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
282
283 if (system.Memory().IsValidVirtualAddress(addr)) {
284 replaced_instructions[addr] = system.Memory().Read32(addr);
285 system.Memory().Write32(addr, arch->BreakpointInstruction());
286 system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
287
288 SendReply(GDB_STUB_REPLY_OK);
289 } else {
290 SendReply(GDB_STUB_REPLY_ERR);
291 }
292 break;
293 }
294 case 'z': {
295 const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
296 const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
297
298 const auto orig_insn{replaced_instructions.find(addr)};
299 if (system.Memory().IsValidVirtualAddress(addr) &&
300 orig_insn != replaced_instructions.end()) {
301 system.Memory().Write32(addr, orig_insn->second);
302 system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
303 replaced_instructions.erase(addr);
304
305 SendReply(GDB_STUB_REPLY_OK);
306 } else {
307 SendReply(GDB_STUB_REPLY_ERR);
308 }
309 break;
310 }
311 default:
312 SendReply(GDB_STUB_REPLY_EMPTY);
313 break;
314 }
315}
316
317// Structure offsets are from Atmosphere
318// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
319
320static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
321 const Kernel::KThread* thread) {
322 // Read thread type from TLS
323 const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)};
324 const VAddr argument_thread_type{thread->GetArgument()};
325
326 if (argument_thread_type && tls_thread_type != argument_thread_type) {
327 // Probably not created by nnsdk, no name available.
328 return std::nullopt;
329 }
330
331 if (!tls_thread_type) {
332 return std::nullopt;
333 }
334
335 const u16 version{memory.Read16(tls_thread_type + 0x26)};
336 VAddr name_pointer{};
337 if (version == 1) {
338 name_pointer = memory.Read32(tls_thread_type + 0xe4);
339 } else {
340 name_pointer = memory.Read32(tls_thread_type + 0xe8);
341 }
342
343 if (!name_pointer) {
344 // No name provided.
345 return std::nullopt;
346 }
347
348 return memory.ReadCString(name_pointer, 256);
349}
350
351static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
352 const Kernel::KThread* thread) {
353 // Read thread type from TLS
354 const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)};
355 const VAddr argument_thread_type{thread->GetArgument()};
356
357 if (argument_thread_type && tls_thread_type != argument_thread_type) {
358 // Probably not created by nnsdk, no name available.
359 return std::nullopt;
360 }
361
362 if (!tls_thread_type) {
363 return std::nullopt;
364 }
365
366 const u16 version{memory.Read16(tls_thread_type + 0x46)};
367 VAddr name_pointer{};
368 if (version == 1) {
369 name_pointer = memory.Read64(tls_thread_type + 0x1a0);
370 } else {
371 name_pointer = memory.Read64(tls_thread_type + 0x1a8);
372 }
373
374 if (!name_pointer) {
375 // No name provided.
376 return std::nullopt;
377 }
378
379 return memory.ReadCString(name_pointer, 256);
380}
381
382static std::optional<std::string> GetThreadName(Core::System& system,
383 const Kernel::KThread* thread) {
384 if (system.CurrentProcess()->Is64BitProcess()) {
385 return GetNameFromThreadType64(system.Memory(), thread);
386 } else {
387 return GetNameFromThreadType32(system.Memory(), thread);
388 }
389}
390
391static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
392 switch (thread->GetWaitReasonForDebugging()) {
393 case Kernel::ThreadWaitReasonForDebugging::Sleep:
394 return "Sleep";
395 case Kernel::ThreadWaitReasonForDebugging::IPC:
396 return "IPC";
397 case Kernel::ThreadWaitReasonForDebugging::Synchronization:
398 return "Synchronization";
399 case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
400 return "ConditionVar";
401 case Kernel::ThreadWaitReasonForDebugging::Arbitration:
402 return "Arbitration";
403 case Kernel::ThreadWaitReasonForDebugging::Suspended:
404 return "Suspended";
405 default:
406 return "Unknown";
407 }
408}
409
410static std::string GetThreadState(const Kernel::KThread* thread) {
411 switch (thread->GetState()) {
412 case Kernel::ThreadState::Initialized:
413 return "Initialized";
414 case Kernel::ThreadState::Waiting:
415 return fmt::format("Waiting ({})", GetThreadWaitReason(thread));
416 case Kernel::ThreadState::Runnable:
417 return "Runnable";
418 case Kernel::ThreadState::Terminated:
419 return "Terminated";
420 default:
421 return "Unknown";
422 }
423}
424
425void GDBStub::HandleQuery(std::string_view command) {
426 if (command.starts_with("TStatus")) {
427 // no tracepoint support
428 SendReply("T0");
429 } else if (command.starts_with("Supported")) {
430 SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;"
431 "vContSupported+;QStartNoAckMode+");
432 } else if (command.starts_with("Xfer:features:read:target.xml:")) {
433 const auto offset{command.substr(30)};
434 const auto amount{command.substr(command.find(',') + 1)};
435
436 const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
437 const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
438 const auto target_xml{arch->GetTargetXML()};
439
440 if (offset_val + amount_val > target_xml.size()) {
441 SendReply("l" + target_xml.substr(offset_val));
442 } else {
443 SendReply("m" + target_xml.substr(offset_val, amount_val));
444 }
445 } else if (command.starts_with("Offsets")) {
446 Loader::AppLoader::Modules modules;
447 system.GetAppLoader().ReadNSOModules(modules);
448
449 const auto main = std::find_if(modules.begin(), modules.end(),
450 [](const auto& key) { return key.second == "main"; });
451 if (main != modules.end()) {
452 SendReply(fmt::format("TextSeg={:x}", main->first));
453 } else {
454 SendReply(fmt::format("TextSeg={:x}",
455 system.CurrentProcess()->PageTable().GetCodeRegionStart()));
456 }
457 } else if (command.starts_with("fThreadInfo")) {
458 // beginning of list
459 const auto& threads = system.GlobalSchedulerContext().GetThreadList();
460 std::vector<std::string> thread_ids;
461 for (const auto& thread : threads) {
462 thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
463 }
464 SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
465 } else if (command.starts_with("sThreadInfo")) {
466 // end of list
467 SendReply("l");
468 } else if (command.starts_with("Xfer:threads:read::")) {
469 std::string buffer;
470 buffer += R"(<?xml version="1.0"?>)";
471 buffer += "<threads>";
472
473 const auto& threads = system.GlobalSchedulerContext().GetThreadList();
474 for (const auto* thread : threads) {
475 auto thread_name{GetThreadName(system, thread)};
476 if (!thread_name) {
477 thread_name = fmt::format("Thread {:d}", thread->GetThreadID());
478 }
479
480 buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
481 thread->GetThreadID(), thread->GetActiveCore(),
482 EscapeXML(*thread_name), GetThreadState(thread));
483 }
484
485 buffer += "</threads>";
486
487 const auto offset{command.substr(19)};
488 const auto amount{command.substr(command.find(',') + 1)};
489
490 const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
491 const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
492
493 if (offset_val + amount_val > buffer.size()) {
494 SendReply("l" + buffer.substr(offset_val));
495 } else {
496 SendReply("m" + buffer.substr(offset_val, amount_val));
497 }
498 } else if (command.starts_with("Attached")) {
499 SendReply("0");
500 } else if (command.starts_with("StartNoAckMode")) {
501 no_ack = true;
502 SendReply(GDB_STUB_REPLY_OK);
503 } else {
504 SendReply(GDB_STUB_REPLY_EMPTY);
505 }
506}
507
508void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) {
509 if (command == "?") {
510 // Continuing and stepping are supported
511 // (signal is ignored, but required for GDB to use vCont)
512 SendReply("vCont;c;C;s;S");
513 return;
514 }
515
516 Kernel::KThread* stepped_thread{nullptr};
517 bool lock_execution{true};
518
519 std::vector<std::string> entries;
520 boost::split(entries, command.substr(1), boost::is_any_of(";"));
521 for (const auto& thread_action : entries) {
522 std::vector<std::string> parts;
523 boost::split(parts, thread_action, boost::is_any_of(":"));
524
525 if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) {
526 lock_execution = false;
527 }
528 if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) {
529 stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
530 }
531 }
532
533 if (stepped_thread) {
534 backend.SetActiveThread(stepped_thread);
535 actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked
536 : DebuggerAction::StepThreadUnlocked);
537 } else {
538 actions.push_back(DebuggerAction::Continue);
539 }
540}
541
542Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
543 const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
544 for (auto* thread : threads) {
545 if (thread->GetThreadID() == thread_id) {
546 return thread;
547 }
548 }
549
550 return nullptr;
551}
552
553std::vector<char>::const_iterator GDBStub::CommandEnd() const {
554 // Find the end marker
555 const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
556
557 // Require the checksum to be present
558 return std::min(end + 2, current_command.end());
559}
560
561std::optional<std::string> GDBStub::DetachCommand() {
562 // Slice the string part from the beginning to the end marker
563 const auto end{CommandEnd()};
564
565 // Extract possible command data
566 std::string data(current_command.data(), end - current_command.begin() + 1);
567
568 // Shift over the remaining contents
569 current_command.erase(current_command.begin(), end + 1);
570
571 // Validate received command
572 if (data[0] != GDB_STUB_START) {
573 LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
574 return std::nullopt;
575 }
576
577 u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
578 u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
579
580 // Verify checksum
581 if (calculated != received) {
582 LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
583 calculated, received);
584 return std::nullopt;
585 }
586
587 return data.substr(1, data.size() - 4);
588}
589
590void GDBStub::SendReply(std::string_view data) {
591 const auto escaped{EscapeGDB(data)};
592 const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END,
593 CalculateChecksum(escaped))};
594 LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
595
596 // C++ string support is complete rubbish
597 const u8* output_begin = reinterpret_cast<const u8*>(output.data());
598 const u8* output_end = output_begin + output.size();
599 backend.WriteToClient(std::span<const u8>(output_begin, output_end));
600}
601
602void GDBStub::SendStatus(char status) {
603 if (no_ack) {
604 return;
605 }
606
607 std::array<u8, 1> buf = {static_cast<u8>(status)};
608 LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
609 backend.WriteToClient(buf);
610}
611
612} // namespace Core
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
new file mode 100644
index 000000000..1bb638187
--- /dev/null
+++ b/src/core/debugger/gdbstub.h
@@ -0,0 +1,48 @@
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() override;
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 HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
32 void HandleQuery(std::string_view command);
33 std::vector<char>::const_iterator CommandEnd() const;
34 std::optional<std::string> DetachCommand();
35 Kernel::KThread* GetThreadByID(u64 thread_id);
36
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 bool no_ack{};
46};
47
48} // namespace Core
diff --git a/src/core/debugger/gdbstub_arch.cpp b/src/core/debugger/gdbstub_arch.cpp
new file mode 100644
index 000000000..750c353b9
--- /dev/null
+++ b/src/core/debugger/gdbstub_arch.cpp
@@ -0,0 +1,483 @@
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 <architecture>aarch64</architecture>
50 <feature name="org.gnu.gdb.aarch64.core">
51 <reg name="x0" bitsize="64"/>
52 <reg name="x1" bitsize="64"/>
53 <reg name="x2" bitsize="64"/>
54 <reg name="x3" bitsize="64"/>
55 <reg name="x4" bitsize="64"/>
56 <reg name="x5" bitsize="64"/>
57 <reg name="x6" bitsize="64"/>
58 <reg name="x7" bitsize="64"/>
59 <reg name="x8" bitsize="64"/>
60 <reg name="x9" bitsize="64"/>
61 <reg name="x10" bitsize="64"/>
62 <reg name="x11" bitsize="64"/>
63 <reg name="x12" bitsize="64"/>
64 <reg name="x13" bitsize="64"/>
65 <reg name="x14" bitsize="64"/>
66 <reg name="x15" bitsize="64"/>
67 <reg name="x16" bitsize="64"/>
68 <reg name="x17" bitsize="64"/>
69 <reg name="x18" bitsize="64"/>
70 <reg name="x19" bitsize="64"/>
71 <reg name="x20" bitsize="64"/>
72 <reg name="x21" bitsize="64"/>
73 <reg name="x22" bitsize="64"/>
74 <reg name="x23" bitsize="64"/>
75 <reg name="x24" bitsize="64"/>
76 <reg name="x25" bitsize="64"/>
77 <reg name="x26" bitsize="64"/>
78 <reg name="x27" bitsize="64"/>
79 <reg name="x28" bitsize="64"/>
80 <reg name="x29" bitsize="64"/>
81 <reg name="x30" bitsize="64"/>
82 <reg name="sp" bitsize="64" type="data_ptr"/>
83 <reg name="pc" bitsize="64" type="code_ptr"/>
84 <flags id="cpsr_flags" size="4">
85 <field name="SP" start="0" end="0"/>
86 <field name="" start="1" end="1"/>
87 <field name="EL" start="2" end="3"/>
88 <field name="nRW" start="4" end="4"/>
89 <field name="" start="5" end="5"/>
90 <field name="F" start="6" end="6"/>
91 <field name="I" start="7" end="7"/>
92 <field name="A" start="8" end="8"/>
93 <field name="D" start="9" end="9"/>
94 <field name="IL" start="20" end="20"/>
95 <field name="SS" start="21" end="21"/>
96 <field name="V" start="28" end="28"/>
97 <field name="C" start="29" end="29"/>
98 <field name="Z" start="30" end="30"/>
99 <field name="N" start="31" end="31"/>
100 </flags>
101 <reg name="cpsr" bitsize="32" type="cpsr_flags"/>
102 </feature>
103 <feature name="org.gnu.gdb.aarch64.fpu">
104 <vector id="v2d" type="ieee_double" count="2"/>
105 <vector id="v2u" type="uint64" count="2"/>
106 <vector id="v2i" type="int64" count="2"/>
107 <vector id="v4f" type="ieee_single" count="4"/>
108 <vector id="v4u" type="uint32" count="4"/>
109 <vector id="v4i" type="int32" count="4"/>
110 <vector id="v8u" type="uint16" count="8"/>
111 <vector id="v8i" type="int16" count="8"/>
112 <vector id="v16u" type="uint8" count="16"/>
113 <vector id="v16i" type="int8" count="16"/>
114 <vector id="v1u" type="uint128" count="1"/>
115 <vector id="v1i" type="int128" count="1"/>
116 <union id="vnd">
117 <field name="f" type="v2d"/>
118 <field name="u" type="v2u"/>
119 <field name="s" type="v2i"/>
120 </union>
121 <union id="vns">
122 <field name="f" type="v4f"/>
123 <field name="u" type="v4u"/>
124 <field name="s" type="v4i"/>
125 </union>
126 <union id="vnh">
127 <field name="u" type="v8u"/>
128 <field name="s" type="v8i"/>
129 </union>
130 <union id="vnb">
131 <field name="u" type="v16u"/>
132 <field name="s" type="v16i"/>
133 </union>
134 <union id="vnq">
135 <field name="u" type="v1u"/>
136 <field name="s" type="v1i"/>
137 </union>
138 <union id="aarch64v">
139 <field name="d" type="vnd"/>
140 <field name="s" type="vns"/>
141 <field name="h" type="vnh"/>
142 <field name="b" type="vnb"/>
143 <field name="q" type="vnq"/>
144 </union>
145 <reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
146 <reg name="v1" bitsize="128" type="aarch64v" />
147 <reg name="v2" bitsize="128" type="aarch64v" />
148 <reg name="v3" bitsize="128" type="aarch64v" />
149 <reg name="v4" bitsize="128" type="aarch64v" />
150 <reg name="v5" bitsize="128" type="aarch64v" />
151 <reg name="v6" bitsize="128" type="aarch64v" />
152 <reg name="v7" bitsize="128" type="aarch64v" />
153 <reg name="v8" bitsize="128" type="aarch64v" />
154 <reg name="v9" bitsize="128" type="aarch64v" />
155 <reg name="v10" bitsize="128" type="aarch64v"/>
156 <reg name="v11" bitsize="128" type="aarch64v"/>
157 <reg name="v12" bitsize="128" type="aarch64v"/>
158 <reg name="v13" bitsize="128" type="aarch64v"/>
159 <reg name="v14" bitsize="128" type="aarch64v"/>
160 <reg name="v15" bitsize="128" type="aarch64v"/>
161 <reg name="v16" bitsize="128" type="aarch64v"/>
162 <reg name="v17" bitsize="128" type="aarch64v"/>
163 <reg name="v18" bitsize="128" type="aarch64v"/>
164 <reg name="v19" bitsize="128" type="aarch64v"/>
165 <reg name="v20" bitsize="128" type="aarch64v"/>
166 <reg name="v21" bitsize="128" type="aarch64v"/>
167 <reg name="v22" bitsize="128" type="aarch64v"/>
168 <reg name="v23" bitsize="128" type="aarch64v"/>
169 <reg name="v24" bitsize="128" type="aarch64v"/>
170 <reg name="v25" bitsize="128" type="aarch64v"/>
171 <reg name="v26" bitsize="128" type="aarch64v"/>
172 <reg name="v27" bitsize="128" type="aarch64v"/>
173 <reg name="v28" bitsize="128" type="aarch64v"/>
174 <reg name="v29" bitsize="128" type="aarch64v"/>
175 <reg name="v30" bitsize="128" type="aarch64v"/>
176 <reg name="v31" bitsize="128" type="aarch64v"/>
177 <reg name="fpsr" bitsize="32"/>
178 <reg name="fpcr" bitsize="32"/>
179 </feature>
180</target>)";
181
182 return target_xml;
183}
184
185std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const {
186 if (!thread) {
187 return "";
188 }
189
190 const auto& context{thread->GetContext64()};
191 const auto& gprs{context.cpu_registers};
192 const auto& fprs{context.vector_registers};
193
194 if (id <= SP_REGISTER) {
195 return ValueToHex(gprs[id]);
196 } else if (id == PC_REGISTER) {
197 return ValueToHex(context.pc);
198 } else if (id == PSTATE_REGISTER) {
199 return ValueToHex(context.pstate);
200 } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) {
201 return ValueToHex(fprs[id - Q0_REGISTER]);
202 } else if (id == FPSR_REGISTER) {
203 return ValueToHex(context.fpsr);
204 } else if (id == FPCR_REGISTER) {
205 return ValueToHex(context.fpcr);
206 } else {
207 return "";
208 }
209}
210
211void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
212 if (!thread) {
213 return;
214 }
215
216 auto& context{thread->GetContext64()};
217
218 if (id <= SP_REGISTER) {
219 context.cpu_registers[id] = HexToValue<u64>(value);
220 } else if (id == PC_REGISTER) {
221 context.pc = HexToValue<u64>(value);
222 } else if (id == PSTATE_REGISTER) {
223 context.pstate = HexToValue<u32>(value);
224 } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) {
225 context.vector_registers[id - Q0_REGISTER] = HexToValue<u128>(value);
226 } else if (id == FPSR_REGISTER) {
227 context.fpsr = HexToValue<u32>(value);
228 } else if (id == FPCR_REGISTER) {
229 context.fpcr = HexToValue<u32>(value);
230 }
231}
232
233std::string GDBStubA64::ReadRegisters(const Kernel::KThread* thread) const {
234 std::string output;
235
236 for (size_t reg = 0; reg <= FPCR_REGISTER; reg++) {
237 output += RegRead(thread, reg);
238 }
239
240 return output;
241}
242
243void GDBStubA64::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
244 for (size_t i = 0, reg = 0; reg <= FPCR_REGISTER; reg++) {
245 if (reg <= SP_REGISTER || reg == PC_REGISTER) {
246 RegWrite(thread, reg, register_data.substr(i, 16));
247 i += 16;
248 } else if (reg == PSTATE_REGISTER || reg == FPCR_REGISTER || reg == FPSR_REGISTER) {
249 RegWrite(thread, reg, register_data.substr(i, 8));
250 i += 8;
251 } else if (reg >= Q0_REGISTER && reg < FPCR_REGISTER) {
252 RegWrite(thread, reg, register_data.substr(i, 32));
253 i += 32;
254 }
255 }
256}
257
258std::string GDBStubA64::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
259 return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
260 RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
261 LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
262}
263
264u32 GDBStubA64::BreakpointInstruction() const {
265 // A64: brk #0
266 return 0xd4200000;
267}
268
269std::string GDBStubA32::GetTargetXML() const {
270 constexpr const char* target_xml =
271 R"(<?xml version="1.0"?>
272<!DOCTYPE target SYSTEM "gdb-target.dtd">
273<target version="1.0">
274 <architecture>arm</architecture>
275 <feature name="org.gnu.gdb.arm.core">
276 <reg name="r0" bitsize="32" type="uint32"/>
277 <reg name="r1" bitsize="32" type="uint32"/>
278 <reg name="r2" bitsize="32" type="uint32"/>
279 <reg name="r3" bitsize="32" type="uint32"/>
280 <reg name="r4" bitsize="32" type="uint32"/>
281 <reg name="r5" bitsize="32" type="uint32"/>
282 <reg name="r6" bitsize="32" type="uint32"/>
283 <reg name="r7" bitsize="32" type="uint32"/>
284 <reg name="r8" bitsize="32" type="uint32"/>
285 <reg name="r9" bitsize="32" type="uint32"/>
286 <reg name="r10" bitsize="32" type="uint32"/>
287 <reg name="r11" bitsize="32" type="uint32"/>
288 <reg name="r12" bitsize="32" type="uint32"/>
289 <reg name="sp" bitsize="32" type="data_ptr"/>
290 <reg name="lr" bitsize="32" type="code_ptr"/>
291 <reg name="pc" bitsize="32" type="code_ptr"/>
292 <!-- The CPSR is register 25, rather than register 16, because
293 the FPA registers historically were placed between the PC
294 and the CPSR in the "g" packet. -->
295 <reg name="cpsr" bitsize="32" regnum="25"/>
296 </feature>
297 <feature name="org.gnu.gdb.arm.vfp">
298 <vector id="neon_uint8x8" type="uint8" count="8"/>
299 <vector id="neon_uint16x4" type="uint16" count="4"/>
300 <vector id="neon_uint32x2" type="uint32" count="2"/>
301 <vector id="neon_float32x2" type="ieee_single" count="2"/>
302 <union id="neon_d">
303 <field name="u8" type="neon_uint8x8"/>
304 <field name="u16" type="neon_uint16x4"/>
305 <field name="u32" type="neon_uint32x2"/>
306 <field name="u64" type="uint64"/>
307 <field name="f32" type="neon_float32x2"/>
308 <field name="f64" type="ieee_double"/>
309 </union>
310 <vector id="neon_uint8x16" type="uint8" count="16"/>
311 <vector id="neon_uint16x8" type="uint16" count="8"/>
312 <vector id="neon_uint32x4" type="uint32" count="4"/>
313 <vector id="neon_uint64x2" type="uint64" count="2"/>
314 <vector id="neon_float32x4" type="ieee_single" count="4"/>
315 <vector id="neon_float64x2" type="ieee_double" count="2"/>
316 <union id="neon_q">
317 <field name="u8" type="neon_uint8x16"/>
318 <field name="u16" type="neon_uint16x8"/>
319 <field name="u32" type="neon_uint32x4"/>
320 <field name="u64" type="neon_uint64x2"/>
321 <field name="f32" type="neon_float32x4"/>
322 <field name="f64" type="neon_float64x2"/>
323 </union>
324 <reg name="d0" bitsize="64" type="neon_d" regnum="32"/>
325 <reg name="d1" bitsize="64" type="neon_d"/>
326 <reg name="d2" bitsize="64" type="neon_d"/>
327 <reg name="d3" bitsize="64" type="neon_d"/>
328 <reg name="d4" bitsize="64" type="neon_d"/>
329 <reg name="d5" bitsize="64" type="neon_d"/>
330 <reg name="d6" bitsize="64" type="neon_d"/>
331 <reg name="d7" bitsize="64" type="neon_d"/>
332 <reg name="d8" bitsize="64" type="neon_d"/>
333 <reg name="d9" bitsize="64" type="neon_d"/>
334 <reg name="d10" bitsize="64" type="neon_d"/>
335 <reg name="d11" bitsize="64" type="neon_d"/>
336 <reg name="d12" bitsize="64" type="neon_d"/>
337 <reg name="d13" bitsize="64" type="neon_d"/>
338 <reg name="d14" bitsize="64" type="neon_d"/>
339 <reg name="d15" bitsize="64" type="neon_d"/>
340 <reg name="d16" bitsize="64" type="neon_d"/>
341 <reg name="d17" bitsize="64" type="neon_d"/>
342 <reg name="d18" bitsize="64" type="neon_d"/>
343 <reg name="d19" bitsize="64" type="neon_d"/>
344 <reg name="d20" bitsize="64" type="neon_d"/>
345 <reg name="d21" bitsize="64" type="neon_d"/>
346 <reg name="d22" bitsize="64" type="neon_d"/>
347 <reg name="d23" bitsize="64" type="neon_d"/>
348 <reg name="d24" bitsize="64" type="neon_d"/>
349 <reg name="d25" bitsize="64" type="neon_d"/>
350 <reg name="d26" bitsize="64" type="neon_d"/>
351 <reg name="d27" bitsize="64" type="neon_d"/>
352 <reg name="d28" bitsize="64" type="neon_d"/>
353 <reg name="d29" bitsize="64" type="neon_d"/>
354 <reg name="d30" bitsize="64" type="neon_d"/>
355 <reg name="d31" bitsize="64" type="neon_d"/>
356
357 <reg name="q0" bitsize="128" type="neon_q" regnum="64"/>
358 <reg name="q1" bitsize="128" type="neon_q"/>
359 <reg name="q2" bitsize="128" type="neon_q"/>
360 <reg name="q3" bitsize="128" type="neon_q"/>
361 <reg name="q4" bitsize="128" type="neon_q"/>
362 <reg name="q5" bitsize="128" type="neon_q"/>
363 <reg name="q6" bitsize="128" type="neon_q"/>
364 <reg name="q7" bitsize="128" type="neon_q"/>
365 <reg name="q8" bitsize="128" type="neon_q"/>
366 <reg name="q9" bitsize="128" type="neon_q"/>
367 <reg name="q10" bitsize="128" type="neon_q"/>
368 <reg name="q10" bitsize="128" type="neon_q"/>
369 <reg name="q12" bitsize="128" type="neon_q"/>
370 <reg name="q13" bitsize="128" type="neon_q"/>
371 <reg name="q14" bitsize="128" type="neon_q"/>
372 <reg name="q15" bitsize="128" type="neon_q"/>
373
374 <reg name="fpscr" bitsize="32" type="int" group="float" regnum="80"/>
375 </feature>
376</target>)";
377
378 return target_xml;
379}
380
381std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const {
382 if (!thread) {
383 return "";
384 }
385
386 const auto& context{thread->GetContext32()};
387 const auto& gprs{context.cpu_registers};
388 const auto& fprs{context.extension_registers};
389
390 if (id <= PC_REGISTER) {
391 return ValueToHex(gprs[id]);
392 } else if (id == CPSR_REGISTER) {
393 return ValueToHex(context.cpsr);
394 } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
395 const u64 dN{GetSIMDRegister<u64>(fprs, id - D0_REGISTER)};
396 return ValueToHex(dN);
397 } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
398 const u128 qN{GetSIMDRegister<u128>(fprs, id - Q0_REGISTER)};
399 return ValueToHex(qN);
400 } else if (id == FPSCR_REGISTER) {
401 return ValueToHex(context.fpscr);
402 } else {
403 return "";
404 }
405}
406
407void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
408 if (!thread) {
409 return;
410 }
411
412 auto& context{thread->GetContext32()};
413 auto& fprs{context.extension_registers};
414
415 if (id <= PC_REGISTER) {
416 context.cpu_registers[id] = HexToValue<u32>(value);
417 } else if (id == CPSR_REGISTER) {
418 context.cpsr = HexToValue<u32>(value);
419 } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
420 PutSIMDRegister(fprs, id - D0_REGISTER, HexToValue<u64>(value));
421 } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
422 PutSIMDRegister(fprs, id - Q0_REGISTER, HexToValue<u128>(value));
423 } else if (id == FPSCR_REGISTER) {
424 context.fpscr = HexToValue<u32>(value);
425 }
426}
427
428std::string GDBStubA32::ReadRegisters(const Kernel::KThread* thread) const {
429 std::string output;
430
431 for (size_t reg = 0; reg <= FPSCR_REGISTER; reg++) {
432 const bool gpr{reg <= PC_REGISTER};
433 const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
434 const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
435
436 if (!(gpr || dfpr || qfpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER)) {
437 continue;
438 }
439
440 output += RegRead(thread, reg);
441 }
442
443 return output;
444}
445
446void GDBStubA32::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
447 for (size_t i = 0, reg = 0; reg <= FPSCR_REGISTER; reg++) {
448 const bool gpr{reg <= PC_REGISTER};
449 const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
450 const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
451
452 if (gpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER) {
453 RegWrite(thread, reg, register_data.substr(i, 8));
454 i += 8;
455 } else if (dfpr) {
456 RegWrite(thread, reg, register_data.substr(i, 16));
457 i += 16;
458 } else if (qfpr) {
459 RegWrite(thread, reg, register_data.substr(i, 32));
460 i += 32;
461 }
462
463 if (reg == PC_REGISTER) {
464 reg = CPSR_REGISTER - 1;
465 } else if (reg == CPSR_REGISTER) {
466 reg = D0_REGISTER - 1;
467 }
468 }
469}
470
471std::string GDBStubA32::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
472 return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
473 RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
474 LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
475}
476
477u32 GDBStubA32::BreakpointInstruction() const {
478 // A32: trap
479 // T32: trap + b #4
480 return 0xe7ffdefe;
481}
482
483} // namespace Core
diff --git a/src/core/debugger/gdbstub_arch.h b/src/core/debugger/gdbstub_arch.h
new file mode 100644
index 000000000..4d039a9f7
--- /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 FPSR_REGISTER = 66;
44 static constexpr u32 FPCR_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/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index ab9ce6a86..940334f59 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -198,6 +198,10 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
198 resource_limit_release_hint = false; 198 resource_limit_release_hint = false;
199 cpu_time = 0; 199 cpu_time = 0;
200 200
201 // Set debug context.
202 stack_top = user_stack_top;
203 argument = arg;
204
201 // Clear our stack parameters. 205 // Clear our stack parameters.
202 std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0, 206 std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0,
203 sizeof(StackParameters)); 207 sizeof(StackParameters));
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index b55a922ab..f4d83f99a 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -100,6 +100,12 @@ enum class ThreadWaitReasonForDebugging : u32 {
100 Suspended, ///< Thread is waiting due to process suspension 100 Suspended, ///< Thread is waiting due to process suspension
101}; 101};
102 102
103enum class StepState : u32 {
104 NotStepping, ///< Thread is not currently stepping
105 StepPending, ///< Thread will step when next scheduled
106 StepPerformed, ///< Thread has stepped, waiting to be scheduled again
107};
108
103[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel); 109[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel);
104[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel); 110[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel);
105[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel); 111[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
@@ -267,6 +273,14 @@ public:
267 273
268 void SetState(ThreadState state); 274 void SetState(ThreadState state);
269 275
276 [[nodiscard]] StepState GetStepState() const {
277 return step_state;
278 }
279
280 void SetStepState(StepState state) {
281 step_state = state;
282 }
283
270 [[nodiscard]] s64 GetLastScheduledTick() const { 284 [[nodiscard]] s64 GetLastScheduledTick() const {
271 return last_scheduled_tick; 285 return last_scheduled_tick;
272 } 286 }
@@ -646,6 +660,14 @@ public:
646 void IfDummyThreadTryWait(); 660 void IfDummyThreadTryWait();
647 void IfDummyThreadEndWait(); 661 void IfDummyThreadEndWait();
648 662
663 [[nodiscard]] uintptr_t GetArgument() const {
664 return argument;
665 }
666
667 [[nodiscard]] VAddr GetUserStackTop() const {
668 return stack_top;
669 }
670
649private: 671private:
650 static constexpr size_t PriorityInheritanceCountMax = 10; 672 static constexpr size_t PriorityInheritanceCountMax = 10;
651 union SyncObjectBuffer { 673 union SyncObjectBuffer {
@@ -769,6 +791,7 @@ private:
769 std::shared_ptr<Common::Fiber> host_context{}; 791 std::shared_ptr<Common::Fiber> host_context{};
770 bool is_single_core{}; 792 bool is_single_core{};
771 ThreadType thread_type{}; 793 ThreadType thread_type{};
794 StepState step_state{};
772 std::mutex dummy_wait_lock; 795 std::mutex dummy_wait_lock;
773 std::condition_variable dummy_wait_cv; 796 std::condition_variable dummy_wait_cv;
774 797
@@ -776,6 +799,8 @@ private:
776 std::vector<KSynchronizationObject*> wait_objects_for_debugging; 799 std::vector<KSynchronizationObject*> wait_objects_for_debugging;
777 VAddr mutex_wait_address_for_debugging{}; 800 VAddr mutex_wait_address_for_debugging{};
778 ThreadWaitReasonForDebugging wait_reason_for_debugging{}; 801 ThreadWaitReasonForDebugging wait_reason_for_debugging{};
802 uintptr_t argument;
803 VAddr stack_top;
779 804
780public: 805public:
781 using ConditionVariableThreadTreeType = ConditionVariableThreadTree; 806 using ConditionVariableThreadTreeType = ConditionVariableThreadTree;
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/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 6a6325e38..256695804 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -277,3 +277,7 @@ else()
277 $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> 277 $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
278 ) 278 )
279endif() 279endif()
280
281if (ARCHITECTURE_x86_64)
282 target_link_libraries(video_core PRIVATE dynarmic)
283endif()
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 7d0cb8fce..3a4646289 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -595,8 +595,8 @@ void Maxwell3D::DrawArrays() {
595 595
596std::optional<u64> Maxwell3D::GetQueryResult() { 596std::optional<u64> Maxwell3D::GetQueryResult() {
597 switch (regs.query.query_get.select) { 597 switch (regs.query.query_get.select) {
598 case Regs::QuerySelect::Zero: 598 case Regs::QuerySelect::Payload:
599 return 0; 599 return regs.query.query_sequence;
600 case Regs::QuerySelect::SamplesPassed: 600 case Regs::QuerySelect::SamplesPassed:
601 // Deferred. 601 // Deferred.
602 rasterizer->Query(regs.query.QueryAddress(), QueryType::SamplesPassed, 602 rasterizer->Query(regs.query.QueryAddress(), QueryType::SamplesPassed,
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index c0c2c7d96..434ba0877 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -93,7 +93,7 @@ public:
93 }; 93 };
94 94
95 enum class QuerySelect : u32 { 95 enum class QuerySelect : u32 {
96 Zero = 0, 96 Payload = 0,
97 TimeElapsed = 2, 97 TimeElapsed = 2,
98 TransformFeedbackPrimitivesGenerated = 11, 98 TransformFeedbackPrimitivesGenerated = 11,
99 PrimitivesGenerated = 18, 99 PrimitivesGenerated = 18,
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 404acdd05..07df9675d 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -319,3 +319,7 @@ endif()
319if (NOT APPLE) 319if (NOT APPLE)
320 target_compile_definitions(yuzu PRIVATE HAS_OPENGL) 320 target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
321endif() 321endif()
322
323if (ARCHITECTURE_x86_64)
324 target_link_libraries(yuzu PRIVATE dynarmic)
325endif()
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..8e7f91a0f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1015,6 +1015,10 @@ void GMainWindow::SetDefaultUIGeometry() {
1015void GMainWindow::RestoreUIState() { 1015void GMainWindow::RestoreUIState() {
1016 setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint); 1016 setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
1017 restoreGeometry(UISettings::values.geometry); 1017 restoreGeometry(UISettings::values.geometry);
1018 // Work-around because the games list isn't supposed to be full screen
1019 if (isFullScreen()) {
1020 showNormal();
1021 }
1018 restoreState(UISettings::values.state); 1022 restoreState(UISettings::values.state);
1019 render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint); 1023 render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint);
1020 render_window->restoreGeometry(UISettings::values.renderwindow_geometry); 1024 render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
@@ -3152,7 +3156,7 @@ void GMainWindow::OnTasStateChanged() {
3152} 3156}
3153 3157
3154void GMainWindow::UpdateStatusBar() { 3158void GMainWindow::UpdateStatusBar() {
3155 if (emu_thread == nullptr) { 3159 if (emu_thread == nullptr || !system->IsPoweredOn()) {
3156 status_bar_update_timer.stop(); 3160 status_bar_update_timer.stop();
3157 return; 3161 return;
3158 } 3162 }