// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin and contributors. // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "gdb_protocol.h" #include "bus.h" #include "cpu_core_private.h" #include "cpu_core.h" #include "system.h" #include "common/log.h" #include "common/small_string.h" #include "common/string_util.h" #include #include #include #include #include #include Log_SetChannel(GDBProtocol); namespace GDBProtocol { static u8* GetMemoryPointer(PhysicalMemoryAddress address, u32 length) { auto region = Bus::GetMemoryRegionForAddress(address); if (region) { u8* data = GetMemoryRegionPointer(*region); if (data && (address + length <= GetMemoryRegionEnd(*region))) { return data + (address - GetMemoryRegionStart(*region)); } } return nullptr; } static u8 ComputeChecksum(const std::string_view& str) { u8 checksum = 0; for (char c : str) { checksum = (checksum + c) % 256; } return checksum; } static std::optional DeserializePacket(const std::string_view& in) { if ((in.size() < 4) || (in[0] != '$') || (in[in.size()-3] != '#')) { return std::nullopt; } std::string_view data = in.substr(1, in.size()-4); u8 packetChecksum = StringUtil::FromChars(in.substr(in.size()-2, 2), 16).value_or(0); u8 computedChecksum = ComputeChecksum(data); if (packetChecksum == computedChecksum) { return { data }; } else { return std::nullopt; } } static std::string SerializePacket(const std::string_view& in) { std::stringstream ss; ss << '$' << in << '#' << TinyString::from_format("{:02x}", ComputeChecksum(in)); return ss.str(); } /// List of GDB remote protocol registers for MIPS III (excluding FP). static const std::array REGISTERS { &CPU::g_state.regs.r[0], &CPU::g_state.regs.r[1], &CPU::g_state.regs.r[2], &CPU::g_state.regs.r[3], &CPU::g_state.regs.r[4], &CPU::g_state.regs.r[5], &CPU::g_state.regs.r[6], &CPU::g_state.regs.r[7], &CPU::g_state.regs.r[8], &CPU::g_state.regs.r[9], &CPU::g_state.regs.r[10], &CPU::g_state.regs.r[11], &CPU::g_state.regs.r[12], &CPU::g_state.regs.r[13], &CPU::g_state.regs.r[14], &CPU::g_state.regs.r[15], &CPU::g_state.regs.r[16], &CPU::g_state.regs.r[17], &CPU::g_state.regs.r[18], &CPU::g_state.regs.r[19], &CPU::g_state.regs.r[20], &CPU::g_state.regs.r[21], &CPU::g_state.regs.r[22], &CPU::g_state.regs.r[23], &CPU::g_state.regs.r[24], &CPU::g_state.regs.r[25], &CPU::g_state.regs.r[26], &CPU::g_state.regs.r[27], &CPU::g_state.regs.r[28], &CPU::g_state.regs.r[29], &CPU::g_state.regs.r[30], &CPU::g_state.regs.r[31], &CPU::g_state.cop0_regs.sr.bits, &CPU::g_state.regs.lo, &CPU::g_state.regs.hi, &CPU::g_state.cop0_regs.BadVaddr, &CPU::g_state.cop0_regs.cause.bits, &CPU::g_state.pc, }; /// Number of registers in GDB remote protocol for MIPS III. constexpr int NUM_GDB_REGISTERS = 73; /// Get stop reason. static std::optional Cmd$_questionMark(const std::string_view& data) { return { "S02" }; } /// Get general registers. static std::optional Cmd$g(const std::string_view& data) { std::stringstream ss; for (u32* reg : REGISTERS) { // Data is in host order (little endian). ss << StringUtil::EncodeHex(reinterpret_cast(reg), 4); } // Pad with dummy data (FP registers stuff). for (int i = 0; i < NUM_GDB_REGISTERS - static_cast(REGISTERS.size()); i++) { ss << "00000000"; } return { ss.str() }; } /// Set general registers. static std::optional Cmd$G(const std::string_view& data) { if (data.size() == NUM_GDB_REGISTERS*8) { int offset = 0; for (u32* reg : REGISTERS) { // Data is in host order (little endian). auto value = StringUtil::DecodeHex({data.data()+offset, 8}); if (value) { *reg = *reinterpret_cast(&(*value)[0]); } offset += 8; } } else { Log_ErrorPrintf("Wrong payload size for 'G' command, expected %d got %zu", NUM_GDB_REGISTERS*8, data.size()); } return { "" }; } /// Get memory. static std::optional Cmd$m(const std::string_view& data) { std::stringstream ss{std::string{data}}; std::string dataAddress, dataLength; std::getline(ss, dataAddress, ','); std::getline(ss, dataLength, '\0'); auto address = StringUtil::FromChars(dataAddress, 16); auto length = StringUtil::FromChars(dataLength, 16); if (address && length) { PhysicalMemoryAddress phys_addr = *address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK; u32 phys_length = *length; u8* ptr_data = GetMemoryPointer(phys_addr, phys_length); if (ptr_data) { return { StringUtil::EncodeHex(ptr_data, phys_length) }; } } return { "E00" }; } /// Set memory. static std::optional Cmd$M(const std::string_view& data) { std::stringstream ss{std::string{data}}; std::string dataAddress, dataLength, dataPayload; std::getline(ss, dataAddress, ','); std::getline(ss, dataLength, ':'); std::getline(ss, dataPayload, '\0'); auto address = StringUtil::FromChars(dataAddress, 16); auto length = StringUtil::FromChars(dataLength, 16); auto payload = StringUtil::DecodeHex(dataPayload); if (address && length && payload && (payload->size() == *length)) { u32 phys_addr = *address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK; u32 phys_length = *length; u8* ptr_data = GetMemoryPointer(phys_addr, phys_length); if (ptr_data) { memcpy(ptr_data, payload->data(), phys_length); return { "OK" }; } } return { "E00" }; } /// Remove hardware breakpoint. static std::optional Cmd$z1(const std::string_view& data) { auto address = StringUtil::FromChars(data, 16); if (address) { CPU::RemoveBreakpoint(CPU::BreakpointType::Execute, *address); return { "OK" }; } else { return std::nullopt; } } /// Insert hardware breakpoint. static std::optional Cmd$Z1(const std::string_view& data) { auto address = StringUtil::FromChars(data, 16); if (address) { CPU::AddBreakpoint(CPU::BreakpointType::Execute, *address, false); return { "OK" }; } else { return std::nullopt; } } static std::optional Cmd$vMustReplyEmpty(const std::string_view& data) { return { "" }; } static std::optional Cmd$qSupported(const std::string_view& data) { return { "" }; } /// List of all GDB remote protocol packets supported by us. static const std::map(const std::string_view&)>> COMMANDS { { "?", Cmd$_questionMark }, { "g", Cmd$g }, { "G", Cmd$G }, { "m", Cmd$m }, { "M", Cmd$M }, { "z0,", Cmd$z1 }, { "Z0,", Cmd$Z1 }, { "z1,", Cmd$z1 }, { "Z1,", Cmd$Z1 }, { "vMustReplyEmpty", Cmd$vMustReplyEmpty }, { "qSupported", Cmd$qSupported }, }; bool IsPacketInterrupt(const std::string_view& data) { return (data.size() >= 1) && (data[data.size()-1] == '\003'); } bool IsPacketContinue(const std::string_view& data) { return (data.size() >= 5) && (data.substr(data.size()-5) == "$c#63"); } bool IsPacketComplete(const std::string_view& data) { return ((data.size() == 1) && (data[0] == '\003')) || ((data.size() > 3) && (*(data.end()-3) == '#')); } std::string ProcessPacket(const std::string_view& data) { std::string_view trimmedData = data; // Eat ACKs. while (!trimmedData.empty() && (trimmedData[0] == '+' || trimmedData[0] == '-')) { if (trimmedData[0] == '-') { Log_ErrorPrint("Received negative ack"); } trimmedData = trimmedData.substr(1); } // Validate packet. auto packet = DeserializePacket(trimmedData); if (!packet) { Log_ErrorPrintf("Malformed packet '%*s'", static_cast(trimmedData.size()), trimmedData.data()); return "-"; } std::optional reply = { "" }; // Try to invoke packet command. bool processed = false; for (const auto& command : COMMANDS) { if (packet->starts_with(command.first)) { Log_DebugPrintf("Processing command '%s'", command.first); // Invoke command, remove command name from payload. reply = command.second(packet->substr(strlen(command.first))); processed = true; break; } } if (!processed) { Log_WarningPrintf("Failed to process packet '%*s'", static_cast(trimmedData.size()), trimmedData.data()); } return reply ? "+"+SerializePacket(*reply) : "+"; } } // namespace GDBProtocol