diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 84bf926e3..285012e73 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -28,6 +28,8 @@ add_library(core digital_controller.h dma.cpp dma.h + gdb_protocol.cpp + gdb_protocol.h gpu.cpp gpu.h gpu_backend.cpp diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index dbeaeba0b..0ca308ce4 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -124,6 +124,7 @@ + @@ -198,6 +199,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 6da3d2139..adf4f86e5 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -6,6 +6,7 @@ + @@ -64,6 +65,7 @@ + diff --git a/src/core/gdb_protocol.cpp b/src/core/gdb_protocol.cpp new file mode 100644 index 000000000..34713c0c1 --- /dev/null +++ b/src/core/gdb_protocol.cpp @@ -0,0 +1,319 @@ +#include "gdb_protocol.h" +#include "bus.h" +#include "cpu_core_private.h" +#include "common/log.h" +#include "common/string_util.h" +#include "cpu_core.h" +#include "frontend-common/common_host_interface.h" +#include "system.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 << '#' << StringUtil::StdStringFromFormat("%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.regs.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 - 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 %d", 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(*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(*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'", trimmedData.size(), trimmedData.data()); + return "-"; + } + + std::optional reply = { "" }; + + // Try to invoke packet command. + bool processed = false; + for (const auto& command : COMMANDS) { + if (StringUtil::StartsWith(packet->data(), 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'", trimmedData.size(), trimmedData.data()); + } + return reply ? "+"+SerializePacket(*reply) : "+"; +} + +} // namespace GDBProtocol diff --git a/src/core/gdb_protocol.h b/src/core/gdb_protocol.h new file mode 100644 index 000000000..25737486b --- /dev/null +++ b/src/core/gdb_protocol.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace GDBProtocol +{ + + bool IsPacketInterrupt(const std::string_view& data); + bool IsPacketContinue(const std::string_view& data); + + bool IsPacketComplete(const std::string_view& data); + std::string ProcessPacket(const std::string_view& data); + +} // namespace GDBProtocol