summaryrefslogtreecommitdiffstats
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() {
log_path("DataStorage_NANDDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir));
log_path("DataStorage_SDMCDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir));
log_setting("Debugging_ProgramArgs", values.program_args.GetValue());
+ log_setting("Debugging_GDBStub", values.use_gdbstub.GetValue());
log_setting("Input_EnableMotion", values.motion_enabled.GetValue());
log_setting("Input_EnableVibration", values.vibration_enabled.GetValue());
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 {
// Debugging
bool record_frame_times;
BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
- BasicSetting<u16> gdbstub_port{0, "gdbstub_port"};
+ BasicSetting<u16> gdbstub_port{6543, "gdbstub_port"};
BasicSetting<std::string> program_args{std::string(), "program_args"};
BasicSetting<bool> dump_exefs{false, "dump_exefs"};
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
crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
crypto/xts_encryption_layer.h
+ debugger/debugger_interface.h
+ debugger/debugger.cpp
+ debugger/debugger.h
+ debugger/gdbstub_arch.cpp
+ debugger/gdbstub_arch.h
+ debugger/gdbstub.cpp
+ debugger/gdbstub.h
device_memory.cpp
device_memory.h
file_sys/bis_factory.cpp
@@ -761,6 +768,9 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
+if (MINGW)
+ target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
+endif()
if (ENABLE_WEB_SERVICE)
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 @@
#include "core/arm/arm_interface.h"
#include "core/arm/symbols.h"
#include "core/core.h"
+#include "core/debugger/debugger.h"
#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/svc.h"
#include "core/loader/loader.h"
#include "core/memory.h"
@@ -88,4 +90,48 @@ void ARM_Interface::LogBacktrace() const {
}
}
+void ARM_Interface::Run() {
+ using Kernel::StepState;
+ using Kernel::SuspendType;
+
+ while (true) {
+ Kernel::KThread* current_thread{system.Kernel().CurrentScheduler()->GetCurrentThread()};
+ Dynarmic::HaltReason hr{};
+
+ // Notify the debugger and go to sleep if a step was performed
+ // and this thread has been scheduled again.
+ if (current_thread->GetStepState() == StepState::StepPerformed) {
+ system.GetDebugger().NotifyThreadStopped(current_thread);
+ current_thread->RequestSuspend(SuspendType::Debug);
+ break;
+ }
+
+ // Otherwise, run the thread.
+ if (current_thread->GetStepState() == StepState::StepPending) {
+ hr = StepJit();
+
+ if (Has(hr, step_thread)) {
+ current_thread->SetStepState(StepState::StepPerformed);
+ }
+ } else {
+ hr = RunJit();
+ }
+
+ // Notify the debugger and go to sleep if a breakpoint was hit.
+ if (Has(hr, breakpoint)) {
+ system.GetDebugger().NotifyThreadStopped(current_thread);
+ current_thread->RequestSuspend(Kernel::SuspendType::Debug);
+ break;
+ }
+
+ // Handle syscalls and scheduling (this may change the current thread)
+ if (Has(hr, svc_call)) {
+ Kernel::Svc::Call(system, GetSvcNumber());
+ }
+ if (Has(hr, break_loop) || !uses_wall_clock) {
+ break;
+ }
+ }
+}
+
} // 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 @@
#include <array>
#include <vector>
+
+#include <dynarmic/interface/halt_reason.h>
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hardware_properties.h"
@@ -64,10 +67,7 @@ public:
static_assert(sizeof(ThreadContext64) == 0x320);
/// Runs the CPU until an event happens
- virtual void Run() = 0;
-
- /// Step CPU by one instruction
- virtual void Step() = 0;
+ void Run();
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
@@ -194,6 +194,11 @@ public:
void LogBacktrace() const;
+ static constexpr Dynarmic::HaltReason step_thread = Dynarmic::HaltReason::Step;
+ static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
+ static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
+ static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
+
protected:
/// System context that this ARM interface is running under.
System& system;
@@ -201,6 +206,10 @@ protected:
bool uses_wall_clock;
static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
+
+ virtual Dynarmic::HaltReason RunJit() = 0;
+ virtual Dynarmic::HaltReason StepJit() = 0;
+ virtual u32 GetSvcNumber() const = 0;
};
} // 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 @@
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/debugger/debugger.h"
+#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
@@ -24,9 +26,6 @@ namespace Core {
using namespace Common::Literals;
-constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
-constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
-
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
@@ -78,16 +77,21 @@ public:
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
+ if (parent.system.DebuggerEnabled()) {
+ parent.jit.load()->Regs()[15] = pc;
+ parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
+ return;
+ }
+
parent.LogBacktrace();
LOG_CRITICAL(Core_ARM,
"ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
- UNIMPLEMENTED();
}
void CallSVC(u32 swi) override {
parent.svc_swi = swi;
- parent.jit.load()->HaltExecution(svc_call);
+ parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
}
void AddTicks(u64 ticks) override {
@@ -232,20 +236,16 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
return std::make_unique<Dynarmic::A32::Jit>(config);
}
-void ARM_Dynarmic_32::Run() {
- while (true) {
- const auto hr = jit.load()->Run();
- if (Has(hr, svc_call)) {
- Kernel::Svc::Call(system, svc_swi);
- }
- if (Has(hr, break_loop) || !uses_wall_clock) {
- break;
- }
- }
+Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() {
+ return jit.load()->Run();
+}
+
+Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() {
+ return jit.load()->Step();
}
-void ARM_Dynarmic_32::Step() {
- jit.load()->Step();
+u32 ARM_Dynarmic_32::GetSvcNumber() const {
+ return svc_swi;
}
ARM_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:
void SetVectorReg(int index, u128 value) override;
u32 GetPSTATE() const override;
void SetPSTATE(u32 pstate) override;
- void Run() override;
- void Step() override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
@@ -70,6 +68,11 @@ public:
std::vector<BacktraceEntry> GetBacktrace() const override;
+protected:
+ Dynarmic::HaltReason RunJit() override;
+ Dynarmic::HaltReason StepJit() override;
+ u32 GetSvcNumber() const override;
+
private:
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
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 @@
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/debugger/debugger.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/svc.h"
@@ -25,9 +26,6 @@ namespace Core {
using Vector = Dynarmic::A64::Vector;
using namespace Common::Literals;
-constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
-constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
-
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
@@ -119,8 +117,13 @@ public:
case Dynarmic::A64::Exception::SendEventLocal:
case Dynarmic::A64::Exception::Yield:
return;
- case Dynarmic::A64::Exception::Breakpoint:
default:
+ if (parent.system.DebuggerEnabled()) {
+ parent.jit.load()->SetPC(pc);
+ parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
+ return;
+ }
+
parent.LogBacktrace();
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
@@ -129,7 +132,7 @@ public:
void CallSVC(u32 swi) override {
parent.svc_swi = swi;
- parent.jit.load()->HaltExecution(svc_call);
+ parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
}
void AddTicks(u64 ticks) override {
@@ -293,20 +296,16 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
return std::make_shared<Dynarmic::A64::Jit>(config);
}
-void ARM_Dynarmic_64::Run() {
- while (true) {
- const auto hr = jit.load()->Run();
- if (Has(hr, svc_call)) {
- Kernel::Svc::Call(system, svc_swi);
- }
- if (Has(hr, break_loop) || !uses_wall_clock) {
- break;
- }
- }
+Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() {
+ return jit.load()->Run();
+}
+
+Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() {
+ return jit.load()->Step();
}
-void ARM_Dynarmic_64::Step() {
- jit.load()->Step();
+u32 ARM_Dynarmic_64::GetSvcNumber() const {
+ return svc_swi;
}
ARM_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:
void SetVectorReg(int index, u128 value) override;
u32 GetPSTATE() const override;
void SetPSTATE(u32 pstate) override;
- void Run() override;
- void Step() override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
@@ -64,6 +62,11 @@ public:
std::vector<BacktraceEntry> GetBacktrace() const override;
+protected:
+ Dynarmic::HaltReason RunJit() override;
+ Dynarmic::HaltReason StepJit() override;
+ u32 GetSvcNumber() const override;
+
private:
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
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 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
+#include "core/debugger/debugger.h"
#include "core/device_memory.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/mode.h"
@@ -171,6 +172,10 @@ struct System::Impl {
}
}
+ void InitializeDebugger(System& system, u16 port) {
+ debugger = std::make_unique<Debugger>(system, port);
+ }
+
SystemResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
LOG_DEBUG(Core, "initialized OK");
@@ -329,6 +334,7 @@ struct System::Impl {
gpu_core->NotifyShutdown();
}
+ debugger.reset();
services.reset();
service_manager.reset();
cheat_engine.reset();
@@ -436,6 +442,9 @@ struct System::Impl {
/// Network instance
Network::NetworkInstance network_instance;
+ /// Debugger
+ std::unique_ptr<Core::Debugger> debugger;
+
SystemResultStatus status = SystemResultStatus::Success;
std::string status_details = "";
@@ -472,10 +481,6 @@ SystemResultStatus System::Pause() {
return impl->Pause();
}
-SystemResultStatus System::SingleStep() {
- return SystemResultStatus::Success;
-}
-
void System::InvalidateCpuInstructionCaches() {
impl->kernel.InvalidateAllInstructionCaches();
}
@@ -496,6 +501,10 @@ void System::UnstallCPU() {
impl->UnstallCPU();
}
+void System::InitializeDebugger() {
+ impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
+}
+
SystemResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
u64 program_id, std::size_t program_index) {
return impl->Load(*this, emu_window, filepath, program_id, program_index);
@@ -809,6 +818,18 @@ bool System::IsMulticore() const {
return impl->is_multicore;
}
+bool System::DebuggerEnabled() const {
+ return Settings::values.use_gdbstub.GetValue();
+}
+
+Core::Debugger& System::GetDebugger() {
+ return *impl->debugger;
+}
+
+const Core::Debugger& System::GetDebugger() const {
+ return *impl->debugger;
+}
+
void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
impl->execute_program_callback = std::move(callback);
}
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 {
class ARM_Interface;
class CpuManager;
+class Debugger;
class DeviceMemory;
class ExclusiveMonitor;
class SpeedLimiter;
@@ -148,12 +149,6 @@ public:
[[nodiscard]] SystemResultStatus Pause();
/**
- * Step the CPU one instruction
- * @return Result status, indicating whether or not the operation succeeded.
- */
- [[nodiscard]] SystemResultStatus SingleStep();
-
- /**
* Invalidate the CPU instruction caches
* This function should only be used by GDB Stub to support breakpoints, memory updates and
* step/continue commands.
@@ -169,6 +164,11 @@ public:
void UnstallCPU();
/**
+ * Initialize the debugger.
+ */
+ void InitializeDebugger();
+
+ /**
* Load an executable application.
* @param emu_window Reference to the host-system window used for video output and keyboard
* input.
@@ -354,6 +354,9 @@ public:
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
+ [[nodiscard]] Core::Debugger& GetDebugger();
+ [[nodiscard]] const Core::Debugger& GetDebugger() const;
+
void SetExitLock(bool locked);
[[nodiscard]] bool GetExitLock() const;
@@ -375,6 +378,9 @@ public:
/// Tells if system is running on multicore.
[[nodiscard]] bool IsMulticore() const;
+ /// Tells if the system debugger is enabled.
+ [[nodiscard]] bool DebuggerEnabled() const;
+
/// Type used for the frontend to designate a callback for System to re-launch the application
/// using a specified program index.
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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <mutex>
+#include <thread>
+
+#include <boost/asio.hpp>
+#include <boost/process/async_pipe.hpp>
+
+#include "common/logging/log.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/debugger/debugger.h"
+#include "core/debugger/debugger_interface.h"
+#include "core/debugger/gdbstub.h"
+#include "core/hle/kernel/global_scheduler_context.h"
+
+template <typename Readable, typename Buffer, typename Callback>
+static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
+ static_assert(std::is_trivial_v<Buffer>);
+ auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
+ r.async_read_some(boost_buffer, [&](const boost::system::error_code& error, size_t bytes_read) {
+ if (!error.failed()) {
+ const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
+ std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
+ c(received_data);
+ }
+
+ AsyncReceiveInto(r, buffer, c);
+ });
+}
+
+template <typename Readable, typename Buffer>
+static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
+ static_assert(std::is_trivial_v<Buffer>);
+ auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
+ size_t bytes_read = r.read_some(boost_buffer);
+ const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
+ std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
+ return received_data;
+}
+
+namespace Core {
+
+class DebuggerImpl : public DebuggerBackend {
+public:
+ explicit DebuggerImpl(Core::System& system_, u16 port)
+ : system{system_}, signal_pipe{io_context}, client_socket{io_context} {
+ frontend = std::make_unique<GDBStub>(*this, system);
+ InitializeServer(port);
+ }
+
+ ~DebuggerImpl() override {
+ ShutdownServer();
+ }
+
+ bool NotifyThreadStopped(Kernel::KThread* thread) {
+ std::scoped_lock lk{connection_lock};
+
+ if (stopped) {
+ // Do not notify the debugger about another event.
+ // It should be ignored.
+ return false;
+ }
+ stopped = true;
+
+ signal_pipe.write_some(boost::asio::buffer(&thread, sizeof(thread)));
+ return true;
+ }
+
+ std::span<const u8> ReadFromClient() override {
+ return ReceiveInto(client_socket, client_data);
+ }
+
+ void WriteToClient(std::span<const u8> data) override {
+ client_socket.write_some(boost::asio::buffer(data.data(), data.size_bytes()));
+ }
+
+ void SetActiveThread(Kernel::KThread* thread) override {
+ active_thread = thread;
+ }
+
+ Kernel::KThread* GetActiveThread() override {
+ return active_thread;
+ }
+
+private:
+ void InitializeServer(u16 port) {
+ using boost::asio::ip::tcp;
+
+ LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
+
+ // Run the connection thread.
+ connection_thread = std::jthread([&, port](std::stop_token stop_token) {
+ try {
+ // Initialize the listening socket and accept a new client.
+ tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
+ tcp::acceptor acceptor{io_context, endpoint};
+
+ acceptor.async_accept(client_socket, [](const auto&) {});
+ io_context.run_one();
+ io_context.restart();
+
+ if (stop_token.stop_requested()) {
+ return;
+ }
+
+ ThreadLoop(stop_token);
+ } catch (const std::exception& ex) {
+ LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
+ }
+ });
+ }
+
+ void ShutdownServer() {
+ connection_thread.request_stop();
+ io_context.stop();
+ connection_thread.join();
+ }
+
+ void ThreadLoop(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("yuzu:Debugger");
+
+ // Set up the client signals for new data.
+ AsyncReceiveInto(signal_pipe, active_thread, [&](auto d) { PipeData(d); });
+ AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
+
+ // Stop the emulated CPU.
+ AllCoreStop();
+
+ // Set the active thread.
+ UpdateActiveThread();
+
+ // Set up the frontend.
+ frontend->Connected();
+
+ // Main event loop.
+ while (!stop_token.stop_requested() && io_context.run()) {
+ }
+ }
+
+ void PipeData(std::span<const u8> data) {
+ AllCoreStop();
+ UpdateActiveThread();
+ frontend->Stopped(active_thread);
+ }
+
+ void ClientData(std::span<const u8> data) {
+ const auto actions{frontend->ClientData(data)};
+ for (const auto action : actions) {
+ switch (action) {
+ case DebuggerAction::Interrupt: {
+ {
+ std::scoped_lock lk{connection_lock};
+ stopped = true;
+ }
+ AllCoreStop();
+ UpdateActiveThread();
+ frontend->Stopped(active_thread);
+ break;
+ }
+ case DebuggerAction::Continue:
+ active_thread->SetStepState(Kernel::StepState::NotStepping);
+ ResumeInactiveThreads();
+ AllCoreResume();
+ break;
+ case DebuggerAction::StepThreadUnlocked:
+ active_thread->SetStepState(Kernel::StepState::StepPending);
+ ResumeInactiveThreads();
+ AllCoreResume();
+ break;
+ case DebuggerAction::StepThreadLocked:
+ active_thread->SetStepState(Kernel::StepState::StepPending);
+ SuspendInactiveThreads();
+ AllCoreResume();
+ break;
+ case DebuggerAction::ShutdownEmulation: {
+ // Suspend all threads and release any locks held
+ active_thread->RequestSuspend(Kernel::SuspendType::Debug);
+ SuspendInactiveThreads();
+ AllCoreResume();
+
+ // Spawn another thread that will exit after shutdown,
+ // to avoid a deadlock
+ Core::System* system_ref{&system};
+ std::thread t([system_ref] { system_ref->Exit(); });
+ t.detach();
+ break;
+ }
+ }
+ }
+ }
+
+ void AllCoreStop() {
+ if (!suspend) {
+ suspend = system.StallCPU();
+ }
+ }
+
+ void AllCoreResume() {
+ stopped = false;
+ system.UnstallCPU();
+ suspend.reset();
+ }
+
+ void SuspendInactiveThreads() {
+ for (auto* thread : ThreadList()) {
+ if (thread != active_thread) {
+ thread->RequestSuspend(Kernel::SuspendType::Debug);
+ }
+ }
+ }
+
+ void ResumeInactiveThreads() {
+ for (auto* thread : ThreadList()) {
+ if (thread != active_thread) {
+ thread->Resume(Kernel::SuspendType::Debug);
+ thread->SetStepState(Kernel::StepState::NotStepping);
+ }
+ }
+ }
+
+ void UpdateActiveThread() {
+ const auto& threads{ThreadList()};
+ if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
+ active_thread = threads[0];
+ }
+ active_thread->Resume(Kernel::SuspendType::Debug);
+ active_thread->SetStepState(Kernel::StepState::NotStepping);
+ }
+
+ const std::vector<Kernel::KThread*>& ThreadList() {
+ return system.GlobalSchedulerContext().GetThreadList();
+ }
+
+private:
+ System& system;
+ std::unique_ptr<DebuggerFrontend> frontend;
+
+ std::jthread connection_thread;
+ std::mutex connection_lock;
+ boost::asio::io_context io_context;
+ boost::process::async_pipe signal_pipe;
+ boost::asio::ip::tcp::socket client_socket;
+ std::optional<std::unique_lock<std::mutex>> suspend;
+
+ Kernel::KThread* active_thread;
+ bool stopped;
+
+ std::array<u8, 4096> client_data;
+};
+
+Debugger::Debugger(Core::System& system, u16 port) {
+ try {
+ impl = std::make_unique<DebuggerImpl>(system, port);
+ } catch (const std::exception& ex) {
+ LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what());
+ }
+}
+
+Debugger::~Debugger() = default;
+
+bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
+ return impl && impl->NotifyThreadStopped(thread);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class KThread;
+}
+
+namespace Core {
+class System;
+
+class DebuggerImpl;
+
+class Debugger {
+public:
+ /**
+ * Blocks and waits for a connection on localhost, port `server_port`.
+ * Does not create the debugger if the port is already in use.
+ */
+ explicit Debugger(Core::System& system, u16 server_port);
+ ~Debugger();
+
+ /**
+ * Notify the debugger that the given thread is stopped
+ * (due to a breakpoint, or due to stopping after a successful step).
+ *
+ * The debugger will asynchronously halt emulation after the notification has
+ * occurred. If another thread attempts to notify before emulation has stopped,
+ * it is ignored and this method will return false. Otherwise it will return true.
+ */
+ bool NotifyThreadStopped(Kernel::KThread* thread);
+
+private:
+ std::unique_ptr<DebuggerImpl> impl;
+};
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <span>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class KThread;
+}
+
+namespace Core {
+
+enum class DebuggerAction {
+ Interrupt, ///< Stop emulation as soon as possible.
+ Continue, ///< Resume emulation.
+ StepThreadLocked, ///< Step the currently-active thread without resuming others.
+ StepThreadUnlocked, ///< Step the currently-active thread and resume others.
+ ShutdownEmulation, ///< Shut down the emulator.
+};
+
+class DebuggerBackend {
+public:
+ virtual ~DebuggerBackend() = default;
+
+ /**
+ * Can be invoked from a callback to synchronously wait for more data.
+ * Will return as soon as least one byte is received. Reads up to 4096 bytes.
+ */
+ virtual std::span<const u8> ReadFromClient() = 0;
+
+ /**
+ * Can be invoked from a callback to write data to the client.
+ * Returns immediately after the data is sent.
+ */
+ virtual void WriteToClient(std::span<const u8> data) = 0;
+
+ /**
+ * Gets the currently active thread when the debugger is stopped.
+ */
+ virtual Kernel::KThread* GetActiveThread() = 0;
+
+ /**
+ * Sets the currently active thread when the debugger is stopped.
+ */
+ virtual void SetActiveThread(Kernel::KThread* thread) = 0;
+};
+
+class DebuggerFrontend {
+public:
+ explicit DebuggerFrontend(DebuggerBackend& backend_) : backend{backend_} {}
+
+ virtual ~DebuggerFrontend() = default;
+
+ /**
+ * Called after the client has successfully connected to the port.
+ */
+ virtual void Connected() = 0;
+
+ /**
+ * Called when emulation has stopped.
+ */
+ virtual void Stopped(Kernel::KThread* thread) = 0;
+
+ /**
+ * Called when new data is asynchronously received on the client socket.
+ * A list of actions to perform is returned.
+ */
+ [[nodiscard]] virtual std::vector<DebuggerAction> ClientData(std::span<const u8> data) = 0;
+
+protected:
+ DebuggerBackend& backend;
+};
+
+} // namespace Core
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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <atomic>
+#include <numeric>
+#include <optional>
+#include <thread>
+
+#include <boost/algorithm/string.hpp>
+
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/debugger/gdbstub.h"
+#include "core/debugger/gdbstub_arch.h"
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/loader/loader.h"
+#include "core/memory.h"
+
+namespace Core {
+
+constexpr char GDB_STUB_START = '$';
+constexpr char GDB_STUB_END = '#';
+constexpr char GDB_STUB_ACK = '+';
+constexpr char GDB_STUB_NACK = '-';
+constexpr char GDB_STUB_INT3 = 0x03;
+constexpr int GDB_STUB_SIGTRAP = 5;
+
+constexpr char GDB_STUB_REPLY_ERR[] = "E01";
+constexpr char GDB_STUB_REPLY_OK[] = "OK";
+constexpr char GDB_STUB_REPLY_EMPTY[] = "";
+
+static u8 CalculateChecksum(std::string_view data) {
+ return std::accumulate(data.begin(), data.end(), u8{0},
+ [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); });
+}
+
+static std::string EscapeGDB(std::string_view data) {
+ std::string escaped;
+ escaped.reserve(data.size());
+
+ for (char c : data) {
+ switch (c) {
+ case '#':
+ escaped += "}\x03";
+ break;
+ case '$':
+ escaped += "}\x04";
+ break;
+ case '*':
+ escaped += "}\x0a";
+ break;
+ case '}':
+ escaped += "}\x5d";
+ break;
+ default:
+ escaped += c;
+ break;
+ }
+ }
+
+ return escaped;
+}
+
+static std::string EscapeXML(std::string_view data) {
+ std::string escaped;
+ escaped.reserve(data.size());
+
+ for (char c : data) {
+ switch (c) {
+ case '&':
+ escaped += "&amp;";
+ break;
+ case '"':
+ escaped += "&quot;";
+ break;
+ case '<':
+ escaped += "&lt;";
+ break;
+ case '>':
+ escaped += "&gt;";
+ break;
+ default:
+ escaped += c;
+ break;
+ }
+ }
+
+ return escaped;
+}
+
+GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
+ : DebuggerFrontend(backend_), system{system_} {
+ if (system.CurrentProcess()->Is64BitProcess()) {
+ arch = std::make_unique<GDBStubA64>();
+ } else {
+ arch = std::make_unique<GDBStubA32>();
+ }
+}
+
+GDBStub::~GDBStub() = default;
+
+void GDBStub::Connected() {}
+
+void GDBStub::Stopped(Kernel::KThread* thread) {
+ SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
+}
+
+std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
+ std::vector<DebuggerAction> actions;
+ current_command.insert(current_command.end(), data.begin(), data.end());
+
+ while (current_command.size() != 0) {
+ ProcessData(actions);
+ }
+
+ return actions;
+}
+
+void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
+ const char c{current_command[0]};
+
+ // Acknowledgement
+ if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
+ current_command.erase(current_command.begin());
+ return;
+ }
+
+ // Interrupt
+ if (c == GDB_STUB_INT3) {
+ LOG_INFO(Debug_GDBStub, "Received interrupt");
+ current_command.erase(current_command.begin());
+ actions.push_back(DebuggerAction::Interrupt);
+ SendStatus(GDB_STUB_ACK);
+ return;
+ }
+
+ // Otherwise, require the data to be the start of a command
+ if (c != GDB_STUB_START) {
+ LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
+ current_command.clear();
+ SendStatus(GDB_STUB_NACK);
+ return;
+ }
+
+ // Continue reading until command is complete
+ while (CommandEnd() == current_command.end()) {
+ const auto new_data{backend.ReadFromClient()};
+ current_command.insert(current_command.end(), new_data.begin(), new_data.end());
+ }
+
+ // Execute and respond to GDB
+ const auto command{DetachCommand()};
+
+ if (command) {
+ SendStatus(GDB_STUB_ACK);
+ ExecuteCommand(*command, actions);
+ } else {
+ SendStatus(GDB_STUB_NACK);
+ }
+}
+
+void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
+ LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
+
+ if (packet.length() == 0) {
+ SendReply(GDB_STUB_REPLY_ERR);
+ return;
+ }
+
+ if (packet.starts_with("vCont")) {
+ HandleVCont(packet.substr(5), actions);
+ return;
+ }
+
+ std::string_view command{packet.substr(1, packet.size())};
+
+ switch (packet[0]) {
+ case 'H': {
+ Kernel::KThread* thread{nullptr};
+ s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
+ if (thread_id >= 1) {
+ thread = GetThreadByID(thread_id);
+ } else {
+ thread = backend.GetActiveThread();
+ }
+
+ if (thread) {
+ SendReply(GDB_STUB_REPLY_OK);
+ backend.SetActiveThread(thread);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'T': {
+ s64 thread_id{strtoll(command.data(), nullptr, 16)};
+ if (GetThreadByID(thread_id)) {
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'Q':
+ case 'q':
+ HandleQuery(command);
+ break;
+ case '?':
+ SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
+ break;
+ case 'k':
+ LOG_INFO(Debug_GDBStub, "Shutting down emulation");
+ actions.push_back(DebuggerAction::ShutdownEmulation);
+ break;
+ case 'g':
+ SendReply(arch->ReadRegisters(backend.GetActiveThread()));
+ break;
+ case 'G':
+ arch->WriteRegisters(backend.GetActiveThread(), command);
+ SendReply(GDB_STUB_REPLY_OK);
+ break;
+ case 'p': {
+ const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ SendReply(arch->RegRead(backend.GetActiveThread(), reg));
+ break;
+ }
+ case 'P': {
+ const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
+ const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
+ break;
+ }
+ case 'm': {
+ const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
+
+ if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
+ std::vector<u8> mem(size);
+ system.Memory().ReadBlock(addr, mem.data(), size);
+
+ SendReply(Common::HexToString(mem));
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'M': {
+ const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
+
+ const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
+ const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
+
+ const auto mem_substr{std::string_view(command).substr(mem_sep)};
+ const auto mem{Common::HexStringToVector(mem_substr, false)};
+
+ if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
+ system.Memory().WriteBlock(addr, mem.data(), size);
+ system.InvalidateCpuInstructionCacheRange(addr, size);
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 's':
+ actions.push_back(DebuggerAction::StepThreadLocked);
+ break;
+ case 'C':
+ case 'c':
+ actions.push_back(DebuggerAction::Continue);
+ break;
+ case 'Z': {
+ const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
+
+ if (system.Memory().IsValidVirtualAddress(addr)) {
+ replaced_instructions[addr] = system.Memory().Read32(addr);
+ system.Memory().Write32(addr, arch->BreakpointInstruction());
+ system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
+
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ case 'z': {
+ const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
+ const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
+
+ const auto orig_insn{replaced_instructions.find(addr)};
+ if (system.Memory().IsValidVirtualAddress(addr) &&
+ orig_insn != replaced_instructions.end()) {
+ system.Memory().Write32(addr, orig_insn->second);
+ system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
+ replaced_instructions.erase(addr);
+
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_ERR);
+ }
+ break;
+ }
+ default:
+ SendReply(GDB_STUB_REPLY_EMPTY);
+ break;
+ }
+}
+
+// Structure offsets are from Atmosphere
+// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
+
+static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
+ const Kernel::KThread* thread) {
+ // Read thread type from TLS
+ const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)};
+ const VAddr argument_thread_type{thread->GetArgument()};
+
+ if (argument_thread_type && tls_thread_type != argument_thread_type) {
+ // Probably not created by nnsdk, no name available.
+ return std::nullopt;
+ }
+
+ if (!tls_thread_type) {
+ return std::nullopt;
+ }
+
+ const u16 version{memory.Read16(tls_thread_type + 0x26)};
+ VAddr name_pointer{};
+ if (version == 1) {
+ name_pointer = memory.Read32(tls_thread_type + 0xe4);
+ } else {
+ name_pointer = memory.Read32(tls_thread_type + 0xe8);
+ }
+
+ if (!name_pointer) {
+ // No name provided.
+ return std::nullopt;
+ }
+
+ return memory.ReadCString(name_pointer, 256);
+}
+
+static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
+ const Kernel::KThread* thread) {
+ // Read thread type from TLS
+ const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)};
+ const VAddr argument_thread_type{thread->GetArgument()};
+
+ if (argument_thread_type && tls_thread_type != argument_thread_type) {
+ // Probably not created by nnsdk, no name available.
+ return std::nullopt;
+ }
+
+ if (!tls_thread_type) {
+ return std::nullopt;
+ }
+
+ const u16 version{memory.Read16(tls_thread_type + 0x46)};
+ VAddr name_pointer{};
+ if (version == 1) {
+ name_pointer = memory.Read64(tls_thread_type + 0x1a0);
+ } else {
+ name_pointer = memory.Read64(tls_thread_type + 0x1a8);
+ }
+
+ if (!name_pointer) {
+ // No name provided.
+ return std::nullopt;
+ }
+
+ return memory.ReadCString(name_pointer, 256);
+}
+
+static std::optional<std::string> GetThreadName(Core::System& system,
+ const Kernel::KThread* thread) {
+ if (system.CurrentProcess()->Is64BitProcess()) {
+ return GetNameFromThreadType64(system.Memory(), thread);
+ } else {
+ return GetNameFromThreadType32(system.Memory(), thread);
+ }
+}
+
+static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
+ switch (thread->GetWaitReasonForDebugging()) {
+ case Kernel::ThreadWaitReasonForDebugging::Sleep:
+ return "Sleep";
+ case Kernel::ThreadWaitReasonForDebugging::IPC:
+ return "IPC";
+ case Kernel::ThreadWaitReasonForDebugging::Synchronization:
+ return "Synchronization";
+ case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
+ return "ConditionVar";
+ case Kernel::ThreadWaitReasonForDebugging::Arbitration:
+ return "Arbitration";
+ case Kernel::ThreadWaitReasonForDebugging::Suspended:
+ return "Suspended";
+ default:
+ return "Unknown";
+ }
+}
+
+static std::string GetThreadState(const Kernel::KThread* thread) {
+ switch (thread->GetState()) {
+ case Kernel::ThreadState::Initialized:
+ return "Initialized";
+ case Kernel::ThreadState::Waiting:
+ return fmt::format("Waiting ({})", GetThreadWaitReason(thread));
+ case Kernel::ThreadState::Runnable:
+ return "Runnable";
+ case Kernel::ThreadState::Terminated:
+ return "Terminated";
+ default:
+ return "Unknown";
+ }
+}
+
+void GDBStub::HandleQuery(std::string_view command) {
+ if (command.starts_with("TStatus")) {
+ // no tracepoint support
+ SendReply("T0");
+ } else if (command.starts_with("Supported")) {
+ SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;"
+ "vContSupported+;QStartNoAckMode+");
+ } else if (command.starts_with("Xfer:features:read:target.xml:")) {
+ const auto offset{command.substr(30)};
+ const auto amount{command.substr(command.find(',') + 1)};
+
+ const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
+ const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
+ const auto target_xml{arch->GetTargetXML()};
+
+ if (offset_val + amount_val > target_xml.size()) {
+ SendReply("l" + target_xml.substr(offset_val));
+ } else {
+ SendReply("m" + target_xml.substr(offset_val, amount_val));
+ }
+ } else if (command.starts_with("Offsets")) {
+ Loader::AppLoader::Modules modules;
+ system.GetAppLoader().ReadNSOModules(modules);
+
+ const auto main = std::find_if(modules.begin(), modules.end(),
+ [](const auto& key) { return key.second == "main"; });
+ if (main != modules.end()) {
+ SendReply(fmt::format("TextSeg={:x}", main->first));
+ } else {
+ SendReply(fmt::format("TextSeg={:x}",
+ system.CurrentProcess()->PageTable().GetCodeRegionStart()));
+ }
+ } else if (command.starts_with("fThreadInfo")) {
+ // beginning of list
+ const auto& threads = system.GlobalSchedulerContext().GetThreadList();
+ std::vector<std::string> thread_ids;
+ for (const auto& thread : threads) {
+ thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
+ }
+ SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
+ } else if (command.starts_with("sThreadInfo")) {
+ // end of list
+ SendReply("l");
+ } else if (command.starts_with("Xfer:threads:read::")) {
+ std::string buffer;
+ buffer += R"(<?xml version="1.0"?>)";
+ buffer += "<threads>";
+
+ const auto& threads = system.GlobalSchedulerContext().GetThreadList();
+ for (const auto* thread : threads) {
+ auto thread_name{GetThreadName(system, thread)};
+ if (!thread_name) {
+ thread_name = fmt::format("Thread {:d}", thread->GetThreadID());
+ }
+
+ buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
+ thread->GetThreadID(), thread->GetActiveCore(),
+ EscapeXML(*thread_name), GetThreadState(thread));
+ }
+
+ buffer += "</threads>";
+
+ const auto offset{command.substr(19)};
+ const auto amount{command.substr(command.find(',') + 1)};
+
+ const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
+ const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
+
+ if (offset_val + amount_val > buffer.size()) {
+ SendReply("l" + buffer.substr(offset_val));
+ } else {
+ SendReply("m" + buffer.substr(offset_val, amount_val));
+ }
+ } else if (command.starts_with("Attached")) {
+ SendReply("0");
+ } else if (command.starts_with("StartNoAckMode")) {
+ no_ack = true;
+ SendReply(GDB_STUB_REPLY_OK);
+ } else {
+ SendReply(GDB_STUB_REPLY_EMPTY);
+ }
+}
+
+void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) {
+ if (command == "?") {
+ // Continuing and stepping are supported
+ // (signal is ignored, but required for GDB to use vCont)
+ SendReply("vCont;c;C;s;S");
+ return;
+ }
+
+ Kernel::KThread* stepped_thread{nullptr};
+ bool lock_execution{true};
+
+ std::vector<std::string> entries;
+ boost::split(entries, command.substr(1), boost::is_any_of(";"));
+ for (const auto& thread_action : entries) {
+ std::vector<std::string> parts;
+ boost::split(parts, thread_action, boost::is_any_of(":"));
+
+ if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) {
+ lock_execution = false;
+ }
+ if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) {
+ stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
+ }
+ }
+
+ if (stepped_thread) {
+ backend.SetActiveThread(stepped_thread);
+ actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked
+ : DebuggerAction::StepThreadUnlocked);
+ } else {
+ actions.push_back(DebuggerAction::Continue);
+ }
+}
+
+Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
+ const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
+ for (auto* thread : threads) {
+ if (thread->GetThreadID() == thread_id) {
+ return thread;
+ }
+ }
+
+ return nullptr;
+}
+
+std::vector<char>::const_iterator GDBStub::CommandEnd() const {
+ // Find the end marker
+ const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
+
+ // Require the checksum to be present
+ return std::min(end + 2, current_command.end());
+}
+
+std::optional<std::string> GDBStub::DetachCommand() {
+ // Slice the string part from the beginning to the end marker
+ const auto end{CommandEnd()};
+
+ // Extract possible command data
+ std::string data(current_command.data(), end - current_command.begin() + 1);
+
+ // Shift over the remaining contents
+ current_command.erase(current_command.begin(), end + 1);
+
+ // Validate received command
+ if (data[0] != GDB_STUB_START) {
+ LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
+ return std::nullopt;
+ }
+
+ u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
+ u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
+
+ // Verify checksum
+ if (calculated != received) {
+ LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
+ calculated, received);
+ return std::nullopt;
+ }
+
+ return data.substr(1, data.size() - 4);
+}
+
+void GDBStub::SendReply(std::string_view data) {
+ const auto escaped{EscapeGDB(data)};
+ const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END,
+ CalculateChecksum(escaped))};
+ LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
+
+ // C++ string support is complete rubbish
+ const u8* output_begin = reinterpret_cast<const u8*>(output.data());
+ const u8* output_end = output_begin + output.size();
+ backend.WriteToClient(std::span<const u8>(output_begin, output_end));
+}
+
+void GDBStub::SendStatus(char status) {
+ if (no_ack) {
+ return;
+ }
+
+ std::array<u8, 1> buf = {static_cast<u8>(status)};
+ LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
+ backend.WriteToClient(buf);
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+#include "core/debugger/debugger_interface.h"
+#include "core/debugger/gdbstub_arch.h"
+
+namespace Core {
+
+class System;
+
+class GDBStub : public DebuggerFrontend {
+public:
+ explicit GDBStub(DebuggerBackend& backend, Core::System& system);
+ ~GDBStub() override;
+
+ void Connected() override;
+ void Stopped(Kernel::KThread* thread) override;
+ std::vector<DebuggerAction> ClientData(std::span<const u8> data) override;
+
+private:
+ void ProcessData(std::vector<DebuggerAction>& actions);
+ void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
+ void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
+ void HandleQuery(std::string_view command);
+ std::vector<char>::const_iterator CommandEnd() const;
+ std::optional<std::string> DetachCommand();
+ Kernel::KThread* GetThreadByID(u64 thread_id);
+
+ void SendReply(std::string_view data);
+ void SendStatus(char status);
+
+private:
+ Core::System& system;
+ std::unique_ptr<GDBStubArch> arch;
+ std::vector<char> current_command;
+ std::map<VAddr, u32> replaced_instructions;
+ bool no_ack{};
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/hex_util.h"
+#include "core/debugger/gdbstub_arch.h"
+#include "core/hle/kernel/k_thread.h"
+
+namespace Core {
+
+template <typename T>
+static T HexToValue(std::string_view hex) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ T value{};
+ const auto mem{Common::HexStringToVector(hex, false)};
+ std::memcpy(&value, mem.data(), std::min(mem.size(), sizeof(T)));
+ return value;
+}
+
+template <typename T>
+static std::string ValueToHex(const T value) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ std::array<u8, sizeof(T)> mem{};
+ std::memcpy(mem.data(), &value, sizeof(T));
+ return Common::HexToString(mem);
+}
+
+template <typename T>
+static T GetSIMDRegister(const std::array<u32, 64>& simd_regs, size_t offset) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ T value{};
+ std::memcpy(&value, reinterpret_cast<const u8*>(simd_regs.data()) + sizeof(T) * offset,
+ sizeof(T));
+ return value;
+}
+
+template <typename T>
+static void PutSIMDRegister(std::array<u32, 64>& simd_regs, size_t offset, const T value) {
+ static_assert(std::is_trivially_copyable_v<T>);
+ std::memcpy(reinterpret_cast<u8*>(simd_regs.data()) + sizeof(T) * offset, &value, sizeof(T));
+}
+
+// For sample XML files see the GDB source /gdb/features
+// This XML defines what the registers are for this specific ARM device
+std::string GDBStubA64::GetTargetXML() const {
+ constexpr const char* target_xml =
+ R"(<?xml version="1.0"?>
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+<target version="1.0">
+ <architecture>aarch64</architecture>
+ <feature name="org.gnu.gdb.aarch64.core">
+ <reg name="x0" bitsize="64"/>
+ <reg name="x1" bitsize="64"/>
+ <reg name="x2" bitsize="64"/>
+ <reg name="x3" bitsize="64"/>
+ <reg name="x4" bitsize="64"/>
+ <reg name="x5" bitsize="64"/>
+ <reg name="x6" bitsize="64"/>
+ <reg name="x7" bitsize="64"/>
+ <reg name="x8" bitsize="64"/>
+ <reg name="x9" bitsize="64"/>
+ <reg name="x10" bitsize="64"/>
+ <reg name="x11" bitsize="64"/>
+ <reg name="x12" bitsize="64"/>
+ <reg name="x13" bitsize="64"/>
+ <reg name="x14" bitsize="64"/>
+ <reg name="x15" bitsize="64"/>
+ <reg name="x16" bitsize="64"/>
+ <reg name="x17" bitsize="64"/>
+ <reg name="x18" bitsize="64"/>
+ <reg name="x19" bitsize="64"/>
+ <reg name="x20" bitsize="64"/>
+ <reg name="x21" bitsize="64"/>
+ <reg name="x22" bitsize="64"/>
+ <reg name="x23" bitsize="64"/>
+ <reg name="x24" bitsize="64"/>
+ <reg name="x25" bitsize="64"/>
+ <reg name="x26" bitsize="64"/>
+ <reg name="x27" bitsize="64"/>
+ <reg name="x28" bitsize="64"/>
+ <reg name="x29" bitsize="64"/>
+ <reg name="x30" bitsize="64"/>
+ <reg name="sp" bitsize="64" type="data_ptr"/>
+ <reg name="pc" bitsize="64" type="code_ptr"/>
+ <flags id="cpsr_flags" size="4">
+ <field name="SP" start="0" end="0"/>
+ <field name="" start="1" end="1"/>
+ <field name="EL" start="2" end="3"/>
+ <field name="nRW" start="4" end="4"/>
+ <field name="" start="5" end="5"/>
+ <field name="F" start="6" end="6"/>
+ <field name="I" start="7" end="7"/>
+ <field name="A" start="8" end="8"/>
+ <field name="D" start="9" end="9"/>
+ <field name="IL" start="20" end="20"/>
+ <field name="SS" start="21" end="21"/>
+ <field name="V" start="28" end="28"/>
+ <field name="C" start="29" end="29"/>
+ <field name="Z" start="30" end="30"/>
+ <field name="N" start="31" end="31"/>
+ </flags>
+ <reg name="cpsr" bitsize="32" type="cpsr_flags"/>
+ </feature>
+ <feature name="org.gnu.gdb.aarch64.fpu">
+ <vector id="v2d" type="ieee_double" count="2"/>
+ <vector id="v2u" type="uint64" count="2"/>
+ <vector id="v2i" type="int64" count="2"/>
+ <vector id="v4f" type="ieee_single" count="4"/>
+ <vector id="v4u" type="uint32" count="4"/>
+ <vector id="v4i" type="int32" count="4"/>
+ <vector id="v8u" type="uint16" count="8"/>
+ <vector id="v8i" type="int16" count="8"/>
+ <vector id="v16u" type="uint8" count="16"/>
+ <vector id="v16i" type="int8" count="16"/>
+ <vector id="v1u" type="uint128" count="1"/>
+ <vector id="v1i" type="int128" count="1"/>
+ <union id="vnd">
+ <field name="f" type="v2d"/>
+ <field name="u" type="v2u"/>
+ <field name="s" type="v2i"/>
+ </union>
+ <union id="vns">
+ <field name="f" type="v4f"/>
+ <field name="u" type="v4u"/>
+ <field name="s" type="v4i"/>
+ </union>
+ <union id="vnh">
+ <field name="u" type="v8u"/>
+ <field name="s" type="v8i"/>
+ </union>
+ <union id="vnb">
+ <field name="u" type="v16u"/>
+ <field name="s" type="v16i"/>
+ </union>
+ <union id="vnq">
+ <field name="u" type="v1u"/>
+ <field name="s" type="v1i"/>
+ </union>
+ <union id="aarch64v">
+ <field name="d" type="vnd"/>
+ <field name="s" type="vns"/>
+ <field name="h" type="vnh"/>
+ <field name="b" type="vnb"/>
+ <field name="q" type="vnq"/>
+ </union>
+ <reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
+ <reg name="v1" bitsize="128" type="aarch64v" />
+ <reg name="v2" bitsize="128" type="aarch64v" />
+ <reg name="v3" bitsize="128" type="aarch64v" />
+ <reg name="v4" bitsize="128" type="aarch64v" />
+ <reg name="v5" bitsize="128" type="aarch64v" />
+ <reg name="v6" bitsize="128" type="aarch64v" />
+ <reg name="v7" bitsize="128" type="aarch64v" />
+ <reg name="v8" bitsize="128" type="aarch64v" />
+ <reg name="v9" bitsize="128" type="aarch64v" />
+ <reg name="v10" bitsize="128" type="aarch64v"/>
+ <reg name="v11" bitsize="128" type="aarch64v"/>
+ <reg name="v12" bitsize="128" type="aarch64v"/>
+ <reg name="v13" bitsize="128" type="aarch64v"/>
+ <reg name="v14" bitsize="128" type="aarch64v"/>
+ <reg name="v15" bitsize="128" type="aarch64v"/>
+ <reg name="v16" bitsize="128" type="aarch64v"/>
+ <reg name="v17" bitsize="128" type="aarch64v"/>
+ <reg name="v18" bitsize="128" type="aarch64v"/>
+ <reg name="v19" bitsize="128" type="aarch64v"/>
+ <reg name="v20" bitsize="128" type="aarch64v"/>
+ <reg name="v21" bitsize="128" type="aarch64v"/>
+ <reg name="v22" bitsize="128" type="aarch64v"/>
+ <reg name="v23" bitsize="128" type="aarch64v"/>
+ <reg name="v24" bitsize="128" type="aarch64v"/>
+ <reg name="v25" bitsize="128" type="aarch64v"/>
+ <reg name="v26" bitsize="128" type="aarch64v"/>
+ <reg name="v27" bitsize="128" type="aarch64v"/>
+ <reg name="v28" bitsize="128" type="aarch64v"/>
+ <reg name="v29" bitsize="128" type="aarch64v"/>
+ <reg name="v30" bitsize="128" type="aarch64v"/>
+ <reg name="v31" bitsize="128" type="aarch64v"/>
+ <reg name="fpsr" bitsize="32"/>
+ <reg name="fpcr" bitsize="32"/>
+ </feature>
+</target>)";
+
+ return target_xml;
+}
+
+std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const {
+ if (!thread) {
+ return "";
+ }
+
+ const auto& context{thread->GetContext64()};
+ const auto& gprs{context.cpu_registers};
+ const auto& fprs{context.vector_registers};
+
+ if (id <= SP_REGISTER) {
+ return ValueToHex(gprs[id]);
+ } else if (id == PC_REGISTER) {
+ return ValueToHex(context.pc);
+ } else if (id == PSTATE_REGISTER) {
+ return ValueToHex(context.pstate);
+ } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) {
+ return ValueToHex(fprs[id - Q0_REGISTER]);
+ } else if (id == FPSR_REGISTER) {
+ return ValueToHex(context.fpsr);
+ } else if (id == FPCR_REGISTER) {
+ return ValueToHex(context.fpcr);
+ } else {
+ return "";
+ }
+}
+
+void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
+ if (!thread) {
+ return;
+ }
+
+ auto& context{thread->GetContext64()};
+
+ if (id <= SP_REGISTER) {
+ context.cpu_registers[id] = HexToValue<u64>(value);
+ } else if (id == PC_REGISTER) {
+ context.pc = HexToValue<u64>(value);
+ } else if (id == PSTATE_REGISTER) {
+ context.pstate = HexToValue<u32>(value);
+ } else if (id >= Q0_REGISTER && id < FPSR_REGISTER) {
+ context.vector_registers[id - Q0_REGISTER] = HexToValue<u128>(value);
+ } else if (id == FPSR_REGISTER) {
+ context.fpsr = HexToValue<u32>(value);
+ } else if (id == FPCR_REGISTER) {
+ context.fpcr = HexToValue<u32>(value);
+ }
+}
+
+std::string GDBStubA64::ReadRegisters(const Kernel::KThread* thread) const {
+ std::string output;
+
+ for (size_t reg = 0; reg <= FPCR_REGISTER; reg++) {
+ output += RegRead(thread, reg);
+ }
+
+ return output;
+}
+
+void GDBStubA64::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
+ for (size_t i = 0, reg = 0; reg <= FPCR_REGISTER; reg++) {
+ if (reg <= SP_REGISTER || reg == PC_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 16));
+ i += 16;
+ } else if (reg == PSTATE_REGISTER || reg == FPCR_REGISTER || reg == FPSR_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 8));
+ i += 8;
+ } else if (reg >= Q0_REGISTER && reg < FPCR_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 32));
+ i += 32;
+ }
+ }
+}
+
+std::string GDBStubA64::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
+ return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
+ RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
+ LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
+}
+
+u32 GDBStubA64::BreakpointInstruction() const {
+ // A64: brk #0
+ return 0xd4200000;
+}
+
+std::string GDBStubA32::GetTargetXML() const {
+ constexpr const char* target_xml =
+ R"(<?xml version="1.0"?>
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+<target version="1.0">
+ <architecture>arm</architecture>
+ <feature name="org.gnu.gdb.arm.core">
+ <reg name="r0" bitsize="32" type="uint32"/>
+ <reg name="r1" bitsize="32" type="uint32"/>
+ <reg name="r2" bitsize="32" type="uint32"/>
+ <reg name="r3" bitsize="32" type="uint32"/>
+ <reg name="r4" bitsize="32" type="uint32"/>
+ <reg name="r5" bitsize="32" type="uint32"/>
+ <reg name="r6" bitsize="32" type="uint32"/>
+ <reg name="r7" bitsize="32" type="uint32"/>
+ <reg name="r8" bitsize="32" type="uint32"/>
+ <reg name="r9" bitsize="32" type="uint32"/>
+ <reg name="r10" bitsize="32" type="uint32"/>
+ <reg name="r11" bitsize="32" type="uint32"/>
+ <reg name="r12" bitsize="32" type="uint32"/>
+ <reg name="sp" bitsize="32" type="data_ptr"/>
+ <reg name="lr" bitsize="32" type="code_ptr"/>
+ <reg name="pc" bitsize="32" type="code_ptr"/>
+ <!-- The CPSR is register 25, rather than register 16, because
+ the FPA registers historically were placed between the PC
+ and the CPSR in the "g" packet. -->
+ <reg name="cpsr" bitsize="32" regnum="25"/>
+ </feature>
+ <feature name="org.gnu.gdb.arm.vfp">
+ <vector id="neon_uint8x8" type="uint8" count="8"/>
+ <vector id="neon_uint16x4" type="uint16" count="4"/>
+ <vector id="neon_uint32x2" type="uint32" count="2"/>
+ <vector id="neon_float32x2" type="ieee_single" count="2"/>
+ <union id="neon_d">
+ <field name="u8" type="neon_uint8x8"/>
+ <field name="u16" type="neon_uint16x4"/>
+ <field name="u32" type="neon_uint32x2"/>
+ <field name="u64" type="uint64"/>
+ <field name="f32" type="neon_float32x2"/>
+ <field name="f64" type="ieee_double"/>
+ </union>
+ <vector id="neon_uint8x16" type="uint8" count="16"/>
+ <vector id="neon_uint16x8" type="uint16" count="8"/>
+ <vector id="neon_uint32x4" type="uint32" count="4"/>
+ <vector id="neon_uint64x2" type="uint64" count="2"/>
+ <vector id="neon_float32x4" type="ieee_single" count="4"/>
+ <vector id="neon_float64x2" type="ieee_double" count="2"/>
+ <union id="neon_q">
+ <field name="u8" type="neon_uint8x16"/>
+ <field name="u16" type="neon_uint16x8"/>
+ <field name="u32" type="neon_uint32x4"/>
+ <field name="u64" type="neon_uint64x2"/>
+ <field name="f32" type="neon_float32x4"/>
+ <field name="f64" type="neon_float64x2"/>
+ </union>
+ <reg name="d0" bitsize="64" type="neon_d" regnum="32"/>
+ <reg name="d1" bitsize="64" type="neon_d"/>
+ <reg name="d2" bitsize="64" type="neon_d"/>
+ <reg name="d3" bitsize="64" type="neon_d"/>
+ <reg name="d4" bitsize="64" type="neon_d"/>
+ <reg name="d5" bitsize="64" type="neon_d"/>
+ <reg name="d6" bitsize="64" type="neon_d"/>
+ <reg name="d7" bitsize="64" type="neon_d"/>
+ <reg name="d8" bitsize="64" type="neon_d"/>
+ <reg name="d9" bitsize="64" type="neon_d"/>
+ <reg name="d10" bitsize="64" type="neon_d"/>
+ <reg name="d11" bitsize="64" type="neon_d"/>
+ <reg name="d12" bitsize="64" type="neon_d"/>
+ <reg name="d13" bitsize="64" type="neon_d"/>
+ <reg name="d14" bitsize="64" type="neon_d"/>
+ <reg name="d15" bitsize="64" type="neon_d"/>
+ <reg name="d16" bitsize="64" type="neon_d"/>
+ <reg name="d17" bitsize="64" type="neon_d"/>
+ <reg name="d18" bitsize="64" type="neon_d"/>
+ <reg name="d19" bitsize="64" type="neon_d"/>
+ <reg name="d20" bitsize="64" type="neon_d"/>
+ <reg name="d21" bitsize="64" type="neon_d"/>
+ <reg name="d22" bitsize="64" type="neon_d"/>
+ <reg name="d23" bitsize="64" type="neon_d"/>
+ <reg name="d24" bitsize="64" type="neon_d"/>
+ <reg name="d25" bitsize="64" type="neon_d"/>
+ <reg name="d26" bitsize="64" type="neon_d"/>
+ <reg name="d27" bitsize="64" type="neon_d"/>
+ <reg name="d28" bitsize="64" type="neon_d"/>
+ <reg name="d29" bitsize="64" type="neon_d"/>
+ <reg name="d30" bitsize="64" type="neon_d"/>
+ <reg name="d31" bitsize="64" type="neon_d"/>
+
+ <reg name="q0" bitsize="128" type="neon_q" regnum="64"/>
+ <reg name="q1" bitsize="128" type="neon_q"/>
+ <reg name="q2" bitsize="128" type="neon_q"/>
+ <reg name="q3" bitsize="128" type="neon_q"/>
+ <reg name="q4" bitsize="128" type="neon_q"/>
+ <reg name="q5" bitsize="128" type="neon_q"/>
+ <reg name="q6" bitsize="128" type="neon_q"/>
+ <reg name="q7" bitsize="128" type="neon_q"/>
+ <reg name="q8" bitsize="128" type="neon_q"/>
+ <reg name="q9" bitsize="128" type="neon_q"/>
+ <reg name="q10" bitsize="128" type="neon_q"/>
+ <reg name="q10" bitsize="128" type="neon_q"/>
+ <reg name="q12" bitsize="128" type="neon_q"/>
+ <reg name="q13" bitsize="128" type="neon_q"/>
+ <reg name="q14" bitsize="128" type="neon_q"/>
+ <reg name="q15" bitsize="128" type="neon_q"/>
+
+ <reg name="fpscr" bitsize="32" type="int" group="float" regnum="80"/>
+ </feature>
+</target>)";
+
+ return target_xml;
+}
+
+std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const {
+ if (!thread) {
+ return "";
+ }
+
+ const auto& context{thread->GetContext32()};
+ const auto& gprs{context.cpu_registers};
+ const auto& fprs{context.extension_registers};
+
+ if (id <= PC_REGISTER) {
+ return ValueToHex(gprs[id]);
+ } else if (id == CPSR_REGISTER) {
+ return ValueToHex(context.cpsr);
+ } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
+ const u64 dN{GetSIMDRegister<u64>(fprs, id - D0_REGISTER)};
+ return ValueToHex(dN);
+ } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
+ const u128 qN{GetSIMDRegister<u128>(fprs, id - Q0_REGISTER)};
+ return ValueToHex(qN);
+ } else if (id == FPSCR_REGISTER) {
+ return ValueToHex(context.fpscr);
+ } else {
+ return "";
+ }
+}
+
+void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const {
+ if (!thread) {
+ return;
+ }
+
+ auto& context{thread->GetContext32()};
+ auto& fprs{context.extension_registers};
+
+ if (id <= PC_REGISTER) {
+ context.cpu_registers[id] = HexToValue<u32>(value);
+ } else if (id == CPSR_REGISTER) {
+ context.cpsr = HexToValue<u32>(value);
+ } else if (id >= D0_REGISTER && id < Q0_REGISTER) {
+ PutSIMDRegister(fprs, id - D0_REGISTER, HexToValue<u64>(value));
+ } else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
+ PutSIMDRegister(fprs, id - Q0_REGISTER, HexToValue<u128>(value));
+ } else if (id == FPSCR_REGISTER) {
+ context.fpscr = HexToValue<u32>(value);
+ }
+}
+
+std::string GDBStubA32::ReadRegisters(const Kernel::KThread* thread) const {
+ std::string output;
+
+ for (size_t reg = 0; reg <= FPSCR_REGISTER; reg++) {
+ const bool gpr{reg <= PC_REGISTER};
+ const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
+ const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
+
+ if (!(gpr || dfpr || qfpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER)) {
+ continue;
+ }
+
+ output += RegRead(thread, reg);
+ }
+
+ return output;
+}
+
+void GDBStubA32::WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const {
+ for (size_t i = 0, reg = 0; reg <= FPSCR_REGISTER; reg++) {
+ const bool gpr{reg <= PC_REGISTER};
+ const bool dfpr{reg >= D0_REGISTER && reg < Q0_REGISTER};
+ const bool qfpr{reg >= Q0_REGISTER && reg < FPSCR_REGISTER};
+
+ if (gpr || reg == CPSR_REGISTER || reg == FPSCR_REGISTER) {
+ RegWrite(thread, reg, register_data.substr(i, 8));
+ i += 8;
+ } else if (dfpr) {
+ RegWrite(thread, reg, register_data.substr(i, 16));
+ i += 16;
+ } else if (qfpr) {
+ RegWrite(thread, reg, register_data.substr(i, 32));
+ i += 32;
+ }
+
+ if (reg == PC_REGISTER) {
+ reg = CPSR_REGISTER - 1;
+ } else if (reg == CPSR_REGISTER) {
+ reg = D0_REGISTER - 1;
+ }
+ }
+}
+
+std::string GDBStubA32::ThreadStatus(const Kernel::KThread* thread, u8 signal) const {
+ return fmt::format("T{:02x}{:02x}:{};{:02x}:{};{:02x}:{};thread:{:x};", signal, PC_REGISTER,
+ RegRead(thread, PC_REGISTER), SP_REGISTER, RegRead(thread, SP_REGISTER),
+ LR_REGISTER, RegRead(thread, LR_REGISTER), thread->GetThreadID());
+}
+
+u32 GDBStubA32::BreakpointInstruction() const {
+ // A32: trap
+ // T32: trap + b #4
+ return 0xe7ffdefe;
+}
+
+} // namespace Core
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 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+class KThread;
+}
+
+namespace Core {
+
+class GDBStubArch {
+public:
+ virtual std::string GetTargetXML() const = 0;
+ virtual std::string RegRead(const Kernel::KThread* thread, size_t id) const = 0;
+ virtual void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const = 0;
+ virtual std::string ReadRegisters(const Kernel::KThread* thread) const = 0;
+ virtual void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const = 0;
+ virtual std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const = 0;
+ virtual u32 BreakpointInstruction() const = 0;
+};
+
+class GDBStubA64 final : public GDBStubArch {
+public:
+ std::string GetTargetXML() const override;
+ std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
+ void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
+ std::string ReadRegisters(const Kernel::KThread* thread) const override;
+ void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
+ std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
+ u32 BreakpointInstruction() const override;
+
+private:
+ static constexpr u32 LR_REGISTER = 30;
+ static constexpr u32 SP_REGISTER = 31;
+ static constexpr u32 PC_REGISTER = 32;
+ static constexpr u32 PSTATE_REGISTER = 33;
+ static constexpr u32 Q0_REGISTER = 34;
+ static constexpr u32 FPSR_REGISTER = 66;
+ static constexpr u32 FPCR_REGISTER = 67;
+};
+
+class GDBStubA32 final : public GDBStubArch {
+public:
+ std::string GetTargetXML() const override;
+ std::string RegRead(const Kernel::KThread* thread, size_t id) const override;
+ void RegWrite(Kernel::KThread* thread, size_t id, std::string_view value) const override;
+ std::string ReadRegisters(const Kernel::KThread* thread) const override;
+ void WriteRegisters(Kernel::KThread* thread, std::string_view register_data) const override;
+ std::string ThreadStatus(const Kernel::KThread* thread, u8 signal) const override;
+ u32 BreakpointInstruction() const override;
+
+private:
+ static constexpr u32 SP_REGISTER = 13;
+ static constexpr u32 LR_REGISTER = 14;
+ static constexpr u32 PC_REGISTER = 15;
+ static constexpr u32 CPSR_REGISTER = 25;
+ static constexpr u32 D0_REGISTER = 32;
+ static constexpr u32 Q0_REGISTER = 64;
+ static constexpr u32 FPSCR_REGISTER = 80;
+};
+
+} // namespace Core
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
{
KScopedSchedulerLock lock{kernel};
thread->SetState(ThreadState::Runnable);
+
+ if (system.DebuggerEnabled()) {
+ thread->RequestSuspend(SuspendType::Debug);
+ }
}
}
} // 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
resource_limit_release_hint = false;
cpu_time = 0;
+ // Set debug context.
+ stack_top = user_stack_top;
+ argument = arg;
+
// Clear our stack parameters.
std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0,
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 {
Suspended, ///< Thread is waiting due to process suspension
};
+enum class StepState : u32 {
+ NotStepping, ///< Thread is not currently stepping
+ StepPending, ///< Thread will step when next scheduled
+ StepPerformed, ///< Thread has stepped, waiting to be scheduled again
+};
+
[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel);
[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel);
[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
@@ -267,6 +273,14 @@ public:
void SetState(ThreadState state);
+ [[nodiscard]] StepState GetStepState() const {
+ return step_state;
+ }
+
+ void SetStepState(StepState state) {
+ step_state = state;
+ }
+
[[nodiscard]] s64 GetLastScheduledTick() const {
return last_scheduled_tick;
}
@@ -646,6 +660,14 @@ public:
void IfDummyThreadTryWait();
void IfDummyThreadEndWait();
+ [[nodiscard]] uintptr_t GetArgument() const {
+ return argument;
+ }
+
+ [[nodiscard]] VAddr GetUserStackTop() const {
+ return stack_top;
+ }
+
private:
static constexpr size_t PriorityInheritanceCountMax = 10;
union SyncObjectBuffer {
@@ -769,6 +791,7 @@ private:
std::shared_ptr<Common::Fiber> host_context{};
bool is_single_core{};
ThreadType thread_type{};
+ StepState step_state{};
std::mutex dummy_wait_lock;
std::condition_variable dummy_wait_cv;
@@ -776,6 +799,8 @@ private:
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
VAddr mutex_wait_address_for_debugging{};
ThreadWaitReasonForDebugging wait_reason_for_debugging{};
+ uintptr_t argument;
+ VAddr stack_top;
public:
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 {
return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
}
+bool Memory::IsValidVirtualAddressRange(VAddr base, u64 size) const {
+ VAddr end = base + size;
+ VAddr page = Common::AlignDown(base, PAGE_SIZE);
+
+ for (; page < end; page += PAGE_SIZE) {
+ if (!IsValidVirtualAddress(page)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
u8* Memory::GetPointer(VAddr vaddr) {
return impl->GetPointer(vaddr);
}
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:
[[nodiscard]] bool IsValidVirtualAddress(VAddr vaddr) const;
/**
+ * Checks whether or not the supplied range of addresses are all valid
+ * virtual addresses for the current process.
+ *
+ * @param base The address to begin checking.
+ * @param size The amount of bytes to check.
+ *
+ * @returns True if all bytes in the given range are valid, false otherwise.
+ */
+ [[nodiscard]] bool IsValidVirtualAddressRange(VAddr base, u64 size) const;
+
+ /**
* Gets a pointer to the given address.
*
* @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()
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
)
endif()
+
+if (ARCHITECTURE_x86_64)
+ target_link_libraries(video_core PRIVATE dynarmic)
+endif()
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() {
std::optional<u64> Maxwell3D::GetQueryResult() {
switch (regs.query.query_get.select) {
- case Regs::QuerySelect::Zero:
- return 0;
+ case Regs::QuerySelect::Payload:
+ return regs.query.query_sequence;
case Regs::QuerySelect::SamplesPassed:
// Deferred.
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:
};
enum class QuerySelect : u32 {
- Zero = 0,
+ Payload = 0,
TimeElapsed = 2,
TransformFeedbackPrimitivesGenerated = 11,
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()
if (NOT APPLE)
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
endif()
+
+if (ARCHITECTURE_x86_64)
+ target_link_libraries(yuzu PRIVATE dynarmic)
+endif()
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() {
auto& gpu = system.GPU();
auto stop_token = stop_source.get_token();
+ bool debugger_should_start = system.DebuggerEnabled();
system.RegisterHostThread();
@@ -89,6 +90,12 @@ void EmuThread::run() {
this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails());
}
+
+ if (debugger_should_start) {
+ system.InitializeDebugger();
+ debugger_should_start = false;
+ }
+
running_wait.Wait();
result = system.Pause();
if (result != Core::SystemResultStatus::Success) {
@@ -102,11 +109,9 @@ void EmuThread::run() {
was_active = true;
emit DebugModeEntered();
}
- } else if (exec_step) {
- UNIMPLEMENTED();
} else {
std::unique_lock lock{running_mutex};
- running_cv.wait(lock, stop_token, [this] { return IsRunning() || exec_step; });
+ running_cv.wait(lock, stop_token, [this] { return IsRunning(); });
}
}
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:
void run() override;
/**
- * Steps the emulation thread by a single CPU instruction (if the CPU is not already running)
- * @note This function is thread-safe
- */
- void ExecStep() {
- exec_step = true;
- running_cv.notify_all();
- }
-
- /**
* Sets whether the emulation thread is running or not
* @param running Boolean value, set the emulation thread to running if true
* @note This function is thread-safe
@@ -99,7 +90,6 @@ public:
}
private:
- bool exec_step = false;
bool running = false;
std::stop_source stop_source;
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() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
Settings::values.record_frame_times =
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
+
+ ReadBasicSetting(Settings::values.use_gdbstub);
+ ReadBasicSetting(Settings::values.gdbstub_port);
ReadBasicSetting(Settings::values.program_args);
ReadBasicSetting(Settings::values.dump_exefs);
ReadBasicSetting(Settings::values.dump_nso);
@@ -1095,6 +1098,8 @@ void Config::SaveDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
+ WriteBasicSetting(Settings::values.use_gdbstub);
+ WriteBasicSetting(Settings::values.gdbstub_port);
WriteBasicSetting(Settings::values.program_args);
WriteBasicSetting(Settings::values.dump_exefs);
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)
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
+
+ connect(ui->toggle_gdbstub, &QCheckBox::toggled,
+ [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
}
ConfigureDebug::~ConfigureDebug() = default;
void ConfigureDebug::SetConfiguration() {
const bool runtime_lock = !system.IsPoweredOn();
-
+ ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
+ ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
+ ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
ui->toggle_console->setEnabled(runtime_lock);
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
@@ -71,6 +76,8 @@ void ConfigureDebug::SetConfiguration() {
}
void ConfigureDebug::ApplyConfiguration() {
+ Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
+ Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
UISettings::values.show_console = ui->toggle_console->isChecked();
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
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 @@
<class>ConfigureDebug</class>
<widget class="QWidget" name="ConfigureDebug">
<layout class="QVBoxLayout" name="verticalLayout_1">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Debugger</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="toggle_gdbstub">
+ <property name="text">
+ <string>Enable GDB Stub</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="gdbport_spinbox">
+ <property name="minimum">
+ <number>1024</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<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() {
void GMainWindow::RestoreUIState() {
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
restoreGeometry(UISettings::values.geometry);
+ // Work-around because the games list isn't supposed to be full screen
+ if (isFullScreen()) {
+ showNormal();
+ }
restoreState(UISettings::values.state);
render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint);
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
@@ -3152,7 +3156,7 @@ void GMainWindow::OnTasStateChanged() {
}
void GMainWindow::UpdateStatusBar() {
- if (emu_thread == nullptr) {
+ if (emu_thread == nullptr || !system->IsPoweredOn()) {
status_bar_update_timer.stop();
return;
}