New clion_adaptation environment config param, to allow for use of Bloom with CLion's new "debug server" functionality

This commit is contained in:
Nav
2025-02-02 15:52:26 +00:00
parent b06e8cc9ad
commit a9c8f24f8b
12 changed files with 69 additions and 152 deletions

View File

@@ -517,6 +517,7 @@ void Application::stopTargetController() {
void Application::startDebugServer() { void Application::startDebugServer() {
this->debugServer = std::make_unique<DebugServer::DebugServerComponent>( this->debugServer = std::make_unique<DebugServer::DebugServerComponent>(
this->environmentConfig.value(),
this->debugServerConfig.value() this->debugServerConfig.value()
); );
this->debugServerThread = std::thread{&DebugServer::DebugServerComponent::run, this->debugServer.get()}; this->debugServerThread = std::thread{&DebugServer::DebugServerComponent::run, this->debugServer.get()};
@@ -637,7 +638,10 @@ void Application::onDebugServerThreadStateChanged(const Events::DebugServerThrea
} }
void Application::onDebugSessionFinished(const Events::DebugSessionFinished& event) { void Application::onDebugSessionFinished(const Events::DebugSessionFinished& event) {
if (this->environmentConfig->shutdownPostDebugSession || Services::ProcessService::isManagedByClion()) { if (
this->environmentConfig->shutdownPostDebugSession
|| (this->environmentConfig->clionAdaptation && Services::ProcessService::isManagedByClion())
) {
this->triggerShutdown(); this->triggerShutdown();
} }
} }

View File

@@ -13,8 +13,12 @@ namespace DebugServer
{ {
using namespace Events; using namespace Events;
DebugServerComponent::DebugServerComponent(const DebugServerConfig& debugServerConfig) DebugServerComponent::DebugServerComponent(
: debugServerConfig(debugServerConfig) const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig
)
: environmentConfig(environmentConfig)
, debugServerConfig(debugServerConfig)
, targetDescriptor((Services::TargetControllerService()).getTargetDescriptor()) , targetDescriptor((Services::TargetControllerService()).getTargetDescriptor())
{} {}
@@ -49,6 +53,7 @@ namespace DebugServer
} }
return std::make_unique<DebugServer::Gdb::AvrGdb::AvrGdbRsp>( return std::make_unique<DebugServer::Gdb::AvrGdb::AvrGdbRsp>(
this->environmentConfig,
this->debugServerConfig, this->debugServerConfig,
this->targetDescriptor, this->targetDescriptor,
*(this->eventListener.get()), *(this->eventListener.get()),
@@ -64,6 +69,7 @@ namespace DebugServer
} }
return std::make_unique<DebugServer::Gdb::RiscVGdb::RiscVGdbRsp>( return std::make_unique<DebugServer::Gdb::RiscVGdb::RiscVGdbRsp>(
this->environmentConfig,
this->debugServerConfig, this->debugServerConfig,
this->targetDescriptor, this->targetDescriptor,
*(this->eventListener.get()), *(this->eventListener.get()),

View File

@@ -25,7 +25,10 @@ namespace DebugServer
class DebugServerComponent: public Thread class DebugServerComponent: public Thread
{ {
public: public:
explicit DebugServerComponent(const DebugServerConfig& debugServerConfig); explicit DebugServerComponent(
const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig
);
/** /**
* Entry point for the DebugServer. This must called from a dedicated thread. * Entry point for the DebugServer. This must called from a dedicated thread.
@@ -44,9 +47,7 @@ namespace DebugServer
*/ */
EventListenerPointer eventListener = std::make_shared<EventListener>("DebugServerEventListener"); EventListenerPointer eventListener = std::make_shared<EventListener>("DebugServerEventListener");
/** EnvironmentConfig environmentConfig;
* Project configuration for the debug server (extracted from the user project's bloom.yaml).
*/
DebugServerConfig debugServerConfig; DebugServerConfig debugServerConfig;
/** /**

View File

@@ -29,12 +29,14 @@ namespace DebugServer::Gdb::AvrGdb
using Targets::TargetRegisterType; using Targets::TargetRegisterType;
AvrGdbRsp::AvrGdbRsp( AvrGdbRsp::AvrGdbRsp(
const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig, const DebugServerConfig& debugServerConfig,
const Targets::TargetDescriptor& targetDescriptor, const Targets::TargetDescriptor& targetDescriptor,
EventListener& eventListener, EventListener& eventListener,
EventFdNotifier& eventNotifier EventFdNotifier& eventNotifier
) )
: GdbRspDebugServer( : GdbRspDebugServer(
environmentConfig,
debugServerConfig, debugServerConfig,
targetDescriptor, targetDescriptor,
AvrGdbTargetDescriptor{targetDescriptor}, AvrGdbTargetDescriptor{targetDescriptor},

View File

@@ -14,6 +14,7 @@ namespace DebugServer::Gdb::AvrGdb
{ {
public: public:
AvrGdbRsp( AvrGdbRsp(
const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig, const DebugServerConfig& debugServerConfig,
const Targets::TargetDescriptor& targetDescriptor, const Targets::TargetDescriptor& targetDescriptor,
EventListener& eventListener, EventListener& eventListener,

View File

@@ -16,8 +16,9 @@ namespace DebugServer::Gdb::CommandPackets
using ::Exceptions::Exception; using ::Exceptions::Exception;
Detach::Detach(const RawPacket& rawPacket) Detach::Detach(const RawPacket& rawPacket, const EnvironmentConfig& environmentConfig)
: CommandPacket(rawPacket) : CommandPacket(rawPacket)
, environmentConfig(environmentConfig)
{} {}
void Detach::handle( void Detach::handle(
@@ -29,7 +30,14 @@ namespace DebugServer::Gdb::CommandPackets
Logger::info("Handling Detach packet"); Logger::info("Handling Detach packet");
try { try {
if (Services::ProcessService::isManagedByClion()) { if (this->environmentConfig.clionAdaptation && Services::ProcessService::isManagedByClion()) {
/*
* CLion is about to kill Bloom's process. This is our last chance to detach from the target and
* disconnect from the debug tool, cleanly.
*
* So we have to force the TC to shut down immediately, to prevent CLion from killing Bloom and leaving
* the debug tool and target in an undefined state.
*/
targetControllerService.shutdown(); targetControllerService.shutdown();
} }

View File

@@ -2,12 +2,14 @@
#include "CommandPacket.hpp" #include "CommandPacket.hpp"
#include "src/ProjectConfig.hpp"
namespace DebugServer::Gdb::CommandPackets namespace DebugServer::Gdb::CommandPackets
{ {
class Detach: public CommandPacket class Detach: public CommandPacket
{ {
public: public:
explicit Detach(const RawPacket& rawPacket); explicit Detach(const RawPacket& rawPacket, const EnvironmentConfig& environmentConfig);
void handle( void handle(
DebugSession& debugSession, DebugSession& debugSession,
@@ -15,5 +17,8 @@ namespace DebugServer::Gdb::CommandPackets
const Targets::TargetDescriptor& targetDescriptor, const Targets::TargetDescriptor& targetDescriptor,
Services::TargetControllerService& targetControllerService Services::TargetControllerService& targetControllerService
) override; ) override;
private:
const EnvironmentConfig& environmentConfig;
}; };
} }

View File

@@ -86,13 +86,15 @@ namespace DebugServer::Gdb
{ {
public: public:
explicit GdbRspDebugServer( explicit GdbRspDebugServer(
const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig, const DebugServerConfig& debugServerConfig,
const Targets::TargetDescriptor& targetDescriptor, const Targets::TargetDescriptor& targetDescriptor,
GdbTargetDescriptorType&& gdbTargetDescriptor, GdbTargetDescriptorType&& gdbTargetDescriptor,
EventListener& eventListener, EventListener& eventListener,
EventFdNotifier& eventNotifier EventFdNotifier& eventNotifier
) )
: debugServerConfig(GdbDebugServerConfig{debugServerConfig}) : environmentConfig(environmentConfig)
, debugServerConfig(GdbDebugServerConfig{debugServerConfig})
, targetDescriptor(targetDescriptor) , targetDescriptor(targetDescriptor)
, gdbTargetDescriptor(std::move(gdbTargetDescriptor)) , gdbTargetDescriptor(std::move(gdbTargetDescriptor))
, eventListener(eventListener) , eventListener(eventListener)
@@ -179,9 +181,10 @@ namespace DebugServer::Gdb
std::bind(&GdbRspDebugServer::onTargetStateChanged, this, std::placeholders::_1) std::bind(&GdbRspDebugServer::onTargetStateChanged, this, std::placeholders::_1)
); );
if (Services::ProcessService::isManagedByClion()) { if (this->environmentConfig.clionAdaptation && Services::ProcessService::isManagedByClion()) {
Logger::warning( Logger::warning(
"Bloom's process is being managed by CLion - Bloom will automatically shutdown upon detaching from GDB." "Bloom's process is being managed by CLion - Bloom will automatically shut down upon"
" detaching from GDB."
); );
} }
} }
@@ -286,6 +289,7 @@ namespace DebugServer::Gdb
} }
protected: protected:
const EnvironmentConfig& environmentConfig;
GdbDebugServerConfig debugServerConfig; GdbDebugServerConfig debugServerConfig;
const Targets::TargetDescriptor& targetDescriptor; const Targets::TargetDescriptor& targetDescriptor;
GdbTargetDescriptorType gdbTargetDescriptor; GdbTargetDescriptorType gdbTargetDescriptor;
@@ -434,7 +438,7 @@ namespace DebugServer::Gdb
} }
if (rawPacket[1] == 'D') { if (rawPacket[1] == 'D') {
return std::make_unique<CommandPackets::Detach>(rawPacket); return std::make_unique<CommandPackets::Detach>(rawPacket, this->environmentConfig);
} }
const auto rawPacketString = std::string{rawPacket.begin() + 1, rawPacket.end()}; const auto rawPacketString = std::string{rawPacket.begin() + 1, rawPacket.end()};

View File

@@ -26,12 +26,14 @@ namespace DebugServer::Gdb::RiscVGdb
using Targets::TargetRegisterType; using Targets::TargetRegisterType;
RiscVGdbRsp::RiscVGdbRsp( RiscVGdbRsp::RiscVGdbRsp(
const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig, const DebugServerConfig& debugServerConfig,
const Targets::TargetDescriptor& targetDescriptor, const Targets::TargetDescriptor& targetDescriptor,
EventListener& eventListener, EventListener& eventListener,
EventFdNotifier& eventNotifier EventFdNotifier& eventNotifier
) )
: GdbRspDebugServer( : GdbRspDebugServer(
environmentConfig,
debugServerConfig, debugServerConfig,
targetDescriptor, targetDescriptor,
RiscVGdbTargetDescriptor{targetDescriptor}, RiscVGdbTargetDescriptor{targetDescriptor},

View File

@@ -17,6 +17,7 @@ namespace DebugServer::Gdb::RiscVGdb
{ {
public: public:
RiscVGdbRsp( RiscVGdbRsp(
const EnvironmentConfig& environmentConfig,
const DebugServerConfig& debugServerConfig, const DebugServerConfig& debugServerConfig,
const Targets::TargetDescriptor& targetDescriptor, const Targets::TargetDescriptor& targetDescriptor,
EventListener& eventListener, EventListener& eventListener,

View File

@@ -152,6 +152,10 @@ EnvironmentConfig::EnvironmentConfig(std::string name, const YAML::Node& environ
this->shutdownPostDebugSession this->shutdownPostDebugSession
); );
} }
if (environmentNode["clion_adaptation"]) {
this->clionAdaptation = environmentNode["clion_adaptation"].as<bool>(this->clionAdaptation);
}
} }
TargetConfig::TargetConfig(const YAML::Node& targetNode) { TargetConfig::TargetConfig(const YAML::Node& targetNode) {

View File

@@ -30,57 +30,17 @@
*/ */
struct TargetConfig struct TargetConfig
{ {
/**
* The name of the selected target.
*/
std::string name; std::string name;
/**
* The physical interface is the interface used for communication between the debug tool and the connected
* target.
*/
Targets::TargetPhysicalInterface physicalInterface; Targets::TargetPhysicalInterface physicalInterface;
/**
* Determines whether Bloom will resume target execution after activation.
*/
bool resumeOnStartup = false; bool resumeOnStartup = false;
/**
* Determines whether Bloom will make use of the target's hardware breakpoint resources (if available).
*/
bool hardwareBreakpoints = true; bool hardwareBreakpoints = true;
/**
* Determines whether Bloom will employ a cache for the target's program memory.
*/
bool programMemoryCache = true; bool programMemoryCache = true;
/**
* Determines whether Bloom will employ "delta programming" during programming sessions.
*
* Not all targets support delta programming.
*/
bool deltaProgramming = true; bool deltaProgramming = true;
/**
* Determines if Bloom will reserve a single hardware breakpoint for stepping operations.
*/
std::optional<bool> reserveSteppingBreakpoint = std::nullopt; std::optional<bool> reserveSteppingBreakpoint = std::nullopt;
/**
* For extracting any target specific configuration. See Avr8TargetConfig::Avr8TargetConfig() for an example of
* this.
*/
YAML::Node targetNode; YAML::Node targetNode;
TargetConfig() = default; TargetConfig() = default;
/**
* Obtains config parameters from YAML node.
*
* @param targetNode
*/
explicit TargetConfig(const YAML::Node& targetNode); explicit TargetConfig(const YAML::Node& targetNode);
}; };
@@ -93,23 +53,11 @@ struct TargetConfig
*/ */
struct DebugToolConfig struct DebugToolConfig
{ {
/**
* The name of the selected debug tool.
*/
std::string name; std::string name;
/**
* For extracting any debug tool specific configuration.
*/
YAML::Node toolNode; YAML::Node toolNode;
DebugToolConfig() = default; DebugToolConfig() = default;
/**
* Obtains config parameters from YAML node.
*
* @param toolNode
*/
explicit DebugToolConfig(const YAML::Node& toolNode); explicit DebugToolConfig(const YAML::Node& toolNode);
}; };
@@ -118,51 +66,21 @@ struct DebugToolConfig
*/ */
struct DebugServerConfig struct DebugServerConfig
{ {
/**
* The name of the selected debug server.
*/
std::string name; std::string name;
/**
* For extracting any debug server specific configuration. See GdbDebugServerConfig::GdbDebugServerConfig() and
* GdbRspDebugServer::GdbRspDebugServer() for an example of this.
*/
YAML::Node debugServerNode; YAML::Node debugServerNode;
DebugServerConfig() = default; DebugServerConfig() = default;
/**
* Obtains config parameters from YAML node.
*
* @param debugServerNode
*/
explicit DebugServerConfig(const YAML::Node& debugServerNode); explicit DebugServerConfig(const YAML::Node& debugServerNode);
}; };
struct InsightConfig struct InsightConfig
{ {
/**
* If true, the Insight GUI will be activated immediately at startup.
*/
bool activateOnStartup = false; bool activateOnStartup = false;
/**
* If true, Bloom will shutdown when the user closes the Insight GUI.
*/
bool shutdownOnClose = false; bool shutdownOnClose = false;
std::optional<std::string> defaultVariantKey = std::nullopt;
/**
* The key of the variant to select by default, in the Insight GUi.
*/
std::optional<std::string> defaultVariantKey;
InsightConfig() = default; InsightConfig() = default;
/**
* Obtains config parameters from YAML node.
*
* @param insightNode
*/
explicit InsightConfig(const YAML::Node& insightNode); explicit InsightConfig(const YAML::Node& insightNode);
}; };
@@ -174,49 +92,33 @@ struct InsightConfig
*/ */
struct EnvironmentConfig struct EnvironmentConfig
{ {
/**
* The environment name is stored as the key to the YAML map containing the environment parameters.
*
* Environment names must be unique.
*/
std::string name; std::string name;
/**
* Flag to determine whether Bloom should shutdown at the end of a debug session.
*/
bool shutdownPostDebugSession = false; bool shutdownPostDebugSession = false;
/** /**
* Configuration for the environment's selected debug tool. * CLion expects the debug server (in this case, Bloom) to shut down immediately upon receiving the detach command
* via GDB. It does not allow for any time to shut down after that - it just kills the process with a SIGKILL.
* *
* Each environment can select only one debug tool. * I raised this with JetBrains, a number of years ago: https://youtrack.jetbrains.com/issue/CPP-28843
*
* They didn't seem to have any interest in fixing the issue, so I had to include some CLion-specific behaviour
* in Bloom, to prevent CLion from killing Bloom's process before we got a chance to shut down cleanly. It was a
* hacky solution, but it worked.
*
* Recently (in version 2024.3), CLion introduced "debug servers", which allows Bloom to remain running in the
* background, between debug sessions, reducing the time it takes to stop and start new debug sessions.
*
* This doesn't fix the issue described above - CLion still behaves like an aggressive dickhead, sending SIGKILL
* signals whenever it likes. However, Bloom's CLion-specific functionality may conflict with this new "debug server"
* functionality, so it's necessary to allow the user to disable it in their project config.
*/ */
bool clionAdaptation = true;
DebugToolConfig debugToolConfig; DebugToolConfig debugToolConfig;
/**
* Configuration for the environment's selected target.
*
* Each environment can select only one target.
*/
TargetConfig targetConfig; TargetConfig targetConfig;
/**
* Configuration for the environment's debug server. Users can define this at the application level if
* they desire.
*/
std::optional<DebugServerConfig> debugServerConfig; std::optional<DebugServerConfig> debugServerConfig;
/**
* Insight configuration can be defined at an environment level as well as at an application level.
*/
std::optional<InsightConfig> insightConfig; std::optional<InsightConfig> insightConfig;
/**
* Obtains config parameters from YAML node.
*
* @param name
* @param environmentNode
*/
EnvironmentConfig(std::string name, const YAML::Node& environmentNode); EnvironmentConfig(std::string name, const YAML::Node& environmentNode);
}; };
@@ -225,33 +127,10 @@ struct EnvironmentConfig
*/ */
struct ProjectConfig struct ProjectConfig
{ {
/**
* A mapping of environment names to EnvironmentConfig objects.
*/
std::map<std::string, EnvironmentConfig> environments; std::map<std::string, EnvironmentConfig> environments;
/**
* Application level debug server configuration. We use this as a fallback if no debug server config is
* provided at the environment level.
*/
std::optional<DebugServerConfig> debugServerConfig; std::optional<DebugServerConfig> debugServerConfig;
/**
* Application level Insight configuration. We use this as a fallback if no Insight config is provided at
* the environment level.
*
* We don't use std::optional here because the InsightConfig has no mandatory parameters, so users may wish to
* omit the 'insight' node from their bloom.yaml file, entirely. In this case, Bloom should fall back to a default
* constructed, project-level, InsightConfig instance.
*/
InsightConfig insightConfig = {}; InsightConfig insightConfig = {};
bool debugLogging = false; bool debugLogging = false;
/**
* Obtains config parameters from YAML node.
*
* @param configNode
*/
explicit ProjectConfig(const YAML::Node& configNode); explicit ProjectConfig(const YAML::Node& configNode);
}; };