From eebba986b5a6ee9b9d335687cbe18b8a4930a0a8 Mon Sep 17 00:00:00 2001 From: Nav Date: Sat, 16 Nov 2024 20:43:22 +0000 Subject: [PATCH] RISC-V GDB server --- src/DebugServer/CMakeLists.txt | 14 ++ src/DebugServer/DebugServerComponent.cpp | 16 ++ .../Gdb/RiscVGdb/CommandPackets/FlashDone.cpp | 103 +++++++++++++ .../Gdb/RiscVGdb/CommandPackets/FlashDone.hpp | 28 ++++ .../RiscVGdb/CommandPackets/FlashErase.cpp | 74 ++++++++++ .../RiscVGdb/CommandPackets/FlashErase.hpp | 31 ++++ .../RiscVGdb/CommandPackets/FlashWrite.cpp | 99 +++++++++++++ .../RiscVGdb/CommandPackets/FlashWrite.hpp | 30 ++++ .../RiscVGdb/CommandPackets/ReadMemory.cpp | 137 ++++++++++++++++++ .../RiscVGdb/CommandPackets/ReadMemory.hpp | 47 ++++++ .../RiscVGdb/CommandPackets/ReadMemoryMap.cpp | 99 +++++++++++++ .../RiscVGdb/CommandPackets/ReadMemoryMap.hpp | 37 +++++ .../RiscVGdb/CommandPackets/ReadRegister.cpp | 95 ++++++++++++ .../RiscVGdb/CommandPackets/ReadRegister.hpp | 26 ++++ .../RiscVGdb/CommandPackets/ReadRegisters.cpp | 81 +++++++++++ .../RiscVGdb/CommandPackets/ReadRegisters.hpp | 27 ++++ .../RiscVGdbCommandPacketInterface.hpp | 31 ++++ .../VContSupportedActionsQuery.cpp | 24 +++ .../VContSupportedActionsQuery.hpp | 25 ++++ .../RiscVGdb/CommandPackets/WriteMemory.cpp | 132 +++++++++++++++++ .../RiscVGdb/CommandPackets/WriteMemory.hpp | 42 ++++++ .../RiscVGdb/CommandPackets/WriteRegister.cpp | 94 ++++++++++++ .../RiscVGdb/CommandPackets/WriteRegister.hpp | 29 ++++ src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.cpp | 135 +++++++++++++++++ src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.hpp | 35 +++++ .../Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp | 40 +++++ .../Gdb/RiscVGdb/RiscVGdbTargetDescriptor.hpp | 29 ++++ src/Targets/RiscV/Wch/WchRiscV.cpp | 26 +++- 28 files changed, 1582 insertions(+), 4 deletions(-) create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/RiscVGdbCommandPacketInterface.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.hpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp create mode 100644 src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.hpp diff --git a/src/DebugServer/CMakeLists.txt b/src/DebugServer/CMakeLists.txt index 548800bc..20099a94 100755 --- a/src/DebugServer/CMakeLists.txt +++ b/src/DebugServer/CMakeLists.txt @@ -43,6 +43,20 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/VContRangeStep.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/EepromFill.cpp + + # RISC-V GDB RSP Server + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/RiscVGdbRsp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/ReadRegisters.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/ReadRegister.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/WriteRegister.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/ReadMemory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/WriteMemory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/FlashErase.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/FlashWrite.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/FlashDone.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.cpp ) if (NOT EXCLUDE_INSIGHT) diff --git a/src/DebugServer/DebugServerComponent.cpp b/src/DebugServer/DebugServerComponent.cpp index 3711f72b..0929dbcb 100644 --- a/src/DebugServer/DebugServerComponent.cpp +++ b/src/DebugServer/DebugServerComponent.cpp @@ -4,6 +4,7 @@ // Debug server implementations #include "Gdb/AvrGdb/AvrGdbRsp.hpp" +#include "Gdb/RiscVGdb/RiscVGdbRsp.hpp" #include "src/Exceptions/InvalidConfig.hpp" #include "src/Logger/Logger.hpp" @@ -55,6 +56,21 @@ namespace DebugServer ); } }, + { + "riscv-gdb-rsp", + [this] () -> std::unique_ptr { + if (this->targetDescriptor.family != Targets::TargetFamily::RISC_V) { + throw Exceptions::Exception{"The RISC-V GDB RSP server is only compatible with RISC-V targets."}; + } + + return std::make_unique( + this->debugServerConfig, + this->targetDescriptor, + *(this->eventListener.get()), + this->interruptEventNotifier + ); + } + }, }; } diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.cpp new file mode 100644 index 00000000..70543f2a --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.cpp @@ -0,0 +1,103 @@ +#include "FlashDone.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" +#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp" + +#include "src/Logger/Logger.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + using ResponsePackets::OkResponsePacket; + + using namespace Exceptions; + + FlashDone::FlashDone(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + {} + + void FlashDone::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling FlashDone packet"); + + try { + if (!debugSession.programmingSession.has_value()) { + /* + * GDB will send a VFlashDone packet even it only performs an erase. In this case, there's nothing more + * to do, as erase operations are executed immediately. + */ + targetControllerService.disableProgrammingMode(); + debugSession.connection.writePacket(OkResponsePacket{}); + return; + } + + Logger::info( + "Flushing " + std::to_string(debugSession.programmingSession->buffer.size()) + + " bytes to target's program memory" + ); + + const auto addressRange = Targets::TargetMemoryAddressRange{ + debugSession.programmingSession->startAddress, + debugSession.programmingSession->startAddress + + static_cast(debugSession.programmingSession->buffer.size()) - 1 + }; + + const auto memorySegmentDescriptors = gdbTargetDescriptor.systemAddressSpaceDescriptor.getIntersectingMemorySegmentDescriptors( + addressRange + ); + + if (memorySegmentDescriptors.size() != 1) { + throw Exception{ + memorySegmentDescriptors.empty() + ? "Invalid command - no containing memory segments found for the given address range" + : "Invalid command - address range intersects with multiple memory segments" + }; + } + + const auto& segmentDescriptor = *(memorySegmentDescriptors.front()); + if (!segmentDescriptor.programmingModeAccess.writeable) { + throw Exception{"Memory segment (\"" + segmentDescriptor.name + "\") not writable in programming mode"}; + } + + targetControllerService.enableProgrammingMode(); + + targetControllerService.writeMemory( + gdbTargetDescriptor.systemAddressSpaceDescriptor, + segmentDescriptor, + debugSession.programmingSession->startAddress, + std::move(debugSession.programmingSession->buffer) + ); + + debugSession.programmingSession.reset(); + + Logger::warning("Program memory updated"); + targetControllerService.disableProgrammingMode(); + + Logger::warning("Resetting target"); + targetControllerService.resetTarget(); + Logger::info("Target reset complete"); + + debugSession.connection.writePacket(OkResponsePacket{}); + + } catch (const Exception& exception) { + Logger::error("Failed to handle FlashDone packet - " + exception.getMessage()); + debugSession.programmingSession.reset(); + + try { + targetControllerService.disableProgrammingMode(); + + } catch (const Exception& exception) { + Logger::error("Failed to disable programming mode - " + exception.getMessage()); + } + + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.hpp new file mode 100644 index 00000000..93aafddb --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashDone.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetAddressSpaceDescriptor.hpp" +#include "src/Targets/TargetMemorySegmentDescriptor.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class FlashDone + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + explicit FlashDone(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.cpp new file mode 100644 index 00000000..686cae15 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.cpp @@ -0,0 +1,74 @@ +#include "FlashErase.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" +#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Logger/Logger.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + using ResponsePackets::OkResponsePacket; + + using namespace Exceptions; + + FlashErase::FlashErase(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + { + using Services::StringService; + + if (rawPacket.size() < 8) { + throw Exception{"Invalid packet length"}; + } + + const auto command = std::string{this->data.begin() + 12, this->data.end()}; + + const auto delimiterPos = command.find_first_of(','); + if (delimiterPos == std::string::npos) { + throw Exception{"Invalid packet"}; + } + + this->startAddress = StringService::toUint32(command.substr(0, delimiterPos), 16); + this->bytes = StringService::toUint32(command.substr(delimiterPos + 1), 16); + } + + void FlashErase::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling FlashErase packet"); + + try { + targetControllerService.enableProgrammingMode(); + + Logger::warning("Erasing program memory, in preparation for programming"); + + // We don't erase a specific address range - we just erase the entire program memory. + targetControllerService.eraseMemory( + gdbTargetDescriptor.systemAddressSpaceDescriptor, + gdbTargetDescriptor.programMemorySegmentDescriptor + ); + + debugSession.connection.writePacket(OkResponsePacket{}); + + } catch (const Exception& exception) { + Logger::error("Failed to erase flash memory - " + exception.getMessage()); + debugSession.programmingSession.reset(); + + try { + targetControllerService.disableProgrammingMode(); + + } catch (const Exception& exception) { + Logger::error("Failed to disable programming mode - " + exception.getMessage()); + } + + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.hpp new file mode 100644 index 00000000..676d438a --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashErase.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetAddressSpaceDescriptor.hpp" +#include "src/Targets/TargetMemorySegmentDescriptor.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class FlashErase + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + std::uint32_t startAddress = 0; + std::uint32_t bytes = 0; + + explicit FlashErase(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.cpp new file mode 100644 index 00000000..5e5c588d --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.cpp @@ -0,0 +1,99 @@ +#include "FlashWrite.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" +#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Logger/Logger.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + using ResponsePackets::OkResponsePacket; + + using namespace Exceptions; + + FlashWrite::FlashWrite(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + { + using Services::StringService; + + if (this->data.size() < 15) { + throw Exception{"Invalid packet length"}; + } + + /* + * The flash write ('vFlashWrite') packet consists of two segments: an address and a buffer, seperated by a + * colon. + */ + const auto delimiterIt = std::find(this->data.begin() + 12, this->data.end(), ':'); + + if (delimiterIt == this->data.end()) { + throw Exception{"Failed to find colon delimiter in write flash packet."}; + } + + this->startAddress = StringService::toUint32(std::string{this->data.begin() + 12, delimiterIt}, 16); + this->buffer = Targets::TargetMemoryBuffer{delimiterIt + 1, this->data.end()}; + } + + void FlashWrite::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling FlashWrite packet"); + + try { + if (this->buffer.empty()) { + throw Exception{"Received empty buffer from GDB"}; + } + + if (!debugSession.programmingSession.has_value()) { + debugSession.programmingSession = ProgrammingSession{this->startAddress, this->buffer}; + + } else { + auto& programmingSession = debugSession.programmingSession.value(); + const auto currentEndAddress = programmingSession.startAddress + programmingSession.buffer.size() - 1; + const auto expectedStartAddress = (currentEndAddress + 1); + + if (this->startAddress < expectedStartAddress) { + throw Exception{"Invalid start address from GDB - the buffer would overlap a previous buffer"}; + } + + if (this->startAddress > expectedStartAddress) { + // There is a gap in the buffer sent by GDB. Fill it with 0xFF + programmingSession.buffer.insert( + programmingSession.buffer.end(), + this->startAddress - expectedStartAddress, + 0xFF + ); + } + + programmingSession.buffer.insert( + programmingSession.buffer.end(), + this->buffer.begin(), + this->buffer.end() + ); + } + + debugSession.connection.writePacket(OkResponsePacket{}); + + } catch (const Exception& exception) { + Logger::error("Failed to handle FlashWrite packet - " + exception.getMessage()); + debugSession.programmingSession.reset(); + + try { + targetControllerService.disableProgrammingMode(); + + } catch (const Exception& exception) { + Logger::error("Failed to disable programming mode - " + exception.getMessage()); + } + + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.hpp new file mode 100644 index 00000000..c3257a04 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/FlashWrite.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetMemory.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class FlashWrite + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + Targets::TargetMemoryAddress startAddress; + Targets::TargetMemoryBuffer buffer; + + explicit FlashWrite(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.cpp new file mode 100644 index 00000000..82bb2085 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.cpp @@ -0,0 +1,137 @@ +#include "ReadMemory.hpp" + +#include + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" +#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Logger/Logger.hpp" + +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + using ResponsePackets::ResponsePacket; + + using Exceptions::Exception; + + ReadMemory::ReadMemory(const RawPacket& rawPacket, const RiscVGdbTargetDescriptor& gdbTargetDescriptor) + : ReadMemory(rawPacket, gdbTargetDescriptor, ReadMemory::extractPacketData(rawPacket)) + {} + + void ReadMemory::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling ReadMemory packet"); + + try { + if (this->bytes == 0) { + debugSession.connection.writePacket(ResponsePacket{Targets::TargetMemoryBuffer{}}); + return; + } + + const auto addressRange = Targets::TargetMemoryAddressRange{ + this->startAddress, + this->startAddress + this->bytes - 1 + }; + + const auto memorySegmentDescriptors = this->addressSpaceDescriptor.getIntersectingMemorySegmentDescriptors( + addressRange + ); + + /* + * First pass to ensure that we can read all of the memory before attempting to do so. And to ensure that + * the requested address range completely resides within known memory segments. + */ + auto accessibleBytes = Targets::TargetMemorySize{0}; + for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) { + if (!memorySegmentDescriptor->debugModeAccess.readable) { + throw Exception{ + "Attempted to access restricted memory segment (" + memorySegmentDescriptor->key + + ") - segment not readable in debug mode" + }; + } + + accessibleBytes += memorySegmentDescriptor->addressRange.intersectingSize(addressRange); + } + + if (accessibleBytes < this->bytes) { + Logger::debug( + "GDB requested access to memory which does not reside within any memory segment - returning error " + "response" + ); + debugSession.connection.writePacket(ErrorResponsePacket{}); + return; + } + + auto buffer = Targets::TargetMemoryBuffer(this->bytes, 0x00); + + { + const auto atomicSession = targetControllerService.makeAtomicSession(); + + for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) { + const auto segmentStartAddress = std::max( + this->startAddress, + memorySegmentDescriptor->addressRange.startAddress + ); + + const auto segmentBuffer = targetControllerService.readMemory( + this->addressSpaceDescriptor, + *memorySegmentDescriptor, + segmentStartAddress, + memorySegmentDescriptor->addressRange.intersectingSize(addressRange) + ); + + const auto bufferOffsetIt = buffer.begin() + (segmentStartAddress - this->startAddress); + assert(segmentBuffer.size() <= std::distance(bufferOffsetIt, buffer.end())); + + std::copy(segmentBuffer.begin(), segmentBuffer.end(), bufferOffsetIt); + } + } + + debugSession.connection.writePacket(ResponsePacket{Services::StringService::toHex(buffer)}); + + } catch (const Exception& exception) { + Logger::error("Failed to read memory from target - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } + + ReadMemory::PacketData ReadMemory::extractPacketData(const RawPacket& rawPacket) { + using Services::StringService; + + if (rawPacket.size() < 8) { + throw Exception{"Invalid packet length"}; + } + + const auto command = std::string{rawPacket.begin() + 2, rawPacket.end() - 3}; + + const auto delimiterPos = command.find_first_of(','); + if (delimiterPos == std::string::npos) { + throw Exception{"Invalid packet"}; + } + + return { + .gdbStartAddress = StringService::toUint32(command.substr(0, delimiterPos), 16), + .bytes = StringService::toUint32(command.substr(delimiterPos + 1), 16) + }; + } + + ReadMemory::ReadMemory( + const RawPacket& rawPacket, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + ReadMemory::PacketData&& packetData + ) + : CommandPacket(rawPacket) + , addressSpaceDescriptor(gdbTargetDescriptor.systemAddressSpaceDescriptor) + , startAddress(packetData.gdbStartAddress) + , bytes(packetData.bytes) + {} +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.hpp new file mode 100644 index 00000000..7ef5e813 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemory.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetAddressSpaceDescriptor.hpp" +#include "src/Targets/TargetMemory.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class ReadMemory + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + const Targets::TargetAddressSpaceDescriptor& addressSpaceDescriptor; + + Targets::TargetMemoryAddress startAddress; + Targets::TargetMemorySize bytes; + + ReadMemory(const RawPacket& rawPacket, const RiscVGdbTargetDescriptor& gdbTargetDescriptor); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + + private: + struct PacketData + { + GdbMemoryAddress gdbStartAddress; + std::uint32_t bytes; + }; + + static PacketData extractPacketData(const RawPacket& rawPacket); + ReadMemory( + const RawPacket& rawPacket, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + PacketData&& packetData + ); + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.cpp new file mode 100644 index 00000000..735efd34 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.cpp @@ -0,0 +1,99 @@ +#include "ReadMemoryMap.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Helpers/BiMap.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + using Services::StringService; + + using ResponsePackets::ResponsePacket; + + using Exceptions::Exception; + + ReadMemoryMap::ReadMemoryMap(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + { + if (this->data.size() < 26) { + throw Exception{"Invalid packet length"}; + } + + /* + * The read memory map ('qXfer:memory-map:read::...') packet consists of two segments, an offset and a length. + * These are separated by a comma character. + */ + const auto command = std::string{this->data.begin() + 23, this->data.end()}; + + const auto delimiterPos = command.find_first_of(','); + if (delimiterPos == std::string::npos) { + throw Exception{"Invalid packet"}; + } + + this->offset = StringService::toUint32(command.substr(0, delimiterPos), 16); + this->length = StringService::toUint32(command.substr(delimiterPos + 1), 16); + } + + void ReadMemoryMap::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + using Targets::TargetMemorySegmentType; + + Logger::info("Handling ReadMemoryMap packet"); + + static const auto gdbMemoryTypesBySegmentType = BiMap{ + {TargetMemorySegmentType::FLASH, "flash"}, + {TargetMemorySegmentType::RAM, "ram"}, + {TargetMemorySegmentType::IO, "ram"}, + {TargetMemorySegmentType::ALIASED, "flash"}, // TODO: Assumption made here. Will hold for now. Review later + }; + + auto memoryMap = std::string{"\n"}; + + for (const auto& [segmentKey, segmentDescriptor] : gdbTargetDescriptor.systemAddressSpaceDescriptor.segmentDescriptorsByKey) { + const auto gdbMemType = gdbMemoryTypesBySegmentType.valueAt(segmentDescriptor.type); + if (!gdbMemType.has_value()) { + continue; + } + + const auto segmentWritable = ( + segmentDescriptor.debugModeAccess.writeable + || segmentDescriptor.programmingModeAccess.writeable + ); + + memoryMap += "" + std::to_string(*(segmentDescriptor.pageSize)) + + "\n\n"; + } else { + memoryMap += "/>\n"; + } + } + + memoryMap += ""; + + auto responseData = std::vector{'l'}; + + if (this->offset < memoryMap.size() && this->length > 0) { + responseData.insert( + responseData.end(), + memoryMap.begin() + this->offset, + memoryMap.begin() + this->offset + std::min( + static_cast(this->length), + static_cast(memoryMap.size() - this->offset) + ) + ); + } + + debugSession.connection.writePacket(ResponsePacket{responseData}); + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.hpp new file mode 100644 index 00000000..297a9e7e --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadMemoryMap.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetAddressSpaceDescriptor.hpp" +#include "src/Targets/TargetMemorySegmentDescriptor.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class ReadMemoryMap + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + /** + * The offset of the memory map, from which to read. + */ + std::uint32_t offset = 0; + + /** + * The length of the memory map to read. + */ + std::uint32_t length = 0; + + explicit ReadMemoryMap(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.cpp new file mode 100644 index 00000000..404f40e2 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.cpp @@ -0,0 +1,95 @@ +#include "ReadRegister.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" + +#include "src/Targets/TargetRegisterDescriptor.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Logger/Logger.hpp" + +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using Targets::TargetRegisterDescriptors; + + using ResponsePackets::ResponsePacket; + using ResponsePackets::ErrorResponsePacket; + + using Exceptions::Exception; + + ReadRegister::ReadRegister(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + { + using Services::StringService; + + if (this->data.size() < 2) { + throw Exception{"Invalid packet length"}; + } + + this->registerId = static_cast( + StringService::toUint32(std::string{this->data.begin() + 1, this->data.end()}, 16) + ); + } + + void ReadRegister::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling ReadRegister packet"); + + try { + Logger::debug("Reading GDB register ID: " + std::to_string(this->registerId)); + + if (this->registerId == gdbTargetDescriptor.programCounterGdbRegisterId) { + /* + * GDB has requested the program counter. We can't access this in the same way as we do with other + * registers. + */ + const auto programCounter = targetControllerService.getProgramCounter(); + + debugSession.connection.writePacket( + ResponsePacket{Services::StringService::toHex(Targets::TargetMemoryBuffer{ + static_cast(programCounter), + static_cast(programCounter >> 8), + static_cast(programCounter >> 16), + static_cast(programCounter >> 24), + })} + ); + + return; + } + + const auto gdbRegisterDescriptorIt = gdbTargetDescriptor.gdbRegisterDescriptorsById.find(this->registerId); + const auto targetRegisterDescriptorIt = gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.find( + this->registerId + ); + + if ( + gdbRegisterDescriptorIt == gdbTargetDescriptor.gdbRegisterDescriptorsById.end() + || targetRegisterDescriptorIt == gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.end() + ) { + throw Exception{"Unknown GDB register ID (" + std::to_string(this->registerId) + ")"}; + } + + auto registerValue = targetControllerService.readRegister(*(targetRegisterDescriptorIt->second)); + std::reverse(registerValue.begin(), registerValue.end()); // MSB to LSB + + const auto& gdbRegisterDescriptor = gdbRegisterDescriptorIt->second; + if (registerValue.size() < gdbRegisterDescriptor.size) { + // The register on the target is smaller than the size expected by GDB. + registerValue.insert(registerValue.end(), (gdbRegisterDescriptor.size - registerValue.size()), 0x00); + } + + debugSession.connection.writePacket(ResponsePacket{Services::StringService::toHex(registerValue)}); + + } catch (const Exception& exception) { + Logger::error("Failed to read general registers - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.hpp new file mode 100644 index 00000000..c1def4b8 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegister.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/DebugServer/Gdb/RegisterDescriptor.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class ReadRegister + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + GdbRegisterId registerId; + + explicit ReadRegister(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.cpp new file mode 100644 index 00000000..faf3d3f9 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.cpp @@ -0,0 +1,81 @@ +#include "ReadRegisters.hpp" + +#include +#include +#include + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" + +#include "src/Targets/TargetRegisterDescriptor.hpp" +#include "src/Targets/TargetMemory.hpp" + +#include "src/Logger/Logger.hpp" + +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ResponsePacket; + using ResponsePackets::ErrorResponsePacket; + + using Exceptions::Exception; + + ReadRegisters::ReadRegisters(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + {} + + void ReadRegisters::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling ReadRegisters packet"); + + try { + const auto totalRegBytes = gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.size() * 4; + auto buffer = Targets::TargetMemoryBuffer(totalRegBytes, 0x00); + + auto gpRegDescriptors = Targets::TargetRegisterDescriptors{}; + std::transform( + gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.begin(), + gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.end(), + std::back_inserter(gpRegDescriptors), + [] (const auto& pair) { + return pair.second; + } + ); + + { + const auto atomicSession = targetControllerService.makeAtomicSession(); + + for (auto& [regDesc, regVal] : targetControllerService.readRegisters(gpRegDescriptors)) { + const auto bufferOffset = ( + regDesc.startAddress - gdbTargetDescriptor.gpRegistersMemorySegmentDescriptor.addressRange.startAddress + ) * gdbTargetDescriptor.gpRegistersMemorySegmentDescriptor.addressSpaceUnitSize; + + assert((buffer.size() - bufferOffset) >= regVal.size()); + + /* + * GDB expects register values in LSB form, which is why we use reverse iterators below. + */ + std::copy(regVal.rbegin(), regVal.rend(), buffer.begin() + bufferOffset); + } + + const auto pcValue = targetControllerService.getProgramCounter(); + buffer[totalRegBytes - 4] = static_cast(pcValue); + buffer[totalRegBytes - 3] = static_cast(pcValue >> 8); + buffer[totalRegBytes - 2] = static_cast(pcValue >> 16); + buffer[totalRegBytes - 1] = static_cast(pcValue >> 24); + } + + debugSession.connection.writePacket(ResponsePacket{Services::StringService::toHex(buffer)}); + + } catch (const Exception& exception) { + Logger::error("Failed to read registers - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.hpp new file mode 100644 index 00000000..839e2146 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/ReadRegisters.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/DebugServer/Gdb/RegisterDescriptor.hpp" +#include "src/Targets/TargetMemorySegmentDescriptor.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class ReadRegisters + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + explicit ReadRegisters(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/RiscVGdbCommandPacketInterface.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/RiscVGdbCommandPacketInterface.hpp new file mode 100644 index 00000000..c68a454f --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/RiscVGdbCommandPacketInterface.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" +#include "src/DebugServer/Gdb/DebugSession.hpp" +#include "src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.hpp" +#include "src/Targets/TargetDescriptor.hpp" +#include "src/Services/TargetControllerService.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class RiscVGdbCommandPacketInterface + { + public: + virtual ~RiscVGdbCommandPacketInterface() = default; + + /** + * Should handle the command for the current active debug session. + * + * @param debugSession + * The current active debug session. + * + * @param TargetControllerService + */ + virtual void handle( + DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) = 0; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.cpp new file mode 100644 index 00000000..c202bfca --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.cpp @@ -0,0 +1,24 @@ +#include "VContSupportedActionsQuery.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp" + +#include "src/Logger/Logger.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + VContSupportedActionsQuery::VContSupportedActionsQuery(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + {} + + void VContSupportedActionsQuery::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling VContSupportedActionsQuery packet"); + debugSession.connection.writePacket(ResponsePackets::ResponsePacket{"vCont;c;C;s;S"}); + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.hpp new file mode 100644 index 00000000..6bbba447 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/VContSupportedActionsQuery.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class VContSupportedActionsQuery + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + explicit VContSupportedActionsQuery(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.cpp new file mode 100644 index 00000000..13ccd623 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.cpp @@ -0,0 +1,132 @@ +#include "WriteMemory.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" +#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Logger/Logger.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + using ResponsePackets::OkResponsePacket; + + using namespace Exceptions; + + WriteMemory::WriteMemory(const RawPacket& rawPacket, const RiscVGdbTargetDescriptor& gdbTargetDescriptor) + : WriteMemory(rawPacket, gdbTargetDescriptor, WriteMemory::extractPacketData(rawPacket)) + {} + + void WriteMemory::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling WriteMemory packet"); + + try { + if (this->buffer.empty()) { + debugSession.connection.writePacket(OkResponsePacket{}); + return; + } + + const auto& systemAddressSpaceDescriptor = gdbTargetDescriptor.systemAddressSpaceDescriptor; + const auto addressRange = Targets::TargetMemoryAddressRange{ + this->startAddress, + this->startAddress + static_cast(this->buffer.size()) - 1 + }; + + const auto memorySegmentDescriptors = systemAddressSpaceDescriptor.getIntersectingMemorySegmentDescriptors( + addressRange + ); + + auto accessibleBytes = Targets::TargetMemorySize{0}; + for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) { + if (!memorySegmentDescriptor->debugModeAccess.writeable) { + throw Exception{ + "Attempted to access restricted memory segment (" + memorySegmentDescriptor->key + + ") - segment not writeable in debug mode" + }; + } + + accessibleBytes += memorySegmentDescriptor->addressRange.intersectingSize(addressRange); + } + + if (accessibleBytes < this->bytes) { + throw Exception{"GDB requested access to memory which does not reside within any memory segment"}; + } + + { + const auto atomicSession = targetControllerService.makeAtomicSession(); + + for (const auto* memorySegmentDescriptor : memorySegmentDescriptors) { + const auto segmentStartAddress = std::max( + this->startAddress, + memorySegmentDescriptor->addressRange.startAddress + ); + + const auto bufferOffsetIt = buffer.begin() + (segmentStartAddress - this->startAddress); + targetControllerService.writeMemory( + systemAddressSpaceDescriptor, + *memorySegmentDescriptor, + segmentStartAddress, + Targets::TargetMemoryBuffer{ + bufferOffsetIt, + bufferOffsetIt + memorySegmentDescriptor->addressRange.intersectingSize(addressRange) + } + ); + } + } + + debugSession.connection.writePacket(OkResponsePacket{}); + + } catch (const Exception& exception) { + Logger::error("Failed to write memory to target - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } + + WriteMemory::PacketData WriteMemory::extractPacketData(const RawPacket& rawPacket) { + using Services::StringService; + + if (rawPacket.size() < 8) { + throw Exception{"Invalid packet length"}; + } + + const auto command = std::string{rawPacket.begin() + 2, rawPacket.end() - 3}; + + const auto commaDelimiterPos = command.find_first_of(','); + const auto colonDelimiterPos = command.find_first_of(':'); + if (commaDelimiterPos == std::string::npos || colonDelimiterPos == std::string::npos) { + throw Exception{"Invalid packet"}; + } + + return { + .gdbStartAddress = StringService::toUint32(command.substr(0, commaDelimiterPos), 16), + .bytes = StringService::toUint32( + command.substr(commaDelimiterPos + 1, colonDelimiterPos - (commaDelimiterPos + 1)), + 16 + ), + .buffer = StringService::dataFromHex(command.substr(colonDelimiterPos + 1)) + }; + } + + WriteMemory::WriteMemory( + const RawPacket& rawPacket, + const RiscVGdbTargetDescriptor&, + PacketData&& packetData + ) + : CommandPacket(rawPacket) + , startAddress(packetData.gdbStartAddress) + , bytes(packetData.bytes) + , buffer(std::move(packetData.buffer)) + { + if (this->buffer.size() != this->bytes) { + throw Exception{"Buffer size does not match length value given in write memory packet"}; + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.hpp new file mode 100644 index 00000000..8c27b549 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteMemory.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetMemory.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class WriteMemory + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + Targets::TargetMemoryAddress startAddress; + Targets::TargetMemorySize bytes; + Targets::TargetMemoryBuffer buffer; + + explicit WriteMemory(const RawPacket& rawPacket, const RiscVGdbTargetDescriptor& gdbTargetDescriptor); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + + private: + struct PacketData + { + GdbMemoryAddress gdbStartAddress; + std::uint32_t bytes; + Targets::TargetMemoryBuffer buffer; + }; + + static PacketData extractPacketData(const RawPacket& rawPacket); + WriteMemory(const RawPacket& rawPacket, const RiscVGdbTargetDescriptor& gdbTargetDescriptor, PacketData&& packetData); + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.cpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.cpp new file mode 100644 index 00000000..d434f687 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.cpp @@ -0,0 +1,94 @@ +#include "WriteRegister.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp" +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" + +#include "src/Services/StringService.hpp" +#include "src/Logger/Logger.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ResponsePacket; + using ResponsePackets::OkResponsePacket; + using ResponsePackets::ErrorResponsePacket; + + using Exceptions::Exception; + + WriteRegister::WriteRegister(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + { + using Services::StringService; + + if (this->data.size() < 4) { + throw Exception{"Invalid WriteRegister command packet - insufficient data in packet."}; + } + + // The P packet updates a single register + auto command = std::string{this->data.begin() + 1, this->data.end()}; + + const auto delimiterPos = command.find_first_of('='); + if (delimiterPos == std::string::npos) { + throw Exception{"Invalid packet"}; + } + + this->registerId = static_cast(StringService::toUint32(command.substr(0, delimiterPos), 16)); + this->registerValue = Services::StringService::dataFromHex(command.substr(delimiterPos + 1)); + + if (this->registerValue.empty()) { + throw Exception{"Invalid WriteRegister command packet - missing register value"}; + } + + // LSB to MSB + std::reverse(this->registerValue.begin(), this->registerValue.end()); + } + + void WriteRegister::handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + TargetControllerService& targetControllerService + ) { + Logger::info("Handling WriteRegister packet"); + + try { + if (this->registerId == gdbTargetDescriptor.programCounterGdbRegisterId) { + if (this->registerValue.size() != 4) { + throw Exception{"Invalid PC value register size"}; + } + + targetControllerService.setProgramCounter( + static_cast( + this->registerValue[0] << 24 + | this->registerValue[1] << 16 + | this->registerValue[2] << 8 + | this->registerValue[3] + ) + ); + + debugSession.connection.writePacket(OkResponsePacket{}); + return; + } + + const auto gdbRegisterDescriptorIt = gdbTargetDescriptor.gdbRegisterDescriptorsById.find(this->registerId); + const auto targetRegisterDescriptorIt = gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.find( + this->registerId + ); + if ( + gdbRegisterDescriptorIt == gdbTargetDescriptor.gdbRegisterDescriptorsById.end() + || targetRegisterDescriptorIt == gdbTargetDescriptor.targetRegisterDescriptorsByGdbId.end() + ) { + throw Exception{"Unknown GDB register ID (" + std::to_string(this->registerId) + ")"}; + } + + targetControllerService.writeRegister(*(targetRegisterDescriptorIt->second), this->registerValue); + debugSession.connection.writePacket(OkResponsePacket{}); + + } catch (const Exception& exception) { + Logger::error("Failed to write registers - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket{}); + } + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.hpp b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.hpp new file mode 100644 index 00000000..64860990 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/CommandPackets/WriteRegister.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "RiscVGdbCommandPacketInterface.hpp" +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/DebugServer/Gdb/RegisterDescriptor.hpp" + +#include "src/Targets/TargetMemory.hpp" + +namespace DebugServer::Gdb::RiscVGdb::CommandPackets +{ + class WriteRegister + : public RiscVGdbCommandPacketInterface + , private Gdb::CommandPackets::CommandPacket + { + public: + GdbRegisterId registerId; + Targets::TargetMemoryBuffer registerValue; + + explicit WriteRegister(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + const RiscVGdbTargetDescriptor& gdbTargetDescriptor, + const Targets::TargetDescriptor& targetDescriptor, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.cpp b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.cpp new file mode 100644 index 00000000..f8a9ad84 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.cpp @@ -0,0 +1,135 @@ +#include "RiscVGdbRsp.hpp" + +#include "src/Services/StringService.hpp" + +// Command packets +#include "CommandPackets/ReadRegister.hpp" +#include "CommandPackets/ReadRegisters.hpp" +#include "CommandPackets/WriteRegister.hpp" +#include "CommandPackets/ReadMemory.hpp" +#include "CommandPackets/WriteMemory.hpp" +#include "CommandPackets/ReadMemoryMap.hpp" +#include "CommandPackets/FlashErase.hpp" +#include "CommandPackets/FlashWrite.hpp" +#include "CommandPackets/FlashDone.hpp" +#include "CommandPackets/VContSupportedActionsQuery.hpp" + +#include "src/DebugServer/Gdb/CommandPackets/Monitor.hpp" + +namespace DebugServer::Gdb::RiscVGdb +{ + using namespace Exceptions; + + using Targets::TargetRegisterDescriptor; + using Targets::TargetRegisterType; + + RiscVGdbRsp::RiscVGdbRsp( + const DebugServerConfig& debugServerConfig, + const Targets::TargetDescriptor& targetDescriptor, + EventListener& eventListener, + EventFdNotifier& eventNotifier + ) + : GdbRspDebugServer( + debugServerConfig, + targetDescriptor, + RiscVGdbTargetDescriptor{targetDescriptor}, + eventListener, + eventNotifier + ) + {} + + std::string RiscVGdbRsp::getName() const { + return "RISC-V GDB RSP Server"; + } + + std::unique_ptr RiscVGdbRsp::rawPacketToCommandPacket( + const RawPacket& rawPacket + ) { + using Gdb::CommandPackets::Monitor; + + using CommandPackets::ReadRegister; + using CommandPackets::ReadRegisters; + using CommandPackets::WriteRegister; + using CommandPackets::ReadMemory; + using CommandPackets::WriteMemory; + using CommandPackets::ReadMemoryMap; + using CommandPackets::FlashErase; + using CommandPackets::FlashWrite; + using CommandPackets::FlashDone; + using CommandPackets::VContSupportedActionsQuery; + + if (rawPacket.size() < 2) { + throw ::Exceptions::Exception{"Invalid raw packet - no data"}; + } + + if (rawPacket[1] == 'p') { + return std::make_unique(rawPacket); + } + + if (rawPacket[1] == 'g') { + return std::make_unique(rawPacket); + } + + if (rawPacket[1] == 'P') { + return std::make_unique(rawPacket); + } + + if (rawPacket[1] == 'm') { + return std::make_unique(rawPacket, this->gdbTargetDescriptor); + } + + if (rawPacket[1] == 'M') { + return std::make_unique(rawPacket, this->gdbTargetDescriptor); + } + + if (rawPacket.size() > 1) { + const auto rawPacketString = std::string{rawPacket.begin() + 1, rawPacket.end()}; + + if (rawPacketString.find("qXfer:memory-map:read::") == 0) { + return std::make_unique(rawPacket); + } + + if (rawPacketString.find("vFlashErase") == 0) { + return std::make_unique(rawPacket); + } + + if (rawPacketString.find("vFlashWrite") == 0) { + return std::make_unique(rawPacket); + } + + if (rawPacketString.find("vFlashDone") == 0) { + return std::make_unique(rawPacket); + } + + if (rawPacketString.find("vCont?") == 0) { + return std::make_unique(rawPacket); + } + } + + return nullptr; + } + + std::set>> RiscVGdbRsp::getSupportedFeatures() { + auto output = std::set>>{ + {Feature::HARDWARE_BREAKPOINTS, std::nullopt}, + {Feature::SOFTWARE_BREAKPOINTS, std::nullopt}, + {Feature::MEMORY_MAP_READ, std::nullopt}, + {Feature::VCONT_ACTIONS_QUERY, std::nullopt}, + }; + + if (!this->debugServerConfig.packetAcknowledgement) { + output.emplace(Feature::NO_ACK_MODE, std::nullopt); + } + + return output; + } + + void RiscVGdbRsp::handleTargetStoppedGdbResponse(Targets::TargetMemoryAddress programAddress) { + using Services::StringService; + + Logger::debug("Target stopped at byte address: 0x" + StringService::toHex(programAddress)); + + // Report the stop to GDB + return GdbRspDebugServer::handleTargetStoppedGdbResponse(programAddress); + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.hpp b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.hpp new file mode 100644 index 00000000..717e8701 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbRsp.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "src/DebugServer/Gdb/GdbRspDebugServer.hpp" +#include "RiscVGdbTargetDescriptor.hpp" +#include "CommandPackets/RiscVGdbCommandPacketInterface.hpp" + +namespace DebugServer::Gdb::RiscVGdb +{ + class RiscVGdbRsp + : public GdbRspDebugServer< + RiscVGdbTargetDescriptor, + DebugSession, + CommandPackets::RiscVGdbCommandPacketInterface + > + { + public: + RiscVGdbRsp( + const DebugServerConfig& debugServerConfig, + const Targets::TargetDescriptor& targetDescriptor, + EventListener& eventListener, + EventFdNotifier& eventNotifier + ); + + std::string getName() const override; + + protected: + std::unique_ptr rawPacketToCommandPacket( + const RawPacket& rawPacket + ) override; + std::set>> getSupportedFeatures() override; + void handleTargetStoppedGdbResponse(Targets::TargetMemoryAddress programAddress) override; + }; +} diff --git a/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp new file mode 100644 index 00000000..ae42cd38 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.cpp @@ -0,0 +1,40 @@ +#include "RiscVGdbTargetDescriptor.hpp" + +#include "src/Exceptions/Exception.hpp" + +namespace DebugServer::Gdb::RiscVGdb +{ + using Targets::TargetRegisterDescriptor; + using Targets::TargetRegisterType; + + using Exceptions::Exception; + + RiscVGdbTargetDescriptor::RiscVGdbTargetDescriptor(const Targets::TargetDescriptor& targetDescriptor) + : systemAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("system")) + , cpuAddressSpaceDescriptor(targetDescriptor.getAddressSpaceDescriptor("debug_module")) + , programMemorySegmentDescriptor(this->systemAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory")) + , gpRegistersMemorySegmentDescriptor(this->cpuAddressSpaceDescriptor.getMemorySegmentDescriptor("gp_registers")) + , cpuGpPeripheralDescriptor(targetDescriptor.getPeripheralDescriptor("cpu")) + , cpuGpRegisterGroupDescriptor(this->cpuGpPeripheralDescriptor.getRegisterGroupDescriptor("gpr")) + , programCounterGdbRegisterId(static_cast(this->cpuGpRegisterGroupDescriptor.registerDescriptorsByKey.size())) + { + // Create the GDB register descriptors and populate the mappings for the general purpose registers (ID 0->31) + for (const auto& [key, descriptor] : this->cpuGpRegisterGroupDescriptor.registerDescriptorsByKey) { + if (descriptor.type != TargetRegisterType::GENERAL_PURPOSE_REGISTER) { + continue; + } + + const auto gdbRegisterId = static_cast( + descriptor.startAddress - this->gpRegistersMemorySegmentDescriptor.addressRange.startAddress + ); + + this->gdbRegisterDescriptorsById.emplace(gdbRegisterId, RegisterDescriptor{gdbRegisterId, 4}); + this->targetRegisterDescriptorsByGdbId.emplace(gdbRegisterId, &descriptor); + } + + this->gdbRegisterDescriptorsById.emplace( + this->programCounterGdbRegisterId, + RegisterDescriptor{this->programCounterGdbRegisterId, 4} + ); + } +} diff --git a/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.hpp b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.hpp new file mode 100644 index 00000000..90c41756 --- /dev/null +++ b/src/DebugServer/Gdb/RiscVGdb/RiscVGdbTargetDescriptor.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "src/DebugServer/Gdb/TargetDescriptor.hpp" + +#include "src/Targets/TargetDescriptor.hpp" +#include "src/Targets/TargetAddressSpaceDescriptor.hpp" +#include "src/Targets/TargetMemorySegmentDescriptor.hpp" +#include "src/Targets/TargetPeripheralDescriptor.hpp" +#include "src/Targets/TargetRegisterGroupDescriptor.hpp" + +namespace DebugServer::Gdb::RiscVGdb +{ + class RiscVGdbTargetDescriptor: public DebugServer::Gdb::TargetDescriptor + { + public: + const Targets::TargetAddressSpaceDescriptor& systemAddressSpaceDescriptor; + const Targets::TargetAddressSpaceDescriptor& cpuAddressSpaceDescriptor; + + const Targets::TargetMemorySegmentDescriptor& programMemorySegmentDescriptor; + const Targets::TargetMemorySegmentDescriptor& gpRegistersMemorySegmentDescriptor; + + const Targets::TargetPeripheralDescriptor& cpuGpPeripheralDescriptor; + const Targets::TargetRegisterGroupDescriptor& cpuGpRegisterGroupDescriptor; + + const GdbRegisterId programCounterGdbRegisterId; + + explicit RiscVGdbTargetDescriptor(const Targets::TargetDescriptor& targetDescriptor); + }; +} diff --git a/src/Targets/RiscV/Wch/WchRiscV.cpp b/src/Targets/RiscV/Wch/WchRiscV.cpp index 75c7fadf..f16ad7eb 100644 --- a/src/Targets/RiscV/Wch/WchRiscV.cpp +++ b/src/Targets/RiscV/Wch/WchRiscV.cpp @@ -85,6 +85,20 @@ namespace Targets::RiscV::Wch sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_program_memory").inspectionEnabled = true; sysAddressSpaceDescriptor.getMemorySegmentDescriptor("internal_ram").inspectionEnabled = true; + /* + * WCH targets typically possess a memory segment that is mapped to program memory. We cannot write to this + * segment directly, which is why it's described as read-only in Bloom's TDFs. However, we enable writing to + * the segment by forwarding any write operations to the appropriate (aliased) segment. + * + * For this reason, we adjust the access member on the memory segment descriptor so that other components + * within Bloom will see the segment as writeable. + * + * See the overridden WchRiscV::writeMemory() member function below, for more. + */ + sysAddressSpaceDescriptor.getMemorySegmentDescriptor( + this->mappedProgramMemorySegmentDescriptor.key + ).programmingModeAccess.writeable = true; + return descriptor; } @@ -95,12 +109,16 @@ namespace Targets::RiscV::Wch const TargetMemoryBuffer& buffer ) { /* - * WCH targets have a memory segment that maps to either the program memory segment or the boot program + * WCH targets have an alias segment that maps to either the program memory segment or the boot program * memory segment. * - * Reading directly from the mapped memory segment is fine, but we cannot write to it - the operation just - * fails silently. We handle this by altering the write operation so that we write to the appropriate, - * non-mapped segment. + * Reading directly from this memory segment is fine, but we cannot write to it - the operation just fails + * silently. We handle this by forwarding any write operations on that segment to the appropriate (aliased) + * segment. + * + * @TODO: Currently, this just assumes that the alias segment always maps to the program memory segment, but I + * believe it may map to the boot program memory segment in some cases. This needs to be revisited + * before v1.1.0. */ if (memorySegmentDescriptor == this->mappedProgramMemorySegmentDescriptor) { const auto newAddress = startAddress - this->mappedProgramMemorySegmentDescriptor.addressRange.startAddress