| |
| |
| |
|
|
| |
|
|
| #include <algorithm> |
| #include <atomic> |
| #include <csignal> |
| #include <cstdarg> |
| #include <cstdio> |
| #include <cstring> |
| #include <map> |
| #include <numeric> |
| #include <fcntl.h> |
| #include <fmt/format.h> |
|
|
| #ifdef _WIN32 |
| #include <winsock2.h> |
| |
| #include <io.h> |
| #include <iphlpapi.h> |
| #include <ws2tcpip.h> |
| #define SHUT_RDWR 2 |
| #else |
| #include <netinet/in.h> |
| #include <sys/select.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #endif |
|
|
| #include "common/logging/log.h" |
| #include "core/arm/arm_interface.h" |
| #include "core/core.h" |
| #include "core/gdbstub/gdbstub.h" |
| #include "core/gdbstub/hio.h" |
| #include "core/hle/kernel/process.h" |
| #include "core/memory.h" |
|
|
| namespace GDBStub { |
| namespace { |
| constexpr int GDB_BUFFER_SIZE = 10000; |
|
|
| constexpr char GDB_STUB_START = '$'; |
| constexpr char GDB_STUB_END = '#'; |
| constexpr char GDB_STUB_ACK = '+'; |
| constexpr char GDB_STUB_NACK = '-'; |
|
|
| #ifndef SIGTRAP |
| constexpr u32 SIGTRAP = 5; |
| #endif |
|
|
| #ifndef SIGTERM |
| constexpr u32 SIGTERM = 15; |
| #endif |
|
|
| #ifndef MSG_WAITALL |
| constexpr u32 MSG_WAITALL = 8; |
| #endif |
|
|
| constexpr u32 SP_REGISTER = 13; |
| constexpr u32 LR_REGISTER = 14; |
| constexpr u32 PC_REGISTER = 15; |
| constexpr u32 CPSR_REGISTER = 25; |
| constexpr u32 D0_REGISTER = 26; |
| constexpr u32 FPSCR_REGISTER = 42; |
|
|
| |
| |
| |
| constexpr char target_xml[] = |
| R"(l<?xml version="1.0"?> |
| <!DOCTYPE target SYSTEM "gdb-target.dtd"> |
| <target version="1.0"> |
| <feature name="org.gnu.gdb.arm.core"> |
| <reg name="r0" bitsize="32"/> |
| <reg name="r1" bitsize="32"/> |
| <reg name="r2" bitsize="32"/> |
| <reg name="r3" bitsize="32"/> |
| <reg name="r4" bitsize="32"/> |
| <reg name="r5" bitsize="32"/> |
| <reg name="r6" bitsize="32"/> |
| <reg name="r7" bitsize="32"/> |
| <reg name="r8" bitsize="32"/> |
| <reg name="r9" bitsize="32"/> |
| <reg name="r10" bitsize="32"/> |
| <reg name="r11" bitsize="32"/> |
| <reg name="r12" bitsize="32"/> |
| <reg name="sp" bitsize="32" type="data_ptr"/> |
| <reg name="lr" bitsize="32"/> |
| <reg name="pc" bitsize="32" type="code_ptr"/> |
| |
| <!-- The CPSR is register 25, rather than register 16, because |
| the FPA registers historically were placed between the PC |
| and the CPSR in the "g" packet. --> |
| |
| <reg name="cpsr" bitsize="32" regnum="25"/> |
| </feature> |
| <feature name="org.gnu.gdb.arm.vfp"> |
| <reg name="d0" bitsize="64" type="float"/> |
| <reg name="d1" bitsize="64" type="float"/> |
| <reg name="d2" bitsize="64" type="float"/> |
| <reg name="d3" bitsize="64" type="float"/> |
| <reg name="d4" bitsize="64" type="float"/> |
| <reg name="d5" bitsize="64" type="float"/> |
| <reg name="d6" bitsize="64" type="float"/> |
| <reg name="d7" bitsize="64" type="float"/> |
| <reg name="d8" bitsize="64" type="float"/> |
| <reg name="d9" bitsize="64" type="float"/> |
| <reg name="d10" bitsize="64" type="float"/> |
| <reg name="d11" bitsize="64" type="float"/> |
| <reg name="d12" bitsize="64" type="float"/> |
| <reg name="d13" bitsize="64" type="float"/> |
| <reg name="d14" bitsize="64" type="float"/> |
| <reg name="d15" bitsize="64" type="float"/> |
| <reg name="fpscr" bitsize="32" type="int" group="float"/> |
| </feature> |
| </target> |
| )"; |
|
|
| int gdbserver_socket = -1; |
| bool defer_start = false; |
|
|
| u8 command_buffer[GDB_BUFFER_SIZE]; |
| u32 command_length; |
|
|
| u32 latest_signal = 0; |
| bool memory_break = false; |
|
|
| static Kernel::Thread* current_thread = nullptr; |
|
|
| |
| |
| u16 gdbstub_port = 24689; |
|
|
| bool halt_loop = true; |
| bool step_loop = false; |
| bool send_trap = false; |
|
|
| |
| |
| std::atomic<bool> server_enabled(false); |
|
|
| #ifdef _WIN32 |
| WSADATA InitData; |
| #endif |
|
|
| struct Breakpoint { |
| bool active; |
| VAddr addr; |
| u32 len; |
| std::array<u8, 4> inst; |
| }; |
|
|
| using BreakpointMap = std::map<VAddr, Breakpoint>; |
| BreakpointMap breakpoints_execute; |
| BreakpointMap breakpoints_read; |
| BreakpointMap breakpoints_write; |
| } |
|
|
| static Kernel::Thread* FindThreadById(int id) { |
| u32 num_cores = Core::GetNumCores(); |
| for (u32 i = 0; i < num_cores; ++i) { |
| const auto& threads = |
| Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList(); |
| for (auto& thread : threads) { |
| if (thread->GetThreadId() == static_cast<u32>(id)) { |
| return thread.get(); |
| } |
| } |
| } |
| return nullptr; |
| } |
|
|
| static u32 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) { |
| if (!thread) { |
| return 0; |
| } |
|
|
| if (id <= PC_REGISTER) { |
| return thread->context.cpu_registers[id]; |
| } else if (id == CPSR_REGISTER) { |
| return thread->context.cpsr; |
| } else { |
| return 0; |
| } |
| } |
|
|
| static void RegWrite(std::size_t id, u32 val, Kernel::Thread* thread = nullptr) { |
| if (!thread) { |
| return; |
| } |
|
|
| if (id <= PC_REGISTER) { |
| thread->context.cpu_registers[id] = val; |
| } else if (id == CPSR_REGISTER) { |
| thread->context.cpsr = val; |
| } |
| } |
|
|
| static u64 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) { |
| if (!thread) { |
| return 0; |
| } |
|
|
| if (id >= D0_REGISTER && id < FPSCR_REGISTER) { |
| u64 ret = thread->context.fpu_registers[2 * (id - D0_REGISTER)]; |
| ret |= static_cast<u64>(thread->context.fpu_registers[2 * (id - D0_REGISTER) + 1]) << 32; |
| return ret; |
| } else if (id == FPSCR_REGISTER) { |
| return thread->context.fpscr; |
| } else { |
| return 0; |
| } |
| } |
|
|
| static void FpuWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) { |
| if (!thread) { |
| return; |
| } |
|
|
| if (id >= D0_REGISTER && id < FPSCR_REGISTER) { |
| thread->context.fpu_registers[2 * (id - D0_REGISTER)] = static_cast<u32>(val); |
| thread->context.fpu_registers[2 * (id - D0_REGISTER) + 1] = static_cast<u32>(val >> 32); |
| } else if (id == FPSCR_REGISTER) { |
| thread->context.fpscr = static_cast<u32>(val); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| static u8 HexCharToValue(u8 hex) { |
| if (hex >= '0' && hex <= '9') { |
| return hex - '0'; |
| } else if (hex >= 'a' && hex <= 'f') { |
| return hex - 'a' + 0xA; |
| } else if (hex >= 'A' && hex <= 'F') { |
| return hex - 'A' + 0xA; |
| } |
|
|
| LOG_ERROR(Debug_GDBStub, "Invalid nibble: {:c} {:02x}\n", hex, hex); |
| return 0; |
| } |
|
|
| |
| |
| |
| |
| |
| static u8 NibbleToHex(u8 n) { |
| n &= 0xF; |
| if (n < 0xA) { |
| return '0' + n; |
| } else { |
| return 'a' + n - 0xA; |
| } |
| } |
|
|
| u32 HexToInt(const u8* src, std::size_t len) { |
| u32 output = 0; |
| while (len-- > 0) { |
| output = (output << 4) | HexCharToValue(src[0]); |
| src++; |
| } |
| return output; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) { |
| while (len-- > 0) { |
| u8 tmp = *src++; |
| *dest++ = NibbleToHex(tmp >> 4); |
| *dest++ = NibbleToHex(tmp); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static void GdbHexToMem(u8* dest, const u8* src, std::size_t len) { |
| while (len-- > 0) { |
| *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]); |
| src += 2; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static void IntToGdbHex(u8* dest, u32 v) { |
| for (int i = 0; i < 8; i += 2) { |
| dest[i + 1] = NibbleToHex(v >> (4 * i)); |
| dest[i] = NibbleToHex(v >> (4 * (i + 1))); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| static u32 GdbHexToInt(const u8* src) { |
| u32 output = 0; |
|
|
| for (int i = 0; i < 8; i += 2) { |
| output = (output << 4) | HexCharToValue(src[7 - i - 1]); |
| output = (output << 4) | HexCharToValue(src[7 - i]); |
| } |
|
|
| return output; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static void LongToGdbHex(u8* dest, u64 v) { |
| for (int i = 0; i < 16; i += 2) { |
| dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i))); |
| dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1)))); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| static u64 GdbHexToLong(const u8* src) { |
| u64 output = 0; |
|
|
| for (int i = 0; i < 16; i += 2) { |
| output = (output << 4) | HexCharToValue(src[15 - i - 1]); |
| output = (output << 4) | HexCharToValue(src[15 - i]); |
| } |
|
|
| return output; |
| } |
|
|
| |
| static u8 ReadByte() { |
| u8 c; |
| std::size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL); |
| if (received_size != 1) { |
| LOG_ERROR(Debug_GDBStub, "recv failed : {}", received_size); |
| Shutdown(); |
| } |
|
|
| return c; |
| } |
|
|
| |
| static u8 CalculateChecksum(const u8* buffer, std::size_t length) { |
| return static_cast<u8>(std::accumulate(buffer, buffer + length, 0, std::plus<u8>())); |
| } |
|
|
| |
| |
| |
| |
| |
| static BreakpointMap& GetBreakpointMap(BreakpointType type) { |
| switch (type) { |
| case BreakpointType::Execute: |
| return breakpoints_execute; |
| case BreakpointType::Read: |
| return breakpoints_read; |
| case BreakpointType::Write: |
| return breakpoints_write; |
| default: |
| return breakpoints_read; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static void RemoveBreakpoint(BreakpointType type, VAddr addr) { |
| BreakpointMap& p = GetBreakpointMap(type); |
|
|
| const auto bp = p.find(addr); |
| if (bp == p.end()) { |
| return; |
| } |
|
|
| LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:08x} bytes at {:08x} of type {}", |
| bp->second.len, bp->second.addr, type); |
|
|
| if (type == BreakpointType::Execute) { |
| Core::System::GetInstance().Memory().WriteBlock( |
| *Core::System::GetInstance().Kernel().GetCurrentProcess(), bp->second.addr, |
| bp->second.inst.data(), bp->second.inst.size()); |
| u32 num_cores = Core::GetNumCores(); |
| for (u32 i = 0; i < num_cores; ++i) { |
| Core::GetCore(i).ClearInstructionCache(); |
| } |
| } |
| p.erase(addr); |
| } |
|
|
| BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, BreakpointType type) { |
| const BreakpointMap& p = GetBreakpointMap(type); |
| const auto next_breakpoint = p.lower_bound(addr); |
| BreakpointAddress breakpoint; |
|
|
| if (next_breakpoint != p.end()) { |
| breakpoint.address = next_breakpoint->first; |
| breakpoint.type = type; |
| } else { |
| breakpoint.address = 0; |
| breakpoint.type = BreakpointType::None; |
| } |
|
|
| return breakpoint; |
| } |
|
|
| bool CheckBreakpoint(VAddr addr, BreakpointType type) { |
| if (!IsConnected()) { |
| return false; |
| } |
|
|
| const BreakpointMap& p = GetBreakpointMap(type); |
| const auto bp = p.find(addr); |
|
|
| if (bp == p.end()) { |
| return false; |
| } |
|
|
| u32 len = bp->second.len; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| if (type == BreakpointType::Execute) { |
| len = 1; |
| } |
|
|
| if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) { |
| LOG_DEBUG(Debug_GDBStub, |
| "Found breakpoint type {} @ {:08x}, range: {:08x}" |
| " - {:08x} ({:x} bytes)", |
| type, addr, bp->second.addr, bp->second.addr + len, len); |
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| static void SendPacket(const char packet) { |
| std::size_t sent_size = send(gdbserver_socket, &packet, 1, 0); |
| if (sent_size != 1) { |
| LOG_ERROR(Debug_GDBStub, "send failed"); |
| } |
| } |
|
|
| void SendReply(const char* reply) { |
| if (!IsConnected()) { |
| return; |
| } |
|
|
| std::memset(command_buffer, 0, sizeof(command_buffer)); |
|
|
| command_length = static_cast<u32>(strlen(reply)); |
| if (command_length + 4 > sizeof(command_buffer)) { |
| LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); |
| return; |
| } |
|
|
| std::memcpy(command_buffer + 1, reply, command_length); |
|
|
| u8 checksum = CalculateChecksum(command_buffer, command_length + 1); |
| command_buffer[0] = GDB_STUB_START; |
| command_buffer[command_length + 1] = GDB_STUB_END; |
| command_buffer[command_length + 2] = NibbleToHex(checksum >> 4); |
| command_buffer[command_length + 3] = NibbleToHex(checksum); |
|
|
| u8* ptr = command_buffer; |
| u32 left = command_length + 4; |
| while (left > 0) { |
| s32 sent_size = |
| static_cast<s32>(send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0)); |
| if (sent_size < 0) { |
| LOG_ERROR(Debug_GDBStub, "gdb: send failed"); |
| return Shutdown(); |
| } |
|
|
| left -= sent_size; |
| ptr += sent_size; |
| } |
| } |
|
|
| |
| static void HandleQuery() { |
| const char* query = reinterpret_cast<const char*>(command_buffer + 1); |
| LOG_DEBUG(Debug_GDBStub, "gdb: query '{}'\n", query); |
|
|
| if (strcmp(query, "TStatus") == 0) { |
| SendReply("T0"); |
| } else if (strncmp(query, "Supported", strlen("Supported")) == 0) { |
| |
| SendReply("PacketSize=2000;qXfer:features:read+;qXfer:threads:read+"); |
| } else if (strncmp(query, "Xfer:features:read:target.xml:", |
| strlen("Xfer:features:read:target.xml:")) == 0) { |
| SendReply(target_xml); |
| } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { |
| std::string val = "m"; |
| u32 num_cores = Core::GetNumCores(); |
| for (u32 i = 0; i < num_cores; ++i) { |
| const auto& threads = |
| Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList(); |
| for (const auto& thread : threads) { |
| val += fmt::format("{:x},", thread->GetThreadId()); |
| } |
| } |
| val.pop_back(); |
| SendReply(val.c_str()); |
| } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) { |
| SendReply("l"); |
| } else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) { |
| std::string buffer; |
| buffer += "l<?xml version=\"1.0\"?>"; |
| buffer += "<threads>"; |
| u32 num_cores = Core::GetNumCores(); |
| for (u32 i = 0; i < num_cores; ++i) { |
| const auto& threads = |
| Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList(); |
| for (const auto& thread : threads) { |
| buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*", |
| thread->GetThreadId(), thread->GetThreadId()); |
| } |
| } |
| buffer += "</threads>"; |
| SendReply(buffer.c_str()); |
| } else { |
| SendReply(""); |
| } |
| } |
|
|
| |
| static void HandleSetThread() { |
| int thread_id = -1; |
| if (command_buffer[2] != '-') { |
| thread_id = static_cast<int>(HexToInt(command_buffer + 2, command_length - 2)); |
| } |
| if (thread_id >= 1) { |
| current_thread = FindThreadById(thread_id); |
| } |
| if (!current_thread) { |
| thread_id = 1; |
| current_thread = FindThreadById(thread_id); |
| } |
| if (current_thread) { |
| SendReply("OK"); |
| return; |
| } |
| SendReply("E01"); |
| } |
|
|
| |
| static void HandleThreadAlive() { |
| int thread_id = static_cast<int>(HexToInt(command_buffer + 1, command_length - 1)); |
| if (thread_id == 0) { |
| thread_id = 1; |
| } |
| if (FindThreadById(thread_id)) { |
| SendReply("OK"); |
| return; |
| } |
| SendReply("E01"); |
| } |
|
|
| |
| |
| |
| |
| |
| static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) { |
| if (gdbserver_socket == -1) { |
| return; |
| } |
|
|
| latest_signal = signal; |
|
|
| if (!thread) { |
| full = false; |
| } |
|
|
| std::string buffer; |
| if (full) { |
|
|
| buffer = fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};{:02x}:{:08x}", latest_signal, |
| PC_REGISTER, htonl(Core::GetRunningCore().GetPC()), SP_REGISTER, |
| htonl(Core::GetRunningCore().GetReg(SP_REGISTER)), LR_REGISTER, |
| htonl(Core::GetRunningCore().GetReg(LR_REGISTER))); |
| } else { |
| buffer = fmt::format("T{:02x}", latest_signal); |
| } |
|
|
| if (thread) { |
| buffer += fmt::format(";thread:{:x};", thread->GetThreadId()); |
| } |
|
|
| LOG_DEBUG(Debug_GDBStub, "Response: {}", buffer); |
| SendReply(buffer.c_str()); |
| } |
|
|
| |
| static void ReadCommand() { |
| command_length = 0; |
| std::memset(command_buffer, 0, sizeof(command_buffer)); |
|
|
| u8 c = ReadByte(); |
| if (c == GDB_STUB_ACK) { |
| |
| return; |
| } else if (c == 0x03) { |
| LOG_INFO(Debug_GDBStub, "gdb: found break command\n"); |
| halt_loop = true; |
| SendSignal(current_thread, SIGTRAP); |
| return; |
| } else if (c != GDB_STUB_START) { |
| LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02x}\n", c); |
| return; |
| } |
|
|
| while ((c = ReadByte()) != GDB_STUB_END) { |
| if (command_length >= sizeof(command_buffer)) { |
| LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow\n"); |
| SendPacket(GDB_STUB_NACK); |
| return; |
| } |
| command_buffer[command_length++] = c; |
| } |
|
|
| u8 checksum_received = HexCharToValue(ReadByte()) << 4; |
| checksum_received |= HexCharToValue(ReadByte()); |
|
|
| u8 checksum_calculated = CalculateChecksum(command_buffer, command_length); |
|
|
| if (checksum_received != checksum_calculated) { |
| LOG_ERROR( |
| Debug_GDBStub, |
| "gdb: invalid checksum: calculated {:02x} and read {:02x} for ${}# (length: {})\n", |
| checksum_calculated, checksum_received, reinterpret_cast<const char*>(command_buffer), |
| command_length); |
|
|
| command_length = 0; |
|
|
| SendPacket(GDB_STUB_NACK); |
| return; |
| } |
|
|
| SendPacket(GDB_STUB_ACK); |
| } |
|
|
| |
| static bool IsDataAvailable() { |
| if (!IsConnected()) { |
| return false; |
| } |
|
|
| fd_set fd_socket; |
|
|
| FD_ZERO(&fd_socket); |
| FD_SET(static_cast<u32>(gdbserver_socket), &fd_socket); |
|
|
| struct timeval t; |
| t.tv_sec = 0; |
| t.tv_usec = 0; |
|
|
| if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) { |
| LOG_ERROR(Debug_GDBStub, "select failed"); |
| return false; |
| } |
|
|
| return FD_ISSET(gdbserver_socket, &fd_socket) != 0; |
| } |
|
|
| |
| static void ReadRegister() { |
| static u8 reply[64]; |
| std::memset(reply, 0, sizeof(reply)); |
|
|
| u32 id = HexCharToValue(command_buffer[1]); |
| if (command_buffer[2] != '\0') { |
| id <<= 4; |
| id |= HexCharToValue(command_buffer[2]); |
| } |
|
|
| if (id <= PC_REGISTER) { |
| IntToGdbHex(reply, RegRead(id, current_thread)); |
| } else if (id == CPSR_REGISTER) { |
| IntToGdbHex(reply, RegRead(id, current_thread)); |
| } else if (id >= D0_REGISTER && id < FPSCR_REGISTER) { |
| LongToGdbHex(reply, FpuRead(id, current_thread)); |
| } else if (id == FPSCR_REGISTER) { |
| IntToGdbHex(reply, static_cast<u32>(FpuRead(id, current_thread))); |
| } else { |
| return SendReply("E01"); |
| } |
|
|
| SendReply(reinterpret_cast<char*>(reply)); |
| } |
|
|
| |
| static void ReadRegisters() { |
| static u8 buffer[GDB_BUFFER_SIZE - 4]; |
| std::memset(buffer, 0, sizeof(buffer)); |
|
|
| u8* bufptr = buffer; |
|
|
| for (u32 reg = 0; reg <= PC_REGISTER; reg++) { |
| IntToGdbHex(bufptr + reg * 8, RegRead(reg, current_thread)); |
| } |
|
|
| bufptr += 16 * 8; |
|
|
| IntToGdbHex(bufptr, RegRead(CPSR_REGISTER, current_thread)); |
|
|
| bufptr += 8; |
|
|
| for (u32 reg = D0_REGISTER; reg < FPSCR_REGISTER; reg++) { |
| LongToGdbHex(bufptr + reg * 16, FpuRead(reg, current_thread)); |
| } |
|
|
| bufptr += 16 * 16; |
|
|
| IntToGdbHex(bufptr, static_cast<u32>(FpuRead(FPSCR_REGISTER, current_thread))); |
|
|
| SendReply(reinterpret_cast<char*>(buffer)); |
| } |
|
|
| |
| static void WriteRegister() { |
| const u8* buffer_ptr = command_buffer + 3; |
|
|
| u32 id = HexCharToValue(command_buffer[1]); |
| if (command_buffer[2] != '=') { |
| ++buffer_ptr; |
| id <<= 4; |
| id |= HexCharToValue(command_buffer[2]); |
| } |
|
|
| if (id <= PC_REGISTER) { |
| RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); |
| } else if (id == CPSR_REGISTER) { |
| RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); |
| } else if (id >= D0_REGISTER && id < FPSCR_REGISTER) { |
| FpuWrite(id, GdbHexToLong(buffer_ptr), current_thread); |
| } else if (id == FPSCR_REGISTER) { |
| FpuWrite(id, GdbHexToInt(buffer_ptr), current_thread); |
| } else { |
| return SendReply("E01"); |
| } |
|
|
| Core::GetRunningCore().LoadContext(current_thread->context); |
|
|
| SendReply("OK"); |
| } |
|
|
| |
| static void WriteRegisters() { |
| const u8* buffer_ptr = command_buffer + 1; |
|
|
| if (command_buffer[0] != 'G') |
| return SendReply("E01"); |
|
|
| for (u32 i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) { |
| if (reg <= PC_REGISTER) { |
| RegWrite(reg, GdbHexToInt(buffer_ptr + i * 8)); |
| } else if (reg == CPSR_REGISTER) { |
| RegWrite(reg, GdbHexToInt(buffer_ptr + i * 8)); |
| } else if (reg == CPSR_REGISTER - 1) { |
| |
| } else if (reg < CPSR_REGISTER) { |
| |
| i += 2; |
| } else if (reg >= D0_REGISTER && reg < FPSCR_REGISTER) { |
| FpuWrite(reg, GdbHexToLong(buffer_ptr + i * 16)); |
| i++; |
| } else if (reg == FPSCR_REGISTER) { |
| FpuWrite(reg, GdbHexToInt(buffer_ptr + i * 8)); |
| } |
| } |
|
|
| Core::GetRunningCore().LoadContext(current_thread->context); |
|
|
| SendReply("OK"); |
| } |
|
|
| |
| static void ReadMemory() { |
| static u8 reply[GDB_BUFFER_SIZE - 4]; |
|
|
| auto start_offset = command_buffer + 1; |
| auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); |
| VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); |
|
|
| start_offset = addr_pos + 1; |
| u32 len = |
| HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); |
|
|
| LOG_DEBUG(Debug_GDBStub, "ReadMemory addr: {:08x} len: {:08x}", addr, len); |
|
|
| if (len * 2 > sizeof(reply)) { |
| SendReply("E01"); |
| } |
|
|
| auto& memory = Core::System::GetInstance().Memory(); |
| if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(), |
| addr)) { |
| return SendReply("E00"); |
| } |
|
|
| std::vector<u8> data(len); |
| memory.ReadBlock(addr, data.data(), len); |
|
|
| MemToGdbHex(reply, data.data(), len); |
| reply[len * 2] = '\0'; |
|
|
| auto reply_str = reinterpret_cast<char*>(reply); |
|
|
| LOG_DEBUG(Debug_GDBStub, "ReadMemory result: {}", reply_str); |
| SendReply(reply_str); |
| } |
|
|
| |
| static void WriteMemory() { |
| auto start_offset = command_buffer + 1; |
| auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); |
| VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); |
|
|
| start_offset = addr_pos + 1; |
| auto len_pos = std::find(start_offset, command_buffer + command_length, ':'); |
| u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset)); |
|
|
| auto& memory = Core::System::GetInstance().Memory(); |
| if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(), |
| addr)) { |
| return SendReply("E00"); |
| } |
|
|
| std::vector<u8> data(len); |
|
|
| GdbHexToMem(data.data(), len_pos + 1, len); |
| memory.WriteBlock(addr, data.data(), len); |
| Core::GetRunningCore().ClearInstructionCache(); |
| SendReply("OK"); |
| } |
|
|
| void Break(bool is_memory_break) { |
| send_trap = true; |
|
|
| memory_break = is_memory_break; |
| } |
|
|
| |
| static void Step() { |
| if (command_length > 1) { |
| RegWrite(PC_REGISTER, GdbHexToInt(command_buffer + 1), current_thread); |
| Core::GetRunningCore().LoadContext(current_thread->context); |
| } |
| step_loop = true; |
| halt_loop = true; |
| send_trap = true; |
| Core::GetRunningCore().ClearInstructionCache(); |
| } |
|
|
| bool IsMemoryBreak() { |
| if (!IsConnected()) { |
| return false; |
| } |
|
|
| return memory_break; |
| } |
|
|
| |
| static void Continue() { |
| memory_break = false; |
| step_loop = false; |
| halt_loop = false; |
| Core::GetRunningCore().ClearInstructionCache(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static bool CommitBreakpoint(BreakpointType type, VAddr addr, u32 len) { |
| BreakpointMap& p = GetBreakpointMap(type); |
|
|
| Breakpoint breakpoint; |
| breakpoint.active = true; |
| breakpoint.addr = addr; |
| breakpoint.len = len; |
| Core::System::GetInstance().Memory().ReadBlock( |
| *Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, breakpoint.inst.data(), |
| breakpoint.inst.size()); |
|
|
| static constexpr std::array<u8, 4> btrap{0x70, 0x00, 0x20, 0xe1}; |
| if (type == BreakpointType::Execute) { |
| Core::System::GetInstance().Memory().WriteBlock( |
| *Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, btrap.data(), |
| btrap.size()); |
| Core::GetRunningCore().ClearInstructionCache(); |
| } |
| p.insert({addr, breakpoint}); |
|
|
| LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:08x} bytes at {:08x}\n", type, |
| breakpoint.len, breakpoint.addr); |
|
|
| return true; |
| } |
|
|
| |
| static void AddBreakpoint() { |
| BreakpointType type; |
|
|
| u8 type_id = HexCharToValue(command_buffer[1]); |
| switch (type_id) { |
| case 0: |
| case 1: |
| type = BreakpointType::Execute; |
| break; |
| case 2: |
| type = BreakpointType::Write; |
| break; |
| case 3: |
| type = BreakpointType::Read; |
| break; |
| case 4: |
| type = BreakpointType::Access; |
| break; |
| default: |
| return SendReply("E01"); |
| } |
|
|
| auto start_offset = command_buffer + 3; |
| auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); |
| VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); |
|
|
| start_offset = addr_pos + 1; |
| u32 len = |
| HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); |
|
|
| if (type == BreakpointType::Access) { |
| |
| type = BreakpointType::Read; |
|
|
| if (!CommitBreakpoint(type, addr, len)) { |
| return SendReply("E02"); |
| } |
|
|
| type = BreakpointType::Write; |
| } |
|
|
| if (!CommitBreakpoint(type, addr, len)) { |
| return SendReply("E02"); |
| } |
|
|
| SendReply("OK"); |
| } |
|
|
| |
| static void RemoveBreakpoint() { |
| BreakpointType type; |
|
|
| u8 type_id = HexCharToValue(command_buffer[1]); |
| switch (type_id) { |
| case 0: |
| case 1: |
| type = BreakpointType::Execute; |
| break; |
| case 2: |
| type = BreakpointType::Write; |
| break; |
| case 3: |
| type = BreakpointType::Read; |
| break; |
| case 4: |
| type = BreakpointType::Access; |
| break; |
| default: |
| return SendReply("E01"); |
| } |
|
|
| auto start_offset = command_buffer + 3; |
| auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); |
| VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); |
|
|
| if (type == BreakpointType::Access) { |
| |
| type = BreakpointType::Read; |
| RemoveBreakpoint(type, addr); |
|
|
| type = BreakpointType::Write; |
| } |
|
|
| RemoveBreakpoint(type, addr); |
| SendReply("OK"); |
| } |
|
|
| void HandlePacket(Core::System& system) { |
| if (!IsConnected()) { |
| if (defer_start) { |
| ToggleServer(true); |
| } |
| return; |
| } |
|
|
| if (HandlePendingHioRequestPacket()) { |
| |
| return; |
| } |
|
|
| if (!IsDataAvailable()) { |
| return; |
| } |
|
|
| ReadCommand(); |
| if (command_length == 0) { |
| return; |
| } |
|
|
| LOG_DEBUG(Debug_GDBStub, "Packet: {0:d} ('{0:c}')", command_buffer[0]); |
|
|
| switch (command_buffer[0]) { |
| case 'q': |
| HandleQuery(); |
| break; |
| case 'H': |
| HandleSetThread(); |
| break; |
| case '?': |
| SendSignal(current_thread, latest_signal); |
| break; |
| case 'k': |
| LOG_INFO(Debug_GDBStub, "killed by gdb"); |
| ToggleServer(false); |
| |
| Continue(); |
| return; |
| case 'F': |
| HandleHioReply(system, command_buffer, command_length); |
| break; |
| case 'g': |
| ReadRegisters(); |
| break; |
| case 'G': |
| WriteRegisters(); |
| break; |
| case 'p': |
| ReadRegister(); |
| break; |
| case 'P': |
| WriteRegister(); |
| break; |
| case 'm': |
| ReadMemory(); |
| break; |
| case 'M': |
| WriteMemory(); |
| break; |
| case 's': |
| Step(); |
| return; |
| case 'C': |
| case 'c': |
| Continue(); |
| return; |
| case 'z': |
| RemoveBreakpoint(); |
| break; |
| case 'Z': |
| AddBreakpoint(); |
| break; |
| case 'T': |
| HandleThreadAlive(); |
| break; |
| default: |
| SendReply(""); |
| break; |
| } |
| } |
|
|
| void SetServerPort(u16 port) { |
| gdbstub_port = port; |
| } |
|
|
| void ToggleServer(bool status) { |
| if (status) { |
| server_enabled = status; |
|
|
| |
| if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) { |
| Init(); |
| } |
| } else { |
| |
| if (IsConnected()) { |
| Shutdown(); |
| } |
|
|
| server_enabled = status; |
| } |
| } |
|
|
| void DeferStart() { |
| defer_start = true; |
| } |
|
|
| static void Init(u16 port) { |
| if (!server_enabled) { |
| |
| |
| halt_loop = false; |
| step_loop = false; |
| return; |
| } |
|
|
| |
| halt_loop = true; |
| step_loop = false; |
|
|
| breakpoints_execute.clear(); |
| breakpoints_read.clear(); |
| breakpoints_write.clear(); |
|
|
| |
| LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port); |
|
|
| sockaddr_in saddr_server = {}; |
| saddr_server.sin_family = AF_INET; |
| saddr_server.sin_port = htons(port); |
| saddr_server.sin_addr.s_addr = INADDR_ANY; |
|
|
| #ifdef _WIN32 |
| WSAStartup(MAKEWORD(2, 2), &InitData); |
| #endif |
|
|
| int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0)); |
| if (tmpsock == -1) { |
| LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); |
| } |
|
|
| |
| int reuse_enabled = 1; |
| if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled, |
| sizeof(reuse_enabled)) < 0) { |
| LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option"); |
| } |
|
|
| const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server); |
| socklen_t server_addrlen = sizeof(saddr_server); |
| if (bind(tmpsock, server_addr, server_addrlen) < 0) { |
| LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket"); |
| } |
|
|
| if (listen(tmpsock, 1) < 0) { |
| LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket"); |
| } |
|
|
| |
| LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n"); |
| sockaddr_in saddr_client; |
| sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client); |
| socklen_t client_addrlen = sizeof(saddr_client); |
| gdbserver_socket = static_cast<int>(accept(tmpsock, client_addr, &client_addrlen)); |
| if (gdbserver_socket < 0) { |
| |
| |
| halt_loop = false; |
| step_loop = false; |
|
|
| LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client"); |
| } else { |
| LOG_INFO(Debug_GDBStub, "Client connected.\n"); |
| saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr); |
| } |
|
|
| |
| if (tmpsock != -1) { |
| shutdown(tmpsock, SHUT_RDWR); |
| } |
| } |
|
|
| void Init() { |
| Init(gdbstub_port); |
| } |
|
|
| void Shutdown() { |
| if (!server_enabled) { |
| return; |
| } |
| defer_start = false; |
|
|
| LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); |
| if (gdbserver_socket != -1) { |
| shutdown(gdbserver_socket, SHUT_RDWR); |
| gdbserver_socket = -1; |
| } |
|
|
| #ifdef _WIN32 |
| WSACleanup(); |
| #endif |
|
|
| LOG_INFO(Debug_GDBStub, "GDB stopped."); |
| } |
|
|
| bool IsServerEnabled() { |
| return server_enabled; |
| } |
|
|
| bool IsConnected() { |
| return IsServerEnabled() && gdbserver_socket != -1; |
| } |
|
|
| bool GetCpuHaltFlag() { |
| return halt_loop; |
| } |
|
|
| void SetCpuHaltFlag(bool halt) { |
| halt_loop = halt; |
| } |
|
|
| bool GetCpuStepFlag() { |
| return step_loop; |
| } |
|
|
| void SetCpuStepFlag(bool is_step) { |
| step_loop = is_step; |
| } |
|
|
| void SendTrap(Kernel::Thread* thread, int trap) { |
| if (!send_trap) { |
| return; |
| } |
|
|
| current_thread = thread; |
|
|
| SendSignal(thread, trap); |
|
|
| halt_loop = true; |
| send_trap = false; |
| } |
| }; |
|
|