summaryrefslogtreecommitdiffstats
path: root/src/core/debugger
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/debugger')
-rw-r--r--src/core/debugger/debugger.cpp161
-rw-r--r--src/core/debugger/gdbstub.cpp151
-rw-r--r--src/core/debugger/gdbstub.h1
3 files changed, 245 insertions, 68 deletions
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 339f971e6..1a8e02e6a 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -27,12 +27,21 @@ static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
c(received_data);
+ AsyncReceiveInto(r, buffer, c);
}
-
- AsyncReceiveInto(r, buffer, c);
});
}
+template <typename Callback>
+static void AsyncAccept(boost::asio::ip::tcp::acceptor& acceptor, Callback&& c) {
+ acceptor.async_accept([&, c](const boost::system::error_code& error, auto&& peer_socket) {
+ if (!error.failed()) {
+ c(peer_socket);
+ AsyncAccept(acceptor, c);
+ }
+ });
+}
+
template <typename Readable, typename Buffer>
static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
static_assert(std::is_trivial_v<Buffer>);
@@ -59,9 +68,7 @@ namespace Core {
class DebuggerImpl : public DebuggerBackend {
public:
- explicit DebuggerImpl(Core::System& system_, u16 port)
- : system{system_}, signal_pipe{io_context}, client_socket{io_context} {
- frontend = std::make_unique<GDBStub>(*this, system);
+ explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} {
InitializeServer(port);
}
@@ -70,39 +77,42 @@ public:
}
bool SignalDebugger(SignalInfo signal_info) {
- {
- std::scoped_lock lk{connection_lock};
+ std::scoped_lock lk{connection_lock};
- if (stopped) {
- // Do not notify the debugger about another event.
- // It should be ignored.
- return false;
- }
-
- // Set up the state.
- stopped = true;
- info = signal_info;
+ if (stopped || !state) {
+ // Do not notify the debugger about another event.
+ // It should be ignored.
+ return false;
}
+ // Set up the state.
+ stopped = true;
+ state->info = signal_info;
+
// Write a single byte into the pipe to wake up the debug interface.
- boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
+ boost::asio::write(state->signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
+
return true;
}
+ // These functions are callbacks from the frontend, and the lock will be held.
+ // There is no need to relock it.
+
std::span<const u8> ReadFromClient() override {
- return ReceiveInto(client_socket, client_data);
+ return ReceiveInto(state->client_socket, state->client_data);
}
void WriteToClient(std::span<const u8> data) override {
- boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes()));
+ boost::asio::write(state->client_socket,
+ boost::asio::buffer(data.data(), data.size_bytes()));
}
void SetActiveThread(Kernel::KThread* thread) override {
- active_thread = thread;
+ state->active_thread = thread;
}
Kernel::KThread* GetActiveThread() override {
- return active_thread;
+ return state->active_thread;
}
private:
@@ -113,65 +123,78 @@ private:
// Run the connection thread.
connection_thread = std::jthread([&, port](std::stop_token stop_token) {
+ Common::SetCurrentThreadName("Debugger");
+
try {
// Initialize the listening socket and accept a new client.
tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port};
tcp::acceptor acceptor{io_context, endpoint};
- acceptor.async_accept(client_socket, [](const auto&) {});
- io_context.run_one();
- io_context.restart();
+ AsyncAccept(acceptor, [&](auto&& peer) { AcceptConnection(std::move(peer)); });
- if (stop_token.stop_requested()) {
- return;
+ while (!stop_token.stop_requested() && io_context.run()) {
}
-
- ThreadLoop(stop_token);
} catch (const std::exception& ex) {
LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
}
});
}
- void ShutdownServer() {
- connection_thread.request_stop();
- io_context.stop();
- connection_thread.join();
- }
+ void AcceptConnection(boost::asio::ip::tcp::socket&& peer) {
+ LOG_INFO(Debug_GDBStub, "Accepting new peer connection");
- void ThreadLoop(std::stop_token stop_token) {
- Common::SetCurrentThreadName("Debugger");
+ std::scoped_lock lk{connection_lock};
+
+ // Ensure everything is stopped.
+ PauseEmulation();
+
+ // Set up the new frontend.
+ frontend = std::make_unique<GDBStub>(*this, system);
+
+ // Set the new state. This will tear down any existing state.
+ state = ConnectionState{
+ .client_socket{std::move(peer)},
+ .signal_pipe{io_context},
+ .info{},
+ .active_thread{},
+ .client_data{},
+ .pipe_data{},
+ };
// Set up the client signals for new data.
- AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
- AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
+ AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); });
+ AsyncReceiveInto(state->client_socket, state->client_data, [&](auto d) { ClientData(d); });
// Set the active thread.
UpdateActiveThread();
// Set up the frontend.
frontend->Connected();
+ }
- // Main event loop.
- while (!stop_token.stop_requested() && io_context.run()) {
- }
+ void ShutdownServer() {
+ connection_thread.request_stop();
+ io_context.stop();
+ connection_thread.join();
}
void PipeData(std::span<const u8> data) {
- switch (info.type) {
+ std::scoped_lock lk{connection_lock};
+
+ switch (state->info.type) {
case SignalType::Stopped:
case SignalType::Watchpoint:
// Stop emulation.
PauseEmulation();
// Notify the client.
- active_thread = info.thread;
+ state->active_thread = state->info.thread;
UpdateActiveThread();
- if (info.type == SignalType::Watchpoint) {
- frontend->Watchpoint(active_thread, *info.watchpoint);
+ if (state->info.type == SignalType::Watchpoint) {
+ frontend->Watchpoint(state->active_thread, *state->info.watchpoint);
} else {
- frontend->Stopped(active_thread);
+ frontend->Stopped(state->active_thread);
}
break;
@@ -179,8 +202,8 @@ private:
frontend->ShuttingDown();
// Wait for emulation to shut down gracefully now.
- signal_pipe.close();
- client_socket.shutdown(boost::asio::socket_base::shutdown_both);
+ state->signal_pipe.close();
+ state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
LOG_INFO(Debug_GDBStub, "Shut down server");
break;
@@ -188,17 +211,16 @@ private:
}
void ClientData(std::span<const u8> data) {
+ std::scoped_lock lk{connection_lock};
+
const auto actions{frontend->ClientData(data)};
for (const auto action : actions) {
switch (action) {
case DebuggerAction::Interrupt: {
- {
- std::scoped_lock lk{connection_lock};
- stopped = true;
- }
+ stopped = true;
PauseEmulation();
UpdateActiveThread();
- frontend->Stopped(active_thread);
+ frontend->Stopped(state->active_thread);
break;
}
case DebuggerAction::Continue:
@@ -206,15 +228,15 @@ private:
break;
case DebuggerAction::StepThreadUnlocked:
MarkResumed([&] {
- active_thread->SetStepState(Kernel::StepState::StepPending);
- active_thread->Resume(Kernel::SuspendType::Debug);
- ResumeEmulation(active_thread);
+ state->active_thread->SetStepState(Kernel::StepState::StepPending);
+ state->active_thread->Resume(Kernel::SuspendType::Debug);
+ ResumeEmulation(state->active_thread);
});
break;
case DebuggerAction::StepThreadLocked: {
MarkResumed([&] {
- active_thread->SetStepState(Kernel::StepState::StepPending);
- active_thread->Resume(Kernel::SuspendType::Debug);
+ state->active_thread->SetStepState(Kernel::StepState::StepPending);
+ state->active_thread->Resume(Kernel::SuspendType::Debug);
});
break;
}
@@ -254,15 +276,14 @@ private:
template <typename Callback>
void MarkResumed(Callback&& cb) {
Kernel::KScopedSchedulerLock sl{system.Kernel()};
- std::scoped_lock cl{connection_lock};
stopped = false;
cb();
}
void UpdateActiveThread() {
const auto& threads{ThreadList()};
- if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
- active_thread = threads[0];
+ if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) {
+ state->active_thread = threads[0];
}
}
@@ -274,18 +295,22 @@ private:
System& system;
std::unique_ptr<DebuggerFrontend> frontend;
+ boost::asio::io_context io_context;
std::jthread connection_thread;
std::mutex connection_lock;
- boost::asio::io_context io_context;
- boost::process::async_pipe signal_pipe;
- boost::asio::ip::tcp::socket client_socket;
- SignalInfo info;
- Kernel::KThread* active_thread;
- bool pipe_data;
- bool stopped;
+ struct ConnectionState {
+ boost::asio::ip::tcp::socket client_socket;
+ boost::process::async_pipe signal_pipe;
+
+ SignalInfo info;
+ Kernel::KThread* active_thread;
+ std::array<u8, 4096> client_data;
+ bool pipe_data;
+ };
- std::array<u8, 4096> client_data;
+ std::optional<ConnectionState> state{};
+ bool stopped{};
};
Debugger::Debugger(Core::System& system, u16 port) {
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 884229c77..a64a9ac64 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -606,6 +606,8 @@ void GDBStub::HandleQuery(std::string_view command) {
} else if (command.starts_with("StartNoAckMode")) {
no_ack = true;
SendReply(GDB_STUB_REPLY_OK);
+ } else if (command.starts_with("Rcmd,")) {
+ HandleRcmd(Common::HexStringToVector(command.substr(5), false));
} else {
SendReply(GDB_STUB_REPLY_EMPTY);
}
@@ -645,6 +647,155 @@ void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>&
}
}
+constexpr std::array<std::pair<const char*, Kernel::Svc::MemoryState>, 22> MemoryStateNames{{
+ {"----- Free -----", Kernel::Svc::MemoryState::Free},
+ {"Io ", Kernel::Svc::MemoryState::Io},
+ {"Static ", Kernel::Svc::MemoryState::Static},
+ {"Code ", Kernel::Svc::MemoryState::Code},
+ {"CodeData ", Kernel::Svc::MemoryState::CodeData},
+ {"Normal ", Kernel::Svc::MemoryState::Normal},
+ {"Shared ", Kernel::Svc::MemoryState::Shared},
+ {"AliasCode ", Kernel::Svc::MemoryState::AliasCode},
+ {"AliasCodeData ", Kernel::Svc::MemoryState::AliasCodeData},
+ {"Ipc ", Kernel::Svc::MemoryState::Ipc},
+ {"Stack ", Kernel::Svc::MemoryState::Stack},
+ {"ThreadLocal ", Kernel::Svc::MemoryState::ThreadLocal},
+ {"Transfered ", Kernel::Svc::MemoryState::Transfered},
+ {"SharedTransfered", Kernel::Svc::MemoryState::SharedTransfered},
+ {"SharedCode ", Kernel::Svc::MemoryState::SharedCode},
+ {"Inaccessible ", Kernel::Svc::MemoryState::Inaccessible},
+ {"NonSecureIpc ", Kernel::Svc::MemoryState::NonSecureIpc},
+ {"NonDeviceIpc ", Kernel::Svc::MemoryState::NonDeviceIpc},
+ {"Kernel ", Kernel::Svc::MemoryState::Kernel},
+ {"GeneratedCode ", Kernel::Svc::MemoryState::GeneratedCode},
+ {"CodeOut ", Kernel::Svc::MemoryState::CodeOut},
+ {"Coverage ", Kernel::Svc::MemoryState::Coverage},
+}};
+
+static constexpr const char* GetMemoryStateName(Kernel::Svc::MemoryState state) {
+ for (size_t i = 0; i < MemoryStateNames.size(); i++) {
+ if (std::get<1>(MemoryStateNames[i]) == state) {
+ return std::get<0>(MemoryStateNames[i]);
+ }
+ }
+ return "Unknown ";
+}
+
+static constexpr const char* GetMemoryPermissionString(const Kernel::Svc::MemoryInfo& info) {
+ if (info.state == Kernel::Svc::MemoryState::Free) {
+ return " ";
+ }
+
+ switch (info.permission) {
+ case Kernel::Svc::MemoryPermission::ReadExecute:
+ return "r-x";
+ case Kernel::Svc::MemoryPermission::Read:
+ return "r--";
+ case Kernel::Svc::MemoryPermission::ReadWrite:
+ return "rw-";
+ default:
+ return "---";
+ }
+}
+
+static VAddr GetModuleEnd(Kernel::KPageTable& page_table, VAddr base) {
+ Kernel::Svc::MemoryInfo mem_info;
+ VAddr cur_addr{base};
+
+ // Expect: r-x Code (.text)
+ mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
+ cur_addr = mem_info.base_address + mem_info.size;
+ if (mem_info.state != Kernel::Svc::MemoryState::Code ||
+ mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) {
+ return cur_addr - 1;
+ }
+
+ // Expect: r-- Code (.rodata)
+ mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
+ cur_addr = mem_info.base_address + mem_info.size;
+ if (mem_info.state != Kernel::Svc::MemoryState::Code ||
+ mem_info.permission != Kernel::Svc::MemoryPermission::Read) {
+ return cur_addr - 1;
+ }
+
+ // Expect: rw- CodeData (.data)
+ mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
+ cur_addr = mem_info.base_address + mem_info.size;
+ return cur_addr - 1;
+}
+
+void GDBStub::HandleRcmd(const std::vector<u8>& command) {
+ std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
+ std::string reply;
+
+ auto* process = system.CurrentProcess();
+ auto& page_table = process->PageTable();
+
+ if (command_str == "get info") {
+ Loader::AppLoader::Modules modules;
+ system.GetAppLoader().ReadNSOModules(modules);
+
+ reply = fmt::format("Process: {:#x} ({})\n"
+ "Program Id: {:#018x}\n",
+ process->GetProcessID(), process->GetName(), process->GetProgramID());
+ reply +=
+ fmt::format("Layout:\n"
+ " Alias: {:#012x} - {:#012x}\n"
+ " Heap: {:#012x} - {:#012x}\n"
+ " Aslr: {:#012x} - {:#012x}\n"
+ " Stack: {:#012x} - {:#012x}\n"
+ "Modules:\n",
+ page_table.GetAliasRegionStart(), page_table.GetAliasRegionEnd(),
+ page_table.GetHeapRegionStart(), page_table.GetHeapRegionEnd(),
+ page_table.GetAliasCodeRegionStart(), page_table.GetAliasCodeRegionEnd(),
+ page_table.GetStackRegionStart(), page_table.GetStackRegionEnd());
+
+ for (const auto& [vaddr, name] : modules) {
+ reply += fmt::format(" {:#012x} - {:#012x} {}\n", vaddr,
+ GetModuleEnd(page_table, vaddr), name);
+ }
+ } else if (command_str == "get mappings") {
+ reply = "Mappings:\n";
+ VAddr cur_addr = 0;
+
+ while (true) {
+ using MemoryAttribute = Kernel::Svc::MemoryAttribute;
+
+ auto mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
+
+ if (mem_info.state != Kernel::Svc::MemoryState::Inaccessible ||
+ mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) {
+ const char* state = GetMemoryStateName(mem_info.state);
+ const char* perm = GetMemoryPermissionString(mem_info);
+
+ const char l = True(mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-';
+ const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-';
+ const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-';
+ const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-';
+
+ reply +=
+ fmt::format(" {:#012x} - {:#012x} {} {} {}{}{}{} [{}, {}]\n",
+ mem_info.base_address, mem_info.base_address + mem_info.size - 1,
+ perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count);
+ }
+
+ const uintptr_t next_address = mem_info.base_address + mem_info.size;
+ if (next_address <= cur_addr) {
+ break;
+ }
+
+ cur_addr = next_address;
+ }
+ } else if (command_str == "help") {
+ reply = "Commands:\n get info\n get mappings\n";
+ } else {
+ reply = "Unknown command.\nCommands:\n get info\n get mappings\n";
+ }
+
+ std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()};
+ SendReply(Common::HexToString(reply_span, false));
+}
+
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
for (auto* thread : threads) {
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h
index 0b0f56e4b..368197920 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -32,6 +32,7 @@ private:
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 HandleRcmd(const std::vector<u8>& command);
void HandleBreakpointInsert(std::string_view command);
void HandleBreakpointRemove(std::string_view command);
std::vector<char>::const_iterator CommandEnd() const;