core/debugger: Improved stepping mechanism and misc fixes
This commit is contained in:
		| @@ -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() | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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_, | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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_, | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
| }; | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user