mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-06-05 13:37:07 +02:00
[core/debugger] Protocol-compliant vCont support (#3896)
(gdb) set scheduler-locking on (gdb) continue As discussed in #3848, follow-up to implement vCont support according to spec. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3896 Reviewed-by: Lizzie <lizzie@eden-emu.dev> Reviewed-by: crueter <crueter@eden-emu.dev>
This commit is contained in:
parent
89199f4d27
commit
aadcc24aac
4 changed files with 169 additions and 35 deletions
|
|
@ -247,17 +247,19 @@ private:
|
||||||
case DebuggerAction::Continue:
|
case DebuggerAction::Continue:
|
||||||
MarkResumed([&] { ResumeEmulation(); });
|
MarkResumed([&] { ResumeEmulation(); });
|
||||||
break;
|
break;
|
||||||
case DebuggerAction::StepThreadUnlocked:
|
case DebuggerAction::ContinueThreads: {
|
||||||
MarkResumed([&] {
|
auto* gdb = static_cast<GDBStub*>(frontend.get());
|
||||||
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
MarkResumed([this, threads = std::move(gdb->resume_threads)] {
|
||||||
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
ResumeThreads(threads);
|
||||||
ResumeEmulation(state->active_thread.GetPointerUnsafe());
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case DebuggerAction::StepThreadLocked: {
|
}
|
||||||
MarkResumed([&] {
|
case DebuggerAction::StepThread: {
|
||||||
|
auto* gdb = static_cast<GDBStub*>(frontend.get());
|
||||||
|
MarkResumed([this, threads = std::move(gdb->resume_threads)] {
|
||||||
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||||
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
ResumeThreads(threads, state->active_thread.GetPointerUnsafe());
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +300,22 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResumeThreads(const std::vector<Kernel::KThread*>& threads,
|
||||||
|
Kernel::KThread* except = nullptr) {
|
||||||
|
Kernel::KScopedLightLock ll{debug_process->GetListLock()};
|
||||||
|
Kernel::KScopedSchedulerLock sl{system.Kernel()};
|
||||||
|
|
||||||
|
// Wake up only the specified threads.
|
||||||
|
for (auto* thread : threads) {
|
||||||
|
if (!thread || thread == except) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread->SetStepState(Kernel::StepState::NotStepping);
|
||||||
|
thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Callback>
|
template <typename Callback>
|
||||||
void MarkResumed(Callback&& cb) {
|
void MarkResumed(Callback&& cb) {
|
||||||
stopped = false;
|
stopped = false;
|
||||||
|
|
|
||||||
|
|
@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
|
@ -20,11 +20,11 @@ struct DebugWatchpoint;
|
||||||
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.
|
||||||
StepThreadLocked, ///< Step the currently-active thread without resuming others.
|
ContinueThreads, ///< Resume only specific threads (listed in frontend).
|
||||||
StepThreadUnlocked, ///< Step the currently-active thread and resume others.
|
StepThread, ///< Step the active thread and resume only threads listed in frontend.
|
||||||
ShutdownEmulation, ///< Shut down the emulator.
|
ShutdownEmulation, ///< Shut down the emulator.
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebuggerBackend {
|
class DebuggerBackend {
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@
|
||||||
// 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 <atomic>
|
#include <atomic>
|
||||||
|
#include <cctype>
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include "common/hex_util.h"
|
#include "common/hex_util.h"
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
|
|
@ -273,10 +273,12 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 's':
|
case 's':
|
||||||
actions.push_back(DebuggerAction::StepThreadLocked);
|
resume_threads.clear();
|
||||||
|
actions.push_back(DebuggerAction::StepThread);
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
case 'c':
|
case 'c':
|
||||||
|
resume_threads.clear();
|
||||||
actions.push_back(DebuggerAction::Continue);
|
actions.push_back(DebuggerAction::Continue);
|
||||||
break;
|
break;
|
||||||
case 'Z':
|
case 'Z':
|
||||||
|
|
@ -467,29 +469,142 @@ void GDBStub::HandleQuery(std::string_view sv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDBStub::HandleVCont(std::string_view sv, std::vector<DebuggerAction>& actions) {
|
void GDBStub::HandleVCont(std::string_view sv, std::vector<DebuggerAction>& actions) {
|
||||||
// Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont)
|
// Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont).
|
||||||
|
// Reference: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#vCont-packet
|
||||||
if (sv == "?") {
|
if (sv == "?") {
|
||||||
SendReply("vCont;c;C;s;S");
|
SendReply("vCont;c;C;s;S");
|
||||||
} else {
|
return;
|
||||||
Kernel::KThread* stepped_thread = nullptr;
|
}
|
||||||
bool lock_execution = true;
|
if (sv.empty() || sv.front() != ';') {
|
||||||
std::vector<std::string> entries;
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
boost::split(entries, sv.substr(1), boost::is_any_of(";"));
|
return;
|
||||||
for (auto const& thread_action : entries) {
|
}
|
||||||
std::vector<std::string> parts;
|
|
||||||
boost::split(parts, thread_action, boost::is_any_of(":"));
|
enum class VContAction {
|
||||||
if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C")))
|
Continue,
|
||||||
lock_execution = false;
|
Step,
|
||||||
if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S")))
|
};
|
||||||
stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
|
struct VContDirective {
|
||||||
|
VContAction action;
|
||||||
|
Kernel::KThread* thread{};
|
||||||
|
bool all_threads{};
|
||||||
|
|
||||||
|
bool Matches(Kernel::KThread* candidate) const {
|
||||||
|
return all_threads || thread == candidate;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto is_hex_byte = [](std::string_view value) {
|
||||||
|
return value.size() == 2 && std::isxdigit(static_cast<unsigned char>(value[0])) &&
|
||||||
|
std::isxdigit(static_cast<unsigned char>(value[1]));
|
||||||
|
};
|
||||||
|
const auto is_hex_string = [](std::string_view value) {
|
||||||
|
return std::ranges::all_of(value, [](auto const c) { return std::isxdigit(int(c)); });
|
||||||
|
};
|
||||||
|
|
||||||
|
resume_threads.clear();
|
||||||
|
|
||||||
|
std::vector<VContDirective> directives;
|
||||||
|
std::string_view remaining = sv.substr(1);
|
||||||
|
while (!remaining.empty()) {
|
||||||
|
const auto entry_end = remaining.find(';');
|
||||||
|
const auto entry = remaining.substr(0, entry_end);
|
||||||
|
remaining = entry_end == std::string_view::npos ? std::string_view{} : remaining.substr(entry_end + 1);
|
||||||
|
|
||||||
|
if (entry.empty()) {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stepped_thread) {
|
const auto thread_sep = entry.find(':');
|
||||||
backend.SetActiveThread(stepped_thread);
|
const auto action_token = entry.substr(0, thread_sep);
|
||||||
actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked : DebuggerAction::StepThreadUnlocked);
|
const auto thread_token = thread_sep == std::string_view::npos ? std::string_view{} : entry.substr(thread_sep + 1);
|
||||||
} else {
|
|
||||||
actions.push_back(DebuggerAction::Continue);
|
if (action_token.empty()) {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VContDirective directive;
|
||||||
|
if (action_token == "c") {
|
||||||
|
directive.action = VContAction::Continue;
|
||||||
|
} else if (action_token.front() == 'C' && is_hex_byte(action_token.substr(1))) {
|
||||||
|
directive.action = VContAction::Continue;
|
||||||
|
} else if (action_token == "s") {
|
||||||
|
directive.action = VContAction::Step;
|
||||||
|
} else if (action_token.front() == 'S' && is_hex_byte(action_token.substr(1))) {
|
||||||
|
directive.action = VContAction::Step;
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thread_sep == std::string_view::npos || thread_token == "-1") {
|
||||||
|
directive.all_threads = true;
|
||||||
|
} else if (thread_token == "0") {
|
||||||
|
// A thread-id of 0 selects an arbitrary thread. While stopped, use the
|
||||||
|
// current active thread as that arbitrary choice.
|
||||||
|
directive.thread = backend.GetActiveThread();
|
||||||
|
} else if (thread_token.starts_with('p')) {
|
||||||
|
// We do not currently support multiprocess thread selectors.
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
} else if (is_hex_string(thread_token)) {
|
||||||
|
directive.thread = GetThreadByID(strtoull(std::string(thread_token).c_str(), nullptr, 16));
|
||||||
|
} else {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
directives.push_back(directive);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directives.empty()) {
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the packet exactly as specified by the protocol: for each thread,
|
||||||
|
// the leftmost action with a matching thread-id wins.
|
||||||
|
Kernel::KThread* stepped_thread = nullptr;
|
||||||
|
std::vector<Kernel::KThread*> continue_threads;
|
||||||
|
auto& thread_list = debug_process->GetThreadList();
|
||||||
|
for (auto& thread : thread_list) {
|
||||||
|
const auto directive = std::find_if(directives.begin(), directives.end(),
|
||||||
|
[&](const VContDirective& candidate) {
|
||||||
|
return candidate.Matches(std::addressof(thread));
|
||||||
|
});
|
||||||
|
if (directive == directives.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (directive->action) {
|
||||||
|
case VContAction::Continue:
|
||||||
|
continue_threads.push_back(std::addressof(thread));
|
||||||
|
break;
|
||||||
|
case VContAction::Step:
|
||||||
|
if (stepped_thread) {
|
||||||
|
// The core can step at most one thread at a time.
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stepped_thread = std::addressof(thread);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stepped_thread) {
|
||||||
|
backend.SetActiveThread(stepped_thread);
|
||||||
|
resume_threads = std::move(continue_threads);
|
||||||
|
actions.push_back(DebuggerAction::StepThread);
|
||||||
|
} else if (continue_threads.size() == thread_list.size()) {
|
||||||
|
actions.push_back(DebuggerAction::Continue);
|
||||||
|
} else if (!continue_threads.empty()) {
|
||||||
|
resume_threads = std::move(continue_threads);
|
||||||
|
actions.push_back(DebuggerAction::ContinueThreads);
|
||||||
|
} else {
|
||||||
|
// A resume packet that leaves all threads stopped is not useful to execute.
|
||||||
|
SendReply(GDB_STUB_REPLY_ERR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
|
@ -52,6 +52,7 @@ struct GDBStub : public DebuggerFrontend {
|
||||||
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;
|
||||||
|
std::vector<Kernel::KThread*> resume_threads;
|
||||||
bool no_ack{};
|
bool no_ack{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue