diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index 3f089b7f80..7382b080df 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -247,6 +247,12 @@ private: case DebuggerAction::Continue: MarkResumed([&] { ResumeEmulation(); }); break; + case DebuggerAction::ContinueThreads: { + auto* gdb = static_cast(frontend.get()); + auto threads = std::move(gdb->vcont_threads); + MarkResumed([&] { ResumeThreads(threads); }); + break; + } case DebuggerAction::StepThreadUnlocked: MarkResumed([&] { state->active_thread->SetStepState(Kernel::StepState::StepPending); @@ -298,6 +304,19 @@ private: } } + void ResumeThreads(const std::vector& threads) { + Kernel::KScopedLightLock ll{debug_process->GetListLock()}; + Kernel::KScopedSchedulerLock sl{system.Kernel()}; + + // Wake up only the specified threads. + for (auto* thread : threads) { + if (thread) { + thread->SetStepState(Kernel::StepState::NotStepping); + thread->Resume(Kernel::SuspendType::Debug); + } + } + } + template void MarkResumed(Callback&& cb) { stopped = false; diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h index 4e18749249..372d6aa1cc 100644 --- a/src/core/debugger/debugger_interface.h +++ b/src/core/debugger/debugger_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -22,6 +22,7 @@ namespace Core { enum class DebuggerAction { Interrupt, ///< Stop emulation as soon as possible. Continue, ///< Resume emulation. + ContinueThreads, ///< Resume only specific threads (listed in frontend). StepThreadLocked, ///< Step the currently-active thread without resuming others. StepThreadUnlocked, ///< Step the currently-active thread and resume others. ShutdownEmulation, ///< Shut down the emulator. diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index a5f49f6ff1..81c7b4f56b 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -470,27 +470,57 @@ void GDBStub::HandleVCont(std::string_view sv, std::vector& acti // Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont) if (sv == "?") { SendReply("vCont;c;C;s;S"); - } else { - Kernel::KThread* stepped_thread = nullptr; - bool lock_execution = true; - std::vector entries; - boost::split(entries, sv.substr(1), boost::is_any_of(";")); - for (auto const& thread_action : entries) { - std::vector 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)); - } + return; + } - if (stepped_thread) { - backend.SetActiveThread(stepped_thread); - actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked : DebuggerAction::StepThreadUnlocked); - } else { - actions.push_back(DebuggerAction::Continue); + Kernel::KThread* stepped_thread = nullptr; + bool has_default_continue = false; + std::vector continue_threads; + + std::vector entries; + boost::split(entries, sv.substr(1), boost::is_any_of(";")); + for (auto const& thread_action : entries) { + std::vector parts; + boost::split(parts, thread_action, boost::is_any_of(":")); + + const bool is_step = parts[0] == "s" || parts[0].starts_with("S"); + const bool is_continue = parts[0] == "c" || parts[0].starts_with("C"); + + if (parts.size() == 1) { + // Bare action (no thread ID) = default for all unmentioned threads. + if (is_continue) + has_default_continue = true; + } else if (parts.size() == 2) { + auto* thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16)); + if (is_step && thread) + stepped_thread = thread; + else if (is_continue && thread) + continue_threads.push_back(thread); } } + + if (stepped_thread) { + backend.SetActiveThread(stepped_thread); + if (has_default_continue) { + // Step one thread, continue all others (e.g. vCont;s:4c;c). + actions.push_back(DebuggerAction::StepThreadUnlocked); + } else if (!continue_threads.empty()) { + // Step one thread, continue specific others. + // For now, treat as step-unlocked since we resume the listed threads. + vcont_threads = std::move(continue_threads); + actions.push_back(DebuggerAction::StepThreadUnlocked); + } else { + // Step one thread, keep all others stopped (e.g. vCont;s:4c). + actions.push_back(DebuggerAction::StepThreadLocked); + } + } else if (has_default_continue) { + // Continue all threads. + actions.push_back(DebuggerAction::Continue); + } else if (!continue_threads.empty()) { + // Continue only specific threads. + vcont_threads = std::move(continue_threads); + actions.push_back(DebuggerAction::ContinueThreads); + } } static constexpr const char* GetMemoryStateName(Kernel::Svc::MemoryState state) { diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index 140b0e8e25..2c257b9937 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -52,6 +52,7 @@ struct GDBStub : public DebuggerFrontend { std::unique_ptr arch; std::vector current_command; std::map replaced_instructions; + std::vector vcont_threads; bool no_ack{}; };