| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: GPL-2.0-or-later
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <atomic>
 | 
					
						
							|  |  |  | #include <numeric>
 | 
					
						
							|  |  |  | #include <optional>
 | 
					
						
							|  |  |  | #include <thread>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  | #include <boost/algorithm/string.hpp>
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "common/hex_util.h"
 | 
					
						
							|  |  |  | #include "common/logging/log.h"
 | 
					
						
							|  |  |  | #include "common/scope_exit.h"
 | 
					
						
							|  |  |  | #include "core/arm/arm_interface.h"
 | 
					
						
							|  |  |  | #include "core/core.h"
 | 
					
						
							|  |  |  | #include "core/debugger/gdbstub.h"
 | 
					
						
							|  |  |  | #include "core/debugger/gdbstub_arch.h"
 | 
					
						
							|  |  |  | #include "core/hle/kernel/k_page_table.h"
 | 
					
						
							|  |  |  | #include "core/hle/kernel/k_process.h"
 | 
					
						
							|  |  |  | #include "core/hle/kernel/k_thread.h"
 | 
					
						
							|  |  |  | #include "core/loader/loader.h"
 | 
					
						
							|  |  |  | #include "core/memory.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Core { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | constexpr char GDB_STUB_START = '$'; | 
					
						
							|  |  |  | constexpr char GDB_STUB_END = '#'; | 
					
						
							|  |  |  | constexpr char GDB_STUB_ACK = '+'; | 
					
						
							|  |  |  | constexpr char GDB_STUB_NACK = '-'; | 
					
						
							|  |  |  | constexpr char GDB_STUB_INT3 = 0x03; | 
					
						
							|  |  |  | constexpr int GDB_STUB_SIGTRAP = 5; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | constexpr char GDB_STUB_REPLY_ERR[] = "E01"; | 
					
						
							|  |  |  | constexpr char GDB_STUB_REPLY_OK[] = "OK"; | 
					
						
							|  |  |  | constexpr char GDB_STUB_REPLY_EMPTY[] = ""; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  | static u8 CalculateChecksum(std::string_view data) { | 
					
						
							|  |  |  |     return std::accumulate(data.begin(), data.end(), u8{0}, | 
					
						
							|  |  |  |                            [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static std::string EscapeGDB(std::string_view data) { | 
					
						
							|  |  |  |     std::string escaped; | 
					
						
							|  |  |  |     escaped.reserve(data.size()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (char c : data) { | 
					
						
							|  |  |  |         switch (c) { | 
					
						
							|  |  |  |         case '#': | 
					
						
							|  |  |  |             escaped += "}\x03"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case '$': | 
					
						
							|  |  |  |             escaped += "}\x04"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case '*': | 
					
						
							|  |  |  |             escaped += "}\x0a"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case '}': | 
					
						
							|  |  |  |             escaped += "}\x5d"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             escaped += c; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return escaped; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static std::string EscapeXML(std::string_view data) { | 
					
						
							|  |  |  |     std::string escaped; | 
					
						
							|  |  |  |     escaped.reserve(data.size()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (char c : data) { | 
					
						
							|  |  |  |         switch (c) { | 
					
						
							|  |  |  |         case '&': | 
					
						
							|  |  |  |             escaped += "&"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case '"': | 
					
						
							|  |  |  |             escaped += """; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case '<': | 
					
						
							|  |  |  |             escaped += "<"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case '>': | 
					
						
							|  |  |  |             escaped += ">"; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             escaped += c; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return escaped; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) | 
					
						
							|  |  |  |     : DebuggerFrontend(backend_), system{system_} { | 
					
						
							|  |  |  |     if (system.CurrentProcess()->Is64BitProcess()) { | 
					
						
							|  |  |  |         arch = std::make_unique<GDBStubA64>(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         arch = std::make_unique<GDBStubA32>(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GDBStub::~GDBStub() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::Connected() {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-10 09:17:12 -04:00
										 |  |  | void GDBStub::ShuttingDown() {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | void GDBStub::Stopped(Kernel::KThread* thread) { | 
					
						
							|  |  |  |     SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  | void GDBStub::Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) { | 
					
						
							|  |  |  |     const auto status{arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (watch.type) { | 
					
						
							|  |  |  |     case Kernel::DebugWatchpointType::Read: | 
					
						
							|  |  |  |         SendReply(fmt::format("{}rwatch:{:x};", status, watch.start_address)); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case Kernel::DebugWatchpointType::Write: | 
					
						
							|  |  |  |         SendReply(fmt::format("{}watch:{:x};", status, watch.start_address)); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case Kernel::DebugWatchpointType::ReadOrWrite: | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         SendReply(fmt::format("{}awatch:{:x};", status, watch.start_address)); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) { | 
					
						
							|  |  |  |     std::vector<DebuggerAction> actions; | 
					
						
							|  |  |  |     current_command.insert(current_command.end(), data.begin(), data.end()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while (current_command.size() != 0) { | 
					
						
							|  |  |  |         ProcessData(actions); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return actions; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) { | 
					
						
							|  |  |  |     const char c{current_command[0]}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Acknowledgement
 | 
					
						
							|  |  |  |     if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) { | 
					
						
							|  |  |  |         current_command.erase(current_command.begin()); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Interrupt
 | 
					
						
							|  |  |  |     if (c == GDB_STUB_INT3) { | 
					
						
							|  |  |  |         LOG_INFO(Debug_GDBStub, "Received interrupt"); | 
					
						
							|  |  |  |         current_command.erase(current_command.begin()); | 
					
						
							|  |  |  |         actions.push_back(DebuggerAction::Interrupt); | 
					
						
							|  |  |  |         SendStatus(GDB_STUB_ACK); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Otherwise, require the data to be the start of a command
 | 
					
						
							|  |  |  |     if (c != GDB_STUB_START) { | 
					
						
							|  |  |  |         LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data()); | 
					
						
							|  |  |  |         current_command.clear(); | 
					
						
							|  |  |  |         SendStatus(GDB_STUB_NACK); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Continue reading until command is complete
 | 
					
						
							|  |  |  |     while (CommandEnd() == current_command.end()) { | 
					
						
							|  |  |  |         const auto new_data{backend.ReadFromClient()}; | 
					
						
							|  |  |  |         current_command.insert(current_command.end(), new_data.begin(), new_data.end()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Execute and respond to GDB
 | 
					
						
							|  |  |  |     const auto command{DetachCommand()}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (command) { | 
					
						
							|  |  |  |         SendStatus(GDB_STUB_ACK); | 
					
						
							|  |  |  |         ExecuteCommand(*command, actions); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         SendStatus(GDB_STUB_NACK); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) { | 
					
						
							|  |  |  |     LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (packet.length() == 0) { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |     if (packet.starts_with("vCont")) { | 
					
						
							|  |  |  |         HandleVCont(packet.substr(5), actions); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     std::string_view command{packet.substr(1, packet.size())}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (packet[0]) { | 
					
						
							|  |  |  |     case 'H': { | 
					
						
							|  |  |  |         Kernel::KThread* thread{nullptr}; | 
					
						
							|  |  |  |         s64 thread_id{strtoll(command.data() + 1, nullptr, 16)}; | 
					
						
							|  |  |  |         if (thread_id >= 1) { | 
					
						
							|  |  |  |             thread = GetThreadByID(thread_id); | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             thread = backend.GetActiveThread(); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (thread) { | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_OK); | 
					
						
							|  |  |  |             backend.SetActiveThread(thread); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     case 'T': { | 
					
						
							|  |  |  |         s64 thread_id{strtoll(command.data(), nullptr, 16)}; | 
					
						
							|  |  |  |         if (GetThreadByID(thread_id)) { | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_OK); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |     case 'Q': | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     case 'q': | 
					
						
							|  |  |  |         HandleQuery(command); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case '?': | 
					
						
							|  |  |  |         SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP)); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 'k': | 
					
						
							|  |  |  |         LOG_INFO(Debug_GDBStub, "Shutting down emulation"); | 
					
						
							|  |  |  |         actions.push_back(DebuggerAction::ShutdownEmulation); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 'g': | 
					
						
							|  |  |  |         SendReply(arch->ReadRegisters(backend.GetActiveThread())); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 'G': | 
					
						
							|  |  |  |         arch->WriteRegisters(backend.GetActiveThread(), command); | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_OK); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 'p': { | 
					
						
							|  |  |  |         const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; | 
					
						
							|  |  |  |         SendReply(arch->RegRead(backend.GetActiveThread(), reg)); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     case 'P': { | 
					
						
							|  |  |  |         const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1}; | 
					
						
							|  |  |  |         const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; | 
					
						
							|  |  |  |         arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep)); | 
					
						
							| 
									
										
										
										
											2022-06-25 12:07:20 -04:00
										 |  |  |         SendReply(GDB_STUB_REPLY_OK); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     case 'm': { | 
					
						
							|  |  |  |         const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | 
					
						
							|  |  |  |         const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; | 
					
						
							|  |  |  |         const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (system.Memory().IsValidVirtualAddressRange(addr, size)) { | 
					
						
							|  |  |  |             std::vector<u8> mem(size); | 
					
						
							|  |  |  |             system.Memory().ReadBlock(addr, mem.data(), size); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             SendReply(Common::HexToString(mem)); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     case 'M': { | 
					
						
							|  |  |  |         const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | 
					
						
							|  |  |  |         const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))}; | 
					
						
							|  |  |  |         const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const auto mem_substr{std::string_view(command).substr(mem_sep)}; | 
					
						
							|  |  |  |         const auto mem{Common::HexStringToVector(mem_substr, false)}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (system.Memory().IsValidVirtualAddressRange(addr, size)) { | 
					
						
							|  |  |  |             system.Memory().WriteBlock(addr, mem.data(), size); | 
					
						
							|  |  |  |             system.InvalidateCpuInstructionCacheRange(addr, size); | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_OK); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     case 's': | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |         actions.push_back(DebuggerAction::StepThreadLocked); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         break; | 
					
						
							|  |  |  |     case 'C': | 
					
						
							|  |  |  |     case 'c': | 
					
						
							|  |  |  |         actions.push_back(DebuggerAction::Continue); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |     case 'Z': | 
					
						
							|  |  |  |         HandleBreakpointInsert(command); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 'z': | 
					
						
							|  |  |  |         HandleBreakpointRemove(command); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_EMPTY); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  | enum class BreakpointType { | 
					
						
							|  |  |  |     Software = 0, | 
					
						
							|  |  |  |     Hardware = 1, | 
					
						
							|  |  |  |     WriteWatch = 2, | 
					
						
							|  |  |  |     ReadWatch = 3, | 
					
						
							|  |  |  |     AccessWatch = 4, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::HandleBreakpointInsert(std::string_view command) { | 
					
						
							|  |  |  |     const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))}; | 
					
						
							|  |  |  |     const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | 
					
						
							|  |  |  |     const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') - | 
					
						
							|  |  |  |                         command.begin() + 1}; | 
					
						
							|  |  |  |     const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | 
					
						
							|  |  |  |     const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!system.Memory().IsValidVirtualAddressRange(addr, size)) { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |     bool success{}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (type) { | 
					
						
							|  |  |  |     case BreakpointType::Software: | 
					
						
							|  |  |  |         replaced_instructions[addr] = system.Memory().Read32(addr); | 
					
						
							|  |  |  |         system.Memory().Write32(addr, arch->BreakpointInstruction()); | 
					
						
							|  |  |  |         system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | 
					
						
							|  |  |  |         success = true; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case BreakpointType::WriteWatch: | 
					
						
							|  |  |  |         success = system.CurrentProcess()->InsertWatchpoint(system, addr, size, | 
					
						
							|  |  |  |                                                             Kernel::DebugWatchpointType::Write); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         break; | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |     case BreakpointType::ReadWatch: | 
					
						
							|  |  |  |         success = system.CurrentProcess()->InsertWatchpoint(system, addr, size, | 
					
						
							|  |  |  |                                                             Kernel::DebugWatchpointType::Read); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case BreakpointType::AccessWatch: | 
					
						
							|  |  |  |         success = system.CurrentProcess()->InsertWatchpoint( | 
					
						
							|  |  |  |             system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case BreakpointType::Hardware: | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_EMPTY); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (success) { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_OK); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::HandleBreakpointRemove(std::string_view command) { | 
					
						
							|  |  |  |     const auto type{static_cast<BreakpointType>(strtoll(command.data(), nullptr, 16))}; | 
					
						
							|  |  |  |     const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | 
					
						
							|  |  |  |     const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') - | 
					
						
							|  |  |  |                         command.begin() + 1}; | 
					
						
							|  |  |  |     const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | 
					
						
							|  |  |  |     const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!system.Memory().IsValidVirtualAddressRange(addr, size)) { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool success{}; | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |     switch (type) { | 
					
						
							|  |  |  |     case BreakpointType::Software: { | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         const auto orig_insn{replaced_instructions.find(addr)}; | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |         if (orig_insn != replaced_instructions.end()) { | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |             system.Memory().Write32(addr, orig_insn->second); | 
					
						
							|  |  |  |             system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | 
					
						
							|  |  |  |             replaced_instructions.erase(addr); | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |             success = true; | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |     case BreakpointType::WriteWatch: | 
					
						
							|  |  |  |         success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size, | 
					
						
							|  |  |  |                                                             Kernel::DebugWatchpointType::Write); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case BreakpointType::ReadWatch: | 
					
						
							|  |  |  |         success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size, | 
					
						
							|  |  |  |                                                             Kernel::DebugWatchpointType::Read); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case BreakpointType::AccessWatch: | 
					
						
							|  |  |  |         success = system.CurrentProcess()->RemoveWatchpoint( | 
					
						
							|  |  |  |             system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case BreakpointType::Hardware: | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     default: | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_EMPTY); | 
					
						
							| 
									
										
										
										
											2022-06-06 12:56:01 -04:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (success) { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_OK); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_ERR); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  | // Structure offsets are from Atmosphere
 | 
					
						
							|  |  |  | // See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory, | 
					
						
							|  |  |  |                                                           const Kernel::KThread* thread) { | 
					
						
							|  |  |  |     // Read thread type from TLS
 | 
					
						
							|  |  |  |     const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)}; | 
					
						
							|  |  |  |     const VAddr argument_thread_type{thread->GetArgument()}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (argument_thread_type && tls_thread_type != argument_thread_type) { | 
					
						
							|  |  |  |         // Probably not created by nnsdk, no name available.
 | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!tls_thread_type) { | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const u16 version{memory.Read16(tls_thread_type + 0x26)}; | 
					
						
							|  |  |  |     VAddr name_pointer{}; | 
					
						
							|  |  |  |     if (version == 1) { | 
					
						
							|  |  |  |         name_pointer = memory.Read32(tls_thread_type + 0xe4); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         name_pointer = memory.Read32(tls_thread_type + 0xe8); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!name_pointer) { | 
					
						
							|  |  |  |         // No name provided.
 | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return memory.ReadCString(name_pointer, 256); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory, | 
					
						
							|  |  |  |                                                           const Kernel::KThread* thread) { | 
					
						
							|  |  |  |     // Read thread type from TLS
 | 
					
						
							|  |  |  |     const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)}; | 
					
						
							|  |  |  |     const VAddr argument_thread_type{thread->GetArgument()}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (argument_thread_type && tls_thread_type != argument_thread_type) { | 
					
						
							|  |  |  |         // Probably not created by nnsdk, no name available.
 | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!tls_thread_type) { | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const u16 version{memory.Read16(tls_thread_type + 0x46)}; | 
					
						
							|  |  |  |     VAddr name_pointer{}; | 
					
						
							|  |  |  |     if (version == 1) { | 
					
						
							|  |  |  |         name_pointer = memory.Read64(tls_thread_type + 0x1a0); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         name_pointer = memory.Read64(tls_thread_type + 0x1a8); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!name_pointer) { | 
					
						
							|  |  |  |         // No name provided.
 | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return memory.ReadCString(name_pointer, 256); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static std::optional<std::string> GetThreadName(Core::System& system, | 
					
						
							|  |  |  |                                                 const Kernel::KThread* thread) { | 
					
						
							|  |  |  |     if (system.CurrentProcess()->Is64BitProcess()) { | 
					
						
							|  |  |  |         return GetNameFromThreadType64(system.Memory(), thread); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         return GetNameFromThreadType32(system.Memory(), thread); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  | 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"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 20:42:13 -04:00
										 |  |  | static std::string PaginateBuffer(std::string_view buffer, std::string_view request) { | 
					
						
							|  |  |  |     const auto amount{request.substr(request.find(',') + 1)}; | 
					
						
							|  |  |  |     const auto offset_val{static_cast<u64>(strtoll(request.data(), nullptr, 16))}; | 
					
						
							|  |  |  |     const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (offset_val + amount_val > buffer.size()) { | 
					
						
							|  |  |  |         return fmt::format("l{}", buffer.substr(offset_val)); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         return fmt::format("m{}", buffer.substr(offset_val, amount_val)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | void GDBStub::HandleQuery(std::string_view command) { | 
					
						
							|  |  |  |     if (command.starts_with("TStatus")) { | 
					
						
							|  |  |  |         // no tracepoint support
 | 
					
						
							|  |  |  |         SendReply("T0"); | 
					
						
							|  |  |  |     } else if (command.starts_with("Supported")) { | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |         SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;" | 
					
						
							|  |  |  |                   "vContSupported+;QStartNoAckMode+"); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     } else if (command.starts_with("Xfer:features:read:target.xml:")) { | 
					
						
							|  |  |  |         const auto target_xml{arch->GetTargetXML()}; | 
					
						
							| 
									
										
										
										
											2022-06-03 20:42:13 -04:00
										 |  |  |         SendReply(PaginateBuffer(target_xml, command.substr(30))); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     } else if (command.starts_with("Offsets")) { | 
					
						
							|  |  |  |         Loader::AppLoader::Modules modules; | 
					
						
							|  |  |  |         system.GetAppLoader().ReadNSOModules(modules); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const auto main = std::find_if(modules.begin(), modules.end(), | 
					
						
							|  |  |  |                                        [](const auto& key) { return key.second == "main"; }); | 
					
						
							|  |  |  |         if (main != modules.end()) { | 
					
						
							|  |  |  |             SendReply(fmt::format("TextSeg={:x}", main->first)); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             SendReply(fmt::format("TextSeg={:x}", | 
					
						
							|  |  |  |                                   system.CurrentProcess()->PageTable().GetCodeRegionStart())); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-06-03 20:42:13 -04:00
										 |  |  |     } else if (command.starts_with("Xfer:libraries:read::")) { | 
					
						
							|  |  |  |         Loader::AppLoader::Modules modules; | 
					
						
							|  |  |  |         system.GetAppLoader().ReadNSOModules(modules); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         std::string buffer; | 
					
						
							|  |  |  |         buffer += R"(<?xml version="1.0"?>)"; | 
					
						
							|  |  |  |         buffer += "<library-list>"; | 
					
						
							|  |  |  |         for (const auto& [base, name] : modules) { | 
					
						
							|  |  |  |             buffer += fmt::format(R"(<library name="{}"><segment address="{:#x}"/></library>)", | 
					
						
							|  |  |  |                                   EscapeXML(name), base); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         buffer += "</library-list>"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SendReply(PaginateBuffer(buffer, command.substr(21))); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     } else if (command.starts_with("fThreadInfo")) { | 
					
						
							|  |  |  |         // beginning of list
 | 
					
						
							|  |  |  |         const auto& threads = system.GlobalSchedulerContext().GetThreadList(); | 
					
						
							|  |  |  |         std::vector<std::string> thread_ids; | 
					
						
							|  |  |  |         for (const auto& thread : threads) { | 
					
						
							|  |  |  |             thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID())); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         SendReply(fmt::format("m{}", fmt::join(thread_ids, ","))); | 
					
						
							|  |  |  |     } else if (command.starts_with("sThreadInfo")) { | 
					
						
							|  |  |  |         // end of list
 | 
					
						
							|  |  |  |         SendReply("l"); | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  |     } else if (command.starts_with("Xfer:threads:read::")) { | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         std::string buffer; | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  |         buffer += R"(<?xml version="1.0"?>)"; | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         buffer += "<threads>"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const auto& threads = system.GlobalSchedulerContext().GetThreadList(); | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  |         for (const auto* thread : threads) { | 
					
						
							|  |  |  |             auto thread_name{GetThreadName(system, thread)}; | 
					
						
							|  |  |  |             if (!thread_name) { | 
					
						
							|  |  |  |                 thread_name = fmt::format("Thread {:d}", thread->GetThreadID()); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)", | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |                                   thread->GetThreadID(), thread->GetActiveCore(), | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  |                                   EscapeXML(*thread_name), GetThreadState(thread)); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         buffer += "</threads>"; | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 20:42:13 -04:00
										 |  |  |         SendReply(PaginateBuffer(buffer, command.substr(19))); | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |     } else if (command.starts_with("Attached")) { | 
					
						
							|  |  |  |         SendReply("0"); | 
					
						
							|  |  |  |     } else if (command.starts_with("StartNoAckMode")) { | 
					
						
							|  |  |  |         no_ack = true; | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_OK); | 
					
						
							| 
									
										
										
										
											2022-11-10 19:17:54 -05:00
										 |  |  |     } else if (command.starts_with("Rcmd,")) { | 
					
						
							|  |  |  |         HandleRcmd(Common::HexStringToVector(command.substr(5), false)); | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     } else { | 
					
						
							|  |  |  |         SendReply(GDB_STUB_REPLY_EMPTY); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  | 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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-10 19:17:54 -05:00
										 |  |  | 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)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  | Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { | 
					
						
							|  |  |  |     const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; | 
					
						
							|  |  |  |     for (auto* thread : threads) { | 
					
						
							|  |  |  |         if (thread->GetThreadID() == thread_id) { | 
					
						
							|  |  |  |             return thread; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return nullptr; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::vector<char>::const_iterator GDBStub::CommandEnd() const { | 
					
						
							|  |  |  |     // Find the end marker
 | 
					
						
							|  |  |  |     const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Require the checksum to be present
 | 
					
						
							|  |  |  |     return std::min(end + 2, current_command.end()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | std::optional<std::string> GDBStub::DetachCommand() { | 
					
						
							|  |  |  |     // Slice the string part from the beginning to the end marker
 | 
					
						
							|  |  |  |     const auto end{CommandEnd()}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Extract possible command data
 | 
					
						
							|  |  |  |     std::string data(current_command.data(), end - current_command.begin() + 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Shift over the remaining contents
 | 
					
						
							|  |  |  |     current_command.erase(current_command.begin(), end + 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Validate received command
 | 
					
						
							|  |  |  |     if (data[0] != GDB_STUB_START) { | 
					
						
							|  |  |  |         LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]); | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4)); | 
					
						
							|  |  |  |     u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Verify checksum
 | 
					
						
							|  |  |  |     if (calculated != received) { | 
					
						
							|  |  |  |         LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}", | 
					
						
							|  |  |  |                   calculated, received); | 
					
						
							|  |  |  |         return std::nullopt; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return data.substr(1, data.size() - 4); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::SendReply(std::string_view data) { | 
					
						
							| 
									
										
										
										
											2022-06-01 10:54:44 -04:00
										 |  |  |     const auto escaped{EscapeGDB(data)}; | 
					
						
							|  |  |  |     const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END, | 
					
						
							|  |  |  |                                   CalculateChecksum(escaped))}; | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // C++ string support is complete rubbish
 | 
					
						
							|  |  |  |     const u8* output_begin = reinterpret_cast<const u8*>(output.data()); | 
					
						
							|  |  |  |     const u8* output_end = output_begin + output.size(); | 
					
						
							|  |  |  |     backend.WriteToClient(std::span<const u8>(output_begin, output_end)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GDBStub::SendStatus(char status) { | 
					
						
							| 
									
										
										
										
											2022-05-31 14:37:37 -04:00
										 |  |  |     if (no_ack) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-30 19:35:01 -04:00
										 |  |  |     std::array<u8, 1> buf = {static_cast<u8>(status)}; | 
					
						
							|  |  |  |     LOG_TRACE(Debug_GDBStub, "Writing status: {}", status); | 
					
						
							|  |  |  |     backend.WriteToClient(buf); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } // namespace Core
 |