Initial commit
This commit is contained in:
121
src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp
Normal file
121
src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "src/DebugServers/GdbRsp/Register.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The mask used by the AVR GDB client to encode the memory type into memory addresses.
|
||||
* See AvrGdbRsp::getMemoryTypeFromGdbAddress() for more.
|
||||
*/
|
||||
unsigned int gdbInternalMemoryMask = 0xFE0000u;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* Only general purpose registers have register IDs. The others do not require an ID.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() override {
|
||||
static BiMap<GdbRegisterNumber, TargetRegisterDescriptor> mapping = {
|
||||
{0, TargetRegisterDescriptor(0, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{1, TargetRegisterDescriptor(1, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{2, TargetRegisterDescriptor(2, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{3, TargetRegisterDescriptor(3, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{4, TargetRegisterDescriptor(4, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{5, TargetRegisterDescriptor(5, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{6, TargetRegisterDescriptor(6, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{7, TargetRegisterDescriptor(7, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{8, TargetRegisterDescriptor(8, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{9, TargetRegisterDescriptor(9, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{10, TargetRegisterDescriptor(10, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{11, TargetRegisterDescriptor(11, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{12, TargetRegisterDescriptor(12, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{13, TargetRegisterDescriptor(13, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{14, TargetRegisterDescriptor(14, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{15, TargetRegisterDescriptor(15, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{16, TargetRegisterDescriptor(16, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{17, TargetRegisterDescriptor(17, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{18, TargetRegisterDescriptor(18, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{19, TargetRegisterDescriptor(19, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{20, TargetRegisterDescriptor(20, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{21, TargetRegisterDescriptor(21, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{22, TargetRegisterDescriptor(22, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{23, TargetRegisterDescriptor(23, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{24, TargetRegisterDescriptor(24, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{25, TargetRegisterDescriptor(25, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{26, TargetRegisterDescriptor(26, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{27, TargetRegisterDescriptor(27, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{28, TargetRegisterDescriptor(28, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{29, TargetRegisterDescriptor(29, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{30, TargetRegisterDescriptor(30, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{31, TargetRegisterDescriptor(31, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{32, TargetRegisterDescriptor(TargetRegisterType::STATUS_REGISTER)},
|
||||
{33, TargetRegisterDescriptor(TargetRegisterType::STACK_POINTER)},
|
||||
{34, TargetRegisterDescriptor(TargetRegisterType::PROGRAM_COUNTER)},
|
||||
};
|
||||
|
||||
return mapping;
|
||||
};
|
||||
|
||||
/**
|
||||
* avr-gdb uses the most significant 15 bits in memory addresses to indicate the type of memory being
|
||||
* addressed.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) override {
|
||||
if (address & this->gdbInternalMemoryMask) {
|
||||
return TargetMemoryType::RAM;
|
||||
}
|
||||
|
||||
return TargetMemoryType::FLASH;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strips the most significant 15 bits from a GDB memory address.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) override {
|
||||
return address & this->gdbInternalMemoryMask ? (address & ~(this->gdbInternalMemoryMask)) : address;
|
||||
};
|
||||
|
||||
public:
|
||||
AvrGdbRsp(EventManager& eventManager) : GdbRspDebugServer(eventManager) {};
|
||||
|
||||
std::string getName() const override {
|
||||
return "AVR GDB Remote Serial Protocol Debug Server";
|
||||
}
|
||||
};
|
||||
}
|
||||
11
src/DebugServers/GdbRsp/BreakpointType.hpp
Normal file
11
src/DebugServers/GdbRsp/BreakpointType.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class BreakpointType: int
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
SOFTWARE_BREAKPOINT = 1,
|
||||
HARDWARE_BREAKPOINT = 2,
|
||||
};
|
||||
}
|
||||
8
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
Normal file
8
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void CommandPacket::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
51
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp
Normal file
51
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
class GdbRspDebugServer;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::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:
|
||||
CommandPacket(const std::vector<unsigned char>& rawPacket) : Packet(rawPacket) {}
|
||||
|
||||
/**
|
||||
* Double dispatches the packet to the appropriate overload of handleGdbPacket(), within the passed instance of
|
||||
* GdbRspDebugServer. If there is no overload defined for a specific CommandPacket-inherited type, the
|
||||
* generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) is used.
|
||||
*
|
||||
* @param gdbRspDebugServer
|
||||
*/
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer);
|
||||
};
|
||||
}
|
||||
129
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
Normal file
129
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
#include "CommandPacketFactory.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
std::unique_ptr<CommandPacket> CommandPacketFactory::create(std::vector<unsigned char> 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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
} else if (rawPacketString[1] == 'g' || rawPacketString[1] == 'p') {
|
||||
return std::make_unique<CommandPackets::ReadGeneralRegisters>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'G' || rawPacketString[1] == 'P') {
|
||||
return std::make_unique<CommandPackets::WriteGeneralRegisters>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'c') {
|
||||
return std::make_unique<CommandPackets::ContinueExecution>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 's') {
|
||||
return std::make_unique<CommandPackets::StepExecution>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'm') {
|
||||
return std::make_unique<CommandPackets::ReadMemory>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'M') {
|
||||
return std::make_unique<CommandPackets::WriteMemory>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'Z') {
|
||||
return std::make_unique<CommandPackets::SetBreakpoint>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'z') {
|
||||
return std::make_unique<CommandPackets::RemoveBreakpoint>(rawPacket);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<CommandPacket>(rawPacket);
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned char>> CommandPacketFactory::extractRawPackets(std::vector<unsigned char> buffer) {
|
||||
std::vector<std::vector<unsigned char>> output;
|
||||
|
||||
std::size_t bufferIndex;
|
||||
std::size_t bufferSize = buffer.size();
|
||||
unsigned char byte;
|
||||
for (bufferIndex = 0; bufferIndex < bufferSize; bufferIndex++) {
|
||||
byte = buffer[bufferIndex];
|
||||
|
||||
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
|
||||
std::vector<unsigned char> rawPacket;
|
||||
rawPacket.push_back('$');
|
||||
|
||||
auto packetIndex = bufferIndex;
|
||||
bool validPacket = false;
|
||||
bool isByteEscaped = false;
|
||||
|
||||
for (packetIndex++; packetIndex < bufferSize; packetIndex++) {
|
||||
byte = buffer[packetIndex];
|
||||
|
||||
if (byte == '}' && !isByteEscaped) {
|
||||
isByteEscaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (byte == '$' && !isByteEscaped) {
|
||||
// Unexpected end of packet
|
||||
validPacket = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (byte == '#' && !isByteEscaped) {
|
||||
// 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(buffer[++packetIndex]);
|
||||
rawPacket.push_back(buffer[++packetIndex]);
|
||||
validPacket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isByteEscaped) {
|
||||
// Escaped bytes are XOR'd with a 0x20 mask.
|
||||
byte ^= 0x20;
|
||||
isByteEscaped = false;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
}
|
||||
|
||||
if (validPacket) {
|
||||
output.push_back(rawPacket);
|
||||
bufferIndex = packetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Command packets
|
||||
#include "CommandPacket.hpp"
|
||||
#include "InterruptExecution.hpp"
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
#include "ReadGeneralRegisters.hpp"
|
||||
#include "WriteGeneralRegisters.hpp"
|
||||
#include "ReadMemory.hpp"
|
||||
#include "WriteMemory.hpp"
|
||||
#include "StepExecution.hpp"
|
||||
#include "ContinueExecution.hpp"
|
||||
#include "SetBreakpoint.hpp"
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using namespace CommandPackets;
|
||||
|
||||
/**
|
||||
* The CommandPacketFactory class provides a means for extracting raw packet data from a raw buffer, and
|
||||
* constructing the appropriate CommandPacket objects.
|
||||
*/
|
||||
class CommandPacketFactory
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Extracts raw GDB RSP packets from buffer.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer from which to extract the raw GDB RSP packets.
|
||||
*
|
||||
* @return
|
||||
* A vector of raw packets.
|
||||
*/
|
||||
static std::vector<std::vector<unsigned char>> extractRawPackets(std::vector<unsigned char> buffer);
|
||||
|
||||
/**
|
||||
* Constructs the appropriate CommandPacket object from a single raw GDB RSP packet.
|
||||
*
|
||||
* @param rawPacket
|
||||
* The raw GDB RSP packet from which to construct the CommandPacket object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::unique_ptr<CommandPacket> create(std::vector<unsigned char> rawPacket);
|
||||
};
|
||||
}
|
||||
18
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
Normal file
18
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "ContinueExecution.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.hpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
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;
|
||||
|
||||
ContinueExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "InterruptExecution.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void InterruptExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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 CommandPacketFactory::extractRawPackets().
|
||||
*/
|
||||
class InterruptExecution: public CommandPacket
|
||||
{
|
||||
public:
|
||||
InterruptExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ReadGeneralRegisters.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void ReadGeneralRegisters::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 ReadGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ReadGeneralRegisters 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 ReadGeneralRegisters: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
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<int> registerNumber;
|
||||
|
||||
ReadGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
43
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
Normal file
43
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "ReadMemory.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
42
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.hpp
Normal file
42
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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 CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Number of bytes to read.
|
||||
*/
|
||||
std::uint32_t bytes;
|
||||
|
||||
ReadMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
44
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.hpp
Normal file
44
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../BreakpointType.hpp"
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature: int;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address;
|
||||
|
||||
RemoveBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
39
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
Normal file
39
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "SetBreakpoint.hpp"
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
44
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.hpp
Normal file
44
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../BreakpointType.hpp"
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature: int;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address;
|
||||
|
||||
SetBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
18
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
Normal file
18
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "StepExecution.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
32
src/DebugServers/GdbRsp/CommandPackets/StepExecution.hpp
Normal file
32
src/DebugServers/GdbRsp/CommandPackets/StepExecution.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The address from which to begin the step.
|
||||
*/
|
||||
std::optional<size_t> fromProgramCounter;
|
||||
|
||||
StepExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
std::set<Feature> supportedFeatures;
|
||||
|
||||
void init();
|
||||
|
||||
public:
|
||||
SupportedFeaturesQuery(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
bool isFeatureSupported(const Feature& feature) const {
|
||||
return this->supportedFeatures.find(feature) != this->supportedFeatures.end();
|
||||
}
|
||||
|
||||
const std::set<Feature>& getSupportedFeatures() const {
|
||||
return this->supportedFeatures;
|
||||
}
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "WriteGeneralRegisters.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void WriteGeneralRegisters::init() {
|
||||
// The P packet updates a single register
|
||||
auto packet = std::string(this->data.begin(), this->data.end());
|
||||
|
||||
if (packet.size() < 6) {
|
||||
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 = packetSegments.front().mid(1).toUInt(nullptr, 16);
|
||||
this->registerValue = this->hexToData(packetSegments.back().toStdString());
|
||||
std::reverse(this->registerValue.begin(), this->registerValue.end());
|
||||
}
|
||||
|
||||
void WriteGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using Bloom::Targets::TargetRegister;
|
||||
using Bloom::Targets::TargetRegisterMap;
|
||||
|
||||
/**
|
||||
* The WriteGeneralRegisters class implements the structure for "G" and "P" packets. Upon receiving this packet,
|
||||
* server is expected to write register values to the target.
|
||||
*/
|
||||
class WriteGeneralRegisters: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
TargetRegisterMap registerMap;
|
||||
int registerNumber;
|
||||
std::vector<unsigned char> registerValue;
|
||||
|
||||
WriteGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
52
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
Normal file
52
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "WriteMemory.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
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 = this->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::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.hpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using Bloom::Targets::TargetMemoryBuffer;
|
||||
|
||||
/**
|
||||
* 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 CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Like with the ReadMemory command packet, the start address carries additional bits that indicate
|
||||
* the memory type.
|
||||
*/
|
||||
std::uint32_t startAddress;
|
||||
|
||||
TargetMemoryBuffer buffer;
|
||||
|
||||
WriteMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
208
src/DebugServers/GdbRsp/Connection.cpp
Normal file
208
src/DebugServers/GdbRsp/Connection.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
#include "Connection.hpp"
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "CommandPackets/CommandPacketFactory.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using namespace Bloom::DebugServers::Gdb::Exceptions;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void Connection::accept(int serverSocketFileDescriptor) {
|
||||
int socketAddressLength = sizeof(this->socketAddress);
|
||||
|
||||
this->socketFileDescriptor = ::accept(
|
||||
serverSocketFileDescriptor,
|
||||
(sockaddr*)& (this->socketAddress),
|
||||
(socklen_t*)& socketAddressLength
|
||||
);
|
||||
|
||||
if (this->socketFileDescriptor == -1) {
|
||||
throw Exception("Failed to accept GDB Remote Serial Protocol connection");
|
||||
}
|
||||
|
||||
::fcntl(
|
||||
this->socketFileDescriptor,
|
||||
F_SETFL,
|
||||
fcntl(this->socketFileDescriptor, F_GETFL, 0) | O_NONBLOCK
|
||||
);
|
||||
|
||||
// Create event FD
|
||||
this->eventFileDescriptor = ::epoll_create(2);
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = this->socketFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->socketFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed to create event FD for GDB client connection - could not add client connection "
|
||||
"socket FD to epoll FD");
|
||||
}
|
||||
|
||||
this->enableReadInterrupts();
|
||||
}
|
||||
|
||||
void Connection::disableReadInterrupts() {
|
||||
if (::epoll_ctl(
|
||||
this->eventFileDescriptor,
|
||||
EPOLL_CTL_DEL,
|
||||
this->interruptEventNotifier->getFileDescriptor(),
|
||||
NULL) != 0
|
||||
) {
|
||||
throw Exception("Failed to disable GDB client connection read interrupts - epoll_ctl failed");
|
||||
}
|
||||
|
||||
this->readInterruptEnabled = false;
|
||||
}
|
||||
|
||||
void Connection::enableReadInterrupts() {
|
||||
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = interruptFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed to enable GDB client connection read interrupts - epoll_ctl failed");
|
||||
}
|
||||
|
||||
this->readInterruptEnabled = true;
|
||||
}
|
||||
|
||||
void Connection::close() noexcept {
|
||||
if (this->socketFileDescriptor > 0) {
|
||||
::close(this->socketFileDescriptor);
|
||||
this->socketFileDescriptor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::write(const std::vector<unsigned char>& buffer) {
|
||||
Logger::debug("Writing packet: " + std::string(buffer.begin(), buffer.end()));
|
||||
if (::write(this->socketFileDescriptor, buffer.data(), buffer.size()) == -1) {
|
||||
if (errno == EPIPE || errno == ECONNRESET) {
|
||||
// Connection was closed
|
||||
throw ClientDisconnected();
|
||||
|
||||
} else {
|
||||
throw ClientCommunicationError("Failed to write " + std::to_string(buffer.size())
|
||||
+ " bytes to GDP client socket - error no: " + std::to_string(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) != '+');
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Connection::read(size_t bytes, bool interruptible, std::optional<int> msTimeout) {
|
||||
auto output = std::vector<unsigned char>();
|
||||
constexpr size_t bufferSize = 1024;
|
||||
std::array<unsigned char, bufferSize> buffer;
|
||||
ssize_t bytesRead;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
std::array<struct epoll_event, 1> events = {};
|
||||
|
||||
int eventCount = ::epoll_wait(
|
||||
this->eventFileDescriptor,
|
||||
events.data(),
|
||||
1,
|
||||
msTimeout.value_or(-1)
|
||||
);
|
||||
|
||||
if (eventCount > 0) {
|
||||
for (size_t i = 0; i < eventCount; i++) {
|
||||
auto fileDescriptor = events[i].data.fd;
|
||||
|
||||
if (fileDescriptor == 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, 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, 300);
|
||||
|
||||
if (!bytes.empty()) {
|
||||
return bytes.front();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<CommandPacket>> Connection::readPackets() {
|
||||
auto buffer = this->read();
|
||||
Logger::debug("GDB client data received (" + std::to_string(buffer.size()) + " bytes): " + std::string(buffer.begin(), buffer.end()));
|
||||
|
||||
auto rawPackets = CommandPacketFactory::extractRawPackets(buffer);
|
||||
std::vector<std::unique_ptr<CommandPacket>> output;
|
||||
|
||||
for (const auto& rawPacket : rawPackets) {
|
||||
try {
|
||||
output.push_back(CommandPacketFactory::create(rawPacket));
|
||||
this->write({'+'});
|
||||
|
||||
} catch (const ClientDisconnected& exception) {
|
||||
throw exception;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to parse GDB packet - " + exception.getMessage());
|
||||
this->write({'-'});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
137
src/DebugServers/GdbRsp/Connection.hpp
Normal file
137
src/DebugServers/GdbRsp/Connection.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <netinet/in.h>
|
||||
#include <queue>
|
||||
#include <array>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp"
|
||||
#include "src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using namespace CommandPackets;
|
||||
using namespace ResponsePackets;
|
||||
|
||||
/**
|
||||
* The Connection class represents an active connection between the GDB RSP server and client.
|
||||
*
|
||||
* All interfacing with the GDB client should take place here.
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
private:
|
||||
int socketFileDescriptor = -1;
|
||||
int eventFileDescriptor = -1;
|
||||
|
||||
struct sockaddr_in socketAddress = {};
|
||||
int maxPacketSize = 1024;
|
||||
|
||||
/**
|
||||
* The interruptEventNotifier allows us to interrupt blocking IO calls on the GDB debug server.
|
||||
* Under the hood, this is just a wrapper for a Linux event notifier. See the EventNotifier class for more.
|
||||
*/
|
||||
std::shared_ptr<EventNotifier> interruptEventNotifier = nullptr;
|
||||
bool readInterruptEnabled = false;
|
||||
|
||||
/**
|
||||
* 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 affect if this->readInterruptEnabled
|
||||
* is false.
|
||||
*
|
||||
* @param msTimeout
|
||||
* 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<int> msTimeout = 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);
|
||||
|
||||
void disableReadInterrupts();
|
||||
|
||||
void enableReadInterrupts();
|
||||
|
||||
public:
|
||||
/**
|
||||
* When the GDB client is waiting for the target to reach a breakpoint, this is set to true so we know when to
|
||||
* notify the client.
|
||||
*
|
||||
* @TODO: This is pretty gross. Consider rethinking it.
|
||||
*/
|
||||
bool waitingForBreak = false;
|
||||
|
||||
Connection(std::shared_ptr<EventNotifier> interruptEventNotifier)
|
||||
: interruptEventNotifier(interruptEventNotifier) {};
|
||||
|
||||
/**
|
||||
* Accepts a connection on serverSocketFileDescriptor.
|
||||
*
|
||||
* @param serverSocketFileDescriptor
|
||||
*/
|
||||
void accept(int serverSocketFileDescriptor);
|
||||
|
||||
/**
|
||||
* Closes the connection with the client.
|
||||
*/
|
||||
void close() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the human readable IP address of the connected client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string getIpAddress() {
|
||||
std::array<char, INET_ADDRSTRLEN> ipAddress;
|
||||
|
||||
if (::inet_ntop(AF_INET, &(socketAddress.sin_addr), ipAddress.data(), INET_ADDRSTRLEN) == nullptr) {
|
||||
throw Exceptions::Exception("Failed to convert client IP address to text form.");
|
||||
}
|
||||
|
||||
return std::string(ipAddress.data());
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for incoming data from the client and returns any received command packets.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<std::unique_ptr<CommandPacket>> readPackets();
|
||||
|
||||
/**
|
||||
* Sends a response packet to the client.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
void writePacket(const ResponsePacket& packet);
|
||||
|
||||
int getMaxPacketSize() {
|
||||
return this->maxPacketSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::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 Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientCommunicationError(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError() = default;
|
||||
};
|
||||
}
|
||||
29
src/DebugServers/GdbRsp/Exceptions/ClientDisconnected.hpp
Normal file
29
src/DebugServers/GdbRsp/Exceptions/ClientDisconnected.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::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 Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientDisconnected(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientDisconnected(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientDisconnected() = default;
|
||||
};
|
||||
}
|
||||
28
src/DebugServers/GdbRsp/Exceptions/ClientNotSupported.hpp
Normal file
28
src/DebugServers/GdbRsp/Exceptions/ClientNotSupported.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::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 Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientNotSupported(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientNotSupported(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientNotSupported() = default;
|
||||
};
|
||||
}
|
||||
23
src/DebugServers/GdbRsp/Feature.hpp
Normal file
23
src/DebugServers/GdbRsp/Feature.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::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"},
|
||||
};
|
||||
}
|
||||
}
|
||||
404
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
Normal file
404
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
Normal file
@@ -0,0 +1,404 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "GdbRspDebugServer.hpp"
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientNotSupported.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using namespace Exceptions;
|
||||
|
||||
void GdbRspDebugServer::init() {
|
||||
auto ipAddress = this->debugServerConfig.jsonObject.find("ipAddress")->toString().toStdString();
|
||||
auto port = static_cast<std::uint16_t>(this->debugServerConfig.jsonObject.find("port")->toInt());
|
||||
|
||||
if (!ipAddress.empty()) {
|
||||
this->listeningAddress = ipAddress;
|
||||
}
|
||||
|
||||
if (port > 0) {
|
||||
this->listeningPortNumber = port;
|
||||
}
|
||||
|
||||
this->socketAddress.sin_family = AF_INET;
|
||||
this->socketAddress.sin_port = htons(this->listeningPortNumber);
|
||||
|
||||
if (::inet_pton(AF_INET, this->listeningAddress.c_str(), &(this->socketAddress.sin_addr)) == 0) {
|
||||
// Invalid IP address
|
||||
throw InvalidConfig("Invalid IP address provided in config file: (\"" + this->listeningAddress + "\")");
|
||||
}
|
||||
|
||||
int socketFileDescriptor;
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
this->serverSocketFileDescriptor = socketFileDescriptor;
|
||||
|
||||
this->eventFileDescriptor = ::epoll_create(2);
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = this->serverSocketFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->serverSocketFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed epoll_ctl server socket");
|
||||
}
|
||||
|
||||
if (this->interruptEventNotifier != nullptr) {
|
||||
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = interruptFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed epoll_ctl interrupt event fd");
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info("GDB RSP address: " + this->listeningAddress);
|
||||
Logger::info("GDB RSP port: " + std::to_string(this->listeningPortNumber));
|
||||
|
||||
this->eventListener->registerCallbackForEventType<Events::TargetExecutionStopped>(
|
||||
std::bind(&GdbRspDebugServer::onTargetExecutionStopped, this, std::placeholders::_1)
|
||||
);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::serve() {
|
||||
try {
|
||||
if (!this->clientConnection.has_value()) {
|
||||
Logger::info("Waiting for GDB RSP connection");
|
||||
this->waitForConnection();
|
||||
}
|
||||
|
||||
auto packets = this->clientConnection->readPackets();
|
||||
|
||||
for (auto& packet : packets) {
|
||||
// Double-dispatch to appropriate handler
|
||||
packet->dispatchToHandler(*this);
|
||||
}
|
||||
|
||||
} catch (const ClientDisconnected&) {
|
||||
Logger::info("GDB RSP client disconnected");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const ClientCommunicationError& exception) {
|
||||
Logger::error("GDB client communication error - " + exception.getMessage() + " - closing connection");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const ClientNotSupported& exception) {
|
||||
Logger::error("Invalid GDB client - " + exception.getMessage() + " - closing connection");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const DebugServerInterrupted&) {
|
||||
// Server was interrupted
|
||||
Logger::debug("GDB RSP interrupted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::waitForConnection() {
|
||||
if (::listen(this->serverSocketFileDescriptor, 3) != 0) {
|
||||
throw Exception("Failed to listen on server socket");
|
||||
}
|
||||
|
||||
std::array<struct epoll_event, 5> events = {};
|
||||
int eventCount = ::epoll_wait(
|
||||
this->eventFileDescriptor,
|
||||
events.data(),
|
||||
5,
|
||||
-1
|
||||
);
|
||||
|
||||
if (eventCount > 0) {
|
||||
for (size_t i = 0; i < eventCount; i++) {
|
||||
auto fileDescriptor = events[i].data.fd;
|
||||
|
||||
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
|
||||
// Interrupted
|
||||
this->interruptEventNotifier->clear();
|
||||
throw DebugServerInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
this->clientConnection = Connection(this->interruptEventNotifier);
|
||||
this->clientConnection->accept(this->serverSocketFileDescriptor);
|
||||
|
||||
Logger::info("Accepted GDP RSP connection from " + this->clientConnection->getIpAddress());
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionStarted>());
|
||||
} else {
|
||||
// This method should not return until a connection has been established (or an exception is thrown)
|
||||
return this->waitForConnection();
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::close() {
|
||||
this->closeClientConnection();
|
||||
|
||||
if (this->serverSocketFileDescriptor > 0) {
|
||||
::close(this->serverSocketFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPacket& packet) {
|
||||
auto packetData = packet.getData();
|
||||
auto packetString = std::string(packetData.begin(), packetData.end());
|
||||
|
||||
if (packetString[0] == '?') {
|
||||
// Status report
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
|
||||
|
||||
} else if (packetString.find("qAttached") == 0) {
|
||||
Logger::debug("Handling qAttached");
|
||||
this->clientConnection->writePacket(ResponsePacket({1}));
|
||||
|
||||
} else {
|
||||
Logger::debug("Unknown GDB RSP packet: " + packetString + " - returning empty response");
|
||||
|
||||
// Respond with an empty packet
|
||||
this->clientConnection->writePacket(ResponsePacket({0}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>) {
|
||||
if (this->clientConnection.has_value() && this->clientConnection->waitingForBreak) {
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
|
||||
this->clientConnection->waitingForBreak = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet) {
|
||||
Logger::debug("Handling QuerySupport packet");
|
||||
|
||||
if (!packet.isFeatureSupported(Feature::HARDWARE_BREAKPOINTS)
|
||||
&& !packet.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 = ResponsePackets::SupportedFeaturesResponse({
|
||||
{Feature::SOFTWARE_BREAKPOINTS, std::nullopt},
|
||||
{Feature::PACKET_SIZE, std::to_string(this->clientConnection->getMaxPacketSize())},
|
||||
});
|
||||
|
||||
this->clientConnection->writePacket(response);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet) {
|
||||
Logger::debug("Handling ReadGeneralRegisters packet");
|
||||
|
||||
try {
|
||||
auto descriptors = TargetRegisterDescriptors();
|
||||
|
||||
if (packet.registerNumber.has_value()) {
|
||||
Logger::debug("Reading register number: " + std::to_string(packet.registerNumber.value()));
|
||||
descriptors.push_back(this->getRegisterDescriptorFromNumber(packet.registerNumber.value()));
|
||||
|
||||
} else {
|
||||
// Read all descriptors
|
||||
auto descriptorMapping = this->getRegisterNumberToDescriptorMapping();
|
||||
for (auto& descriptor : descriptorMapping.getMap()) {
|
||||
descriptors.push_back(descriptor.second);
|
||||
}
|
||||
}
|
||||
|
||||
auto registerSet = this->readGeneralRegistersFromTarget(descriptors);
|
||||
auto registerNumberToDescriptorMapping = this->getRegisterNumberToDescriptorMapping();
|
||||
|
||||
/*
|
||||
* Remove any registers that are not mapped to GDB register numbers (as we won't know where to place
|
||||
* them in our response to GDB). All registers that are expected from the GDB client should be mapped
|
||||
* to register numbers.
|
||||
*
|
||||
* Registers that are not mapped to a GDB register number are presumed to be unknown to GDB, so GDB shouldn't
|
||||
* complain about not receiving them.
|
||||
*/
|
||||
registerSet.erase(
|
||||
std::remove_if(
|
||||
registerSet.begin(),
|
||||
registerSet.end(),
|
||||
[®isterNumberToDescriptorMapping](const TargetRegister& reg) {
|
||||
return !registerNumberToDescriptorMapping.contains(reg.descriptor);
|
||||
}
|
||||
),
|
||||
registerSet.end()
|
||||
);
|
||||
|
||||
/*
|
||||
* 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, ®isterNumberToDescriptorMapping](const TargetRegister& registerA, const TargetRegister& registerB) {
|
||||
return registerNumberToDescriptorMapping.valueAt(registerA.descriptor) <
|
||||
registerNumberToDescriptorMapping.valueAt(registerB.descriptor);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Finally, implode the register values, convert to hexadecimal form and send to the GDB client.
|
||||
*/
|
||||
auto registers = std::vector<unsigned char>();
|
||||
for (const auto& reg : registerSet) {
|
||||
registers.insert(registers.end(), reg.value.begin(), reg.value.end());
|
||||
}
|
||||
|
||||
auto responseRegisters = Packet::dataToHex(registers);
|
||||
this->clientConnection->writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(responseRegisters.begin(), responseRegisters.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read general registers - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet) {
|
||||
Logger::debug("Handling WriteGeneralRegisters packet");
|
||||
|
||||
try {
|
||||
auto registerDescriptor = this->getRegisterDescriptorFromNumber(packet.registerNumber);
|
||||
this->writeGeneralRegistersToTarget({TargetRegister(registerDescriptor, packet.registerValue)});
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write general registers - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ContinueExecution& packet) {
|
||||
Logger::debug("Handling ContinueExecution packet");
|
||||
|
||||
try {
|
||||
this->continueTargetExecution(packet.fromProgramCounter);
|
||||
this->clientConnection->waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to continue execution on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::StepExecution& packet) {
|
||||
Logger::debug("Handling StepExecution packet");
|
||||
|
||||
try {
|
||||
this->stepTargetExecution(packet.fromProgramCounter);
|
||||
this->clientConnection->waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to step execution on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadMemory& packet) {
|
||||
Logger::debug("Handling ReadMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
|
||||
auto memoryBuffer = this->readMemoryFromTarget(memoryType, startAddress, packet.bytes);
|
||||
|
||||
auto hexMemoryBuffer = Packet::dataToHex(memoryBuffer);
|
||||
this->clientConnection->writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(hexMemoryBuffer.begin(), hexMemoryBuffer.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read memory from target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteMemory& packet) {
|
||||
Logger::debug("Handling WriteMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
|
||||
this->writeMemoryToTarget(memoryType, startAddress, packet.buffer);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write memory two target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SetBreakpoint& packet) {
|
||||
Logger::debug("Handling SetBreakpoint packet");
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = packet.address;
|
||||
this->setBreakpointOnTarget(breakpoint);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to set breakpoint on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::RemoveBreakpoint& packet) {
|
||||
Logger::debug("Removing breakpoint at address " + std::to_string(packet.address));
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = packet.address;
|
||||
this->removeBreakpointOnTarget(breakpoint);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to remove breakpoint on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::InterruptExecution& packet) {
|
||||
Logger::debug("Handling InterruptExecution packet");
|
||||
|
||||
try {
|
||||
this->stopTargetExecution();
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::INTERRUPTED));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to interrupt execution - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
257
src/DebugServers/GdbRsp/GdbRspDebugServer.hpp
Normal file
257
src/DebugServers/GdbRsp/GdbRspDebugServer.hpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../DebugServer.hpp"
|
||||
#include "Connection.hpp"
|
||||
#include "Signal.hpp"
|
||||
#include "Register.hpp"
|
||||
#include "Feature.hpp"
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
#include "CommandPackets/CommandPacketFactory.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
// Response packets
|
||||
#include "ResponsePackets/SupportedFeaturesResponse.hpp"
|
||||
#include "ResponsePackets/TargetStopped.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterType;
|
||||
using Bloom::Targets::TargetRegisterDescriptor;
|
||||
|
||||
/**
|
||||
* 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 DebugServer
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* The port number for the GDB server to listen on.
|
||||
*
|
||||
* This will be pulled from the user's project configuration, if set. Otherwise it will default to whatever is
|
||||
* set here.
|
||||
*/
|
||||
std::uint16_t listeningPortNumber = 1055;
|
||||
|
||||
/**
|
||||
* The address for the GDB server to listen on.
|
||||
*
|
||||
* Like the port number, this can also be pulled from the user's project configuration.
|
||||
*/
|
||||
std::string listeningAddress = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* Listening socket address
|
||||
*/
|
||||
struct sockaddr_in socketAddress = {};
|
||||
|
||||
/**
|
||||
* Listening socket file descriptor
|
||||
*/
|
||||
int serverSocketFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* We don't listen on the this->serverSocketFileDescriptor directly. Instead, we add it to an epoll set, along
|
||||
* with the this->interruptEventNotifier FD. This allows us to interrupt any blocking socket IO calls when
|
||||
* we have other things to do.
|
||||
*
|
||||
* See GdbRspDebugServer::init()
|
||||
* See DebugServer::interruptEventNotifier
|
||||
* See EventNotifier
|
||||
*/
|
||||
int eventFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* SO_REUSEADDR option value for listening socket.
|
||||
*/
|
||||
int enableReuseAddressSocketOption = 1;
|
||||
|
||||
/**
|
||||
* The current active GDB client connection, if any.
|
||||
*/
|
||||
std::optional<Connection> clientConnection;
|
||||
|
||||
/**
|
||||
* Prepares the GDB server for listing on the selected address and port.
|
||||
*/
|
||||
void init() override;
|
||||
|
||||
/**
|
||||
* Closes any client connection as well as the listening socket file descriptor.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* See DebugServer::serve()
|
||||
*/
|
||||
void serve() override;
|
||||
|
||||
/**
|
||||
* Waits for a GDB client to connect on the listening socket. Accepts the connection and
|
||||
* sets this->clientConnection.
|
||||
*/
|
||||
void waitForConnection();
|
||||
|
||||
void closeClientConnection() {
|
||||
if (this->clientConnection.has_value()) {
|
||||
this->clientConnection->close();
|
||||
this->clientConnection = std::nullopt;
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionFinished>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GDB clients encode memory type information (flash, ram, eeprom, etc) in memory addresses. This is typically
|
||||
* hardcoded in the GDB client source. This method extracts memory type information from a given memory address.
|
||||
* The specifics of the encoding may vary with targets, which is why this method is virtual. For an example,
|
||||
* see the implementation of this method in AvrGdbRsp.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
virtual TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Removes memory type information from memory address.
|
||||
* See comment for GdbRspDebugServer::getMemoryTypeFromGdbAddress()
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
virtual std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Like with the method of encoding memory type information onto memory addresses, GDB clients also expect
|
||||
* a pre-defined set of registers. The defined set being dependant on the target. This is hardcoded in the the
|
||||
* GDB client source. The order of the registers is also pre-defined in the GDB client.
|
||||
*
|
||||
* For an example, see the implementation of this method in the AvrGdbRsp class.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() = 0;
|
||||
|
||||
/**
|
||||
* Obtains the appropriate register descriptor from a register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegisterDescriptor getRegisterDescriptorFromNumber(GdbRegisterNumber number) {
|
||||
auto mapping = this->getRegisterNumberToDescriptorMapping();
|
||||
|
||||
if (!mapping.contains(number)) {
|
||||
throw Exception("Unknown register from GDB - register number (" + std::to_string(number)
|
||||
+ ") not mapped to any register descriptor.");
|
||||
}
|
||||
|
||||
return mapping.valueAt(number).value();
|
||||
}
|
||||
|
||||
public:
|
||||
GdbRspDebugServer(EventManager& eventManager) : DebugServer(eventManager) {};
|
||||
|
||||
std::string getName() const override {
|
||||
return "GDB Remote Serial Protocol DebugServer";
|
||||
};
|
||||
|
||||
/**
|
||||
* 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(EventPointer<Events::TargetExecutionStopped>);
|
||||
|
||||
/**
|
||||
* Handles any other GDB command packet that has not been promoted to a more specific type.
|
||||
* This would be packets like "?" and "qAttached".
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPacket& packet);
|
||||
|
||||
/**
|
||||
* Handles the supported features query ("qSupported") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet);
|
||||
|
||||
/**
|
||||
* Handles the read registers ("g" and "p") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet);
|
||||
|
||||
/**
|
||||
* Handles the write general registers ("G" and "P") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet);
|
||||
|
||||
/**
|
||||
* Handles the continue execution ("c") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ContinueExecution& packet);
|
||||
|
||||
/**
|
||||
* Handles the step execution ("s") packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::StepExecution& packet);
|
||||
|
||||
/**
|
||||
* Handles the read memory ("m") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ReadMemory& packet);
|
||||
|
||||
/**
|
||||
* Handles the write memory ("M") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::WriteMemory& packet);
|
||||
|
||||
/**
|
||||
* Handles the set breakpoint ("Z") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::SetBreakpoint& packet);
|
||||
|
||||
/**
|
||||
* Handles the remove breakpoint ("z") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::RemoveBreakpoint& packet);
|
||||
|
||||
/**
|
||||
* Handles the interrupt command packet.
|
||||
* Will attempt to halt execution on the target. Should respond with a "stop reply" packet, or an error code.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::InterruptExecution& packet);
|
||||
};
|
||||
}
|
||||
123
src/DebugServers/GdbRsp/Packet.hpp
Normal file
123
src/DebugServers/GdbRsp/Packet.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <QString>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
protected:
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
void init(const std::vector<unsigned char>& rawPacket) {
|
||||
this->data.insert(
|
||||
this->data.begin(),
|
||||
rawPacket.begin() + 1,
|
||||
rawPacket.end() - 3
|
||||
);
|
||||
}
|
||||
public:
|
||||
Packet() = default;
|
||||
Packet(const std::vector<unsigned char>& rawPacket) {
|
||||
this->init(rawPacket);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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(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;
|
||||
}
|
||||
|
||||
virtual ~Packet() = default;
|
||||
};
|
||||
}
|
||||
6
src/DebugServers/GdbRsp/Register.hpp
Normal file
6
src/DebugServers/GdbRsp/Register.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using GdbRegisterNumber = int;
|
||||
}
|
||||
25
src/DebugServers/GdbRsp/ResponsePackets/Ok.hpp
Normal file
25
src/DebugServers/GdbRsp/ResponsePackets/Ok.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* OK response packet expected by the GDB client, in response to certain commands.
|
||||
*/
|
||||
class Ok: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
Ok() = default;
|
||||
|
||||
std::vector<unsigned char> getData() const override {
|
||||
return {'O', 'K'};
|
||||
}
|
||||
};
|
||||
}
|
||||
22
src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp
Normal file
22
src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::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,23 @@
|
||||
#include "SupportedFeaturesResponse.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::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,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* The SupportedFeaturesResponse class implements the response packet structure for the "qSupported" command.
|
||||
*/
|
||||
class SupportedFeaturesResponse: public ResponsePacket
|
||||
{
|
||||
protected:
|
||||
std::set<std::pair<Feature, std::optional<std::string>>> supportedFeatures;
|
||||
|
||||
public:
|
||||
SupportedFeaturesResponse() = default;
|
||||
SupportedFeaturesResponse(const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures)
|
||||
: supportedFeatures(supportedFeatures) {};
|
||||
|
||||
std::vector<unsigned char> getData() const override;
|
||||
};
|
||||
}
|
||||
47
src/DebugServers/GdbRsp/ResponsePackets/TargetStopped.hpp
Normal file
47
src/DebugServers/GdbRsp/ResponsePackets/TargetStopped.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Signal.hpp"
|
||||
#include "../StopReason.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterMap;
|
||||
|
||||
/**
|
||||
* 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<TargetRegisterMap> registerMap;
|
||||
std::optional<StopReason> stopReason;
|
||||
|
||||
TargetStopped(Signal signal) : signal(signal) {}
|
||||
|
||||
std::vector<unsigned char> getData() const override {
|
||||
std::string output = "T" + this->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() + ":;";
|
||||
}
|
||||
}
|
||||
|
||||
if (this->registerMap.has_value()) {
|
||||
for (const auto& [registerId, registerValue] : this->registerMap.value()) {
|
||||
output += this->dataToHex({static_cast<unsigned char>(registerId)});
|
||||
output += ":" + this->dataToHex(registerValue.value) + ";";
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
};
|
||||
}
|
||||
10
src/DebugServers/GdbRsp/Signal.hpp
Normal file
10
src/DebugServers/GdbRsp/Signal.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class Signal: unsigned char
|
||||
{
|
||||
TRAP = 5,
|
||||
INTERRUPTED = 2,
|
||||
};
|
||||
}
|
||||
19
src/DebugServers/GdbRsp/StopReason.hpp
Normal file
19
src/DebugServers/GdbRsp/StopReason.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::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"},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user