Implemented support for breakpoint caching in the GDB server
This commit is contained in:
@@ -29,6 +29,13 @@ namespace Bloom::DebugServer::Gdb::AvrGdb
|
|||||||
this->gdbTargetDescriptor = TargetDescriptor(
|
this->gdbTargetDescriptor = TargetDescriptor(
|
||||||
this->targetControllerService.getTargetDescriptor()
|
this->targetControllerService.getTargetDescriptor()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!this->debugServerConfig.breakpointCachingEnabled) {
|
||||||
|
Logger::warning(
|
||||||
|
"Breakpoint caching has been disabled - this could result in excessive wear of the AVR target's"
|
||||||
|
" flash memory"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Gdb::CommandPackets::CommandPacket> AvrGdbRsp::resolveCommandPacket(
|
std::unique_ptr<Gdb::CommandPackets::CommandPacket> AvrGdbRsp::resolveCommandPacket(
|
||||||
|
|||||||
@@ -36,7 +36,13 @@ namespace Bloom::DebugServer::Gdb::CommandPackets
|
|||||||
class CommandPacket: public Packet
|
class CommandPacket: public Packet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit CommandPacket(const RawPacket& rawPacket): Packet(rawPacket) {}
|
explicit CommandPacket(const RawPacket& rawPacket)
|
||||||
|
: Packet(rawPacket)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual bool requiresBreakpointFlush() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should handle the command for the current active debug session.
|
* Should handle the command for the current active debug session.
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ namespace Bloom::DebugServer::Gdb::CommandPackets
|
|||||||
|
|
||||||
explicit ContinueExecution(const RawPacket& rawPacket);
|
explicit ContinueExecution(const RawPacket& rawPacket);
|
||||||
|
|
||||||
|
bool requiresBreakpointFlush() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void handle(
|
void handle(
|
||||||
DebugSession& debugSession,
|
DebugSession& debugSession,
|
||||||
Services::TargetControllerService& targetControllerService
|
Services::TargetControllerService& targetControllerService
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ namespace Bloom::DebugServer::Gdb::CommandPackets
|
|||||||
public:
|
public:
|
||||||
explicit Detach(const RawPacket& rawPacket);
|
explicit Detach(const RawPacket& rawPacket);
|
||||||
|
|
||||||
|
bool requiresBreakpointFlush() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void handle(
|
void handle(
|
||||||
DebugSession& debugSession,
|
DebugSession& debugSession,
|
||||||
Services::TargetControllerService& targetControllerService
|
Services::TargetControllerService& targetControllerService
|
||||||
|
|||||||
@@ -51,9 +51,18 @@ namespace Bloom::DebugServer::Gdb::CommandPackets
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RemoveBreakpoint::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) {
|
void RemoveBreakpoint::handle(DebugSession& debugSession, TargetControllerService& targetControllerService) {
|
||||||
Logger::debug("Removing breakpoint at address " + std::to_string(this->address));
|
Logger::debug("Handling RemoveBreakpoint packet");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (debugSession.serverConfig.breakpointCachingEnabled) {
|
||||||
|
debugSession.breakpointAddressesPendingRemoval.insert(this->address);
|
||||||
|
debugSession.connection.writePacket(OkResponsePacket());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::debug("Removing breakpoint at address " + std::to_string(this->address));
|
||||||
|
|
||||||
targetControllerService.removeBreakpoint(TargetBreakpoint(this->address));
|
targetControllerService.removeBreakpoint(TargetBreakpoint(this->address));
|
||||||
debugSession.connection.writePacket(OkResponsePacket());
|
debugSession.connection.writePacket(OkResponsePacket());
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,18 @@ namespace Bloom::DebugServer::Gdb::CommandPackets
|
|||||||
Logger::debug("Handling SetBreakpoint packet");
|
Logger::debug("Handling SetBreakpoint packet");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
!debugSession.serverConfig.breakpointCachingEnabled
|
||||||
|
|| !debugSession.breakpointAddresses.contains(this->address)
|
||||||
|
) {
|
||||||
targetControllerService.setBreakpoint(TargetBreakpoint(this->address));
|
targetControllerService.setBreakpoint(TargetBreakpoint(this->address));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debugSession.serverConfig.breakpointCachingEnabled) {
|
||||||
|
debugSession.breakpointAddresses.insert(this->address);
|
||||||
|
debugSession.breakpointAddressesPendingRemoval.erase(this->address);
|
||||||
|
}
|
||||||
|
|
||||||
debugSession.connection.writePacket(OkResponsePacket());
|
debugSession.connection.writePacket(OkResponsePacket());
|
||||||
|
|
||||||
} catch (const Exception& exception) {
|
} catch (const Exception& exception) {
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ namespace Bloom::DebugServer::Gdb::CommandPackets
|
|||||||
|
|
||||||
explicit StepExecution(const RawPacket& rawPacket);
|
explicit StepExecution(const RawPacket& rawPacket);
|
||||||
|
|
||||||
|
bool requiresBreakpointFlush() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void handle(
|
void handle(
|
||||||
DebugSession& debugSession,
|
DebugSession& debugSession,
|
||||||
Services::TargetControllerService& targetControllerService
|
Services::TargetControllerService& targetControllerService
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
DebugSession::DebugSession(
|
DebugSession::DebugSession(
|
||||||
Connection&& connection,
|
Connection&& connection,
|
||||||
const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures,
|
const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures,
|
||||||
const TargetDescriptor& targetDescriptor
|
const TargetDescriptor& targetDescriptor,
|
||||||
|
const GdbDebugServerConfig& serverConfig
|
||||||
)
|
)
|
||||||
: connection(std::move(connection))
|
: connection(std::move(connection))
|
||||||
, supportedFeatures(supportedFeatures)
|
, supportedFeatures(supportedFeatures)
|
||||||
, gdbTargetDescriptor(targetDescriptor)
|
, gdbTargetDescriptor(targetDescriptor)
|
||||||
|
, serverConfig(serverConfig)
|
||||||
{
|
{
|
||||||
this->supportedFeatures.insert({
|
this->supportedFeatures.insert({
|
||||||
Feature::PACKET_SIZE, std::to_string(Connection::ABSOLUTE_MAXIMUM_PACKET_READ_SIZE)
|
Feature::PACKET_SIZE, std::to_string(Connection::ABSOLUTE_MAXIMUM_PACKET_READ_SIZE)
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "TargetDescriptor.hpp"
|
#include "TargetDescriptor.hpp"
|
||||||
|
#include "GdbDebugServerConfig.hpp"
|
||||||
#include "Connection.hpp"
|
#include "Connection.hpp"
|
||||||
#include "Feature.hpp"
|
#include "Feature.hpp"
|
||||||
#include "ProgrammingSession.hpp"
|
#include "ProgrammingSession.hpp"
|
||||||
|
|
||||||
|
#include "src/Targets/TargetMemory.hpp"
|
||||||
|
|
||||||
namespace Bloom::DebugServer::Gdb
|
namespace Bloom::DebugServer::Gdb
|
||||||
{
|
{
|
||||||
class DebugSession
|
class DebugSession
|
||||||
@@ -29,6 +33,18 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
*/
|
*/
|
||||||
const TargetDescriptor& gdbTargetDescriptor;
|
const TargetDescriptor& gdbTargetDescriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current server configuration.
|
||||||
|
*/
|
||||||
|
const GdbDebugServerConfig& serverConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal bookkeeping of breakpoints managed by GDB. These will both remain empty if the user has disabled
|
||||||
|
* breakpoint caching.
|
||||||
|
*/
|
||||||
|
std::unordered_set<Targets::TargetMemoryAddress> breakpointAddresses;
|
||||||
|
std::unordered_set<Targets::TargetMemoryAddress> breakpointAddressesPendingRemoval;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the GDB client is waiting for the target to halt, this is set to true so we know when to notify the
|
* When the GDB client is waiting for the target to halt, this is set to true so we know when to notify the
|
||||||
* client.
|
* client.
|
||||||
@@ -53,7 +69,8 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
DebugSession(
|
DebugSession(
|
||||||
Connection&& connection,
|
Connection&& connection,
|
||||||
const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures,
|
const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures,
|
||||||
const TargetDescriptor& targetDescriptor
|
const TargetDescriptor& targetDescriptor,
|
||||||
|
const GdbDebugServerConfig& serverConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
DebugSession(const DebugSession& other) = delete;
|
DebugSession(const DebugSession& other) = delete;
|
||||||
|
|||||||
@@ -30,5 +30,17 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (debugServerConfig.debugServerNode["enableBreakpointCaching"]) {
|
||||||
|
if (YamlUtilities::isCastable<bool>(debugServerConfig.debugServerNode["enableBreakpointCaching"])) {
|
||||||
|
this->breakpointCachingEnabled = debugServerConfig.debugServerNode["enableBreakpointCaching"].as<bool>();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Logger::error(
|
||||||
|
"Invalid GDB debug server config parameter ('enableBreakpointCaching') provided - value must be "
|
||||||
|
"castable to a boolean. The parameter will be ignored."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,19 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
*/
|
*/
|
||||||
std::string listeningAddress = "127.0.0.1";
|
std::string listeningAddress = "127.0.0.1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GDB tends to remove all breakpoints when target execution stops, and then installs them again, just before
|
||||||
|
* resuming target execution. This can result in excessive wearing of the target's program memory, as well as
|
||||||
|
* a negative impact on performance.
|
||||||
|
*
|
||||||
|
* When breakpoint caching is enabled, Bloom's GDB server will perform internal bookkeeping of the breakpoints
|
||||||
|
* installed and removed via GDB. Then, just before resuming target execution, it will only apply the necessary
|
||||||
|
* changes to the target, avoiding the excessive wear and IO.
|
||||||
|
*
|
||||||
|
* This param is optional, and is enabled by default.
|
||||||
|
*/
|
||||||
|
bool breakpointCachingEnabled = true;
|
||||||
|
|
||||||
explicit GdbDebugServerConfig(const DebugServerConfig& debugServerConfig);
|
explicit GdbDebugServerConfig(const DebugServerConfig& debugServerConfig);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,8 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
this->activeDebugSession.emplace(
|
this->activeDebugSession.emplace(
|
||||||
std::move(connection),
|
std::move(connection),
|
||||||
this->getSupportedFeatures(),
|
this->getSupportedFeatures(),
|
||||||
this->getGdbTargetDescriptor()
|
this->getGdbTargetDescriptor(),
|
||||||
|
this->debugServerConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -183,6 +184,10 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
const auto commandPacket = this->waitForCommandPacket();
|
const auto commandPacket = this->waitForCommandPacket();
|
||||||
|
|
||||||
if (commandPacket) {
|
if (commandPacket) {
|
||||||
|
if (this->debugServerConfig.breakpointCachingEnabled && commandPacket->requiresBreakpointFlush()) {
|
||||||
|
this->flushBreakpointRemovals();
|
||||||
|
}
|
||||||
|
|
||||||
commandPacket->handle(this->activeDebugSession.value(), this->targetControllerService);
|
commandPacket->handle(this->activeDebugSession.value(), this->targetControllerService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,4 +372,28 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GdbRspDebugServer::flushBreakpointRemovals() {
|
||||||
|
Logger::debug(
|
||||||
|
"Removing " + std::to_string(this->activeDebugSession->breakpointAddressesPendingRemoval.size())
|
||||||
|
+ " breakpoint(s)"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const auto& breakpointAddress : this->activeDebugSession->breakpointAddressesPendingRemoval) {
|
||||||
|
try {
|
||||||
|
Logger::debug("Removing breakpoint at address " + std::to_string(breakpointAddress));
|
||||||
|
|
||||||
|
this->targetControllerService.removeBreakpoint(Targets::TargetBreakpoint(breakpointAddress));
|
||||||
|
this->activeDebugSession->breakpointAddresses.erase(breakpointAddress);
|
||||||
|
|
||||||
|
} catch (const Exception& exception) {
|
||||||
|
Logger::error(
|
||||||
|
"Failed to remove breakpoint at address " + std::to_string(breakpointAddress) + " from target - "
|
||||||
|
+ exception.getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->activeDebugSession->breakpointAddressesPendingRemoval.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,5 +197,10 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
* a "stop reply" packet to the client once the target execution stops.
|
* a "stop reply" packet to the client once the target execution stops.
|
||||||
*/
|
*/
|
||||||
void onTargetExecutionStopped(const Events::TargetExecutionStopped&);
|
void onTargetExecutionStopped(const Events::TargetExecutionStopped&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions any pending breakpoint removals.
|
||||||
|
*/
|
||||||
|
void flushBreakpointRemovals();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user