Merge pull request #8402 from liamwhite/better-step
core/debugger: Improved stepping mechanism and misc fixes
This commit is contained in:
commit
858f8ac6d9
16 changed files with 252 additions and 122 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue