diff --git a/src/DebugServer/CMakeLists.txt b/src/DebugServer/CMakeLists.txt index 64968076..0063078a 100755 --- a/src/DebugServer/CMakeLists.txt +++ b/src/DebugServer/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources( # AVR GDB RSP Server ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/AvrGdbRsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/TargetDescriptor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/DebugSession.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/ReadRegisters.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/ReadRegister.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/WriteRegister.cpp @@ -38,6 +39,10 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/FlashErase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/FlashWrite.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/FlashDone.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/VContContinueExecution.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/VContStepExecution.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Gdb/AvrGdb/CommandPackets/VContRangeStep.cpp ) if (NOT EXCLUDE_INSIGHT) diff --git a/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.cpp b/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.cpp index b3feabb4..64df2aee 100644 --- a/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.cpp +++ b/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.cpp @@ -1,5 +1,7 @@ #include "AvrGdbRsp.hpp" +#include "src/Services/StringService.hpp" + // Command packets #include "CommandPackets/ReadRegister.hpp" #include "CommandPackets/ReadRegisters.hpp" @@ -10,6 +12,10 @@ #include "CommandPackets/FlashErase.hpp" #include "CommandPackets/FlashWrite.hpp" #include "CommandPackets/FlashDone.hpp" +#include "CommandPackets/VContSupportedActionsQuery.hpp" +#include "CommandPackets/VContContinueExecution.hpp" +#include "CommandPackets/VContStepExecution.hpp" +#include "CommandPackets/VContRangeStep.hpp" namespace DebugServer::Gdb::AvrGdb { @@ -24,14 +30,30 @@ namespace DebugServer::Gdb::AvrGdb EventFdNotifier& eventNotifier ) : GdbRspDebugServer(debugServerConfig, eventListener, eventNotifier) + , gdbTargetDescriptor(TargetDescriptor(this->targetControllerService.getTargetDescriptor())) {} - void AvrGdbRsp::init() { - DebugServer::Gdb::GdbRspDebugServer::init(); - - this->gdbTargetDescriptor = TargetDescriptor( - this->targetControllerService.getTargetDescriptor() + DebugSession* AvrGdbRsp::startDebugSession(Connection&& connection) { + this->activeDebugSession.emplace( + std::move(connection), + this->getSupportedFeatures(), + this->gdbTargetDescriptor, + this->debugServerConfig ); + + return &*(this->activeDebugSession); + } + + void AvrGdbRsp::endDebugSession() { + this->activeDebugSession.reset(); + } + + const Gdb::TargetDescriptor& AvrGdbRsp::getGdbTargetDescriptor() { + return this->gdbTargetDescriptor; + } + + DebugSession* AvrGdbRsp::getActiveDebugSession() { + return this->activeDebugSession.has_value() ? &*(this->activeDebugSession) : nullptr; } std::unique_ptr AvrGdbRsp::resolveCommandPacket( @@ -46,6 +68,10 @@ namespace DebugServer::Gdb::AvrGdb using AvrGdb::CommandPackets::FlashErase; using AvrGdb::CommandPackets::FlashWrite; using AvrGdb::CommandPackets::FlashDone; + using AvrGdb::CommandPackets::VContSupportedActionsQuery; + using AvrGdb::CommandPackets::VContContinueExecution; + using AvrGdb::CommandPackets::VContStepExecution; + using AvrGdb::CommandPackets::VContRangeStep; if (rawPacket.size() >= 2) { if (rawPacket[1] == 'p') { @@ -61,11 +87,11 @@ namespace DebugServer::Gdb::AvrGdb } if (rawPacket[1] == 'm') { - return std::make_unique(rawPacket, this->gdbTargetDescriptor.value()); + return std::make_unique(rawPacket, this->gdbTargetDescriptor); } if (rawPacket[1] == 'M') { - return std::make_unique(rawPacket, this->gdbTargetDescriptor.value()); + return std::make_unique(rawPacket, this->gdbTargetDescriptor); } const auto rawPacketString = std::string(rawPacket.begin() + 1, rawPacket.end()); @@ -85,19 +111,109 @@ namespace DebugServer::Gdb::AvrGdb if (rawPacketString.find("vFlashDone") == 0) { return std::make_unique(rawPacket); } + + if (rawPacketString.find("vCont?") == 0) { + return std::make_unique(rawPacket); + } + + if (rawPacketString.find("vCont;c") == 0 || rawPacketString.find("vCont;C") == 0) { + return std::make_unique(rawPacket); + } + + if (rawPacketString.find("vCont;s") == 0 || rawPacketString.find("vCont;S") == 0) { + return std::make_unique(rawPacket); + } + + if (this->debugServerConfig.rangeSteppingEnabled) { + if (rawPacketString.find("vCont;r") == 0) { + return std::make_unique(rawPacket); + } + } } return GdbRspDebugServer::resolveCommandPacket(rawPacket); } std::set>> AvrGdbRsp::getSupportedFeatures() { - auto supportedFeatures = GdbRspDebugServer::getSupportedFeatures(); + return { + {Feature::SOFTWARE_BREAKPOINTS, std::nullopt}, + {Feature::MEMORY_MAP_READ, std::nullopt}, + {Feature::VCONT_ACTIONS_QUERY, std::nullopt}, + }; + } - // The AVR GDB server supports the 'qXfer:memory-map:read' GDB command. - supportedFeatures.insert({ - Feature::MEMORY_MAP_READ, std::nullopt - }); + void AvrGdbRsp::handleTargetStoppedGdbResponse(Targets::TargetProgramCounter programAddress) { + using Services::StringService; - return supportedFeatures; + Logger::debug("Target stopped at byte address: 0x" + StringService::toHex(programAddress)); + + auto& activeRangeSteppingSession = this->activeDebugSession->activeRangeSteppingSession; + + if ( + activeRangeSteppingSession.has_value() + && programAddress >= activeRangeSteppingSession->range.startAddress + && programAddress < activeRangeSteppingSession->range.endAddress + ) { + /* + * The target stopped within the stepping range of an active range stepping session. + * + * We need to figure out why, and determine whether the stop should be reported to GDB. + */ + if (this->activeDebugSession->externalBreakpointAddresses.contains(programAddress)) { + /* + * The target stopped due to an external breakpoint, set by GDB. + * + * We have to end the range stepping session and report this to GDB. + */ + Logger::debug("Reached external breakpoint within stepping range"); + + this->activeDebugSession->terminateRangeSteppingSession(this->targetControllerService); + return GdbRspDebugServer::handleTargetStoppedGdbResponse(programAddress); + } + + if (activeRangeSteppingSession->interceptedAddresses.contains(programAddress)) { + /* + * The target stopped due to an intercepting breakpoint, but we're still within the stepping range, + * which can only mean that we weren't sure where this instruction would lead to. + * + * We must perform a single step and see what happens. + */ + Logger::debug("Reached intercepting breakpoint within stepping range"); + Logger::debug("Attempting single step from 0x" + StringService::toHex(programAddress)); + + activeRangeSteppingSession->singleStepping = true; + this->targetControllerService.stepTargetExecution(std::nullopt); + return; + } + + if (activeRangeSteppingSession->singleStepping) { + /* + * We performed a single step once and we're still within the stepping range, so we're good to + * continue the range stepping session. + */ + Logger::debug("Completed single step from an intercepted address - PC still within stepping range"); + Logger::debug("Continuing range stepping"); + + activeRangeSteppingSession->singleStepping = false; + this->targetControllerService.continueTargetExecution(std::nullopt, std::nullopt); + return; + } + + /* + * If we get here, the target stopped for an unknown reason that doesn't seem to have anything to do with + * the active range stepping session. + * + * This could be due to a permanent breakpoint in the target's program code. + * + * We have to end the range stepping session and report the stop to GDB. + */ + Logger::debug("Target stopped within stepping range, but for an unknown reason"); + + this->activeDebugSession->terminateRangeSteppingSession(this->targetControllerService); + return GdbRspDebugServer::handleTargetStoppedGdbResponse(programAddress); + } + + // Report the stop to GDB + return GdbRspDebugServer::handleTargetStoppedGdbResponse(programAddress); } } diff --git a/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.hpp b/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.hpp index b294b91d..77e6e61b 100644 --- a/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.hpp +++ b/src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.hpp @@ -3,6 +3,7 @@ #include #include "TargetDescriptor.hpp" +#include "DebugSession.hpp" #include "src/DebugServer/Gdb/GdbRspDebugServer.hpp" @@ -22,19 +23,37 @@ namespace DebugServer::Gdb::AvrGdb } protected: - void init() override; + DebugSession* startDebugSession(Connection&& connection) override; - const Gdb::TargetDescriptor& getGdbTargetDescriptor() override { - return this->gdbTargetDescriptor.value(); - } + void endDebugSession() override; + + const Gdb::TargetDescriptor& getGdbTargetDescriptor() override; + + DebugSession* getActiveDebugSession() override; std::unique_ptr resolveCommandPacket( const RawPacket& rawPacket ) override; - std::set>> getSupportedFeatures() override; + /** + * Should return a set of GDB features supported by the AVR GDB server. Each supported feature may come with an + * optional value. + * + * The set of features returned by this function will be stored against the active debug session object. + * + * @return + */ + std::set>> getSupportedFeatures(); + + void handleTargetStoppedGdbResponse(Targets::TargetProgramCounter programAddress) override; private: - std::optional gdbTargetDescriptor; + TargetDescriptor gdbTargetDescriptor; + + /** + * When a connection with a GDB client is established, a new instance of the DebugSession class is created and + * held here. A value of std::nullopt means there is no active debug session present. + */ + std::optional activeDebugSession; }; } diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.cpp index 3538d6d3..8299a45f 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.cpp @@ -19,7 +19,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets : CommandPacket(rawPacket) {} - void FlashDone::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void FlashDone::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling FlashDone packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.hpp index 498a0f6f..8378dc28 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashDone.hpp @@ -19,7 +19,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit FlashDone(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.cpp index 32b32183..70c1167d 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.cpp @@ -48,7 +48,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets } } - void FlashErase::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void FlashErase::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling FlashErase packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.hpp index 004309f7..c263352a 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashErase.hpp @@ -23,7 +23,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit FlashErase(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.cpp index b1cb28bb..5eac7967 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.cpp @@ -48,7 +48,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets this->buffer = Targets::TargetMemoryBuffer(colonIt + 1, this->data.end()); } - void FlashWrite::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void FlashWrite::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling FlashWrite packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.hpp index 2aa2e955..4d4f759a 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/FlashWrite.hpp @@ -23,7 +23,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit FlashWrite(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.cpp index acc7bf6f..098e70e3 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.cpp @@ -62,7 +62,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets } } - void ReadMemory::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void ReadMemory::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling ReadMemory packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.hpp index 998c12e1..4b71bf3b 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.hpp @@ -35,7 +35,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit ReadMemory(const RawPacket& rawPacket, const Gdb::TargetDescriptor& gdbTargetDescriptor); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.cpp index f64f0c5d..2b957e35 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.cpp @@ -50,7 +50,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets } } - void ReadMemoryMap::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void ReadMemoryMap::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling ReadMemoryMap packet"); using Targets::TargetMemoryType; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.hpp index ecfb87a7..09ce2d28 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemoryMap.hpp @@ -26,7 +26,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit ReadMemoryMap(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.cpp index 804ec259..fdd16616 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.cpp @@ -34,7 +34,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets ); } - void ReadRegister::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void ReadRegister::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling ReadRegister packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.hpp index c35658a4..6eb43194 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegister.hpp @@ -18,7 +18,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit ReadRegister(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.cpp index fd432edf..f17dde27 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.cpp @@ -27,7 +27,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets : CommandPacket(rawPacket) {} - void ReadRegisters::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void ReadRegisters::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling ReadRegisters packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.hpp index c85e22eb..788b7ccf 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadRegisters.hpp @@ -18,7 +18,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit ReadRegisters(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContContinueExecution.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContContinueExecution.cpp new file mode 100644 index 00000000..ecbe933c --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContContinueExecution.cpp @@ -0,0 +1,29 @@ +#include "VContContinueExecution.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + + using ::Exceptions::Exception; + + VContContinueExecution::VContContinueExecution(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + {} + + void VContContinueExecution::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { + Logger::info("Handling VContContinueExecution packet"); + + try { + targetControllerService.continueTargetExecution(std::nullopt, std::nullopt); + debugSession.waitingForBreak = true; + + } catch (const Exception& exception) { + Logger::error("Failed to continue execution on target - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket()); + } + } +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContContinueExecution.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContContinueExecution.hpp new file mode 100644 index 00000000..cff79062 --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContContinueExecution.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + /** + * The VContContinueExecution class implements a structure for "vCont;c" and "vCont;C" packets. These packets + * instruct the server to continue execution on the target. + */ + class VContContinueExecution: public Gdb::CommandPackets::CommandPacket + { + public: + explicit VContContinueExecution(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContRangeStep.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContRangeStep.cpp new file mode 100644 index 00000000..b1ab4f72 --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContRangeStep.cpp @@ -0,0 +1,216 @@ +#include "VContRangeStep.hpp" + +#include + +#include "src/Services/Avr8InstructionService.hpp" +#include "src/Services/StringService.hpp" +#include "src/Services/PathService.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" +#include "src/Exceptions/Exception.hpp" +#include "src/Logger/Logger.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + + using ::Exceptions::Exception; + + VContRangeStep::VContRangeStep(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + { + if (this->data.size() < 10) { + throw Exception("Unexpected VContRangeStep packet size"); + } + + /* + * A single vCont packet can consist of multiple actions - we don't accommodate this ATM. We only focus on + * the first action in the packet. + * + * We also ignore the thread id (as it's not necessary here). + */ + + const auto commandData = std::string(this->data.begin() + 7, this->data.end()); + + const auto delimiterPosition = commandData.find(','); + const auto threadIdDelimiterPosition = commandData.find(':'); + + if (delimiterPosition == std::string::npos || delimiterPosition >= (commandData.size() - 1)) { + throw Exception("Invalid VContRangeStep packet"); + } + + const auto& delimiterIt = commandData.begin() + static_cast( + delimiterPosition + ); + const auto startAddressHex = std::string(commandData.begin(), delimiterIt); + const auto endAddressHex = std::string( + delimiterIt + 1, + threadIdDelimiterPosition != std::string::npos + ? commandData.begin() + static_cast(threadIdDelimiterPosition) + : commandData.end() + ); + + this->startAddress = static_cast(std::stoi(startAddressHex, nullptr, 16)); + this->endAddress = static_cast(std::stoi(endAddressHex, nullptr, 16)); + } + + void VContRangeStep::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { + using Services::Avr8InstructionService; + using Services::StringService; + + Logger::info("Handling VContRangeStep packet"); + + Logger::debug("Requested stepping range start address: 0x" + StringService::toHex(this->startAddress)); + Logger::debug("Requested stepping range end address (exclusive): 0x" + StringService::toHex(this->endAddress)); + + try { + const auto& targetDescriptor = debugSession.gdbTargetDescriptor.targetDescriptor; + const auto& programMemoryAddressRange = targetDescriptor.memoryDescriptorsByType.at( + targetDescriptor.programMemoryType + ).addressRange; + + if ( + this->startAddress >= this->endAddress + || (this->startAddress % 2) != 0 + || (this->endAddress % 2) != 0 + || this->startAddress < programMemoryAddressRange.startAddress + || this->endAddress > programMemoryAddressRange.endAddress + ) { + throw Exception("Invalid address range in VContRangeStep"); + } + + if (debugSession.activeRangeSteppingSession.has_value()) { + Logger::warning( + "Attempted to start new range stepping session with one already active - terminating active session" + ); + debugSession.terminateRangeSteppingSession(targetControllerService); + } + + if ((this->endAddress - this->startAddress) == 2) { + // Single step requested. No need for a range step here. + targetControllerService.stepTargetExecution(std::nullopt); + debugSession.waitingForBreak = true; + return; + } + + const auto addressRange = Targets::TargetMemoryAddressRange(this->startAddress, this->endAddress); + auto rangeSteppingSession = RangeSteppingSession(addressRange, {}); + + const auto instructionsByAddress = Avr8InstructionService::fetchInstructions( + addressRange, + targetDescriptor, + targetControllerService + ); + + Logger::debug( + "Inspecting " + std::to_string(instructionsByAddress.size()) + " instructions within stepping range " + "(byte addresses) 0x" + StringService::toHex(addressRange.startAddress) + " -> 0x" + + StringService::toHex(addressRange.endAddress) + ", in preparation for new range stepping session" + ); + + for (const auto& [instructionAddress, instruction] : instructionsByAddress) { + if (!instruction.has_value()) { + /* + * We weren't able to decode the opcode at this address. We have no idea what this instruction + * will do. + */ + Logger::error( + "Failed to decode AVR8 opcode at byte address 0x" + StringService::toHex(instructionAddress) + + " - the instruction will have to be intercepted. Please enable debug logging, reproduce " + "this message and report as an issue via " + Services::PathService::homeDomainName() + + "/report-issue" + ); + + /* + * We have no choice but to intercept it. When we reach it, we'll perform a single step and see + * what happens. + */ + rangeSteppingSession.interceptedAddresses.insert(instructionAddress); + continue; + } + + if (instruction->canChangeProgramFlow) { + const auto destinationAddress = Avr8InstructionService::resolveProgramDestinationAddress( + *instruction, + instructionAddress, + instructionsByAddress + ); + + if (!destinationAddress.has_value()) { + /* + * We don't know where this instruction may jump to, so we'll have to intercept it and perform + * a single step when we reach it. + */ + Logger::debug( + "Intercepting CCPF instruction (\"" + instruction->name + "\") at byte address 0x" + + StringService::toHex(instructionAddress) + ); + rangeSteppingSession.interceptedAddresses.insert(instructionAddress); + continue; + } + + if ( + *destinationAddress < programMemoryAddressRange.startAddress + || *destinationAddress > programMemoryAddressRange.endAddress + ) { + /* + * This instruction may jump to an invalid address. Someone screwed up here - could be + * something wrong in Bloom (opcode decoding bug, incorrect program memory address range in + * the target descriptor, etc.), or the user has an invalid instruction in their program code. + * + * We have no choice but to intercept the instruction. When we reach it, we'll perform a single + * step and see what happens. + */ + Logger::debug( + "Intercepting CCPF instruction (\"" + instruction->name + "\") with invalid destination " + "byte address (0x" + StringService::toHex(*destinationAddress) + "), at byte address 0x" + + StringService::toHex(instructionAddress) + ); + rangeSteppingSession.interceptedAddresses.insert(instructionAddress); + continue; + } + + if ( + *destinationAddress < addressRange.startAddress + || *destinationAddress >= addressRange.endAddress + ) { + /* + * This instruction may jump to an address outside the requested stepping range. + * + * Because we know exactly where it will jump to (if it jumps), we only need to intercept the + * destination address. + */ + Logger::debug( + "Intercepting CCPF instruction (\"" + instruction->name + "\") at destination byte address " + "0x" + StringService::toHex(*destinationAddress) + ); + rangeSteppingSession.interceptedAddresses.insert(*destinationAddress); + } + } + + const auto subsequentInstructionAddress = instructionAddress + instruction->byteSize; + if (subsequentInstructionAddress >= addressRange.endAddress) { + /* + * Once this instruction has been executed, we'll end up outside the stepping range (so we'll want + * to stop there and report back to GDB). + */ + Logger::debug( + "Intercepting subsequent instruction at byte address 0x" + + StringService::toHex(subsequentInstructionAddress) + ); + rangeSteppingSession.interceptedAddresses.insert(subsequentInstructionAddress); + } + } + + debugSession.startRangeSteppingSession(std::move(rangeSteppingSession), targetControllerService); + targetControllerService.continueTargetExecution(std::nullopt, std::nullopt); + debugSession.waitingForBreak = true; + + } catch (const Exception& exception) { + Logger::error("Failed to start new range stepping session - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket()); + } + } +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContRangeStep.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContRangeStep.hpp new file mode 100644 index 00000000..66cc3361 --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContRangeStep.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +#include "src/Targets/TargetMemory.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + /** + * The VContRangeStep class implements a structure for the "vCont;r" packet. This packet instructs the server to + * step through a particular address range, and only report back to GDB when execution leaves that range, or when an + * external breakpoint has been reached. + */ + class VContRangeStep: public Gdb::CommandPackets::CommandPacket + { + public: + Targets::TargetProgramCounter startAddress; + Targets::TargetProgramCounter endAddress; + + explicit VContRangeStep(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContStepExecution.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContStepExecution.cpp new file mode 100644 index 00000000..798606dc --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContStepExecution.cpp @@ -0,0 +1,29 @@ +#include "VContStepExecution.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + using Services::TargetControllerService; + + using ResponsePackets::ErrorResponsePacket; + + using ::Exceptions::Exception; + + VContStepExecution::VContStepExecution(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + {} + + void VContStepExecution::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { + Logger::info("Handling VContStepExecution packet"); + + try { + targetControllerService.stepTargetExecution(std::nullopt); + debugSession.waitingForBreak = true; + + } catch (const Exception& exception) { + Logger::error("Failed to step execution on target - " + exception.getMessage()); + debugSession.connection.writePacket(ErrorResponsePacket()); + } + } +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContStepExecution.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContStepExecution.hpp new file mode 100644 index 00000000..7c341c3e --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContStepExecution.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + /** + * The VContStepExecution class implements a structure for "vCont;s" and "vCont;S" packets. + */ + class VContStepExecution: public Gdb::CommandPackets::CommandPacket + { + public: + explicit VContStepExecution(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.cpp new file mode 100644 index 00000000..cf267c8e --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.cpp @@ -0,0 +1,26 @@ +#include "VContSupportedActionsQuery.hpp" + +#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp" + +#include "src/Logger/Logger.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + using Services::TargetControllerService; + + VContSupportedActionsQuery::VContSupportedActionsQuery(const RawPacket& rawPacket) + : CommandPacket(rawPacket) + {} + + void VContSupportedActionsQuery::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { + Logger::info("Handling VContSupportedActionsQuery packet"); + + // Respond with a SupportedFeaturesResponse packet, listing all supported GDB features by Bloom + debugSession.connection.writePacket(ResponsePackets::ResponsePacket( + debugSession.serverConfig.rangeSteppingEnabled + ? "vCont;c;C;s;S;r" + : "vCont;c;C;s;S" + ) + ); + } +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.hpp new file mode 100644 index 00000000..c2575276 --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/VContSupportedActionsQuery.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp" + +namespace DebugServer::Gdb::AvrGdb::CommandPackets +{ + /** + * The VContSupportedActionsQuery command packet is a query from the GDB client, requesting a list of vCont actions + * supported by the server. + * + * Responses to this command packet should take the form of a ResponsePackets::SupportedFeaturesResponse. + */ + class VContSupportedActionsQuery: public Gdb::CommandPackets::CommandPacket + { + public: + explicit VContSupportedActionsQuery(const RawPacket& rawPacket); + + void handle( + Gdb::DebugSession& debugSession, + Services::TargetControllerService& targetControllerService + ) override; + }; +} diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.cpp index 0f64b37c..f7fd5e1e 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.cpp @@ -68,7 +68,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets } } - void WriteMemory::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void WriteMemory::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling WriteMemory packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.hpp index 7d50c5a0..c4542697 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.hpp @@ -35,7 +35,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit WriteMemory(const RawPacket& rawPacket, const Gdb::TargetDescriptor& gdbTargetDescriptor); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.cpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.cpp index aad4865f..54bfdef7 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.cpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.cpp @@ -46,7 +46,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets std::reverse(this->registerValue.begin(), this->registerValue.end()); } - void WriteRegister::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) { + void WriteRegister::handle(Gdb::DebugSession& debugSession, TargetControllerService& targetControllerService) { Logger::info("Handling WriteRegister packet"); try { diff --git a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.hpp b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.hpp index bb664f1e..9ef7a489 100644 --- a/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.hpp +++ b/src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteRegister.hpp @@ -21,7 +21,7 @@ namespace DebugServer::Gdb::AvrGdb::CommandPackets explicit WriteRegister(const RawPacket& rawPacket); void handle( - DebugSession& debugSession, + Gdb::DebugSession& debugSession, Services::TargetControllerService& targetControllerService ) override; }; diff --git a/src/DebugServer/Gdb/AvrGdb/DebugSession.cpp b/src/DebugServer/Gdb/AvrGdb/DebugSession.cpp new file mode 100644 index 00000000..8f12f26e --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/DebugSession.cpp @@ -0,0 +1,18 @@ +#include "DebugSession.hpp" + +namespace DebugServer::Gdb::AvrGdb +{ + DebugSession::DebugSession( + Connection&& connection, + const std::set>>& supportedFeatures, + const TargetDescriptor& targetDescriptor, + const GdbDebugServerConfig& serverConfig + ) + : Gdb::DebugSession( + std::move(connection), + supportedFeatures, + targetDescriptor, + serverConfig + ) + {} +} diff --git a/src/DebugServer/Gdb/AvrGdb/DebugSession.hpp b/src/DebugServer/Gdb/AvrGdb/DebugSession.hpp new file mode 100644 index 00000000..26876799 --- /dev/null +++ b/src/DebugServer/Gdb/AvrGdb/DebugSession.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "src/DebugServer/Gdb/DebugSession.hpp" + +#include "TargetDescriptor.hpp" + +namespace DebugServer::Gdb::AvrGdb +{ + class DebugSession final: public Gdb::DebugSession + { + public: + DebugSession( + Connection&& connection, + const std::set>>& supportedFeatures, + const TargetDescriptor& targetDescriptor, + const GdbDebugServerConfig& serverConfig + ); + }; +} diff --git a/src/DebugServer/Gdb/CommandPackets/InterruptExecution.cpp b/src/DebugServer/Gdb/CommandPackets/InterruptExecution.cpp index edc55954..2791ca76 100644 --- a/src/DebugServer/Gdb/CommandPackets/InterruptExecution.cpp +++ b/src/DebugServer/Gdb/CommandPackets/InterruptExecution.cpp @@ -26,8 +26,13 @@ namespace DebugServer::Gdb::CommandPackets try { targetControllerService.stopTargetExecution(); - debugSession.connection.writePacket(TargetStopped(Signal::INTERRUPTED)); + + if (debugSession.activeRangeSteppingSession.has_value()) { + debugSession.terminateRangeSteppingSession(targetControllerService); + } + debugSession.waitingForBreak = false; + debugSession.connection.writePacket(TargetStopped(Signal::INTERRUPTED)); } catch (const Exception& exception) { Logger::error("Failed to interrupt execution - " + exception.getMessage()); diff --git a/src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.cpp b/src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.cpp index 532b9b12..79555199 100644 --- a/src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.cpp +++ b/src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.cpp @@ -56,7 +56,7 @@ namespace DebugServer::Gdb::CommandPackets try { Logger::debug("Removing breakpoint at address " + std::to_string(this->address)); - targetControllerService.removeBreakpoint(TargetBreakpoint(this->address)); + debugSession.removeExternalBreakpoint(TargetBreakpoint(this->address), targetControllerService); debugSession.connection.writePacket(OkResponsePacket()); } catch (const Exception& exception) { diff --git a/src/DebugServer/Gdb/CommandPackets/SetBreakpoint.cpp b/src/DebugServer/Gdb/CommandPackets/SetBreakpoint.cpp index 5c3aef4a..285f9abd 100644 --- a/src/DebugServer/Gdb/CommandPackets/SetBreakpoint.cpp +++ b/src/DebugServer/Gdb/CommandPackets/SetBreakpoint.cpp @@ -65,7 +65,7 @@ namespace DebugServer::Gdb::CommandPackets return; } - targetControllerService.setBreakpoint(TargetBreakpoint(this->address)); + debugSession.setExternalBreakpoint(TargetBreakpoint(this->address), targetControllerService); debugSession.connection.writePacket(OkResponsePacket()); } catch (const Exception& exception) { diff --git a/src/DebugServer/Gdb/DebugSession.cpp b/src/DebugServer/Gdb/DebugSession.cpp index 838afbbd..b321d766 100644 --- a/src/DebugServer/Gdb/DebugSession.cpp +++ b/src/DebugServer/Gdb/DebugSession.cpp @@ -25,4 +25,88 @@ namespace DebugServer::Gdb DebugSession::~DebugSession() { EventManager::triggerEvent(std::make_shared()); } + + void DebugSession::setInternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ) { + if (this->internalBreakpointAddresses.contains(breakpoint.address)) { + return; + } + + if (!this->externalBreakpointAddresses.contains(breakpoint.address)) { + targetControllerService.setBreakpoint(breakpoint); + } + + this->internalBreakpointAddresses.insert(breakpoint.address); + } + + void DebugSession::removeInternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ) { + if (!this->internalBreakpointAddresses.contains(breakpoint.address)) { + return; + } + + if (!this->externalBreakpointAddresses.contains(breakpoint.address)) { + targetControllerService.removeBreakpoint(breakpoint); + } + + this->internalBreakpointAddresses.erase(breakpoint.address); + } + + void DebugSession::setExternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ) { + if (this->externalBreakpointAddresses.contains(breakpoint.address)) { + return; + } + + if (!this->internalBreakpointAddresses.contains(breakpoint.address)) { + targetControllerService.setBreakpoint(breakpoint); + } + + this->externalBreakpointAddresses.insert(breakpoint.address); + } + + void DebugSession::removeExternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ) { + if (!this->externalBreakpointAddresses.contains(breakpoint.address)) { + return; + } + + if (!this->internalBreakpointAddresses.contains(breakpoint.address)) { + targetControllerService.removeBreakpoint(breakpoint); + } + + this->externalBreakpointAddresses.erase(breakpoint.address); + } + + void DebugSession::startRangeSteppingSession( + RangeSteppingSession&& session, + Services::TargetControllerService& targetControllerService + ) { + for (const auto& interceptAddress : session.interceptedAddresses) { + this->setInternalBreakpoint(Targets::TargetBreakpoint(interceptAddress), targetControllerService); + } + + this->activeRangeSteppingSession = std::move(session); + } + + void DebugSession::terminateRangeSteppingSession(Services::TargetControllerService& targetControllerService) { + if (!this->activeRangeSteppingSession.has_value()) { + return; + } + + // Clear all intercepting breakpoints + for (const auto& interceptAddress : this->activeRangeSteppingSession->interceptedAddresses) { + this->removeInternalBreakpoint(Targets::TargetBreakpoint(interceptAddress), targetControllerService); + } + + this->activeRangeSteppingSession.reset(); + } } diff --git a/src/DebugServer/Gdb/DebugSession.hpp b/src/DebugServer/Gdb/DebugSession.hpp index 367a53fb..9d0eb28c 100644 --- a/src/DebugServer/Gdb/DebugSession.hpp +++ b/src/DebugServer/Gdb/DebugSession.hpp @@ -2,14 +2,19 @@ #include #include +#include #include "TargetDescriptor.hpp" #include "GdbDebugServerConfig.hpp" #include "Connection.hpp" #include "Feature.hpp" #include "ProgrammingSession.hpp" +#include "RangeSteppingSession.hpp" #include "src/Targets/TargetMemory.hpp" +#include "src/Targets/TargetBreakpoint.hpp" + +#include "src/Services/TargetControllerService.hpp" namespace DebugServer::Gdb { @@ -37,6 +42,22 @@ namespace DebugServer::Gdb */ const GdbDebugServerConfig& serverConfig; + /** + * During a debug session, we can set two types of breakpoints: internal and external. + * + * - Internal breakpoints are set by Bloom's GDB server, for reasons that are specific to Bloom's internals. + * For example, we use internal breakpoints to facilitate range stepping sessions, where we place + * intercepting breakpoints in places where we suspect the target will leave the stepping range. + * + * - External breakpoints are requested by the connected client (GDB), typically on behalf of the user. + * Sometimes GDB will set some internal breakpoints of its own, but from our perspective these are considered + * to be external breakpoints. + * + * We track internal and external breakpoints separately. + */ + std::set internalBreakpointAddresses; + std::set externalBreakpointAddresses; + /** * When the GDB client is waiting for the target to halt, this is set to true so we know when to notify the * client. @@ -60,6 +81,14 @@ namespace DebugServer::Gdb */ std::optional programmingSession = std::nullopt; + /** + * When we're range stepping, we maintain a range stepping session, which holds all info related to that + * particular range stepping session. + * + * This member should only be populated during a range stepping session. + */ + std::optional activeRangeSteppingSession = std::nullopt; + DebugSession( Connection&& connection, const std::set>>& supportedFeatures, @@ -73,6 +102,35 @@ namespace DebugServer::Gdb DebugSession& operator = (const DebugSession& other) = delete; DebugSession& operator = (DebugSession&& other) = delete; - ~DebugSession(); + virtual ~DebugSession(); + + virtual void setInternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ); + + virtual void removeInternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ); + + virtual void setExternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ); + + virtual void removeExternalBreakpoint( + const Targets::TargetBreakpoint& breakpoint, + Services::TargetControllerService& targetControllerService + ); + + virtual void startRangeSteppingSession( + RangeSteppingSession&& session, + Services::TargetControllerService& targetControllerService + ); + + virtual void terminateRangeSteppingSession( + Services::TargetControllerService& targetControllerService + ); }; } diff --git a/src/DebugServer/Gdb/Feature.hpp b/src/DebugServer/Gdb/Feature.hpp index ebf27d9d..06aba8e3 100644 --- a/src/DebugServer/Gdb/Feature.hpp +++ b/src/DebugServer/Gdb/Feature.hpp @@ -10,6 +10,7 @@ namespace DebugServer::Gdb HARDWARE_BREAKPOINTS, PACKET_SIZE, MEMORY_MAP_READ, + VCONT_ACTIONS_QUERY, }; static inline BiMap getGdbFeatureToNameMapping() { @@ -18,6 +19,7 @@ namespace DebugServer::Gdb {Feature::SOFTWARE_BREAKPOINTS, "swbreak"}, {Feature::PACKET_SIZE, "PacketSize"}, {Feature::MEMORY_MAP_READ, "qXfer:memory-map:read"}, + {Feature::VCONT_ACTIONS_QUERY, "vContSupported"}, }; } } diff --git a/src/DebugServer/Gdb/GdbRspDebugServer.cpp b/src/DebugServer/Gdb/GdbRspDebugServer.cpp index 8c19ac13..d9039812 100644 --- a/src/DebugServer/Gdb/GdbRspDebugServer.cpp +++ b/src/DebugServer/Gdb/GdbRspDebugServer.cpp @@ -40,6 +40,7 @@ #include "ResponsePackets/TargetStopped.hpp" #include "src/Services/ProcessService.hpp" +#include "src/Services/StringService.hpp" namespace DebugServer::Gdb { @@ -135,7 +136,7 @@ namespace DebugServer::Gdb } void GdbRspDebugServer::close() { - this->activeDebugSession.reset(); + this->endDebugSession(); if (this->serverSocketFileDescriptor.has_value()) { ::close(this->serverSocketFileDescriptor.value()); @@ -144,19 +145,14 @@ namespace DebugServer::Gdb void GdbRspDebugServer::run() { try { - if (!this->activeDebugSession.has_value()) { + if (this->getActiveDebugSession() == nullptr) { Logger::info("Waiting for GDB RSP connection"); auto connection = this->waitForConnection(); Logger::info("Accepted GDP RSP connection from " + connection.getIpAddress()); - this->activeDebugSession.emplace( - std::move(connection), - this->getSupportedFeatures(), - this->getGdbTargetDescriptor(), - this->debugServerConfig - ); + this->startDebugSession(std::move(connection)); this->targetControllerService.stopTargetExecution(); this->targetControllerService.resetTarget(); @@ -165,29 +161,29 @@ namespace DebugServer::Gdb const auto commandPacket = this->waitForCommandPacket(); if (commandPacket) { - commandPacket->handle(this->activeDebugSession.value(), this->targetControllerService); + commandPacket->handle(*(this->getActiveDebugSession()), this->targetControllerService); } } catch (const ClientDisconnected&) { Logger::info("GDB RSP client disconnected"); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const ClientCommunicationError& exception) { Logger::error( "GDB RSP client communication error - " + exception.getMessage() + " - closing connection" ); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const ClientNotSupported& exception) { Logger::error("Invalid GDB RSP client - " + exception.getMessage() + " - closing connection"); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const DebugSessionInitialisationFailure& exception) { Logger::warning("GDB debug session initialisation failure - " + exception.getMessage()); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const DebugServerInterrupted&) { @@ -219,14 +215,15 @@ namespace DebugServer::Gdb } std::unique_ptr GdbRspDebugServer::waitForCommandPacket() { - const auto rawPackets = this->activeDebugSession->connection.readRawPackets(); + auto* debugSession = this->getActiveDebugSession(); + const auto rawPackets = debugSession->connection.readRawPackets(); if (rawPackets.size() > 1) { const auto& firstRawPacket = rawPackets.front(); if (firstRawPacket.size() == 5 && firstRawPacket[1] == 0x03) { // Interrupt packet that came in too quickly before another packet - this->activeDebugSession->pendingInterrupt = true; + debugSession->pendingInterrupt = true; } // We only process the last packet - any others will probably be duplicates from an impatient client. @@ -312,68 +309,55 @@ namespace DebugServer::Gdb return std::make_unique(rawPacket); } - std::set>> GdbRspDebugServer::getSupportedFeatures() { - return { - {Feature::SOFTWARE_BREAKPOINTS, std::nullopt}, - }; - } + void GdbRspDebugServer::onTargetExecutionStopped(const Events::TargetExecutionStopped& stoppedEvent) { + using Services::StringService; + + auto* debugSession = this->getActiveDebugSession(); - void GdbRspDebugServer::onTargetExecutionStopped(const Events::TargetExecutionStopped&) { try { - if (this->activeDebugSession.has_value() && this->activeDebugSession->waitingForBreak) { - this->activeDebugSession->connection.writePacket( - ResponsePackets::TargetStopped(Signal::TRAP) - ); - this->activeDebugSession->waitingForBreak = false; + if (debugSession != nullptr && debugSession->waitingForBreak) { + this->handleTargetStoppedGdbResponse(stoppedEvent.programCounter); } } catch (const ClientDisconnected&) { Logger::info("GDB RSP client disconnected"); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const ClientCommunicationError& exception) { Logger::error( "GDB RSP client communication error - " + exception.getMessage() + " - closing connection" ); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const DebugServerInterrupted&) { // Server was interrupted Logger::debug("GDB RSP interrupted"); return; + } catch (const Exception& exception) { + Logger::error("Failed to handle target execution stopped event - " + exception.getMessage()); } } void GdbRspDebugServer::onTargetExecutionResumed(const Events::TargetExecutionResumed&) { + auto* debugSession = this->getActiveDebugSession(); + try { - if ( - this->activeDebugSession.has_value() - && this->activeDebugSession->pendingInterrupt - && this->activeDebugSession->waitingForBreak - ) { - Logger::info("Servicing pending interrupt"); - this->targetControllerService.stopTargetExecution(); - - this->activeDebugSession->connection.writePacket( - ResponsePackets::TargetStopped(Signal::INTERRUPTED) - ); - - this->activeDebugSession->pendingInterrupt = false; - this->activeDebugSession->waitingForBreak = false; + if (debugSession != nullptr) { + this->handleTargetResumedGdbResponse(); } } catch (const ClientDisconnected&) { Logger::info("GDB RSP client disconnected"); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const ClientCommunicationError& exception) { Logger::error( "GDB RSP client communication error - " + exception.getMessage() + " - closing connection" ); - this->activeDebugSession.reset(); + this->endDebugSession(); return; } catch (const DebugServerInterrupted&) { @@ -382,7 +366,35 @@ namespace DebugServer::Gdb return; } catch (const Exception& exception) { - Logger::error("Failed to interrupt execution - " + exception.getMessage()); + Logger::error("Failed to handle target execution resumed event - " + exception.getMessage()); + } + } + + void GdbRspDebugServer::handleTargetStoppedGdbResponse(Targets::TargetProgramCounter programAddress) { + auto* debugSession = this->getActiveDebugSession(); + + if (debugSession->activeRangeSteppingSession.has_value()) { + debugSession->terminateRangeSteppingSession(this->targetControllerService); + } + + debugSession->connection.writePacket(ResponsePackets::TargetStopped(Signal::TRAP)); + debugSession->waitingForBreak = false; + } + + void GdbRspDebugServer::handleTargetResumedGdbResponse() { + auto* debugSession = this->getActiveDebugSession(); + + if (debugSession->waitingForBreak && debugSession->pendingInterrupt) { + Logger::info("Servicing pending interrupt"); + this->targetControllerService.stopTargetExecution(); + + if (debugSession->activeRangeSteppingSession.has_value()) { + debugSession->terminateRangeSteppingSession(this->targetControllerService); + } + + debugSession->connection.writePacket(ResponsePackets::TargetStopped(Signal::INTERRUPTED)); + debugSession->pendingInterrupt = false; + debugSession->waitingForBreak = false; } } } diff --git a/src/DebugServer/Gdb/GdbRspDebugServer.hpp b/src/DebugServer/Gdb/GdbRspDebugServer.hpp index 968271d5..5bd17be0 100644 --- a/src/DebugServer/Gdb/GdbRspDebugServer.hpp +++ b/src/DebugServer/Gdb/GdbRspDebugServer.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "src/DebugServer/ServerInterface.hpp" @@ -127,10 +128,25 @@ namespace DebugServer::Gdb std::optional serverSocketFileDescriptor; /** - * When a connection with a GDB client is established, a new instance of the DebugSession class is created and - * held here. A value of std::nullopt means there is no active debug session present. + * Should start a new debug session for the newly established `connection`. + * + * @param connection + * @return */ - std::optional activeDebugSession; + virtual DebugSession* startDebugSession(Connection&& connection) = 0; + + /** + * Should end the currently active debug session, if one exists. + */ + virtual void endDebugSession() = 0; + + /** + * Should return a non-owning pointer of the currently active debug session. Or nullptr if there is no active + * session. + * + * @return + */ + virtual DebugSession* getActiveDebugSession() = 0; /** * Waits for a GDB client to connect on the listening socket. @@ -161,19 +177,6 @@ namespace DebugServer::Gdb */ virtual std::unique_ptr resolveCommandPacket(const RawPacket& rawPacket); - /** - * Should return a set of GDB features supported by the GDB server. Each supported feature may come with an - * optional value. - * - * The set of features returned by this function will be stored against the active debug session object. - * - * Derived GDB server implementations may override this function to include any features that are specific to - * those implementations. - * - * @return - */ - virtual std::set>> getSupportedFeatures(); - /** * Should return the GDB target descriptor for the connected target. * @@ -189,11 +192,14 @@ namespace DebugServer::Gdb * If the GDB client is currently waiting for the target execution to stop, this event handler will issue * a "stop reply" packet to the client once the target execution stops. */ - void onTargetExecutionStopped(const Events::TargetExecutionStopped&); + void onTargetExecutionStopped(const Events::TargetExecutionStopped& stoppedEvent); /** * Services any pending interrupts. */ void onTargetExecutionResumed(const Events::TargetExecutionResumed&); + + virtual void handleTargetStoppedGdbResponse(Targets::TargetProgramCounter programAddress); + virtual void handleTargetResumedGdbResponse(); }; } diff --git a/src/DebugServer/Gdb/RangeSteppingSession.hpp b/src/DebugServer/Gdb/RangeSteppingSession.hpp new file mode 100644 index 00000000..ffbb2aa9 --- /dev/null +++ b/src/DebugServer/Gdb/RangeSteppingSession.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include "src/Targets/TargetMemory.hpp" + +namespace DebugServer::Gdb +{ + /** + * A range stepping session is created upon the receipt of a VContRangeStep command, from GDB. + * + * Any information related to the range stepping session is held here. + * + * See DebugSession::activeRangeSteppingSession for more. + */ + struct RangeSteppingSession + { + /** + * The (byte) address range we're stepping over. + * + * NOTE: range::endAddress is exclusive! + */ + Targets::TargetMemoryAddressRange range; + + /** + * Any program memory (byte) addresses that we had to intercept as part of this session. + */ + std::set interceptedAddresses; + + /** + * Whether we're currently performing a single step, in this session, to observe the behaviour of a particular + * instruction. + * + * See AvrGdbRsp::handleTargetStoppedGdbResponse() for more. + */ + bool singleStepping = false; + + RangeSteppingSession( + const Targets::TargetMemoryAddressRange& range, + const std::set& interceptedAddresses + ) + : range(range) + , interceptedAddresses(interceptedAddresses) + {}; + }; +} diff --git a/src/Services/StringService.cpp b/src/Services/StringService.cpp index e441f302..3072d9e2 100644 --- a/src/Services/StringService.cpp +++ b/src/Services/StringService.cpp @@ -37,6 +37,12 @@ namespace Services return str; } + std::string StringService::toHex(std::uint32_t value) { + auto stream = std::stringstream(); + stream << std::hex << std::setfill('0') << std::setw(8) << static_cast(value); + return stream.str(); + } + std::string StringService::toHex(unsigned char value) { auto stream = std::stringstream(); stream << std::hex << std::setfill('0') << std::setw(2) << static_cast(value); diff --git a/src/Services/StringService.hpp b/src/Services/StringService.hpp index 9c4ec067..9c3b5ffb 100644 --- a/src/Services/StringService.hpp +++ b/src/Services/StringService.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace Services @@ -15,6 +16,7 @@ namespace Services static bool isAscii(const std::string& str); static std::string replaceUnprintable(std::string str); + static std::string toHex(std::uint32_t value); static std::string toHex(unsigned char value); static std::string toHex(const std::vector& data); static std::string toHex(const std::string& data);