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") |     list(APPEND CONAN_REQUIRED_LIBS "boost/1.79.0") | ||||||
| endif() | 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 | # Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS | ||||||
| yuzu_find_packages() | 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 common PRIVATE audio_core video_core) | ||||||
| target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus) | 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) | if (ENABLE_WEB_SERVICE) | ||||||
|     target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) |     target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
| #include "core/debugger/debugger.h" | #include "core/debugger/debugger.h" | ||||||
| #include "core/hle/kernel/k_process.h" | #include "core/hle/kernel/k_process.h" | ||||||
|  | #include "core/hle/kernel/svc.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
|  |  | ||||||
| @@ -89,8 +90,48 @@ void ARM_Interface::LogBacktrace() const { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| bool ARM_Interface::ShouldStep() const { | void ARM_Interface::Run() { | ||||||
|     return system.DebuggerEnabled() && system.GetDebugger().IsStepping(); |     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 | } // namespace Core | ||||||
|   | |||||||
| @@ -6,6 +6,9 @@ | |||||||
|  |  | ||||||
| #include <array> | #include <array> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #include <dynarmic/interface/halt_reason.h> | ||||||
|  |  | ||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/hardware_properties.h" | #include "core/hardware_properties.h" | ||||||
| @@ -64,7 +67,7 @@ public: | |||||||
|     static_assert(sizeof(ThreadContext64) == 0x320); |     static_assert(sizeof(ThreadContext64) == 0x320); | ||||||
|  |  | ||||||
|     /// Runs the CPU until an event happens |     /// Runs the CPU until an event happens | ||||||
|     virtual void Run() = 0; |     void Run(); | ||||||
|  |  | ||||||
|     /// Clear all instruction cache |     /// Clear all instruction cache | ||||||
|     virtual void ClearInstructionCache() = 0; |     virtual void ClearInstructionCache() = 0; | ||||||
| @@ -191,7 +194,10 @@ public: | |||||||
|  |  | ||||||
|     void LogBacktrace() const; |     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: | protected: | ||||||
|     /// System context that this ARM interface is running under. |     /// System context that this ARM interface is running under. | ||||||
| @@ -200,6 +206,10 @@ protected: | |||||||
|     bool uses_wall_clock; |     bool uses_wall_clock; | ||||||
|  |  | ||||||
|     static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out); |     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 | } // namespace Core | ||||||
|   | |||||||
| @@ -26,10 +26,6 @@ namespace Core { | |||||||
|  |  | ||||||
| using namespace Common::Literals; | 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 { | class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { | ||||||
| public: | public: | ||||||
|     explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) |     explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_) | ||||||
| @@ -82,8 +78,8 @@ public: | |||||||
|  |  | ||||||
|     void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { |     void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { | ||||||
|         if (parent.system.DebuggerEnabled()) { |         if (parent.system.DebuggerEnabled()) { | ||||||
|             parent.breakpoint_pc = pc; |             parent.jit.load()->Regs()[15] = pc; | ||||||
|             parent.jit.load()->HaltExecution(breakpoint); |             parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -95,7 +91,7 @@ public: | |||||||
|  |  | ||||||
|     void CallSVC(u32 swi) override { |     void CallSVC(u32 swi) override { | ||||||
|         parent.svc_swi = swi; |         parent.svc_swi = swi; | ||||||
|         parent.jit.load()->HaltExecution(svc_call); |         parent.jit.load()->HaltExecution(ARM_Interface::svc_call); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void AddTicks(u64 ticks) override { |     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); |     return std::make_unique<Dynarmic::A32::Jit>(config); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ARM_Dynarmic_32::Run() { | Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() { | ||||||
|     while (true) { |     return jit.load()->Run(); | ||||||
|         const auto hr = ShouldStep() ? jit.load()->Step() : jit.load()->Run(); | } | ||||||
|         if (Has(hr, svc_call)) { |  | ||||||
|             Kernel::Svc::Call(system, svc_swi); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Check to see if breakpoint is triggered. | Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() { | ||||||
|         // Recheck step condition in case stop is no longer desired. |     return jit.load()->Step(); | ||||||
|         Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread(); | } | ||||||
|         if (Has(hr, breakpoint)) { |  | ||||||
|             jit.load()->Regs()[15] = breakpoint_pc; |  | ||||||
|  |  | ||||||
|             if (system.GetDebugger().NotifyThreadStopped(current_thread)) { | u32 ARM_Dynarmic_32::GetSvcNumber() const { | ||||||
|                 current_thread->RequestSuspend(Kernel::SuspendType::Debug); |     return svc_swi; | ||||||
|             } |  | ||||||
|             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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, | ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, | ||||||
|   | |||||||
| @@ -41,7 +41,6 @@ public: | |||||||
|     void SetVectorReg(int index, u128 value) override; |     void SetVectorReg(int index, u128 value) override; | ||||||
|     u32 GetPSTATE() const override; |     u32 GetPSTATE() const override; | ||||||
|     void SetPSTATE(u32 pstate) override; |     void SetPSTATE(u32 pstate) override; | ||||||
|     void Run() override; |  | ||||||
|     VAddr GetTlsAddress() const override; |     VAddr GetTlsAddress() const override; | ||||||
|     void SetTlsAddress(VAddr address) override; |     void SetTlsAddress(VAddr address) override; | ||||||
|     void SetTPIDR_EL0(u64 value) override; |     void SetTPIDR_EL0(u64 value) override; | ||||||
| @@ -69,6 +68,11 @@ public: | |||||||
|  |  | ||||||
|     std::vector<BacktraceEntry> GetBacktrace() const override; |     std::vector<BacktraceEntry> GetBacktrace() const override; | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  |     Dynarmic::HaltReason RunJit() override; | ||||||
|  |     Dynarmic::HaltReason StepJit() override; | ||||||
|  |     u32 GetSvcNumber() const override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const; |     std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const; | ||||||
|  |  | ||||||
| @@ -94,9 +98,6 @@ private: | |||||||
|  |  | ||||||
|     // SVC callback |     // SVC callback | ||||||
|     u32 svc_swi{}; |     u32 svc_swi{}; | ||||||
|  |  | ||||||
|     // Debug restart address |  | ||||||
|     u32 breakpoint_pc{}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } // namespace Core | } // namespace Core | ||||||
|   | |||||||
| @@ -26,10 +26,6 @@ namespace Core { | |||||||
| using Vector = Dynarmic::A64::Vector; | using Vector = Dynarmic::A64::Vector; | ||||||
| using namespace Common::Literals; | 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 { | class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { | ||||||
| public: | public: | ||||||
|     explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) |     explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_) | ||||||
| @@ -123,8 +119,8 @@ public: | |||||||
|             return; |             return; | ||||||
|         default: |         default: | ||||||
|             if (parent.system.DebuggerEnabled()) { |             if (parent.system.DebuggerEnabled()) { | ||||||
|                 parent.breakpoint_pc = pc; |                 parent.jit.load()->SetPC(pc); | ||||||
|                 parent.jit.load()->HaltExecution(breakpoint); |                 parent.jit.load()->HaltExecution(ARM_Interface::breakpoint); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -136,7 +132,7 @@ public: | |||||||
|  |  | ||||||
|     void CallSVC(u32 swi) override { |     void CallSVC(u32 swi) override { | ||||||
|         parent.svc_swi = swi; |         parent.svc_swi = swi; | ||||||
|         parent.jit.load()->HaltExecution(svc_call); |         parent.jit.load()->HaltExecution(ARM_Interface::svc_call); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void AddTicks(u64 ticks) override { |     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); |     return std::make_shared<Dynarmic::A64::Jit>(config); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ARM_Dynarmic_64::Run() { | Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() { | ||||||
|     while (true) { |     return jit.load()->Run(); | ||||||
|         const auto hr = jit.load()->Run(); | } | ||||||
|         if (Has(hr, svc_call)) { |  | ||||||
|             Kernel::Svc::Call(system, svc_swi); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Check to see if breakpoint is triggered. | Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() { | ||||||
|         // Recheck step condition in case stop is no longer desired. |     return jit.load()->Step(); | ||||||
|         Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread(); | } | ||||||
|         if (Has(hr, breakpoint)) { |  | ||||||
|             jit.load()->SetPC(breakpoint_pc); |  | ||||||
|  |  | ||||||
|             if (system.GetDebugger().NotifyThreadStopped(current_thread)) { | u32 ARM_Dynarmic_64::GetSvcNumber() const { | ||||||
|                 current_thread->RequestSuspend(Kernel::SuspendType::Debug); |     return svc_swi; | ||||||
|             } |  | ||||||
|             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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, | ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, | ||||||
|   | |||||||
| @@ -39,7 +39,6 @@ public: | |||||||
|     void SetVectorReg(int index, u128 value) override; |     void SetVectorReg(int index, u128 value) override; | ||||||
|     u32 GetPSTATE() const override; |     u32 GetPSTATE() const override; | ||||||
|     void SetPSTATE(u32 pstate) override; |     void SetPSTATE(u32 pstate) override; | ||||||
|     void Run() override; |  | ||||||
|     VAddr GetTlsAddress() const override; |     VAddr GetTlsAddress() const override; | ||||||
|     void SetTlsAddress(VAddr address) override; |     void SetTlsAddress(VAddr address) override; | ||||||
|     void SetTPIDR_EL0(u64 value) override; |     void SetTPIDR_EL0(u64 value) override; | ||||||
| @@ -63,6 +62,11 @@ public: | |||||||
|  |  | ||||||
|     std::vector<BacktraceEntry> GetBacktrace() const override; |     std::vector<BacktraceEntry> GetBacktrace() const override; | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  |     Dynarmic::HaltReason RunJit() override; | ||||||
|  |     Dynarmic::HaltReason StepJit() override; | ||||||
|  |     u32 GetSvcNumber() const override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, |     std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table, | ||||||
|                                                 std::size_t address_space_bits) const; |                                                 std::size_t address_space_bits) const; | ||||||
| @@ -87,9 +91,6 @@ private: | |||||||
|  |  | ||||||
|     // SVC callback |     // SVC callback | ||||||
|     u32 svc_swi{}; |     u32 svc_swi{}; | ||||||
|  |  | ||||||
|     // Debug restart address |  | ||||||
|     u64 breakpoint_pc{}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } // namespace Core | } // namespace Core | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | ||||||
| // SPDX-License-Identifier: GPL-2.0-or-later | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
| #include <mutex> | #include <mutex> | ||||||
| #include <thread> | #include <thread> | ||||||
|  |  | ||||||
| @@ -84,31 +85,31 @@ public: | |||||||
|         return active_thread; |         return active_thread; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool IsStepping() const { |  | ||||||
|         return stepping; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     void InitializeServer(u16 port) { |     void InitializeServer(u16 port) { | ||||||
|         using boost::asio::ip::tcp; |         using boost::asio::ip::tcp; | ||||||
|  |  | ||||||
|         LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port); |         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. |         // Run the connection thread. | ||||||
|         connection_thread = std::jthread([&](std::stop_token stop_token) { |         connection_thread = std::jthread([&, port](std::stop_token stop_token) { | ||||||
|             try { |             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); |                 ThreadLoop(stop_token); | ||||||
|             } catch (const std::exception& ex) { |             } catch (const std::exception& ex) { | ||||||
|                 LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); |                 LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             client_socket.shutdown(client_socket.shutdown_both); |  | ||||||
|             client_socket.close(); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -129,8 +130,7 @@ private: | |||||||
|         AllCoreStop(); |         AllCoreStop(); | ||||||
|  |  | ||||||
|         // Set the active thread. |         // Set the active thread. | ||||||
|         active_thread = ThreadList()[0]; |         UpdateActiveThread(); | ||||||
|         active_thread->Resume(Kernel::SuspendType::Debug); |  | ||||||
|  |  | ||||||
|         // Set up the frontend. |         // Set up the frontend. | ||||||
|         frontend->Connected(); |         frontend->Connected(); | ||||||
| @@ -142,7 +142,7 @@ private: | |||||||
|  |  | ||||||
|     void PipeData(std::span<const u8> data) { |     void PipeData(std::span<const u8> data) { | ||||||
|         AllCoreStop(); |         AllCoreStop(); | ||||||
|         active_thread->Resume(Kernel::SuspendType::Debug); |         UpdateActiveThread(); | ||||||
|         frontend->Stopped(active_thread); |         frontend->Stopped(active_thread); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -156,18 +156,22 @@ private: | |||||||
|                     stopped = true; |                     stopped = true; | ||||||
|                 } |                 } | ||||||
|                 AllCoreStop(); |                 AllCoreStop(); | ||||||
|                 active_thread = ThreadList()[0]; |                 UpdateActiveThread(); | ||||||
|                 active_thread->Resume(Kernel::SuspendType::Debug); |  | ||||||
|                 frontend->Stopped(active_thread); |                 frontend->Stopped(active_thread); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             case DebuggerAction::Continue: |             case DebuggerAction::Continue: | ||||||
|                 stepping = false; |                 active_thread->SetStepState(Kernel::StepState::NotStepping); | ||||||
|                 ResumeInactiveThreads(); |                 ResumeInactiveThreads(); | ||||||
|                 AllCoreResume(); |                 AllCoreResume(); | ||||||
|                 break; |                 break; | ||||||
|             case DebuggerAction::StepThread: |             case DebuggerAction::StepThreadUnlocked: | ||||||
|                 stepping = true; |                 active_thread->SetStepState(Kernel::StepState::StepPending); | ||||||
|  |                 ResumeInactiveThreads(); | ||||||
|  |                 AllCoreResume(); | ||||||
|  |                 break; | ||||||
|  |             case DebuggerAction::StepThreadLocked: | ||||||
|  |                 active_thread->SetStepState(Kernel::StepState::StepPending); | ||||||
|                 SuspendInactiveThreads(); |                 SuspendInactiveThreads(); | ||||||
|                 AllCoreResume(); |                 AllCoreResume(); | ||||||
|                 break; |                 break; | ||||||
| @@ -212,10 +216,20 @@ private: | |||||||
|         for (auto* thread : ThreadList()) { |         for (auto* thread : ThreadList()) { | ||||||
|             if (thread != active_thread) { |             if (thread != active_thread) { | ||||||
|                 thread->Resume(Kernel::SuspendType::Debug); |                 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() { |     const std::vector<Kernel::KThread*>& ThreadList() { | ||||||
|         return system.GlobalSchedulerContext().GetThreadList(); |         return system.GlobalSchedulerContext().GetThreadList(); | ||||||
|     } |     } | ||||||
| @@ -233,7 +247,6 @@ private: | |||||||
|  |  | ||||||
|     Kernel::KThread* active_thread; |     Kernel::KThread* active_thread; | ||||||
|     bool stopped; |     bool stopped; | ||||||
|     bool stepping; |  | ||||||
|  |  | ||||||
|     std::array<u8, 4096> client_data; |     std::array<u8, 4096> client_data; | ||||||
| }; | }; | ||||||
| @@ -252,8 +265,4 @@ bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) { | |||||||
|     return impl && impl->NotifyThreadStopped(thread); |     return impl && impl->NotifyThreadStopped(thread); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Debugger::IsStepping() const { |  | ||||||
|     return impl && impl->IsStepping(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } // namespace Core | } // namespace Core | ||||||
|   | |||||||
| @@ -35,11 +35,6 @@ public: | |||||||
|      */ |      */ | ||||||
|     bool NotifyThreadStopped(Kernel::KThread* thread); |     bool NotifyThreadStopped(Kernel::KThread* thread); | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Returns whether a step is in progress. |  | ||||||
|      */ |  | ||||||
|     bool IsStepping() const; |  | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     std::unique_ptr<DebuggerImpl> impl; |     std::unique_ptr<DebuggerImpl> impl; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -16,10 +16,11 @@ class KThread; | |||||||
| namespace Core { | namespace Core { | ||||||
|  |  | ||||||
| enum class DebuggerAction { | enum class DebuggerAction { | ||||||
|     Interrupt,         // Stop emulation as soon as possible. |     Interrupt,          ///< Stop emulation as soon as possible. | ||||||
|     Continue,          // Resume emulation. |     Continue,           ///< Resume emulation. | ||||||
|     StepThread,        // Step the currently-active thread. |     StepThreadLocked,   ///< Step the currently-active thread without resuming others. | ||||||
|     ShutdownEmulation, // Shut down the emulator. |     StepThreadUnlocked, ///< Step the currently-active thread and resume others. | ||||||
|  |     ShutdownEmulation,  ///< Shut down the emulator. | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DebuggerBackend { | class DebuggerBackend { | ||||||
|   | |||||||
| @@ -6,8 +6,7 @@ | |||||||
| #include <optional> | #include <optional> | ||||||
| #include <thread> | #include <thread> | ||||||
|  |  | ||||||
| #include <boost/asio.hpp> | #include <boost/algorithm/string.hpp> | ||||||
| #include <boost/process/async_pipe.hpp> |  | ||||||
|  |  | ||||||
| #include "common/hex_util.h" | #include "common/hex_util.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| @@ -114,6 +113,11 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (packet.starts_with("vCont")) { | ||||||
|  |         HandleVCont(packet.substr(5), actions); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     std::string_view command{packet.substr(1, packet.size())}; |     std::string_view command{packet.substr(1, packet.size())}; | ||||||
|  |  | ||||||
|     switch (packet[0]) { |     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)}; |         s64 thread_id{strtoll(command.data() + 1, nullptr, 16)}; | ||||||
|         if (thread_id >= 1) { |         if (thread_id >= 1) { | ||||||
|             thread = GetThreadByID(thread_id); |             thread = GetThreadByID(thread_id); | ||||||
|  |         } else { | ||||||
|  |             thread = backend.GetActiveThread(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (thread) { |         if (thread) { | ||||||
| @@ -141,6 +147,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |     case 'Q': | ||||||
|     case 'q': |     case 'q': | ||||||
|         HandleQuery(command); |         HandleQuery(command); | ||||||
|         break; |         break; | ||||||
| @@ -204,7 +211,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     case 's': |     case 's': | ||||||
|         actions.push_back(DebuggerAction::StepThread); |         actions.push_back(DebuggerAction::StepThreadLocked); | ||||||
|         break; |         break; | ||||||
|     case 'C': |     case 'C': | ||||||
|     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) { | void GDBStub::HandleQuery(std::string_view command) { | ||||||
|     if (command.starts_with("TStatus")) { |     if (command.starts_with("TStatus")) { | ||||||
|         // no tracepoint support |         // no tracepoint support | ||||||
|         SendReply("T0"); |         SendReply("T0"); | ||||||
|     } else if (command.starts_with("Supported")) { |     } 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:")) { |     } else if (command.starts_with("Xfer:features:read:target.xml:")) { | ||||||
|         const auto offset{command.substr(30)}; |         const auto offset{command.substr(30)}; | ||||||
|         const auto amount{command.substr(command.find(',') + 1)}; |         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(); |         const auto& threads = system.GlobalSchedulerContext().GetThreadList(); | ||||||
|         for (const auto& thread : threads) { |         for (const auto& thread : threads) { | ||||||
|             buffer += |             buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)", | ||||||
|                 fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)", |                                   thread->GetThreadID(), thread->GetActiveCore(), | ||||||
|                             thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID()); |                                   thread->GetThreadID(), GetThreadState(thread)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         buffer += "</threads>"; |         buffer += "</threads>"; | ||||||
|         SendReply(buffer); |         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 { |     } else { | ||||||
|         SendReply(GDB_STUB_REPLY_EMPTY); |         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) { | Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { | ||||||
|     const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; |     const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; | ||||||
|     for (auto* thread : threads) { |     for (auto* thread : threads) { | ||||||
| @@ -374,6 +455,10 @@ void GDBStub::SendReply(std::string_view data) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void GDBStub::SendStatus(char status) { | void GDBStub::SendStatus(char status) { | ||||||
|  |     if (no_ack) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     std::array<u8, 1> buf = {static_cast<u8>(status)}; |     std::array<u8, 1> buf = {static_cast<u8>(status)}; | ||||||
|     LOG_TRACE(Debug_GDBStub, "Writing status: {}", status); |     LOG_TRACE(Debug_GDBStub, "Writing status: {}", status); | ||||||
|     backend.WriteToClient(buf); |     backend.WriteToClient(buf); | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ public: | |||||||
| private: | private: | ||||||
|     void ProcessData(std::vector<DebuggerAction>& actions); |     void ProcessData(std::vector<DebuggerAction>& actions); | ||||||
|     void ExecuteCommand(std::string_view packet, 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); |     void HandleQuery(std::string_view command); | ||||||
|     std::vector<char>::const_iterator CommandEnd() const; |     std::vector<char>::const_iterator CommandEnd() const; | ||||||
|     std::optional<std::string> DetachCommand(); |     std::optional<std::string> DetachCommand(); | ||||||
| @@ -42,6 +43,7 @@ private: | |||||||
|     std::unique_ptr<GDBStubArch> arch; |     std::unique_ptr<GDBStubArch> arch; | ||||||
|     std::vector<char> current_command; |     std::vector<char> current_command; | ||||||
|     std::map<VAddr, u32> replaced_instructions; |     std::map<VAddr, u32> replaced_instructions; | ||||||
|  |     bool no_ack{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| } // namespace Core | } // namespace Core | ||||||
|   | |||||||
| @@ -100,6 +100,12 @@ enum class ThreadWaitReasonForDebugging : u32 { | |||||||
|     Suspended,       ///< Thread is waiting due to process suspension |     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* GetCurrentThreadPointer(KernelCore& kernel); | ||||||
| [[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel); | [[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel); | ||||||
| [[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel); | [[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel); | ||||||
| @@ -267,6 +273,14 @@ public: | |||||||
|  |  | ||||||
|     void SetState(ThreadState state); |     void SetState(ThreadState state); | ||||||
|  |  | ||||||
|  |     [[nodiscard]] StepState GetStepState() const { | ||||||
|  |         return step_state; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void SetStepState(StepState state) { | ||||||
|  |         step_state = state; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     [[nodiscard]] s64 GetLastScheduledTick() const { |     [[nodiscard]] s64 GetLastScheduledTick() const { | ||||||
|         return last_scheduled_tick; |         return last_scheduled_tick; | ||||||
|     } |     } | ||||||
| @@ -769,6 +783,7 @@ private: | |||||||
|     std::shared_ptr<Common::Fiber> host_context{}; |     std::shared_ptr<Common::Fiber> host_context{}; | ||||||
|     bool is_single_core{}; |     bool is_single_core{}; | ||||||
|     ThreadType thread_type{}; |     ThreadType thread_type{}; | ||||||
|  |     StepState step_state{}; | ||||||
|     std::mutex dummy_wait_lock; |     std::mutex dummy_wait_lock; | ||||||
|     std::condition_variable dummy_wait_cv; |     std::condition_variable dummy_wait_cv; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -277,3 +277,7 @@ else() | |||||||
|         $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> |         $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> | ||||||
|     ) |     ) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | if (ARCHITECTURE_x86_64) | ||||||
|  |     target_link_libraries(video_core PRIVATE dynarmic) | ||||||
|  | endif() | ||||||
|   | |||||||
| @@ -319,3 +319,7 @@ endif() | |||||||
| if (NOT APPLE) | if (NOT APPLE) | ||||||
|     target_compile_definitions(yuzu PRIVATE HAS_OPENGL) |     target_compile_definitions(yuzu PRIVATE HAS_OPENGL) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | if (ARCHITECTURE_x86_64) | ||||||
|  |     target_link_libraries(yuzu PRIVATE dynarmic) | ||||||
|  | endif() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user