Renamed GdbRsp directory to Gdb
This commit is contained in:
47
src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.cpp
Normal file
47
src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "AvrGdbRsp.hpp"
|
||||
|
||||
// Command packets
|
||||
#include "CommandPackets/ReadMemory.hpp"
|
||||
#include "CommandPackets/WriteMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
using Bloom::Targets::TargetRegisterDescriptor;
|
||||
using Bloom::Targets::TargetRegisterType;
|
||||
|
||||
AvrGdbRsp::AvrGdbRsp(
|
||||
const DebugServerConfig& debugServerConfig,
|
||||
EventListener& eventListener
|
||||
)
|
||||
: GdbRspDebugServer(debugServerConfig, eventListener)
|
||||
{}
|
||||
|
||||
void AvrGdbRsp::init() {
|
||||
DebugServer::Gdb::GdbRspDebugServer::init();
|
||||
|
||||
this->gdbTargetDescriptor = TargetDescriptor(
|
||||
this->targetControllerConsole.getTargetDescriptor()
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<Gdb::CommandPackets::CommandPacket> AvrGdbRsp::resolveCommandPacket(
|
||||
const RawPacketType& rawPacket
|
||||
) {
|
||||
using AvrGdb::CommandPackets::ReadMemory;
|
||||
using AvrGdb::CommandPackets::WriteMemory;
|
||||
|
||||
if (rawPacket.size() >= 2) {
|
||||
if (rawPacket[1] == 'm') {
|
||||
return std::make_unique<ReadMemory>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacket[1] == 'M') {
|
||||
return std::make_unique<WriteMemory>(rawPacket);
|
||||
}
|
||||
}
|
||||
|
||||
return GdbRspDebugServer::resolveCommandPacket(rawPacket);
|
||||
}
|
||||
}
|
||||
52
src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.hpp
Normal file
52
src/DebugServer/Gdb/AvrGdb/AvrGdbRsp.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "TargetDescriptor.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/GdbRspDebugServer.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb
|
||||
{
|
||||
/**
|
||||
* The AVR GDB client (avr-gdb) defines a set of parameters relating to AVR targets. These parameters are
|
||||
* hardcoded in the AVR GDB source code. The client expects all compatible GDB RSP servers to be aware of
|
||||
* these parameters.
|
||||
*
|
||||
* An example of these hardcoded parameters is target registers and the order in which they are supplied; AVR GDB
|
||||
* clients expect 35 registers to be accessible via the server. 32 of these registers are general purpose CPU
|
||||
* registers. The GP registers are expected to be followed by the status register (SREG), stack pointer
|
||||
* register (SPH & SPL) and the program counter. These must all be given in a specific order, which is
|
||||
* pre-determined by the AVR GDB client. See AvrGdbRsp::getRegisterNumberToDescriptorMapping() for more.
|
||||
*
|
||||
* For more on this, see the AVR GDB source code at https://github.com/bminor/binutils-gdb/blob/master/gdb/avr-tdep.c
|
||||
*
|
||||
* The AvrGdpRsp class extends the generic GDB RSP debug server and implements these AVR specific parameters.
|
||||
*/
|
||||
class AvrGdbRsp: public GdbRspDebugServer
|
||||
{
|
||||
public:
|
||||
AvrGdbRsp(
|
||||
const DebugServerConfig& debugServerConfig,
|
||||
EventListener& eventListener
|
||||
);
|
||||
|
||||
std::string getName() const override {
|
||||
return "AVR GDB Remote Serial Protocol Debug Server";
|
||||
}
|
||||
|
||||
protected:
|
||||
void init() override;
|
||||
|
||||
const Gdb::TargetDescriptor& getGdbTargetDescriptor() override {
|
||||
return this->gdbTargetDescriptor.value();
|
||||
}
|
||||
|
||||
std::unique_ptr<Gdb::CommandPackets::CommandPacket> resolveCommandPacket(
|
||||
const RawPacketType& rawPacket
|
||||
) override;
|
||||
|
||||
private:
|
||||
std::optional<TargetDescriptor> gdbTargetDescriptor;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "AbstractMemoryAccessPacket.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/Signal.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp"
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The ReadMemory class implements a structure for "m" packets. Upon receiving these packets, the server is
|
||||
* expected to read memory from the target and send it the client.
|
||||
*/
|
||||
class AbstractMemoryAccessPacket: public Bloom::DebugServer::Gdb::CommandPackets::CommandPacket
|
||||
{
|
||||
public:
|
||||
explicit AbstractMemoryAccessPacket(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The mask used by the AVR GDB client to encode the memory type into memory addresses.
|
||||
*/
|
||||
static constexpr std::uint32_t AVR_GDB_MEMORY_ADDRESS_MASK = 0xFE0000U;
|
||||
|
||||
/**
|
||||
* avr-gdb uses the most significant 15 bits in memory addresses to indicate the type of memory being
|
||||
* addressed.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
Targets::TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) {
|
||||
if ((address & AbstractMemoryAccessPacket::AVR_GDB_MEMORY_ADDRESS_MASK) != 0U) {
|
||||
return Targets::TargetMemoryType::RAM;
|
||||
}
|
||||
|
||||
return Targets::TargetMemoryType::FLASH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the most significant 15 bits from a GDB memory address.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) {
|
||||
return (address & AbstractMemoryAccessPacket::AVR_GDB_MEMORY_ADDRESS_MASK) != 0U
|
||||
? (address & ~(AbstractMemoryAccessPacket::AVR_GDB_MEMORY_ADDRESS_MASK))
|
||||
: address;
|
||||
}
|
||||
};
|
||||
}
|
||||
70
src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.cpp
Normal file
70
src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "ReadMemory.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
using ResponsePackets::ResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void ReadMemory::init() {
|
||||
if (this->data.size() < 4) {
|
||||
throw Exception("Invalid packet length");
|
||||
}
|
||||
|
||||
auto packetString = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 1),
|
||||
static_cast<int>(this->data.size() - 1)
|
||||
);
|
||||
|
||||
/*
|
||||
* The read memory ('m') packet consists of two segments, an address and a number of bytes to read.
|
||||
* These are separated by a comma character.
|
||||
*/
|
||||
auto packetSegments = packetString.split(",");
|
||||
|
||||
if (packetSegments.size() != 2) {
|
||||
throw Exception(
|
||||
"Unexpected number of segments in packet data: " + std::to_string(packetSegments.size())
|
||||
);
|
||||
}
|
||||
|
||||
bool conversionStatus = false;
|
||||
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse start address from read memory packet data");
|
||||
}
|
||||
|
||||
this->bytes = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse read length from read memory packet data");
|
||||
}
|
||||
}
|
||||
|
||||
void ReadMemory::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling ReadMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(this->startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(this->startAddress);
|
||||
auto memoryBuffer = targetControllerConsole.readMemory(memoryType, startAddress, this->bytes);
|
||||
|
||||
auto hexMemoryBuffer = Packet::dataToHex(memoryBuffer);
|
||||
debugSession.connection.writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(hexMemoryBuffer.begin(), hexMemoryBuffer.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read memory from target - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.hpp
Normal file
40
src/DebugServer/Gdb/AvrGdb/CommandPackets/ReadMemory.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "AbstractMemoryAccessPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The ReadMemory class implements a structure for "m" packets. Upon receiving these packets, the server is
|
||||
* expected to read memory from the target and send it the client.
|
||||
*/
|
||||
class ReadMemory: public AbstractMemoryAccessPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The startAddress sent from the GDB client may include additional bits used to indicate the memory type.
|
||||
* These bits have to be removed from the address before it can be used as a start address. This is not done
|
||||
* here, as it's target specific.
|
||||
*
|
||||
* For an example of where GDB does this, see the AvrGdbRsp class.
|
||||
*/
|
||||
std::uint32_t startAddress = 0;
|
||||
|
||||
/**
|
||||
* Number of bytes to read.
|
||||
*/
|
||||
std::uint32_t bytes = 0;
|
||||
|
||||
explicit ReadMemory(const std::vector<unsigned char>& rawPacket): AbstractMemoryAccessPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
protected:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
86
src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.cpp
Normal file
86
src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "WriteMemory.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 Bloom::DebugServer::Gdb::AvrGdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
using ResponsePackets::OkResponsePacket;
|
||||
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void WriteMemory::init() {
|
||||
if (this->data.size() < 4) {
|
||||
throw Exception("Invalid packet length");
|
||||
}
|
||||
|
||||
auto packetString = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 1),
|
||||
static_cast<int>(this->data.size() - 1)
|
||||
);
|
||||
|
||||
/*
|
||||
* The write memory ('M') packet consists of three segments, an address, a length and a buffer.
|
||||
* The address and length are separated by a comma character, and the buffer proceeds a colon character.
|
||||
*/
|
||||
auto packetSegments = packetString.split(",");
|
||||
if (packetSegments.size() != 2) {
|
||||
throw Exception(
|
||||
"Unexpected number of segments in packet data: " + std::to_string(packetSegments.size())
|
||||
);
|
||||
}
|
||||
|
||||
bool conversionStatus = false;
|
||||
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse start address from write memory packet data");
|
||||
}
|
||||
|
||||
auto lengthAndBufferSegments = packetSegments.at(1).split(":");
|
||||
if (lengthAndBufferSegments.size() != 2) {
|
||||
throw Exception(
|
||||
"Unexpected number of segments in packet data: "
|
||||
+ std::to_string(lengthAndBufferSegments.size())
|
||||
);
|
||||
}
|
||||
|
||||
auto bufferSize = lengthAndBufferSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse write length from write memory packet data");
|
||||
}
|
||||
|
||||
this->buffer = Packet::hexToData(lengthAndBufferSegments.at(1).toStdString());
|
||||
|
||||
if (this->buffer.size() != bufferSize) {
|
||||
throw Exception("Buffer size does not match length value given in write memory packet");
|
||||
}
|
||||
}
|
||||
|
||||
void WriteMemory::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling WriteMemory packet");
|
||||
|
||||
try {
|
||||
const auto memoryType = this->getMemoryTypeFromGdbAddress(this->startAddress);
|
||||
|
||||
if (memoryType == Targets::TargetMemoryType::FLASH) {
|
||||
throw Exception(
|
||||
"GDB client requested a flash memory write - This is not currently supported by Bloom."
|
||||
);
|
||||
}
|
||||
|
||||
const auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(this->startAddress);
|
||||
targetControllerConsole.writeMemory(memoryType, startAddress, this->buffer);
|
||||
|
||||
debugSession.connection.writePacket(OkResponsePacket());
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write memory to target - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.hpp
Normal file
34
src/DebugServer/Gdb/AvrGdb/CommandPackets/WriteMemory.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "AbstractMemoryAccessPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The WriteMemory class implements the structure for "M" packets. Upon receiving this packet, the server is
|
||||
* expected to write data to the target's memory, at the specified start address.
|
||||
*/
|
||||
class WriteMemory: public AbstractMemoryAccessPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Like with the ReadMemory command packet, the start address carries additional bits that indicate
|
||||
* the memory type.
|
||||
*/
|
||||
std::uint32_t startAddress = 0;
|
||||
|
||||
Targets::TargetMemoryBuffer buffer;
|
||||
|
||||
explicit WriteMemory(const std::vector<unsigned char>& rawPacket): AbstractMemoryAccessPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
170
src/DebugServer/Gdb/AvrGdb/TargetDescriptor.cpp
Normal file
170
src/DebugServer/Gdb/AvrGdb/TargetDescriptor.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "TargetDescriptor.hpp"
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterDescriptor;
|
||||
using Bloom::Targets::TargetRegisterType;
|
||||
|
||||
using Bloom::Exceptions::Exception;
|
||||
|
||||
TargetDescriptor::TargetDescriptor(const Bloom::Targets::TargetDescriptor& targetDescriptor)
|
||||
: DebugServer::Gdb::TargetDescriptor(targetDescriptor)
|
||||
{
|
||||
this->loadRegisterMappings();
|
||||
}
|
||||
|
||||
std::optional<GdbRegisterNumberType> TargetDescriptor::getRegisterNumberFromTargetRegisterDescriptor(
|
||||
const Targets::TargetRegisterDescriptor& registerDescriptor
|
||||
) const {
|
||||
return this->targetRegisterDescriptorsByGdbNumber.valueAt(registerDescriptor);
|
||||
}
|
||||
|
||||
const RegisterDescriptor& TargetDescriptor::getRegisterDescriptorFromNumber(GdbRegisterNumberType number) const {
|
||||
if (this->registerDescriptorsByGdbNumber.contains(number)) {
|
||||
return this->registerDescriptorsByGdbNumber.at(number);
|
||||
}
|
||||
|
||||
throw Exception("Unknown register from GDB - register number (" + std::to_string(number)
|
||||
+ ") not mapped to any GDB register descriptor.");
|
||||
}
|
||||
|
||||
const TargetRegisterDescriptor& TargetDescriptor::getTargetRegisterDescriptorFromNumber(
|
||||
GdbRegisterNumberType number
|
||||
) const {
|
||||
if (this->targetRegisterDescriptorsByGdbNumber.contains(number)) {
|
||||
return this->targetRegisterDescriptorsByGdbNumber.at(number);
|
||||
}
|
||||
|
||||
throw Exception("Unknown register from GDB - register number (" + std::to_string(number)
|
||||
+ ") not mapped to any target register descriptor.");
|
||||
}
|
||||
|
||||
const std::vector<GdbRegisterNumberType>& TargetDescriptor::getRegisterNumbers() const {
|
||||
return this->registerNumbers;
|
||||
}
|
||||
|
||||
void TargetDescriptor::loadRegisterMappings() {
|
||||
auto& registerDescriptorsByType = this->targetDescriptor.registerDescriptorsByType;
|
||||
if (!registerDescriptorsByType.contains(TargetRegisterType::STATUS_REGISTER)) {
|
||||
throw Exception("Missing status register descriptor");
|
||||
}
|
||||
|
||||
if (!registerDescriptorsByType.contains(TargetRegisterType::STACK_POINTER)) {
|
||||
throw Exception("Missing stack pointer register descriptor");
|
||||
}
|
||||
|
||||
if (!registerDescriptorsByType.contains(TargetRegisterType::PROGRAM_COUNTER)) {
|
||||
throw Exception("Missing program counter register descriptor");
|
||||
}
|
||||
|
||||
if (!registerDescriptorsByType.contains(TargetRegisterType::GENERAL_PURPOSE_REGISTER)
|
||||
|| registerDescriptorsByType.at(TargetRegisterType::GENERAL_PURPOSE_REGISTER).size() != 32
|
||||
) {
|
||||
throw Exception("Unexpected general purpose register count");
|
||||
}
|
||||
|
||||
/*
|
||||
* For AVR targets, avr-gdb defines 35 registers in total:
|
||||
*
|
||||
* Register number 0 through 31 are general purpose registers
|
||||
* Register number 32 is the status register (SREG)
|
||||
* Register number 33 is the stack pointer register
|
||||
* Register number 34 is the program counter register
|
||||
*/
|
||||
|
||||
// Generate 35 register numbers (0 -> 34)
|
||||
std::iota(this->registerNumbers.begin(), this->registerNumbers.end(), 0);
|
||||
|
||||
/*
|
||||
* Worth noting that gpRegisterDescriptors will always be sorted in the correct order, from register 0 to 31.
|
||||
*
|
||||
* Hmm, but the sorting is based on the start address (see TargetRegisterDescriptor::<() for more). So
|
||||
* effectively, we're assuming that the registers will be laid out in the correct order, in memory. I think
|
||||
* this assumption is fair.
|
||||
*/
|
||||
const auto& gpRegisterDescriptors = registerDescriptorsByType.at(
|
||||
TargetRegisterType::GENERAL_PURPOSE_REGISTER
|
||||
);
|
||||
|
||||
// General purpose registers
|
||||
GdbRegisterNumberType regNumber = 0;
|
||||
for (const auto& descriptor : gpRegisterDescriptors) {
|
||||
this->registerDescriptorsByGdbNumber.insert(std::pair(
|
||||
regNumber,
|
||||
RegisterDescriptor(
|
||||
regNumber,
|
||||
1,
|
||||
"General Purpose Register " + std::to_string(regNumber)
|
||||
)
|
||||
));
|
||||
|
||||
this->targetRegisterDescriptorsByGdbNumber.insert(std::pair(
|
||||
regNumber,
|
||||
descriptor
|
||||
));
|
||||
|
||||
regNumber++;
|
||||
}
|
||||
|
||||
// Status, stack pointer and program counter registers
|
||||
const auto statusDescriptor = RegisterDescriptor(
|
||||
32,
|
||||
1,
|
||||
"Status Register"
|
||||
);
|
||||
|
||||
this->registerDescriptorsByGdbNumber.insert(std::pair(statusDescriptor.number, statusDescriptor));
|
||||
this->targetRegisterDescriptorsByGdbNumber.insert(std::pair(
|
||||
statusDescriptor.number,
|
||||
*(registerDescriptorsByType.at(TargetRegisterType::STATUS_REGISTER).begin())
|
||||
));
|
||||
|
||||
const auto stackPointerDescriptor = RegisterDescriptor(
|
||||
33,
|
||||
2,
|
||||
"Stack Pointer Register"
|
||||
);
|
||||
|
||||
this->registerDescriptorsByGdbNumber.insert(
|
||||
std::pair(stackPointerDescriptor.number, stackPointerDescriptor)
|
||||
);
|
||||
this->targetRegisterDescriptorsByGdbNumber.insert(std::pair(
|
||||
stackPointerDescriptor.number,
|
||||
*(registerDescriptorsByType.at(TargetRegisterType::STACK_POINTER).begin())
|
||||
));
|
||||
|
||||
const auto programCounterDescriptor = RegisterDescriptor(
|
||||
34,
|
||||
4,
|
||||
"Program Counter"
|
||||
);
|
||||
|
||||
this->registerDescriptorsByGdbNumber.insert(std::pair(
|
||||
programCounterDescriptor.number,
|
||||
programCounterDescriptor
|
||||
));
|
||||
this->targetRegisterDescriptorsByGdbNumber.insert(std::pair(
|
||||
programCounterDescriptor.number,
|
||||
*(registerDescriptorsByType.at(TargetRegisterType::PROGRAM_COUNTER).begin())
|
||||
));
|
||||
|
||||
if (registerDescriptorsByType.at(TargetRegisterType::STATUS_REGISTER).size() > statusDescriptor.size) {
|
||||
throw Exception("AVR8 status target register size exceeds the GDB register size.");
|
||||
}
|
||||
|
||||
if (registerDescriptorsByType.at(TargetRegisterType::STACK_POINTER).size() > stackPointerDescriptor.size) {
|
||||
throw Exception("AVR8 stack pointer target register size exceeds the GDB register size.");
|
||||
}
|
||||
|
||||
if (
|
||||
registerDescriptorsByType.at(TargetRegisterType::PROGRAM_COUNTER).size() > programCounterDescriptor.size
|
||||
) {
|
||||
throw Exception("AVR8 program counter size exceeds the GDB register size.");
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/DebugServer/Gdb/AvrGdb/TargetDescriptor.hpp
Normal file
63
src/DebugServer/Gdb/AvrGdb/TargetDescriptor.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/DebugServer/Gdb/TargetDescriptor.hpp"
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::AvrGdb
|
||||
{
|
||||
class TargetDescriptor: public DebugServer::Gdb::TargetDescriptor
|
||||
{
|
||||
public:
|
||||
BiMap<GdbRegisterNumberType, RegisterDescriptor> registerDescriptorsByGdbNumber = {};
|
||||
BiMap<GdbRegisterNumberType, Targets::TargetRegisterDescriptor> targetRegisterDescriptorsByGdbNumber = {};
|
||||
|
||||
explicit TargetDescriptor(const Targets::TargetDescriptor& targetDescriptor);
|
||||
|
||||
/**
|
||||
* Should retrieve the GDB register number, given a target register descriptor. Or std::nullopt if the target
|
||||
* register descriptor isn't mapped to any GDB register.
|
||||
*
|
||||
* @param registerDescriptor
|
||||
* @return
|
||||
*/
|
||||
std::optional<GdbRegisterNumberType> getRegisterNumberFromTargetRegisterDescriptor(
|
||||
const Targets::TargetRegisterDescriptor& registerDescriptor
|
||||
) const override;
|
||||
|
||||
/**
|
||||
* Should retrieve the GDB register descriptor for a given GDB register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
const RegisterDescriptor& getRegisterDescriptorFromNumber(GdbRegisterNumberType number) const override;
|
||||
|
||||
/**
|
||||
* Should retrieve the mapped target register descriptor for a given GDB register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
const Targets::TargetRegisterDescriptor& getTargetRegisterDescriptorFromNumber(
|
||||
GdbRegisterNumberType number
|
||||
) const override;
|
||||
|
||||
const std::vector<GdbRegisterNumberType>& getRegisterNumbers() const override;
|
||||
|
||||
private:
|
||||
std::vector<GdbRegisterNumberType> registerNumbers = std::vector<GdbRegisterNumberType>(35);
|
||||
|
||||
/**
|
||||
* For AVR targets, avr-gdb defines 35 registers in total:
|
||||
*
|
||||
* Register number 0 through 31 are general purpose registers
|
||||
* Register number 32 is the status register (SREG)
|
||||
* Register number 33 is the stack pointer register
|
||||
* Register number 34 is the program counter register
|
||||
*
|
||||
* This function will prepare the appropriate GDB register numbers and mappings.
|
||||
*/
|
||||
void loadRegisterMappings();
|
||||
};
|
||||
}
|
||||
11
src/DebugServer/Gdb/BreakpointType.hpp
Normal file
11
src/DebugServer/Gdb/BreakpointType.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
enum class BreakpointType: int
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
SOFTWARE_BREAKPOINT = 1,
|
||||
HARDWARE_BREAKPOINT = 2,
|
||||
};
|
||||
}
|
||||
45
src/DebugServer/Gdb/CommandPackets/CommandPacket.cpp
Normal file
45
src/DebugServer/Gdb/CommandPackets/CommandPacket.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/Signal.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::ResponsePacket;
|
||||
using ResponsePackets::OkResponsePacket;
|
||||
using ResponsePackets::TargetStopped;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void CommandPacket::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
auto packetData = this->getData();
|
||||
auto packetString = std::string(packetData.begin(), packetData.end());
|
||||
|
||||
if (packetString[0] == '?') {
|
||||
// Status report
|
||||
debugSession.connection.writePacket(TargetStopped(Signal::TRAP));
|
||||
|
||||
} else if (packetString[0] == 'D') {
|
||||
// Detach packet - there's not really anything we need to do here, so just respond with an OK
|
||||
debugSession.connection.writePacket(OkResponsePacket());
|
||||
|
||||
} else if (packetString.find("qAttached") == 0) {
|
||||
Logger::debug("Handling qAttached");
|
||||
debugSession.connection.writePacket(ResponsePacket({1}));
|
||||
|
||||
} else {
|
||||
Logger::debug("Unknown GDB RSP packet: " + packetString + " - returning empty response");
|
||||
|
||||
// Respond with an empty packet
|
||||
debugSession.connection.writePacket(ResponsePacket({0}));
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp
Normal file
51
src/DebugServer/Gdb/CommandPackets/CommandPacket.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServer/Gdb/Packet.hpp"
|
||||
#include "src/DebugServer/Gdb/DebugSession.hpp"
|
||||
|
||||
#include "src/TargetController/TargetControllerConsole.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* GDB RSP command packets are sent to the server, from the GDB client. These packets carry instructions that the
|
||||
* server is expected to carry out. Upon completion, the server is expected to respond to the client with
|
||||
* a ResponsePacket.
|
||||
*
|
||||
* For some command packets, we define a specific data structure by extending this CommandPacket class. These
|
||||
* classes extend the data structure to include fields for data which may be specific to the command.
|
||||
* They also implement additional methods that allow us to easily access the additional data. An example
|
||||
* of this would be the SupportedFeaturesQuery class. It extends the CommandPacket class and provides access
|
||||
* to additional data fields that are specific to the command (in this case, a set of GDB features reported to be
|
||||
* supported by the GDB client).
|
||||
*
|
||||
* Typically, command packets that require specific data structures are handled in a dedicated handler method
|
||||
* in the GdbRspDebugServer. This is done by double dispatching the packet object to the appropriate handler.
|
||||
* See CommandPacket::dispatchToHandler(), GdbRspDebugServer::serve() and the overloads
|
||||
* for GdbRspDebugServer::handleGdbPacket() for more on this.
|
||||
*
|
||||
* Some command packets are so simple they do not require a dedicated data structure. An example of this is
|
||||
* the halt reason packet, which contains nothing more than an ? character in the packet body. These packets are
|
||||
* typically handled in the generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) method.
|
||||
*
|
||||
* See the Packet class for information on how the raw packets are formatted.
|
||||
*/
|
||||
class CommandPacket: public Packet
|
||||
{
|
||||
public:
|
||||
explicit CommandPacket(const std::vector<unsigned char>& rawPacket): Packet(rawPacket) {}
|
||||
|
||||
/**
|
||||
* Should handle the command for the current active debug session.
|
||||
*
|
||||
* @param debugSession
|
||||
* The current active debug session.
|
||||
*
|
||||
* @param targetControllerConsole
|
||||
*/
|
||||
virtual void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole);
|
||||
};
|
||||
}
|
||||
33
src/DebugServer/Gdb/CommandPackets/ContinueExecution.cpp
Normal file
33
src/DebugServer/Gdb/CommandPackets/ContinueExecution.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "ContinueExecution.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
using Exceptions::Exception;
|
||||
|
||||
void ContinueExecution::init() {
|
||||
if (this->data.size() > 1) {
|
||||
this->fromProgramCounter = static_cast<std::uint32_t>(
|
||||
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ContinueExecution::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling ContinueExecution packet");
|
||||
|
||||
try {
|
||||
targetControllerConsole.continueTargetExecution(this->fromProgramCounter);
|
||||
debugSession.waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to continue execution on target - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/DebugServer/Gdb/CommandPackets/ContinueExecution.hpp
Normal file
36
src/DebugServer/Gdb/CommandPackets/ContinueExecution.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The ContinueExecution class implements a structure for "c" packets. These packets instruct the server
|
||||
* to continue execution on the target.
|
||||
*
|
||||
* See @link https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets for more on this.
|
||||
*/
|
||||
class ContinueExecution: public CommandPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The "c" packet can contain an address which defines the point from which the execution should be resumed on
|
||||
* the target.
|
||||
*
|
||||
* Although the packet *can* contain this address, it is not required, hence the optional.
|
||||
*/
|
||||
std::optional<std::uint32_t> fromProgramCounter;
|
||||
|
||||
explicit ContinueExecution(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
init();
|
||||
}
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
29
src/DebugServer/Gdb/CommandPackets/InterruptExecution.cpp
Normal file
29
src/DebugServer/Gdb/CommandPackets/InterruptExecution.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "InterruptExecution.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/Signal.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::TargetStopped;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
using Exceptions::Exception;
|
||||
|
||||
void InterruptExecution::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling InterruptExecution packet");
|
||||
|
||||
try {
|
||||
targetControllerConsole.stopTargetExecution();
|
||||
debugSession.connection.writePacket(TargetStopped(Signal::INTERRUPTED));
|
||||
debugSession.waitingForBreak = false;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to interrupt execution - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/DebugServer/Gdb/CommandPackets/InterruptExecution.hpp
Normal file
22
src/DebugServer/Gdb/CommandPackets/InterruptExecution.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The InterruptException class represents interrupt command packets. Upon receiving an interrupt packet, the
|
||||
* server is expected to interrupt execution on the target.
|
||||
*
|
||||
* Technically, interrupts are not sent by the client in the form of a typical GDP RSP packet. Instead, they're
|
||||
* just sent as a single byte from the client. We fake the packet on our end, to save us the headache of dealing
|
||||
* with this inconsistency. We do this in Connection::readRawPackets().
|
||||
*/
|
||||
class InterruptExecution: public CommandPacket
|
||||
{
|
||||
public:
|
||||
explicit InterruptExecution(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {}
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
};
|
||||
}
|
||||
96
src/DebugServer/Gdb/CommandPackets/ReadRegisters.cpp
Normal file
96
src/DebugServer/Gdb/CommandPackets/ReadRegisters.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "ReadRegisters.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using Targets::TargetRegister;
|
||||
using Targets::TargetRegisterDescriptors;
|
||||
|
||||
using ResponsePackets::ResponsePacket;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void ReadRegisters::init() {
|
||||
if (this->data.size() >= 2 && this->data.front() == 'p') {
|
||||
// This command packet is requesting a specific register
|
||||
this->registerNumber = static_cast<size_t>(
|
||||
std::stoi(std::string(this->data.begin() + 1, this->data.end()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadRegisters::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling ReadRegisters packet");
|
||||
|
||||
try {
|
||||
const auto& targetDescriptor = debugSession.targetDescriptor;
|
||||
auto descriptors = TargetRegisterDescriptors();
|
||||
|
||||
if (this->registerNumber.has_value()) {
|
||||
Logger::debug("Reading register number: " + std::to_string(this->registerNumber.value()));
|
||||
descriptors.insert(
|
||||
targetDescriptor.getTargetRegisterDescriptorFromNumber(this->registerNumber.value())
|
||||
);
|
||||
|
||||
} else {
|
||||
// Read all target registers mapped to a GDB register
|
||||
for (const auto& registerNumber : targetDescriptor.getRegisterNumbers()) {
|
||||
descriptors.insert(targetDescriptor.getTargetRegisterDescriptorFromNumber(registerNumber));
|
||||
}
|
||||
}
|
||||
|
||||
auto registerSet = targetControllerConsole.readRegisters(descriptors);
|
||||
|
||||
/*
|
||||
* Sort each register by their respective GDB register number - this will leave us with a collection of
|
||||
* registers in the order expected by the GDB client.
|
||||
*/
|
||||
std::sort(
|
||||
registerSet.begin(),
|
||||
registerSet.end(),
|
||||
[this, &targetDescriptor] (const TargetRegister& registerA, const TargetRegister& registerB) {
|
||||
return targetDescriptor.getRegisterNumberFromTargetRegisterDescriptor(registerA.descriptor) <
|
||||
targetDescriptor.getRegisterNumberFromTargetRegisterDescriptor(registerB.descriptor);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Finally, reverse the register values (as they're all currently in MSB, but GDB expects them in LSB), ensure
|
||||
* that each register value size matches the size in the associated GDB register descriptor, implode the
|
||||
* values, convert to hexadecimal form and send to the GDB client.
|
||||
*/
|
||||
auto registers = std::vector<unsigned char>();
|
||||
for (auto& reg : registerSet) {
|
||||
std::reverse(reg.value.begin(), reg.value.end());
|
||||
|
||||
const auto gdbRegisterNumber = targetDescriptor.getRegisterNumberFromTargetRegisterDescriptor(
|
||||
reg.descriptor
|
||||
).value();
|
||||
const auto& gdbRegisterDescriptor = targetDescriptor.getRegisterDescriptorFromNumber(gdbRegisterNumber);
|
||||
|
||||
if (reg.value.size() < gdbRegisterDescriptor.size) {
|
||||
reg.value.insert(reg.value.end(), (gdbRegisterDescriptor.size - reg.value.size()), 0x00);
|
||||
}
|
||||
|
||||
registers.insert(registers.end(), reg.value.begin(), reg.value.end());
|
||||
}
|
||||
|
||||
auto responseRegisters = Packet::dataToHex(registers);
|
||||
debugSession.connection.writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(responseRegisters.begin(), responseRegisters.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read general registers - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/DebugServer/Gdb/CommandPackets/ReadRegisters.hpp
Normal file
37
src/DebugServer/Gdb/CommandPackets/ReadRegisters.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/RegisterDescriptor.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The ReadRegisters class implements a structure for "g" and "p" command packets. In response to these
|
||||
* packets, the server is expected to send register values for all registers (for "g" packets) or for a single
|
||||
* register (for "p" packets).
|
||||
*/
|
||||
class ReadRegisters: public CommandPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* "p" packets include a register number to indicate which register is requested for reading. When this is set,
|
||||
* the server is expected to respond with only the value of the requested register.
|
||||
*
|
||||
* If the register number is not supplied (as is the case with "g" packets), the server is expected to respond
|
||||
* with values for all registers.
|
||||
*/
|
||||
std::optional<GdbRegisterNumberType> registerNumber;
|
||||
|
||||
explicit ReadRegisters(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
64
src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.cpp
Normal file
64
src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Targets/TargetBreakpoint.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using Targets::TargetBreakpoint;
|
||||
|
||||
using ResponsePackets::OkResponsePacket;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void RemoveBreakpoint::init() {
|
||||
if (data.size() < 6) {
|
||||
throw Exception("Unexpected RemoveBreakpoint packet size");
|
||||
}
|
||||
|
||||
// z0 = SW breakpoint, z1 = HW breakpoint
|
||||
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
|
||||
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
|
||||
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 2),
|
||||
static_cast<int>(this->data.size() - 2)
|
||||
);
|
||||
|
||||
auto packetSegments = packetData.split(",");
|
||||
if (packetSegments.size() < 3) {
|
||||
throw Exception("Unexpected number of packet segments in RemoveBreakpoint packet");
|
||||
}
|
||||
|
||||
bool conversionStatus = true;
|
||||
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to convert address hex value from RemoveBreakpoint packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveBreakpoint::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Removing breakpoint at address " + std::to_string(this->address));
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = this->address;
|
||||
targetControllerConsole.removeBreakpoint(breakpoint);
|
||||
|
||||
debugSession.connection.writePacket(OkResponsePacket());
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to remove breakpoint on target - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.hpp
Normal file
38
src/DebugServer/Gdb/CommandPackets/RemoveBreakpoint.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/DebugServer/Gdb/BreakpointType.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The RemoveBreakpoint class implements the structure for "z" command packets. Upon receiving this command, the
|
||||
* server is expected to remove a breakpoint at the specified address.
|
||||
*/
|
||||
class RemoveBreakpoint: public CommandPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address = 0;
|
||||
|
||||
explicit RemoveBreakpoint(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
64
src/DebugServer/Gdb/CommandPackets/SetBreakpoint.cpp
Normal file
64
src/DebugServer/Gdb/CommandPackets/SetBreakpoint.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "SetBreakpoint.hpp"
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Targets/TargetBreakpoint.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using Targets::TargetBreakpoint;
|
||||
|
||||
using ResponsePackets::OkResponsePacket;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void SetBreakpoint::init() {
|
||||
if (data.size() < 6) {
|
||||
throw Exception("Unexpected SetBreakpoint packet size");
|
||||
}
|
||||
|
||||
// Z0 = SW breakpoint, Z1 = HW breakpoint
|
||||
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
|
||||
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
|
||||
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 2),
|
||||
static_cast<int>(this->data.size() - 2)
|
||||
);
|
||||
|
||||
auto packetSegments = packetData.split(",");
|
||||
if (packetSegments.size() < 3) {
|
||||
throw Exception("Unexpected number of packet segments in SetBreakpoint packet");
|
||||
}
|
||||
|
||||
bool conversionStatus = true;
|
||||
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to convert address hex value from SetBreakpoint packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SetBreakpoint::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling SetBreakpoint packet");
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = this->address;
|
||||
targetControllerConsole.setBreakpoint(breakpoint);
|
||||
|
||||
debugSession.connection.writePacket(OkResponsePacket());
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to set breakpoint on target - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/DebugServer/Gdb/CommandPackets/SetBreakpoint.hpp
Normal file
38
src/DebugServer/Gdb/CommandPackets/SetBreakpoint.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/DebugServer/Gdb/BreakpointType.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The SetBreakpoint class implements the structure for "Z" command packets. Upon receiving this command, the
|
||||
* server is expected to set a breakpoint at the specified address.
|
||||
*/
|
||||
class SetBreakpoint: public CommandPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address = 0;
|
||||
|
||||
explicit SetBreakpoint(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
34
src/DebugServer/Gdb/CommandPackets/StepExecution.cpp
Normal file
34
src/DebugServer/Gdb/CommandPackets/StepExecution.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "StepExecution.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void StepExecution::init() {
|
||||
if (this->data.size() > 1) {
|
||||
this->fromProgramCounter = static_cast<std::uint32_t>(
|
||||
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void StepExecution::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling StepExecution packet");
|
||||
|
||||
try {
|
||||
targetControllerConsole.stepTargetExecution(this->fromProgramCounter);
|
||||
debugSession.waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to step execution on target - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/DebugServer/Gdb/CommandPackets/StepExecution.hpp
Normal file
31
src/DebugServer/Gdb/CommandPackets/StepExecution.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The StepExecution class implements the structure for "s" command packets. Upon receiving this command, the
|
||||
* server is expected to step execution on the target.
|
||||
*/
|
||||
class StepExecution: public CommandPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The address from which to begin the step.
|
||||
*/
|
||||
std::optional<std::size_t> fromProgramCounter;
|
||||
|
||||
explicit StepExecution(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "src/DebugServer/Gdb/Feature.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/SupportedFeaturesResponse.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/DebugServer/Gdb/Exceptions/ClientNotSupported.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using ResponsePackets::SupportedFeaturesResponse;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Bloom::Exceptions::Exception;
|
||||
using Gdb::Exceptions::ClientNotSupported;
|
||||
|
||||
void SupportedFeaturesQuery::init() {
|
||||
/*
|
||||
* For qSupported packets, supported and unsupported GDB features are reported in the packet
|
||||
* data, where each GDB feature is separated by a semicolon.
|
||||
*/
|
||||
|
||||
// The "qSupported:" prefix occupies 11 bytes
|
||||
if (data.size() > 11) {
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 11),
|
||||
static_cast<int>(this->data.size() - 11)
|
||||
);
|
||||
|
||||
auto featureList = packetData.split(";");
|
||||
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
|
||||
|
||||
for (int i = 0; i < featureList.size(); i++) {
|
||||
auto featureString = featureList.at(i);
|
||||
|
||||
// We only care about supported features. Supported features will precede a '+' character.
|
||||
if (featureString[featureString.size() - 1] == '+') {
|
||||
featureString.remove('+');
|
||||
|
||||
auto feature = gdbFeatureMapping.valueAt(featureString.toStdString());
|
||||
if (feature.has_value()) {
|
||||
this->supportedFeatures.insert(static_cast<decltype(feature)::value_type>(feature.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SupportedFeaturesQuery::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling QuerySupport packet");
|
||||
|
||||
if (!this->isFeatureSupported(Feature::HARDWARE_BREAKPOINTS)
|
||||
&& !this->isFeatureSupported(Feature::SOFTWARE_BREAKPOINTS)
|
||||
) {
|
||||
// All GDB clients are expected to support breakpoints!
|
||||
throw ClientNotSupported("GDB client does not support HW or SW breakpoints");
|
||||
}
|
||||
|
||||
// Respond with a SupportedFeaturesResponse packet, listing all supported GDB features by Bloom
|
||||
auto response = SupportedFeaturesResponse({
|
||||
{Feature::SOFTWARE_BREAKPOINTS, std::nullopt},
|
||||
{Feature::PACKET_SIZE, std::to_string(debugSession.connection.getMaxPacketSize())},
|
||||
});
|
||||
|
||||
debugSession.connection.writePacket(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The SupportedFeaturesQuery command packet is a query from the GDB client, requesting a list of GDB features
|
||||
* supported by the GDB server. The body of this packet also contains a list GDB features that are supported or
|
||||
* unsupported by the GDB client.
|
||||
*
|
||||
* The command packet is identified by its 'qSupported' prefix in the command packet data. Following the prefix is
|
||||
* a list of GDB features that are supported/unsupported by the client. For more info on this command
|
||||
* packet, see the GDP RSP documentation.
|
||||
*
|
||||
* Responses to this command packet should take the form of a ResponsePackets::SupportedFeaturesResponse.
|
||||
*/
|
||||
class SupportedFeaturesQuery: public CommandPacket
|
||||
{
|
||||
public:
|
||||
explicit SupportedFeaturesQuery(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
[[nodiscard]] bool isFeatureSupported(const Feature& feature) const {
|
||||
return this->supportedFeatures.find(feature) != this->supportedFeatures.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::set<Feature>& getSupportedFeatures() const {
|
||||
return this->supportedFeatures;
|
||||
}
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
std::set<Feature> supportedFeatures;
|
||||
|
||||
void init();
|
||||
};
|
||||
}
|
||||
77
src/DebugServer/Gdb/CommandPackets/WriteRegister.cpp
Normal file
77
src/DebugServer/Gdb/CommandPackets/WriteRegister.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "WriteRegister.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
using Targets::TargetRegister;
|
||||
using Targets::TargetRegisterDescriptors;
|
||||
|
||||
using ResponsePackets::ResponsePacket;
|
||||
using ResponsePackets::OkResponsePacket;
|
||||
using ResponsePackets::ErrorResponsePacket;
|
||||
|
||||
using Exceptions::Exception;
|
||||
|
||||
void WriteRegister::init() {
|
||||
// The P packet updates a single register
|
||||
auto packet = std::string(this->data.begin(), this->data.end());
|
||||
|
||||
if (packet.size() < 4) {
|
||||
throw Exception("Invalid P command packet - insufficient data in packet.");
|
||||
}
|
||||
|
||||
if (packet.find('=') == std::string::npos) {
|
||||
throw Exception("Invalid P command packet - unexpected format");
|
||||
}
|
||||
|
||||
auto packetSegments = QString::fromStdString(packet).split("=");
|
||||
this->registerNumber = static_cast<int>(packetSegments.front().mid(1).toUInt(nullptr, 16));
|
||||
this->registerValue = Packet::hexToData(packetSegments.back().toStdString());
|
||||
std::reverse(this->registerValue.begin(), this->registerValue.end());
|
||||
}
|
||||
|
||||
void WriteRegister::handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) {
|
||||
Logger::debug("Handling WriteRegister packet");
|
||||
|
||||
try {
|
||||
auto targetRegisterDescriptor = debugSession.targetDescriptor.getTargetRegisterDescriptorFromNumber(this->registerNumber);
|
||||
|
||||
const auto valueSize = this->registerValue.size();
|
||||
if (valueSize > 0 && valueSize > targetRegisterDescriptor.size) {
|
||||
// Attempt to trim the higher zero-value bytes from the register value, until we reach the correct size.
|
||||
for (auto i = this->registerValue.size() - 1; i >= targetRegisterDescriptor.size; i--) {
|
||||
if (this->registerValue.at(i) != 0x00) {
|
||||
// If we reach a non-zero byte, we cannot trim anymore without changing the data
|
||||
break;
|
||||
}
|
||||
|
||||
this->registerValue.erase(this->registerValue.begin() + i);
|
||||
}
|
||||
|
||||
if (this->registerValue.size() > targetRegisterDescriptor.size) {
|
||||
const auto& gdbRegisterDescriptor = debugSession.targetDescriptor.getRegisterDescriptorFromNumber(this->registerNumber);
|
||||
throw Exception("Cannot set value for " + gdbRegisterDescriptor.name
|
||||
+ " - value size exceeds register size."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
targetControllerConsole.writeRegisters({
|
||||
TargetRegister(targetRegisterDescriptor, this->registerValue)
|
||||
});
|
||||
debugSession.connection.writePacket(OkResponsePacket());
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write registers - " + exception.getMessage());
|
||||
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/DebugServer/Gdb/CommandPackets/WriteRegister.hpp
Normal file
29
src/DebugServer/Gdb/CommandPackets/WriteRegister.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* The WriteRegisters class implements the structure for "P" packets. Upon receiving this packet,
|
||||
* server is expected to update a register value to the target.
|
||||
*/
|
||||
class WriteRegister: public CommandPacket
|
||||
{
|
||||
public:
|
||||
int registerNumber = 0;
|
||||
std::vector<unsigned char> registerValue;
|
||||
|
||||
explicit WriteRegister(const std::vector<unsigned char>& rawPacket): CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
void handle(DebugSession& debugSession, TargetControllerConsole& targetControllerConsole) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
};
|
||||
}
|
||||
269
src/DebugServer/Gdb/Connection.cpp
Normal file
269
src/DebugServer/Gdb/Connection.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
#include "Connection.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
using namespace Exceptions;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
using ResponsePackets::ResponsePacket;
|
||||
|
||||
Connection::Connection(int serverSocketFileDescriptor, EventNotifier& interruptEventNotifier)
|
||||
: interruptEventNotifier(interruptEventNotifier)
|
||||
{
|
||||
this->accept(serverSocketFileDescriptor);
|
||||
|
||||
::fcntl(
|
||||
this->socketFileDescriptor.value(),
|
||||
F_SETFL,
|
||||
::fcntl(this->socketFileDescriptor.value(), F_GETFL, 0) | O_NONBLOCK
|
||||
);
|
||||
|
||||
this->epollInstance.addEntry(
|
||||
this->socketFileDescriptor.value(),
|
||||
static_cast<std::uint16_t>(EpollEvent::READ_READY)
|
||||
);
|
||||
this->enableReadInterrupts();
|
||||
}
|
||||
|
||||
Connection::~Connection() {
|
||||
this->close();
|
||||
}
|
||||
|
||||
std::string Connection::getIpAddress() const {
|
||||
std::array<char, INET_ADDRSTRLEN> ipAddress = {};
|
||||
|
||||
if (::inet_ntop(AF_INET, &(socketAddress.sin_addr), ipAddress.data(), INET_ADDRSTRLEN) == nullptr) {
|
||||
throw Exception("Failed to convert client IP address to text form.");
|
||||
}
|
||||
|
||||
return std::string(ipAddress.data());
|
||||
}
|
||||
|
||||
std::vector<RawPacketType> Connection::readRawPackets() {
|
||||
std::vector<RawPacketType> output;
|
||||
|
||||
const auto bytes = this->read();
|
||||
|
||||
std::size_t bufferSize = bytes.size();
|
||||
for (std::size_t byteIndex = 0; byteIndex < bufferSize; byteIndex++) {
|
||||
auto byte = bytes[byteIndex];
|
||||
|
||||
if (byte == 0x03) {
|
||||
/*
|
||||
* This is an interrupt packet - it doesn't carry any of the usual packet frame bytes, so we'll just
|
||||
* add them here, in order to keep things consistent.
|
||||
*
|
||||
* Because we're effectively faking the packet frame, we can use any value for the checksum.
|
||||
*/
|
||||
output.push_back({'$', byte, '#', 'F', 'F'});
|
||||
|
||||
} else if (byte == '$') {
|
||||
// Beginning of packet
|
||||
RawPacketType rawPacket;
|
||||
rawPacket.push_back('$');
|
||||
|
||||
auto packetIndex = byteIndex;
|
||||
bool validPacket = false;
|
||||
bool isByteEscaped = false;
|
||||
|
||||
for (packetIndex++; packetIndex < bufferSize; packetIndex++) {
|
||||
byte = bytes[packetIndex];
|
||||
|
||||
if (byte == '}' && !isByteEscaped) {
|
||||
isByteEscaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isByteEscaped) {
|
||||
if (byte == '$') {
|
||||
// Unexpected end of packet
|
||||
validPacket = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (byte == '#') {
|
||||
// End of packet data
|
||||
if ((bufferSize - 1) < (packetIndex + 2)) {
|
||||
// There should be at least two more bytes in the buffer, for the checksum.
|
||||
break;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
|
||||
// Add the checksum bytes and break the loop
|
||||
rawPacket.push_back(bytes[++packetIndex]);
|
||||
rawPacket.push_back(bytes[++packetIndex]);
|
||||
validPacket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Escaped bytes are XOR'd with a 0x20 mask.
|
||||
byte ^= 0x20;
|
||||
isByteEscaped = false;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
}
|
||||
|
||||
if (validPacket) {
|
||||
// Acknowledge receipt
|
||||
this->write({'+'});
|
||||
|
||||
output.push_back(rawPacket);
|
||||
byteIndex = packetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void Connection::writePacket(const ResponsePacket& packet) {
|
||||
// Write the packet repeatedly until the GDB client acknowledges it.
|
||||
int attempts = 0;
|
||||
auto rawPacket = packet.toRawPacket();
|
||||
|
||||
do {
|
||||
if (attempts > 10) {
|
||||
throw ClientCommunicationError("Failed to write GDB response packet - client failed to "
|
||||
"acknowledge receipt - retry limit reached");
|
||||
}
|
||||
|
||||
this->write(rawPacket);
|
||||
attempts++;
|
||||
} while (this->readSingleByte(false).value_or(0) != '+');
|
||||
}
|
||||
|
||||
void Connection::accept(int serverSocketFileDescriptor) {
|
||||
int socketAddressLength = sizeof(this->socketAddress);
|
||||
|
||||
const auto socketFileDescriptor = ::accept(
|
||||
serverSocketFileDescriptor,
|
||||
reinterpret_cast<sockaddr*>(&(this->socketAddress)),
|
||||
reinterpret_cast<socklen_t*>(&socketAddressLength)
|
||||
);
|
||||
|
||||
if (socketFileDescriptor < 0) {
|
||||
throw Exception("Failed to accept GDB Remote Serial Protocol connection");
|
||||
}
|
||||
|
||||
this->socketFileDescriptor = socketFileDescriptor;
|
||||
}
|
||||
|
||||
void Connection::close() noexcept {
|
||||
if (this->socketFileDescriptor.value_or(-1) >= 0) {
|
||||
::close(this->socketFileDescriptor.value());
|
||||
this->socketFileDescriptor = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Connection::read(
|
||||
size_t bytes,
|
||||
bool interruptible,
|
||||
std::optional<std::chrono::milliseconds> timeout
|
||||
) {
|
||||
auto output = std::vector<unsigned char>();
|
||||
constexpr size_t bufferSize = 1024;
|
||||
std::array<unsigned char, bufferSize> buffer = {};
|
||||
ssize_t bytesRead = 0;
|
||||
|
||||
if (interruptible) {
|
||||
if (this->readInterruptEnabled != interruptible) {
|
||||
this->enableReadInterrupts();
|
||||
|
||||
} else {
|
||||
// Clear any previous interrupts that are still hanging around
|
||||
this->interruptEventNotifier.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->readInterruptEnabled != interruptible && !interruptible) {
|
||||
this->disableReadInterrupts();
|
||||
}
|
||||
|
||||
const auto eventFileDescriptor = this->epollInstance.waitForEvent(timeout);
|
||||
|
||||
if (
|
||||
!eventFileDescriptor.has_value()
|
||||
|| eventFileDescriptor.value() == this->interruptEventNotifier.getFileDescriptor()
|
||||
) {
|
||||
// Interrupted
|
||||
this->interruptEventNotifier.clear();
|
||||
throw DebugServerInterrupted();
|
||||
}
|
||||
|
||||
size_t bytesToRead = (bytes > bufferSize || bytes == 0) ? bufferSize : bytes;
|
||||
while (
|
||||
bytesToRead > 0
|
||||
&& (bytesRead = ::read(this->socketFileDescriptor.value(), buffer.data(), bytesToRead)) > 0
|
||||
) {
|
||||
output.insert(output.end(), buffer.begin(), buffer.begin() + bytesRead);
|
||||
|
||||
if (bytesRead < bytesToRead) {
|
||||
// No more data available
|
||||
break;
|
||||
}
|
||||
|
||||
bytesToRead = ((bytes - output.size()) > bufferSize || bytes == 0) ? bufferSize : (bytes - output.size());
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
// EOF means the client has disconnected
|
||||
throw ClientDisconnected();
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::optional<unsigned char> Connection::readSingleByte(bool interruptible) {
|
||||
auto bytes = this->read(1, interruptible, std::chrono::milliseconds(300));
|
||||
|
||||
if (!bytes.empty()) {
|
||||
return bytes.front();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Connection::write(const std::vector<unsigned char>& buffer) {
|
||||
Logger::debug("Writing packet: " + std::string(buffer.begin(), buffer.end()));
|
||||
if (::write(this->socketFileDescriptor.value(), buffer.data(), buffer.size()) == -1) {
|
||||
if (errno == EPIPE || errno == ECONNRESET) {
|
||||
// Connection was closed
|
||||
throw ClientDisconnected();
|
||||
}
|
||||
|
||||
throw ClientCommunicationError("Failed to write " + std::to_string(buffer.size())
|
||||
+ " bytes to GDP client socket - error no: "
|
||||
+ std::to_string(errno));
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::disableReadInterrupts() {
|
||||
this->epollInstance.removeEntry(this->interruptEventNotifier.getFileDescriptor());
|
||||
|
||||
this->readInterruptEnabled = false;
|
||||
}
|
||||
|
||||
void Connection::enableReadInterrupts() {
|
||||
this->epollInstance.addEntry(
|
||||
this->interruptEventNotifier.getFileDescriptor(),
|
||||
static_cast<std::uint16_t>(EpollEvent::READ_READY)
|
||||
);
|
||||
|
||||
this->readInterruptEnabled = true;
|
||||
}
|
||||
}
|
||||
159
src/DebugServer/Gdb/Connection.hpp
Normal file
159
src/DebugServer/Gdb/Connection.hpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/Helpers/EpollInstance.hpp"
|
||||
|
||||
#include "src/DebugServer/Gdb/Packet.hpp"
|
||||
#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
/**
|
||||
* The Connection class represents an active connection between the GDB RSP server and client.
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
explicit Connection(int serverSocketFileDescriptor, EventNotifier& interruptEventNotifier);
|
||||
|
||||
Connection() = delete;
|
||||
Connection(const Connection&) = delete;
|
||||
Connection& operator = (Connection&) = delete;
|
||||
|
||||
/*
|
||||
* TODO: Implement this. For now, use the move constructor.
|
||||
*/
|
||||
Connection& operator = (Connection&&) = delete;
|
||||
|
||||
Connection(Connection&& other) noexcept
|
||||
: interruptEventNotifier(other.interruptEventNotifier)
|
||||
, socketFileDescriptor(other.socketFileDescriptor)
|
||||
, epollInstance(std::move(other.epollInstance))
|
||||
, readInterruptEnabled(other.readInterruptEnabled)
|
||||
{
|
||||
other.socketFileDescriptor = std::nullopt;
|
||||
}
|
||||
|
||||
~Connection();
|
||||
|
||||
/**
|
||||
* Obtains the human readable IP address of the connected client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] std::string getIpAddress() const;
|
||||
|
||||
/**
|
||||
* Waits for incoming data from the client and returns the raw GDB packets.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<RawPacketType> readRawPackets();
|
||||
|
||||
/**
|
||||
* Sends a response packet to the client.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
void writePacket(const ResponsePackets::ResponsePacket& packet);
|
||||
|
||||
[[nodiscard]] int getMaxPacketSize() const {
|
||||
return this->maxPacketSize;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<int> socketFileDescriptor;
|
||||
|
||||
struct sockaddr_in socketAddress = {};
|
||||
int maxPacketSize = 1024;
|
||||
|
||||
/**
|
||||
* The interruptEventNotifier (instance of EventNotifier) allows us to interrupt blocking I/O calls on this
|
||||
* connection's socket. Under the hood, the EventNotifier class is just an RAII wrapper for a Linux eventfd
|
||||
* object.
|
||||
*
|
||||
* The file descriptors of the eventfd object and the socket are both added to an EpollInstance (which is just
|
||||
* an RAII wrapper for a Linux epoll instance). The EpollInstance object is then used to wait for events on
|
||||
* either of the two file descriptors. See any of the Connection I/O functions (e.g Connection::read()) for
|
||||
* more on this.
|
||||
*
|
||||
* See the EventNotifier and EpollInstance classes for more.
|
||||
*/
|
||||
EventNotifier& interruptEventNotifier;
|
||||
EpollInstance epollInstance = EpollInstance();
|
||||
|
||||
bool readInterruptEnabled = false;
|
||||
|
||||
/**
|
||||
* Accepts a connection on serverSocketFileDescriptor.
|
||||
*
|
||||
* @param serverSocketFileDescriptor
|
||||
*/
|
||||
void accept(int serverSocketFileDescriptor);
|
||||
|
||||
/**
|
||||
* Closes the connection with the client.
|
||||
*/
|
||||
void close() noexcept;
|
||||
|
||||
/**
|
||||
* Reads data from the client into a raw buffer.
|
||||
*
|
||||
* @param bytes
|
||||
* Number of bytes to read.
|
||||
*
|
||||
* @param interruptible
|
||||
* If this flag is set to false, no other component within Bloom will be able to gracefully interrupt
|
||||
* the read (via means of this->interruptEventNotifier). This flag has no effect if this->readInterruptEnabled
|
||||
* is false.
|
||||
*
|
||||
* @param timeout
|
||||
* The timeout in milliseconds. If not supplied, no timeout will be applied.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> read(
|
||||
std::size_t bytes = 0,
|
||||
bool interruptible = true,
|
||||
std::optional<std::chrono::milliseconds> timeout = std::nullopt
|
||||
);
|
||||
|
||||
/**
|
||||
* Does the same as Connection::read(), but only reads a single byte.
|
||||
*
|
||||
* @param interruptible
|
||||
* See Connection::read().
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::optional<unsigned char> readSingleByte(bool interruptible = true);
|
||||
|
||||
/**
|
||||
* Writes data from a raw buffer to the client connection.
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void write(const std::vector<unsigned char>& buffer);
|
||||
|
||||
/**
|
||||
* Removes this->interruptEventNotifier's file descriptor from the EpollInstance (this->epollInstance),
|
||||
* preventing subsequent I/O operations on this->socketFileDescriptor from being interrupted.
|
||||
*/
|
||||
void disableReadInterrupts();
|
||||
|
||||
/**
|
||||
* Inserts this->interruptEventNotifier's file descriptor into the EpollInstance (this->epollInstance),
|
||||
* allowing for subsequent I/O operations on this->socketFileDescriptor to be interrupted.
|
||||
*/
|
||||
void enableReadInterrupts();
|
||||
};
|
||||
}
|
||||
15
src/DebugServer/Gdb/DebugSession.cpp
Normal file
15
src/DebugServer/Gdb/DebugSession.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "DebugSession.hpp"
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
DebugSession::DebugSession(Connection&& connection, const TargetDescriptor& targetDescriptor)
|
||||
: connection(std::move(connection))
|
||||
, targetDescriptor(targetDescriptor)
|
||||
{}
|
||||
|
||||
void DebugSession::terminate() {
|
||||
|
||||
}
|
||||
}
|
||||
27
src/DebugServer/Gdb/DebugSession.hpp
Normal file
27
src/DebugServer/Gdb/DebugSession.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "TargetDescriptor.hpp"
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
class DebugSession
|
||||
{
|
||||
public:
|
||||
Connection connection;
|
||||
|
||||
const TargetDescriptor& targetDescriptor;
|
||||
|
||||
/**
|
||||
* When the GDB client is waiting for the target to halt, this is set to true so we know when to notify the
|
||||
* client.
|
||||
*/
|
||||
bool waitingForBreak = false;
|
||||
|
||||
DebugSession(Connection&& connection, const TargetDescriptor& targetDescriptor);
|
||||
|
||||
void terminate();
|
||||
};
|
||||
}
|
||||
26
src/DebugServer/Gdb/Exceptions/ClientCommunicationError.hpp
Normal file
26
src/DebugServer/Gdb/Exceptions/ClientCommunicationError.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::Exceptions
|
||||
{
|
||||
/**
|
||||
* In the event that communication between the GDB RSP client and Bloom fails, a ClientCommunicationFailure
|
||||
* exception should be thrown. The GDB debug server handles this by severing the connection.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientCommunicationError: public Bloom::Exceptions::Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientCommunicationError(const std::string& message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError(const char* message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError() = default;
|
||||
};
|
||||
}
|
||||
27
src/DebugServer/Gdb/Exceptions/ClientDisconnected.hpp
Normal file
27
src/DebugServer/Gdb/Exceptions/ClientDisconnected.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::Exceptions
|
||||
{
|
||||
/**
|
||||
* When a GDB RSP client unexpectedly drops the connection in the middle of an IO operation, a ClientDisconnected
|
||||
* exception should be thrown. The GDB debug server handles this by clearing the connection and waiting for a new
|
||||
* one.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientDisconnected: public Bloom::Exceptions::Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientDisconnected(const std::string& message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientDisconnected(const char* message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientDisconnected() = default;
|
||||
};
|
||||
}
|
||||
26
src/DebugServer/Gdb/Exceptions/ClientNotSupported.hpp
Normal file
26
src/DebugServer/Gdb/Exceptions/ClientNotSupported.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::Exceptions
|
||||
{
|
||||
/**
|
||||
* In the event that the GDB debug server determines that the connected client cannot be served,
|
||||
* the ClientNotSupported exception should be thrown.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientNotSupported: public Bloom::Exceptions::Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientNotSupported(const std::string& message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientNotSupported(const char* message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientNotSupported() = default;
|
||||
};
|
||||
}
|
||||
26
src/DebugServer/Gdb/Exceptions/DebugSessionAborted.hpp
Normal file
26
src/DebugServer/Gdb/Exceptions/DebugSessionAborted.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::Exceptions
|
||||
{
|
||||
/**
|
||||
* The GDB server may abort a debug session with the client, if an internal error occurs. One circumstance where
|
||||
* this can happen is when the TargetController is not able to service the debug session for whatever reason.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class DebugSessionAborted: public Bloom::Exceptions::Exception
|
||||
{
|
||||
public:
|
||||
explicit DebugSessionAborted(const std::string& message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit DebugSessionAborted(const char* message): Bloom::Exceptions::Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit DebugSessionAborted() = default;
|
||||
};
|
||||
}
|
||||
23
src/DebugServer/Gdb/Feature.hpp
Normal file
23
src/DebugServer/Gdb/Feature.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
enum class Feature: int
|
||||
{
|
||||
SOFTWARE_BREAKPOINTS,
|
||||
HARDWARE_BREAKPOINTS,
|
||||
PACKET_SIZE,
|
||||
MEMORY_MAP_READ,
|
||||
};
|
||||
|
||||
static inline BiMap<Feature, std::string> getGdbFeatureToNameMapping() {
|
||||
return BiMap<Feature, std::string>{
|
||||
{Feature::HARDWARE_BREAKPOINTS, "hwbreak"},
|
||||
{Feature::SOFTWARE_BREAKPOINTS, "swbreak"},
|
||||
{Feature::PACKET_SIZE, "PacketSize"},
|
||||
{Feature::MEMORY_MAP_READ, "qXfer:memory-map:read"},
|
||||
};
|
||||
}
|
||||
}
|
||||
19
src/DebugServer/Gdb/GdbDebugServerConfig.cpp
Normal file
19
src/DebugServer/Gdb/GdbDebugServerConfig.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "GdbDebugServerConfig.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
GdbDebugServerConfig::GdbDebugServerConfig(const DebugServerConfig& debugServerConfig)
|
||||
: DebugServerConfig(debugServerConfig)
|
||||
{
|
||||
if (debugServerConfig.jsonObject.contains("ipAddress")) {
|
||||
this->listeningAddress = debugServerConfig.jsonObject.value("ipAddress").toString().toStdString();
|
||||
}
|
||||
|
||||
if (debugServerConfig.jsonObject.contains("port")) {
|
||||
const auto portValue = debugServerConfig.jsonObject.value("port");
|
||||
this->listeningPortNumber = static_cast<std::uint16_t>(
|
||||
portValue.isString() ? portValue.toString().toInt(nullptr, 10) : portValue.toInt()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/DebugServer/Gdb/GdbDebugServerConfig.hpp
Normal file
29
src/DebugServer/Gdb/GdbDebugServerConfig.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/ProjectConfig.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
/**
|
||||
* Extending the generic DebugServerConfig struct to accommodate GDB debug server configuration parameters.
|
||||
*/
|
||||
class GdbDebugServerConfig: public DebugServerConfig
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The port number for the GDB server to listen on.
|
||||
*
|
||||
* This parameter is optional. If not specified, the default value set here will be used.
|
||||
*/
|
||||
std::uint16_t listeningPortNumber = 1442;
|
||||
|
||||
/**
|
||||
* The address for the GDB server to listen on.
|
||||
*
|
||||
* This parameter is optional. If not specified, the default value set here will be used.
|
||||
*/
|
||||
std::string listeningAddress = "127.0.0.1";
|
||||
|
||||
explicit GdbDebugServerConfig(const DebugServerConfig& debugServerConfig);
|
||||
};
|
||||
}
|
||||
292
src/DebugServer/Gdb/GdbRspDebugServer.cpp
Normal file
292
src/DebugServer/Gdb/GdbRspDebugServer.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "GdbRspDebugServer.hpp"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientNotSupported.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "Exceptions/DebugSessionAborted.hpp"
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
|
||||
// Command packets
|
||||
#include "CommandPackets/CommandPacket.hpp"
|
||||
#include "CommandPackets/SupportedFeaturesQuery.hpp"
|
||||
#include "CommandPackets/InterruptExecution.hpp"
|
||||
#include "CommandPackets/ContinueExecution.hpp"
|
||||
#include "CommandPackets/StepExecution.hpp"
|
||||
#include "CommandPackets/ReadRegisters.hpp"
|
||||
#include "CommandPackets/WriteRegister.hpp"
|
||||
#include "CommandPackets/SetBreakpoint.hpp"
|
||||
#include "CommandPackets/RemoveBreakpoint.hpp"
|
||||
|
||||
// Response packets
|
||||
#include "ResponsePackets/TargetStopped.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
using namespace Exceptions;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
using CommandPackets::CommandPacket;
|
||||
|
||||
GdbRspDebugServer::GdbRspDebugServer(
|
||||
const DebugServerConfig& debugServerConfig,
|
||||
EventListener& eventListener
|
||||
)
|
||||
: debugServerConfig(GdbDebugServerConfig(debugServerConfig))
|
||||
, eventListener(eventListener)
|
||||
, interruptEventNotifier(eventListener.getInterruptEventNotifier())
|
||||
{
|
||||
assert(this->interruptEventNotifier != nullptr && this->interruptEventNotifier->isInitialised());
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::init() {
|
||||
this->socketAddress.sin_family = AF_INET;
|
||||
this->socketAddress.sin_port = htons(this->debugServerConfig->listeningPortNumber);
|
||||
|
||||
if (::inet_pton(
|
||||
AF_INET,
|
||||
this->debugServerConfig->listeningAddress.c_str(),
|
||||
&(this->socketAddress.sin_addr)
|
||||
) == 0
|
||||
) {
|
||||
// Invalid IP address
|
||||
throw InvalidConfig(
|
||||
"Invalid IP address provided in config file: (\"" + this->debugServerConfig->listeningAddress
|
||||
+ "\")"
|
||||
);
|
||||
}
|
||||
|
||||
int socketFileDescriptor = 0;
|
||||
|
||||
if ((socketFileDescriptor = ::socket(AF_INET, SOCK_STREAM, 0)) == 0) {
|
||||
throw Exception("Failed to create socket file descriptor.");
|
||||
}
|
||||
|
||||
if (::setsockopt(
|
||||
socketFileDescriptor,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
&(this->enableReuseAddressSocketOption),
|
||||
sizeof(this->enableReuseAddressSocketOption)
|
||||
) < 0
|
||||
) {
|
||||
Logger::error("Failed to set socket SO_REUSEADDR option.");
|
||||
}
|
||||
|
||||
if (::bind(
|
||||
socketFileDescriptor,
|
||||
reinterpret_cast<const sockaddr*>(&(this->socketAddress)),
|
||||
sizeof(this->socketAddress)
|
||||
) < 0
|
||||
) {
|
||||
throw Exception("Failed to bind address. The selected port number ("
|
||||
+ std::to_string(this->debugServerConfig->listeningPortNumber) + ") may be in use.");
|
||||
}
|
||||
|
||||
this->serverSocketFileDescriptor = socketFileDescriptor;
|
||||
|
||||
this->epollInstance.addEntry(
|
||||
this->serverSocketFileDescriptor,
|
||||
static_cast<std::uint16_t>(EpollEvent::READ_READY)
|
||||
);
|
||||
|
||||
this->epollInstance.addEntry(
|
||||
this->interruptEventNotifier->getFileDescriptor(),
|
||||
static_cast<std::uint16_t>(EpollEvent::READ_READY)
|
||||
);
|
||||
|
||||
Logger::info("GDB RSP address: " + this->debugServerConfig->listeningAddress);
|
||||
Logger::info("GDB RSP port: " + std::to_string(this->debugServerConfig->listeningPortNumber));
|
||||
|
||||
this->eventListener.registerCallbackForEventType<Events::TargetControllerStateReported>(
|
||||
std::bind(&GdbRspDebugServer::onTargetControllerStateReported, this, std::placeholders::_1)
|
||||
);
|
||||
|
||||
this->eventListener.registerCallbackForEventType<Events::TargetExecutionStopped>(
|
||||
std::bind(&GdbRspDebugServer::onTargetExecutionStopped, this, std::placeholders::_1)
|
||||
);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::close() {
|
||||
this->terminateActiveDebugSession();
|
||||
|
||||
if (this->serverSocketFileDescriptor > 0) {
|
||||
::close(this->serverSocketFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::run() {
|
||||
try {
|
||||
if (!this->activeDebugSession.has_value()) {
|
||||
Logger::info("Waiting for GDB RSP connection");
|
||||
|
||||
auto connection = this->waitForConnection();
|
||||
|
||||
if (!connection.has_value()) {
|
||||
// Likely an interrupt
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info("Accepted GDP RSP connection from " + connection->getIpAddress());
|
||||
|
||||
this->activeDebugSession.emplace(
|
||||
DebugSession(std::move(connection.value()), this->getGdbTargetDescriptor())
|
||||
);
|
||||
|
||||
EventManager::triggerEvent(std::make_shared<Events::DebugSessionStarted>());
|
||||
|
||||
/*
|
||||
* Before proceeding with a new debug session, we must ensure that the TargetController is able to
|
||||
* service it.
|
||||
*/
|
||||
if (!this->targetControllerConsole.isTargetControllerInService()) {
|
||||
this->terminateActiveDebugSession();
|
||||
throw DebugSessionAborted("TargetController not in service");
|
||||
}
|
||||
}
|
||||
|
||||
auto commandPacket = this->waitForCommandPacket();
|
||||
|
||||
if (commandPacket == nullptr) {
|
||||
// Likely an interrupt
|
||||
return;
|
||||
}
|
||||
|
||||
commandPacket->handle(this->activeDebugSession.value(), this->targetControllerConsole);
|
||||
|
||||
} catch (const ClientDisconnected&) {
|
||||
Logger::info("GDB RSP client disconnected");
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const ClientCommunicationError& exception) {
|
||||
Logger::error(
|
||||
"GDB RSP client communication error - " + exception.getMessage() + " - closing connection"
|
||||
);
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const ClientNotSupported& exception) {
|
||||
Logger::error("Invalid GDB RSP client - " + exception.getMessage() + " - closing connection");
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const DebugSessionAborted& exception) {
|
||||
Logger::warning("GDB debug session aborted - " + exception.getMessage());
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const DebugServerInterrupted&) {
|
||||
// Server was interrupted
|
||||
Logger::debug("GDB RSP interrupted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Connection> GdbRspDebugServer::waitForConnection() {
|
||||
if (::listen(this->serverSocketFileDescriptor, 3) != 0) {
|
||||
throw Exception("Failed to listen on server socket");
|
||||
}
|
||||
|
||||
const auto eventFileDescriptor = this->epollInstance.waitForEvent();
|
||||
|
||||
if (
|
||||
!eventFileDescriptor.has_value()
|
||||
|| eventFileDescriptor.value() == this->interruptEventNotifier->getFileDescriptor()
|
||||
) {
|
||||
this->interruptEventNotifier->clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional<Connection>(this->serverSocketFileDescriptor, *(this->interruptEventNotifier));
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandPacket> GdbRspDebugServer::waitForCommandPacket() {
|
||||
const auto rawPackets = this->activeDebugSession->connection.readRawPackets();
|
||||
|
||||
if (rawPackets.empty()) {
|
||||
// The wait was interrupted
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We only process the last packet - any others will probably be duplicates from an impatient client.
|
||||
return this->resolveCommandPacket(rawPackets.back());
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandPacket> GdbRspDebugServer::resolveCommandPacket(const RawPacketType& rawPacket) {
|
||||
if (rawPacket.size() == 5 && rawPacket[1] == 0x03) {
|
||||
// This is an interrupt request - create a fake packet for it
|
||||
return std::make_unique<CommandPackets::InterruptExecution>(rawPacket);
|
||||
}
|
||||
|
||||
const auto rawPacketString = std::string(rawPacket.begin(), rawPacket.end());
|
||||
|
||||
if (rawPacketString.size() >= 2) {
|
||||
/*
|
||||
* First byte of the raw packet will be 0x24 ('$'), so find() should return 1, not 0, when
|
||||
* looking for a command identifier string.
|
||||
*/
|
||||
if (rawPacketString.find("qSupported") == 1) {
|
||||
return std::make_unique<CommandPackets::SupportedFeaturesQuery>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'g' || rawPacketString[1] == 'p') {
|
||||
return std::make_unique<CommandPackets::ReadRegisters>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'P') {
|
||||
return std::make_unique<CommandPackets::WriteRegister>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'c') {
|
||||
return std::make_unique<CommandPackets::ContinueExecution>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 's') {
|
||||
return std::make_unique<CommandPackets::StepExecution>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'Z') {
|
||||
return std::make_unique<CommandPackets::SetBreakpoint>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'z') {
|
||||
return std::make_unique<CommandPackets::RemoveBreakpoint>(rawPacket);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<CommandPacket>(rawPacket);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::terminateActiveDebugSession() {
|
||||
if (this->activeDebugSession.has_value()) {
|
||||
this->activeDebugSession->terminate();
|
||||
this->activeDebugSession = std::nullopt;
|
||||
|
||||
EventManager::triggerEvent(std::make_shared<Events::DebugSessionFinished>());
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetControllerStateReported(const Events::TargetControllerStateReported& event) {
|
||||
if (event.state == TargetControllerState::SUSPENDED && this->activeDebugSession.has_value()) {
|
||||
Logger::warning("Terminating debug session - TargetController suspended unexpectedly");
|
||||
this->terminateActiveDebugSession();
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetExecutionStopped(const Events::TargetExecutionStopped&) {
|
||||
if (this->activeDebugSession.has_value() && this->activeDebugSession->waitingForBreak) {
|
||||
this->activeDebugSession->connection.writePacket(
|
||||
ResponsePackets::TargetStopped(Signal::TRAP)
|
||||
);
|
||||
this->activeDebugSession->waitingForBreak = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/DebugServer/Gdb/GdbRspDebugServer.hpp
Normal file
175
src/DebugServer/Gdb/GdbRspDebugServer.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <optional>
|
||||
|
||||
#include "src/DebugServer/ServerInterface.hpp"
|
||||
|
||||
#include "GdbDebugServerConfig.hpp"
|
||||
#include "src/EventManager/EventListener.hpp"
|
||||
#include "src/Helpers/EpollInstance.hpp"
|
||||
#include "src/TargetController/TargetControllerConsole.hpp"
|
||||
|
||||
#include "Connection.hpp"
|
||||
#include "TargetDescriptor.hpp"
|
||||
#include "DebugSession.hpp"
|
||||
#include "Signal.hpp"
|
||||
#include "RegisterDescriptor.hpp"
|
||||
#include "Feature.hpp"
|
||||
#include "CommandPackets/CommandPacket.hpp"
|
||||
|
||||
#include "src/EventManager/Events/TargetControllerStateReported.hpp"
|
||||
#include "src/EventManager/Events/TargetExecutionStopped.hpp"
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
/**
|
||||
* The GdbRspDebugServer is an implementation of a GDB server using the GDB Remote Serial Protocol.
|
||||
*
|
||||
* This DebugServer employs TCP/IP sockets to interface with GDB clients. The listening address can be configured
|
||||
* in the user's project config file.
|
||||
*
|
||||
* See https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html for more info on the GDB Remote
|
||||
* Serial Protocol.
|
||||
*
|
||||
* @TODO: This could do with some cleaning.
|
||||
*/
|
||||
class GdbRspDebugServer: public ServerInterface
|
||||
{
|
||||
public:
|
||||
explicit GdbRspDebugServer(
|
||||
const DebugServerConfig& debugServerConfig,
|
||||
EventListener& eventListener
|
||||
);
|
||||
|
||||
[[nodiscard]] std::string getName() const override {
|
||||
return "GDB Remote Serial Protocol DebugServer";
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares the GDB server for listing on the selected address and port.
|
||||
*/
|
||||
void init() override;
|
||||
|
||||
/**
|
||||
* Terminates any active debug session and closes the listening socket.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* Waits for a connection from a GDB client or services an active one.
|
||||
*
|
||||
* This function will return when any blocking operation is interrupted via this->interruptEventNotifier.
|
||||
*/
|
||||
void run() override;
|
||||
|
||||
protected:
|
||||
std::optional<GdbDebugServerConfig> debugServerConfig;
|
||||
|
||||
EventListener& eventListener;
|
||||
|
||||
EventNotifier* interruptEventNotifier = nullptr;
|
||||
|
||||
TargetControllerConsole targetControllerConsole = TargetControllerConsole(this->eventListener);
|
||||
|
||||
/**
|
||||
* Listening socket address
|
||||
*/
|
||||
struct sockaddr_in socketAddress = {};
|
||||
|
||||
/**
|
||||
* Listening socket file descriptor
|
||||
*/
|
||||
int serverSocketFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* When waiting for a connection, we don't listen on the this->serverSocketFileDescriptor directly. Instead,
|
||||
* we use an EpollInstance to monitor both this->serverSocketFileDescriptor and this->interruptEventNotifier.
|
||||
* This allows us to interrupt any blocking socket IO calls when EventNotifier::notify() is called on
|
||||
* this->interruptEventNotifier.
|
||||
*
|
||||
* See GdbRspDebugServer::init()
|
||||
* See DebugServer::interruptEventNotifier
|
||||
* See EpollInstance
|
||||
* See EventNotifier
|
||||
*/
|
||||
EpollInstance epollInstance = EpollInstance();
|
||||
|
||||
/**
|
||||
* SO_REUSEADDR option value for listening socket.
|
||||
*/
|
||||
int enableReuseAddressSocketOption = 1;
|
||||
|
||||
/**
|
||||
* 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<DebugSession> activeDebugSession;
|
||||
|
||||
/**
|
||||
* Waits for a GDB client to connect on the listening socket.
|
||||
*
|
||||
* This function may return an std::nullopt, if the waiting was interrupted by some other event.
|
||||
*/
|
||||
std::optional<Connection> waitForConnection();
|
||||
|
||||
/**
|
||||
* Waits for a command packet from the connected GDB client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::unique_ptr<CommandPackets::CommandPacket> waitForCommandPacket();
|
||||
|
||||
/**
|
||||
* Should construct a derived instance of the CommandPackets::CommandPacket class, from a raw packet.
|
||||
*
|
||||
* Derived implementations of this GDB server class can override this function to include support for more
|
||||
* specific GDB commands. For example, the AVR GDB implementation overrides this function to support AVR
|
||||
* specific implementations of the read and write memory GDB commands.
|
||||
*
|
||||
* This function should never return a nullptr. If the command is unknown, it should simply return an
|
||||
* instance of the CommandPackets::CommandPacket base class. The handler (CommandPacket::handle()) for the base
|
||||
* class will handle unknown packets by responding with an empty response packet (as is expected by the GDB
|
||||
* client).
|
||||
*
|
||||
* @param rawPacket
|
||||
* @return
|
||||
*/
|
||||
virtual std::unique_ptr<CommandPackets::CommandPacket> resolveCommandPacket(const RawPacketType& rawPacket);
|
||||
|
||||
/**
|
||||
* Terminates any active debug session (if any) by closing the connection to the GDB client.
|
||||
*/
|
||||
void terminateActiveDebugSession();
|
||||
|
||||
/**
|
||||
* Should return the GDB target descriptor for the connected target.
|
||||
*
|
||||
* NOTE: This function returns a target descriptor specific to GDB and the target. It's not the same data
|
||||
* struct as Targets::TargetDescriptor. But the GDB target descriptor does hold an instance to
|
||||
* Targets::TargetDescriptor. See the Gdb::TargetDescriptor::targetDescriptor class member.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual const TargetDescriptor& getGdbTargetDescriptor() = 0;
|
||||
|
||||
/**
|
||||
* Responds to any unexpected TargetController state changes.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
void onTargetControllerStateReported(const Events::TargetControllerStateReported& event);
|
||||
|
||||
/**
|
||||
* 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&);
|
||||
};
|
||||
}
|
||||
132
src/DebugServer/Gdb/Packet.hpp
Normal file
132
src/DebugServer/Gdb/Packet.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <QString>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
using RawPacketType = std::vector<unsigned char>;
|
||||
|
||||
/**
|
||||
* The Packet class implements the data structure for GDB RSP packets.
|
||||
*
|
||||
* Fore more information on the packet data structure, see https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
|
||||
*/
|
||||
class Packet
|
||||
{
|
||||
public:
|
||||
explicit Packet(const RawPacketType& rawPacket) {
|
||||
this->init(rawPacket);
|
||||
}
|
||||
|
||||
Packet() = default;
|
||||
virtual ~Packet() = default;
|
||||
|
||||
Packet(const Packet& other) = default;
|
||||
Packet(Packet&& other) = default;
|
||||
|
||||
Packet& operator = (const Packet& other) = default;
|
||||
Packet& operator = (Packet&& other) = default;
|
||||
|
||||
[[nodiscard]] virtual std::vector<unsigned char> getData() const {
|
||||
return this->data;
|
||||
}
|
||||
|
||||
void setData(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a raw packet.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] std::vector<unsigned char> toRawPacket() const {
|
||||
std::vector<unsigned char> packet = {'$'};
|
||||
auto data = this->getData();
|
||||
|
||||
for (const auto& byte : data) {
|
||||
// Escape $ and # characters
|
||||
switch (byte) {
|
||||
case '$':
|
||||
case '#': {
|
||||
packet.push_back('}');
|
||||
packet.push_back(byte ^ 0x20);
|
||||
}
|
||||
default: {
|
||||
packet.push_back(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto dataSum = std::accumulate(packet.begin() + 1, packet.end(), 0);
|
||||
packet.push_back('#');
|
||||
auto checkSum = QStringLiteral("%1").arg(dataSum % 256, 2, 16, QLatin1Char('0')).toStdString();
|
||||
|
||||
if (checkSum.size() < 2) {
|
||||
packet.push_back('0');
|
||||
|
||||
if (checkSum.size() < 1) {
|
||||
packet.push_back('0');
|
||||
} else {
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[0]));
|
||||
}
|
||||
}
|
||||
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[0]));
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[1]));
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw data to hexadecimal form, the form in which responses are expected to be delivered from the
|
||||
* server.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
static std::string dataToHex(const std::vector<unsigned char>& data) {
|
||||
std::stringstream stream;
|
||||
stream << std::hex << std::setfill('0');
|
||||
|
||||
for (const auto& byte : data) {
|
||||
stream << std::setw(2) << static_cast<unsigned int>(byte);
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts data in hexadecimal form to raw data.
|
||||
*
|
||||
* @param hexData
|
||||
* @return
|
||||
*/
|
||||
static std::vector<unsigned char> hexToData(const std::string& hexData) {
|
||||
std::vector<unsigned char> output;
|
||||
|
||||
for (auto i = 0; i < hexData.size(); i += 2) {
|
||||
auto hexByte = std::string((hexData.begin() + i), (hexData.begin() + i + 2));
|
||||
output.push_back(static_cast<unsigned char>(std::stoi(hexByte, nullptr, 16)));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
void init(const RawPacketType& rawPacket) {
|
||||
this->data.insert(
|
||||
this->data.begin(),
|
||||
rawPacket.begin() + 1,
|
||||
rawPacket.end() - 3
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
67
src/DebugServer/Gdb/RegisterDescriptor.hpp
Normal file
67
src/DebugServer/Gdb/RegisterDescriptor.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
using GdbRegisterNumberType = int;
|
||||
|
||||
/*
|
||||
* GDB defines a set of registers for each target architecture.
|
||||
*
|
||||
* Each register in the set is assigned a register number, which is used to identify the register.
|
||||
*/
|
||||
struct RegisterDescriptor
|
||||
{
|
||||
GdbRegisterNumberType number;
|
||||
std::uint16_t size;
|
||||
std::string name;
|
||||
|
||||
RegisterDescriptor(GdbRegisterNumberType number, std::uint16_t size, const std::string& name)
|
||||
: number(number), size(size), name(name) {};
|
||||
|
||||
bool operator == (const RegisterDescriptor& other) const {
|
||||
return this->number == other.number;
|
||||
}
|
||||
|
||||
bool operator != (const RegisterDescriptor& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator < (const RegisterDescriptor& rhs) const {
|
||||
return this->number < rhs.number;
|
||||
}
|
||||
|
||||
bool operator > (const RegisterDescriptor& rhs) const {
|
||||
return rhs < *this;
|
||||
}
|
||||
|
||||
bool operator <= (const RegisterDescriptor& rhs) const {
|
||||
return !(rhs < *this);
|
||||
}
|
||||
|
||||
bool operator >= (const RegisterDescriptor& rhs) const {
|
||||
return !(*this < rhs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
/**
|
||||
* Hashing function for RegisterDescriptor type.
|
||||
*
|
||||
* This is required in order to use RegisterDescriptor as a key in an std::unordered_map (see the BiMap
|
||||
* class).
|
||||
*/
|
||||
template<>
|
||||
class hash<Bloom::DebugServer::Gdb::RegisterDescriptor>
|
||||
{
|
||||
public:
|
||||
std::size_t operator () (const Bloom::DebugServer::Gdb::RegisterDescriptor& descriptor) const {
|
||||
// We use the GDB register number as the hash, as it is unique to the register.
|
||||
return static_cast<size_t>(descriptor.number);
|
||||
}
|
||||
};
|
||||
}
|
||||
19
src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp
Normal file
19
src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* Error response packet expected by the GDB client, to indicate an error, in response to certain commands.
|
||||
*/
|
||||
class ErrorResponsePacket: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
ErrorResponsePacket() = default;
|
||||
|
||||
[[nodiscard]] std::vector<unsigned char> getData() const override {
|
||||
return {'E', '0', '1'};
|
||||
}
|
||||
};
|
||||
}
|
||||
19
src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp
Normal file
19
src/DebugServer/Gdb/ResponsePackets/OkResponsePacket.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* OK response packet expected by the GDB client, in response to certain commands.
|
||||
*/
|
||||
class OkResponsePacket: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
OkResponsePacket() = default;
|
||||
|
||||
[[nodiscard]] std::vector<unsigned char> getData() const override {
|
||||
return {'O', 'K'};
|
||||
}
|
||||
};
|
||||
}
|
||||
22
src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp
Normal file
22
src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServer/Gdb/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* Upon receiving a CommandPacket from the connected GDB RSP client, the server is expected to respond with a
|
||||
* response packet.
|
||||
*/
|
||||
class ResponsePacket: public Packet
|
||||
{
|
||||
public:
|
||||
ResponsePacket() = default;
|
||||
explicit ResponsePacket(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "SupportedFeaturesResponse.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::ResponsePackets
|
||||
{
|
||||
std::vector<unsigned char> SupportedFeaturesResponse::getData() const {
|
||||
std::string output = "qSupported:";
|
||||
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
|
||||
|
||||
for (const auto& supportedFeature : this->supportedFeatures) {
|
||||
auto featureString = gdbFeatureMapping.valueAt(supportedFeature.first);
|
||||
|
||||
if (featureString.has_value()) {
|
||||
if (supportedFeature.second.has_value()) {
|
||||
output.append(featureString.value() + "=" + supportedFeature.second.value() + ";");
|
||||
|
||||
} else {
|
||||
output.append(featureString.value() + "+;");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* The SupportedFeaturesResponse class implements the response packet structure for the "qSupported" command.
|
||||
*/
|
||||
class SupportedFeaturesResponse: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
SupportedFeaturesResponse() = default;
|
||||
explicit SupportedFeaturesResponse(std::set<std::pair<Feature, std::optional<std::string>>> supportedFeatures)
|
||||
: supportedFeatures(std::move(supportedFeatures)) {};
|
||||
|
||||
[[nodiscard]] std::vector<unsigned char> getData() const override;
|
||||
|
||||
protected:
|
||||
std::set<std::pair<Feature, std::optional<std::string>>> supportedFeatures;
|
||||
};
|
||||
}
|
||||
37
src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp
Normal file
37
src/DebugServer/Gdb/ResponsePackets/TargetStopped.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Signal.hpp"
|
||||
#include "../StopReason.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* The TargetStopped class implements the response packet structure for any commands that expect a "StopReply"
|
||||
* packet in response.
|
||||
*/
|
||||
class TargetStopped: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
Signal signal;
|
||||
std::optional<StopReason> stopReason;
|
||||
|
||||
explicit TargetStopped(Signal signal): signal(signal) {}
|
||||
|
||||
[[nodiscard]] std::vector<unsigned char> getData() const override {
|
||||
std::string output = "T" + Packet::dataToHex({static_cast<unsigned char>(this->signal)});
|
||||
|
||||
if (this->stopReason.has_value()) {
|
||||
auto stopReasonMapping = getStopReasonToNameMapping();
|
||||
auto stopReasonName = stopReasonMapping.valueAt(this->stopReason.value());
|
||||
|
||||
if (stopReasonName.has_value()) {
|
||||
output += stopReasonName.value() + ":;";
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
};
|
||||
}
|
||||
10
src/DebugServer/Gdb/Signal.hpp
Normal file
10
src/DebugServer/Gdb/Signal.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
enum class Signal: unsigned char
|
||||
{
|
||||
TRAP = 5,
|
||||
INTERRUPTED = 2,
|
||||
};
|
||||
}
|
||||
19
src/DebugServer/Gdb/StopReason.hpp
Normal file
19
src/DebugServer/Gdb/StopReason.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
enum class StopReason: int
|
||||
{
|
||||
SOFTWARE_BREAKPOINT = 0,
|
||||
HARDWARE_BREAKPOINT = 1,
|
||||
};
|
||||
|
||||
static inline BiMap<StopReason, std::string> getStopReasonToNameMapping() {
|
||||
return BiMap<StopReason, std::string>({
|
||||
{StopReason::HARDWARE_BREAKPOINT, "hwbreak"},
|
||||
{StopReason::SOFTWARE_BREAKPOINT, "swbreak"},
|
||||
});
|
||||
}
|
||||
}
|
||||
58
src/DebugServer/Gdb/TargetDescriptor.hpp
Normal file
58
src/DebugServer/Gdb/TargetDescriptor.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "src/Targets/TargetDescriptor.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
#include "RegisterDescriptor.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
struct TargetDescriptor
|
||||
{
|
||||
Targets::TargetDescriptor targetDescriptor;
|
||||
|
||||
explicit TargetDescriptor(const Targets::TargetDescriptor& targetDescriptor)
|
||||
: targetDescriptor(targetDescriptor)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Should retrieve the GDB register number, given a target register descriptor. Or std::nullopt if the target
|
||||
* register descriptor isn't mapped to any GDB register.
|
||||
*
|
||||
* @param registerDescriptor
|
||||
* @return
|
||||
*/
|
||||
virtual std::optional<GdbRegisterNumberType> getRegisterNumberFromTargetRegisterDescriptor(
|
||||
const Targets::TargetRegisterDescriptor& registerDescriptor
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* Should retrieve the GDB register descriptor for a given GDB register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
virtual const RegisterDescriptor& getRegisterDescriptorFromNumber(GdbRegisterNumberType number) const = 0;
|
||||
|
||||
/**
|
||||
* Should retrieve the mapped target register descriptor for a given GDB register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
virtual const Targets::TargetRegisterDescriptor& getTargetRegisterDescriptorFromNumber(
|
||||
GdbRegisterNumberType number
|
||||
) const = 0;
|
||||
|
||||
/**
|
||||
* Should return all allocated GDB register numbers for the target.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual const std::vector<GdbRegisterNumberType>& getRegisterNumbers() const = 0;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user