summaryrefslogtreecommitdiffstats
path: root/src/core/debugger
diff options
context:
space:
mode:
authorLiam <byteslice@airmail.cc>2022-05-31 20:37:37 +0200
committerLiam <byteslice@airmail.cc>2022-06-01 08:15:15 +0200
commit989d4a7a41f449af0ea09e34bee331a3a3ac8170 (patch)
treedf24bd9d7e6942b939e3ea42d08c0d65006e539f /src/core/debugger
parentcore/debugger: Implement new GDB stub debugger (diff)
downloadyuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.gz
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.bz2
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.lz
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.xz
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.zst
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.zip
Diffstat (limited to 'src/core/debugger')
-rw-r--r--src/core/debugger/debugger.cpp61
-rw-r--r--src/core/debugger/debugger.h5
-rw-r--r--src/core/debugger/debugger_interface.h9
-rw-r--r--src/core/debugger/gdbstub.cpp99
-rw-r--r--src/core/debugger/gdbstub.h2
5 files changed, 134 insertions, 42 deletions
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 7a2012d3c..a73f2279d 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -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
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h
index 7acd11815..ea36c6ab2 100644
--- a/src/core/debugger/debugger.h
+++ b/src/core/debugger/debugger.h
@@ -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;
};
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h
index 0b357fcb5..6ae3dc091 100644
--- a/src/core/debugger/debugger_interface.h
+++ b/src/core/debugger/debugger_interface.h
@@ -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 {
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 718c45952..44ebbe3e0 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -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);
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
index b93a3a511..6f8ac2263 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -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