Renamed GdbRsp directory to Gdb
This commit is contained in:
292
src/DebugServer/Gdb/GdbRspDebugServer.cpp
Normal file
292
src/DebugServer/Gdb/GdbRspDebugServer.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "GdbRspDebugServer.hpp"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientNotSupported.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "Exceptions/DebugSessionAborted.hpp"
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
|
||||
// Command packets
|
||||
#include "CommandPackets/CommandPacket.hpp"
|
||||
#include "CommandPackets/SupportedFeaturesQuery.hpp"
|
||||
#include "CommandPackets/InterruptExecution.hpp"
|
||||
#include "CommandPackets/ContinueExecution.hpp"
|
||||
#include "CommandPackets/StepExecution.hpp"
|
||||
#include "CommandPackets/ReadRegisters.hpp"
|
||||
#include "CommandPackets/WriteRegister.hpp"
|
||||
#include "CommandPackets/SetBreakpoint.hpp"
|
||||
#include "CommandPackets/RemoveBreakpoint.hpp"
|
||||
|
||||
// Response packets
|
||||
#include "ResponsePackets/TargetStopped.hpp"
|
||||
|
||||
namespace Bloom::DebugServer::Gdb
|
||||
{
|
||||
using namespace Exceptions;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
using CommandPackets::CommandPacket;
|
||||
|
||||
GdbRspDebugServer::GdbRspDebugServer(
|
||||
const DebugServerConfig& debugServerConfig,
|
||||
EventListener& eventListener
|
||||
)
|
||||
: debugServerConfig(GdbDebugServerConfig(debugServerConfig))
|
||||
, eventListener(eventListener)
|
||||
, interruptEventNotifier(eventListener.getInterruptEventNotifier())
|
||||
{
|
||||
assert(this->interruptEventNotifier != nullptr && this->interruptEventNotifier->isInitialised());
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::init() {
|
||||
this->socketAddress.sin_family = AF_INET;
|
||||
this->socketAddress.sin_port = htons(this->debugServerConfig->listeningPortNumber);
|
||||
|
||||
if (::inet_pton(
|
||||
AF_INET,
|
||||
this->debugServerConfig->listeningAddress.c_str(),
|
||||
&(this->socketAddress.sin_addr)
|
||||
) == 0
|
||||
) {
|
||||
// Invalid IP address
|
||||
throw InvalidConfig(
|
||||
"Invalid IP address provided in config file: (\"" + this->debugServerConfig->listeningAddress
|
||||
+ "\")"
|
||||
);
|
||||
}
|
||||
|
||||
int socketFileDescriptor = 0;
|
||||
|
||||
if ((socketFileDescriptor = ::socket(AF_INET, SOCK_STREAM, 0)) == 0) {
|
||||
throw Exception("Failed to create socket file descriptor.");
|
||||
}
|
||||
|
||||
if (::setsockopt(
|
||||
socketFileDescriptor,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
&(this->enableReuseAddressSocketOption),
|
||||
sizeof(this->enableReuseAddressSocketOption)
|
||||
) < 0
|
||||
) {
|
||||
Logger::error("Failed to set socket SO_REUSEADDR option.");
|
||||
}
|
||||
|
||||
if (::bind(
|
||||
socketFileDescriptor,
|
||||
reinterpret_cast<const sockaddr*>(&(this->socketAddress)),
|
||||
sizeof(this->socketAddress)
|
||||
) < 0
|
||||
) {
|
||||
throw Exception("Failed to bind address. The selected port number ("
|
||||
+ std::to_string(this->debugServerConfig->listeningPortNumber) + ") may be in use.");
|
||||
}
|
||||
|
||||
this->serverSocketFileDescriptor = socketFileDescriptor;
|
||||
|
||||
this->epollInstance.addEntry(
|
||||
this->serverSocketFileDescriptor,
|
||||
static_cast<std::uint16_t>(EpollEvent::READ_READY)
|
||||
);
|
||||
|
||||
this->epollInstance.addEntry(
|
||||
this->interruptEventNotifier->getFileDescriptor(),
|
||||
static_cast<std::uint16_t>(EpollEvent::READ_READY)
|
||||
);
|
||||
|
||||
Logger::info("GDB RSP address: " + this->debugServerConfig->listeningAddress);
|
||||
Logger::info("GDB RSP port: " + std::to_string(this->debugServerConfig->listeningPortNumber));
|
||||
|
||||
this->eventListener.registerCallbackForEventType<Events::TargetControllerStateReported>(
|
||||
std::bind(&GdbRspDebugServer::onTargetControllerStateReported, this, std::placeholders::_1)
|
||||
);
|
||||
|
||||
this->eventListener.registerCallbackForEventType<Events::TargetExecutionStopped>(
|
||||
std::bind(&GdbRspDebugServer::onTargetExecutionStopped, this, std::placeholders::_1)
|
||||
);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::close() {
|
||||
this->terminateActiveDebugSession();
|
||||
|
||||
if (this->serverSocketFileDescriptor > 0) {
|
||||
::close(this->serverSocketFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::run() {
|
||||
try {
|
||||
if (!this->activeDebugSession.has_value()) {
|
||||
Logger::info("Waiting for GDB RSP connection");
|
||||
|
||||
auto connection = this->waitForConnection();
|
||||
|
||||
if (!connection.has_value()) {
|
||||
// Likely an interrupt
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info("Accepted GDP RSP connection from " + connection->getIpAddress());
|
||||
|
||||
this->activeDebugSession.emplace(
|
||||
DebugSession(std::move(connection.value()), this->getGdbTargetDescriptor())
|
||||
);
|
||||
|
||||
EventManager::triggerEvent(std::make_shared<Events::DebugSessionStarted>());
|
||||
|
||||
/*
|
||||
* Before proceeding with a new debug session, we must ensure that the TargetController is able to
|
||||
* service it.
|
||||
*/
|
||||
if (!this->targetControllerConsole.isTargetControllerInService()) {
|
||||
this->terminateActiveDebugSession();
|
||||
throw DebugSessionAborted("TargetController not in service");
|
||||
}
|
||||
}
|
||||
|
||||
auto commandPacket = this->waitForCommandPacket();
|
||||
|
||||
if (commandPacket == nullptr) {
|
||||
// Likely an interrupt
|
||||
return;
|
||||
}
|
||||
|
||||
commandPacket->handle(this->activeDebugSession.value(), this->targetControllerConsole);
|
||||
|
||||
} catch (const ClientDisconnected&) {
|
||||
Logger::info("GDB RSP client disconnected");
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const ClientCommunicationError& exception) {
|
||||
Logger::error(
|
||||
"GDB RSP client communication error - " + exception.getMessage() + " - closing connection"
|
||||
);
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const ClientNotSupported& exception) {
|
||||
Logger::error("Invalid GDB RSP client - " + exception.getMessage() + " - closing connection");
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const DebugSessionAborted& exception) {
|
||||
Logger::warning("GDB debug session aborted - " + exception.getMessage());
|
||||
this->terminateActiveDebugSession();
|
||||
return;
|
||||
|
||||
} catch (const DebugServerInterrupted&) {
|
||||
// Server was interrupted
|
||||
Logger::debug("GDB RSP interrupted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Connection> GdbRspDebugServer::waitForConnection() {
|
||||
if (::listen(this->serverSocketFileDescriptor, 3) != 0) {
|
||||
throw Exception("Failed to listen on server socket");
|
||||
}
|
||||
|
||||
const auto eventFileDescriptor = this->epollInstance.waitForEvent();
|
||||
|
||||
if (
|
||||
!eventFileDescriptor.has_value()
|
||||
|| eventFileDescriptor.value() == this->interruptEventNotifier->getFileDescriptor()
|
||||
) {
|
||||
this->interruptEventNotifier->clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional<Connection>(this->serverSocketFileDescriptor, *(this->interruptEventNotifier));
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandPacket> GdbRspDebugServer::waitForCommandPacket() {
|
||||
const auto rawPackets = this->activeDebugSession->connection.readRawPackets();
|
||||
|
||||
if (rawPackets.empty()) {
|
||||
// The wait was interrupted
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We only process the last packet - any others will probably be duplicates from an impatient client.
|
||||
return this->resolveCommandPacket(rawPackets.back());
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandPacket> GdbRspDebugServer::resolveCommandPacket(const RawPacketType& rawPacket) {
|
||||
if (rawPacket.size() == 5 && rawPacket[1] == 0x03) {
|
||||
// This is an interrupt request - create a fake packet for it
|
||||
return std::make_unique<CommandPackets::InterruptExecution>(rawPacket);
|
||||
}
|
||||
|
||||
const auto rawPacketString = std::string(rawPacket.begin(), rawPacket.end());
|
||||
|
||||
if (rawPacketString.size() >= 2) {
|
||||
/*
|
||||
* First byte of the raw packet will be 0x24 ('$'), so find() should return 1, not 0, when
|
||||
* looking for a command identifier string.
|
||||
*/
|
||||
if (rawPacketString.find("qSupported") == 1) {
|
||||
return std::make_unique<CommandPackets::SupportedFeaturesQuery>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'g' || rawPacketString[1] == 'p') {
|
||||
return std::make_unique<CommandPackets::ReadRegisters>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'P') {
|
||||
return std::make_unique<CommandPackets::WriteRegister>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'c') {
|
||||
return std::make_unique<CommandPackets::ContinueExecution>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 's') {
|
||||
return std::make_unique<CommandPackets::StepExecution>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'Z') {
|
||||
return std::make_unique<CommandPackets::SetBreakpoint>(rawPacket);
|
||||
}
|
||||
|
||||
if (rawPacketString[1] == 'z') {
|
||||
return std::make_unique<CommandPackets::RemoveBreakpoint>(rawPacket);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<CommandPacket>(rawPacket);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::terminateActiveDebugSession() {
|
||||
if (this->activeDebugSession.has_value()) {
|
||||
this->activeDebugSession->terminate();
|
||||
this->activeDebugSession = std::nullopt;
|
||||
|
||||
EventManager::triggerEvent(std::make_shared<Events::DebugSessionFinished>());
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetControllerStateReported(const Events::TargetControllerStateReported& event) {
|
||||
if (event.state == TargetControllerState::SUSPENDED && this->activeDebugSession.has_value()) {
|
||||
Logger::warning("Terminating debug session - TargetController suspended unexpectedly");
|
||||
this->terminateActiveDebugSession();
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetExecutionStopped(const Events::TargetExecutionStopped&) {
|
||||
if (this->activeDebugSession.has_value() && this->activeDebugSession->waitingForBreak) {
|
||||
this->activeDebugSession->connection.writePacket(
|
||||
ResponsePackets::TargetStopped(Signal::TRAP)
|
||||
);
|
||||
this->activeDebugSession->waitingForBreak = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user