- Implemented support for range stepping with GDB (vCont... packets)

- Refactored some bits of the generic GDB server class, along with the AVR-specific implementation
This commit is contained in:
Nav
2023-09-10 22:27:10 +01:00
parent 1d0f30db7a
commit 7d4ce1050f
42 changed files with 901 additions and 103 deletions

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
#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;
};
}

View File

@@ -0,0 +1,216 @@
#include "VContRangeStep.hpp"
#include <string>
#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<decltype(commandData)::difference_type>(
delimiterPosition
);
const auto startAddressHex = std::string(commandData.begin(), delimiterIt);
const auto endAddressHex = std::string(
delimiterIt + 1,
threadIdDelimiterPosition != std::string::npos
? commandData.begin() + static_cast<decltype(commandData)::difference_type>(threadIdDelimiterPosition)
: commandData.end()
);
this->startAddress = static_cast<Targets::TargetProgramCounter>(std::stoi(startAddressHex, nullptr, 16));
this->endAddress = static_cast<Targets::TargetProgramCounter>(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());
}
}
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#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;
};
}

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
#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;
};
}

View File

@@ -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"
)
);
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <set>
#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;
};
}

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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 {

View File

@@ -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;
};