diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e3de90ff4..be70c04ae6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -222,6 +222,11 @@ else()
     list(APPEND CONAN_REQUIRED_LIBS "boost/1.79.0")
 endif()
 
+# boost:asio has functions that require AcceptEx et al
+if (MINGW)
+    find_library(MSWSOCK_LIBRARY mswsock REQUIRED)
+endif()
+
 # Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
 yuzu_find_packages()
 
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 948cc318a7..2bd720f08f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -768,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 1310f72bf9..9b5a5ca576 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -11,6 +11,7 @@
 #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"
 
@@ -89,8 +90,48 @@ void ARM_Interface::LogBacktrace() const {
     }
 }
 
-bool ARM_Interface::ShouldStep() const {
-    return system.DebuggerEnabled() && system.GetDebugger().IsStepping();
+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 7842c626bb..66f6107e9f 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,7 +67,7 @@ public:
     static_assert(sizeof(ThreadContext64) == 0x320);
 
     /// Runs the CPU until an event happens
-    virtual void Run() = 0;
+    void Run();
 
     /// Clear all instruction cache
     virtual void ClearInstructionCache() = 0;
@@ -191,7 +194,10 @@ public:
 
     void LogBacktrace() const;
 
-    bool ShouldStep() 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.
@@ -200,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 894c1c527d..7c82d0b96e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -26,10 +26,6 @@ namespace Core {
 
 using namespace Common::Literals;
 
-constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
-constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
-constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
-
 class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
 public:
     explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
@@ -82,8 +78,8 @@ public:
 
     void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
         if (parent.system.DebuggerEnabled()) {
-            parent.breakpoint_pc = pc;
-            parent.jit.load()->HaltExecution(breakpoint);
+            parent.jit.load()->Regs()[15] = pc;
+            parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
             return;
         }
 
@@ -95,7 +91,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 {
@@ -240,35 +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 = ShouldStep() ? jit.load()->Step() : jit.load()->Run();
-        if (Has(hr, svc_call)) {
-            Kernel::Svc::Call(system, svc_swi);
-        }
+Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() {
+    return jit.load()->Run();
+}
 
-        // Check to see if breakpoint is triggered.
-        // Recheck step condition in case stop is no longer desired.
-        Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
-        if (Has(hr, breakpoint)) {
-            jit.load()->Regs()[15] = breakpoint_pc;
+Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() {
+    return jit.load()->Step();
+}
 
-            if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
-                current_thread->RequestSuspend(Kernel::SuspendType::Debug);
-            }
-            break;
-        }
-        if (ShouldStep()) {
-            // When stepping, this should be the only thread running.
-            ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
-            current_thread->RequestSuspend(Kernel::SuspendType::Debug);
-            break;
-        }
-
-        if (Has(hr, break_loop) || !uses_wall_clock) {
-            break;
-        }
-    }
+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 0557d59403..5b1d60005d 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -41,7 +41,6 @@ public:
     void SetVectorReg(int index, u128 value) override;
     u32 GetPSTATE() const override;
     void SetPSTATE(u32 pstate) override;
-    void Run() override;
     VAddr GetTlsAddress() const override;
     void SetTlsAddress(VAddr address) override;
     void SetTPIDR_EL0(u64 value) override;
@@ -69,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;
 
@@ -94,9 +98,6 @@ private:
 
     // SVC callback
     u32 svc_swi{};
-
-    // Debug restart address
-    u32 breakpoint_pc{};
 };
 
 } // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 1f596cfef1..d4c67eafdd 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -26,10 +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;
-constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
-
 class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
 public:
     explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
@@ -123,8 +119,8 @@ public:
             return;
         default:
             if (parent.system.DebuggerEnabled()) {
-                parent.breakpoint_pc = pc;
-                parent.jit.load()->HaltExecution(breakpoint);
+                parent.jit.load()->SetPC(pc);
+                parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
                 return;
             }
 
@@ -136,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 {
@@ -300,35 +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);
-        }
+Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() {
+    return jit.load()->Run();
+}
 
-        // Check to see if breakpoint is triggered.
-        // Recheck step condition in case stop is no longer desired.
-        Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
-        if (Has(hr, breakpoint)) {
-            jit.load()->SetPC(breakpoint_pc);
+Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() {
+    return jit.load()->Step();
+}
 
-            if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
-                current_thread->RequestSuspend(Kernel::SuspendType::Debug);
-            }
-            break;
-        }
-        if (ShouldStep()) {
-            // When stepping, this should be the only thread running.
-            ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
-            current_thread->RequestSuspend(Kernel::SuspendType::Debug);
-            break;
-        }
-
-        if (Has(hr, break_loop) || !uses_wall_clock) {
-            break;
-        }
-    }
+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 aa7054e0ca..abfbc3c3f3 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -39,7 +39,6 @@ public:
     void SetVectorReg(int index, u128 value) override;
     u32 GetPSTATE() const override;
     void SetPSTATE(u32 pstate) override;
-    void Run() override;
     VAddr GetTlsAddress() const override;
     void SetTlsAddress(VAddr address) override;
     void SetTPIDR_EL0(u64 value) override;
@@ -63,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;
@@ -87,9 +91,6 @@ private:
 
     // SVC callback
     u32 svc_swi{};
-
-    // Debug restart address
-    u64 breakpoint_pc{};
 };
 
 } // namespace Core
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index dd3e330e6a..68ab33e460 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <algorithm>
 #include <mutex>
 #include <thread>
 
@@ -84,31 +85,31 @@ public:
         return active_thread;
     }
 
-    bool IsStepping() const {
-        return stepping;
-    }
-
 private:
     void InitializeServer(u16 port) {
         using boost::asio::ip::tcp;
 
         LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
 
-        // Initialize the listening socket and accept a new client.
-        tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
-        tcp::acceptor acceptor{io_context, endpoint};
-        client_socket = acceptor.accept();
-
         // Run the connection thread.
-        connection_thread = std::jthread([&](std::stop_token stop_token) {
+        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());
             }
-
-            client_socket.shutdown(client_socket.shutdown_both);
-            client_socket.close();
         });
     }
 
@@ -129,8 +130,7 @@ private:
         AllCoreStop();
 
         // Set the active thread.
-        active_thread = ThreadList()[0];
-        active_thread->Resume(Kernel::SuspendType::Debug);
+        UpdateActiveThread();
 
         // Set up the frontend.
         frontend->Connected();
@@ -142,7 +142,7 @@ private:
 
     void PipeData(std::span<const u8> data) {
         AllCoreStop();
-        active_thread->Resume(Kernel::SuspendType::Debug);
+        UpdateActiveThread();
         frontend->Stopped(active_thread);
     }
 
@@ -156,18 +156,22 @@ private:
                     stopped = true;
                 }
                 AllCoreStop();
-                active_thread = ThreadList()[0];
-                active_thread->Resume(Kernel::SuspendType::Debug);
+                UpdateActiveThread();
                 frontend->Stopped(active_thread);
                 break;
             }
             case DebuggerAction::Continue:
-                stepping = false;
+                active_thread->SetStepState(Kernel::StepState::NotStepping);
                 ResumeInactiveThreads();
                 AllCoreResume();
                 break;
-            case DebuggerAction::StepThread:
-                stepping = true;
+            case DebuggerAction::StepThreadUnlocked:
+                active_thread->SetStepState(Kernel::StepState::StepPending);
+                ResumeInactiveThreads();
+                AllCoreResume();
+                break;
+            case DebuggerAction::StepThreadLocked:
+                active_thread->SetStepState(Kernel::StepState::StepPending);
                 SuspendInactiveThreads();
                 AllCoreResume();
                 break;
@@ -212,10 +216,20 @@ private:
         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();
     }
@@ -233,7 +247,6 @@ private:
 
     Kernel::KThread* active_thread;
     bool stopped;
-    bool stepping;
 
     std::array<u8, 4096> client_data;
 };
@@ -252,8 +265,4 @@ bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
     return impl && impl->NotifyThreadStopped(thread);
 }
 
-bool Debugger::IsStepping() const {
-    return impl && impl->IsStepping();
-}
-
 } // namespace Core
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h
index 7acd118157..ea36c6ab20 100644
--- a/src/core/debugger/debugger.h
+++ b/src/core/debugger/debugger.h
@@ -35,11 +35,6 @@ public:
      */
     bool NotifyThreadStopped(Kernel::KThread* thread);
 
-    /**
-     * Returns whether a step is in progress.
-     */
-    bool IsStepping() const;
-
 private:
     std::unique_ptr<DebuggerImpl> impl;
 };
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h
index e6d4c01901..35ba0bc619 100644
--- a/src/core/debugger/debugger_interface.h
+++ b/src/core/debugger/debugger_interface.h
@@ -16,10 +16,11 @@ class KThread;
 namespace Core {
 
 enum class DebuggerAction {
-    Interrupt,         // Stop emulation as soon as possible.
-    Continue,          // Resume emulation.
-    StepThread,        // Step the currently-active thread.
-    ShutdownEmulation, // Shut down the emulator.
+    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 {
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index ee75981658..0c36069a64 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -6,8 +6,7 @@
 #include <optional>
 #include <thread>
 
-#include <boost/asio.hpp>
-#include <boost/process/async_pipe.hpp>
+#include <boost/algorithm/string.hpp>
 
 #include "common/hex_util.h"
 #include "common/logging/log.h"
@@ -114,6 +113,11 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
         return;
     }
 
+    if (packet.starts_with("vCont")) {
+        HandleVCont(packet.substr(5), actions);
+        return;
+    }
+
     std::string_view command{packet.substr(1, packet.size())};
 
     switch (packet[0]) {
@@ -122,6 +126,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
         s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
         if (thread_id >= 1) {
             thread = GetThreadByID(thread_id);
+        } else {
+            thread = backend.GetActiveThread();
         }
 
         if (thread) {
@@ -141,6 +147,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
         }
         break;
     }
+    case 'Q':
     case 'q':
         HandleQuery(command);
         break;
@@ -204,7 +211,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
         break;
     }
     case 's':
-        actions.push_back(DebuggerAction::StepThread);
+        actions.push_back(DebuggerAction::StepThreadLocked);
         break;
     case 'C':
     case 'c':
@@ -248,12 +255,47 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
     }
 }
 
+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+");
+        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)};
@@ -297,18 +339,57 @@ void GDBStub::HandleQuery(std::string_view command) {
 
         const auto& threads = system.GlobalSchedulerContext().GetThreadList();
         for (const auto& thread : threads) {
-            buffer +=
-                fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)",
-                            thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID());
+            buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)",
+                                  thread->GetThreadID(), thread->GetActiveCore(),
+                                  thread->GetThreadID(), GetThreadState(thread));
         }
 
         buffer += "</threads>";
         SendReply(buffer);
+    } 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) {
@@ -374,6 +455,10 @@ void GDBStub::SendReply(std::string_view data) {
 }
 
 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);
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
index e58d60719b..aa1f7de6c0 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -28,6 +28,7 @@ public:
 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();
@@ -42,6 +43,7 @@ private:
     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/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index b55a922abd..60ae0da78e 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;
     }
@@ -769,6 +783,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;
 
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 6a6325e38e..256695804a 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/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 404acdd053..07df9675dd 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()