Initial commit

This commit is contained in:
Nav
2021-04-04 21:04:12 +01:00
commit a29c5e1fec
549 changed files with 441216 additions and 0 deletions

344
src/Application.cpp Normal file
View File

@@ -0,0 +1,344 @@
#include <iostream>
#include <csignal>
#include <QtCore>
#include <thread>
#include <QJsonDocument>
#include <QCoreApplication>
#include <unistd.h>
#include <filesystem>
#include "Application.hpp"
#include "src/Logger/Logger.hpp"
#include "SignalHandler/SignalHandler.hpp"
#include "Exceptions/InvalidConfig.hpp"
using namespace Bloom;
using namespace Bloom::Events;
using namespace Bloom::Exceptions;
int Application::run(const std::vector<std::string>& arguments) {
try {
this->setName("Bloom");
if (!arguments.empty()) {
auto firstArg = arguments.front();
auto commandsToCallbackMapping = this->getCommandToCallbackMapping();
if (commandsToCallbackMapping.contains(firstArg)) {
// User has passed an argument that maps to a command callback - invoke the callback and shutdown
auto returnValue = commandsToCallbackMapping.at(firstArg)();
this->shutdown();
return returnValue;
} else {
// If the first argument didn't map to a command, we assume it's an environment name
this->selectedEnvironmentName = firstArg;
}
}
this->startup();
if (this->insightConfig.insightEnabled) {
this->insight.setApplicationConfig(this->applicationConfig);
this->insight.setEnvironmentConfig(this->environmentConfig);
this->insight.run();
Logger::debug("Insight closed");
this->shutdown();
return EXIT_SUCCESS;
}
// Main event loop
while (Thread::getState() == ThreadState::READY) {
this->applicationEventListener->waitAndDispatch();
}
} catch (const InvalidConfig& exception) {
Logger::error(exception.getMessage());
} catch (const Exception& exception) {
Logger::error(exception.getMessage());
}
this->shutdown();
return EXIT_SUCCESS;
}
void Application::startup() {
auto applicationEventListener = this->applicationEventListener;
this->eventManager.registerListener(applicationEventListener);
applicationEventListener->registerCallbackForEventType<Events::ShutdownApplication>(
std::bind(&Application::handleShutdownApplicationEvent, this, std::placeholders::_1)
);
this->applicationConfig = this->extractConfig();
Logger::configure(this->applicationConfig);
// Start signal handler
this->blockAllSignalsOnCurrentThread();
this->signalHandlerThread = std::thread(&SignalHandler::run, std::ref(this->signalHandler));
auto environmentName = this->selectedEnvironmentName.value_or("default");
Logger::info("Selected environment: " + environmentName);
Logger::debug("Number of environments extracted from config: "
+ std::to_string(this->applicationConfig.environments.size()));
// Validate the selected environment
if (!applicationConfig.environments.contains(environmentName)) {
throw InvalidConfig("Environment (\"" + environmentName + "\") not found in configuration.");
}
this->environmentConfig = applicationConfig.environments.at(environmentName);
this->insightConfig = this->environmentConfig.insightConfig.value_or(this->applicationConfig.insightConfig);
if (this->environmentConfig.debugServerConfig.has_value()) {
this->debugServerConfig = this->environmentConfig.debugServerConfig.value();
} else if (this->applicationConfig.debugServerConfig.has_value()) {
this->debugServerConfig = this->applicationConfig.debugServerConfig.value();
} else {
throw InvalidConfig("Debug server configuration missing.");
}
applicationEventListener->registerCallbackForEventType<Events::TargetControllerStateChanged>(
std::bind(&Application::handleTargetControllerStateChangedEvent, this, std::placeholders::_1)
);
applicationEventListener->registerCallbackForEventType<Events::DebugServerStateChanged>(
std::bind(&Application::onDebugServerStateChanged, this, std::placeholders::_1)
);
this->startTargetController();
this->startDebugServer();
Thread::setState(ThreadState::READY);
}
ApplicationConfig Application::extractConfig() {
auto appConfig = ApplicationConfig();
auto currentPath = std::filesystem::current_path().string();
auto jsonConfigFile = QFile(QString::fromStdString(currentPath + "/bloom.json"));
if (!jsonConfigFile.exists()) {
throw InvalidConfig("Bloom configuration file (bloom.json) not found. Working directory: "
+ currentPath);
}
if (!jsonConfigFile.open(QIODevice::ReadOnly)) {
throw InvalidConfig("Failed to load Bloom configuration file (bloom.json) Working directory: "
+ currentPath);
}
auto jsonObject = QJsonDocument::fromJson(jsonConfigFile.readAll()).object();
appConfig.init(jsonObject);
jsonConfigFile.close();
return appConfig;
}
int Application::presentHelpText() {
/*
* Silence all logging here, as we're just to display the help text and then exit the application. Any
* further logging will just be noise.
*/
Logger::silence();
// The file help.txt is included in the binary image as a resource. See src/resource.qrc
auto helpFile = QFile(":/compiled/resources/help.txt");
if (!helpFile.open(QIODevice::ReadOnly)) {
// This should never happen - if it does, something has gone very wrong
throw Exception("Failed to open help file - please report this issue at https://bloom.oscillate.io/report-issue");
}
std::cout << "Bloom v" << Application::VERSION_STR << std::endl;
std::cout << QTextStream(&helpFile).readAll().toUtf8().constData() << std::endl;
return EXIT_SUCCESS;
}
int Application::presentVersionText() {
Logger::silence();
std::cout << "Bloom v" << Application::VERSION_STR << "\n";
std::cout << "https://bloom.oscillate.io/" << "\n";
std::cout << "Nav Mohammed" << std::endl;
return EXIT_SUCCESS;
}
int Application::initProject() {
auto configFile = QFile(QString::fromStdString(std::filesystem::current_path().string() + "/bloom.json"));
if (configFile.exists()) {
throw Exception("Bloom configuration file (bloom.json) already exists in working directory.");
}
/*
* The file bloom.template.json is just a template Bloom config file that is included in the binary image as
* a resource. See src/resource.qrc
*
* We simply copy the template file into the user's working directory.
*/
auto templateConfigFile = QFile(":/compiled/resources/bloom.template.json");
if (!templateConfigFile.open(QIODevice::ReadOnly)) {
throw Exception("Failed to open template configuration file - please report this issue at https://bloom.oscillate.io/report-issue");
}
if (!configFile.open(QIODevice::ReadWrite)) {
throw Exception("Failed to create Bloom configuration file (bloom.json)");
}
configFile.write(templateConfigFile.readAll());
configFile.close();
templateConfigFile.close();
Logger::info("Bloom configuration file (bloom.json) created in working directory.");
return EXIT_SUCCESS;
}
void Application::shutdown() {
auto appState = Thread::getState();
if (appState == ThreadState::STOPPED || appState == ThreadState::SHUTDOWN_INITIATED) {
return;
}
Thread::setState(ThreadState::SHUTDOWN_INITIATED);
Logger::info("Shutting down Bloom");
this->stopDebugServer();
this->stopTargetController();
if (this->signalHandler.getState() == ThreadState::READY) {
// Signal handler is still running
this->signalHandler.triggerShutdown();
// Send meaningless signal to the SignalHandler thread to have it shutdown.
pthread_kill(this->signalHandlerThread.native_handle(), SIGUSR1);
}
if (this->signalHandlerThread.joinable()) {
Logger::debug("Joining SignalHandler thread");
this->signalHandlerThread.join();
Logger::debug("SignalHandler thread joined");
}
Thread::setState(ThreadState::STOPPED);
}
void Application::startTargetController() {
this->targetController.setApplicationConfig(this->applicationConfig);
this->targetController.setEnvironmentConfig(this->environmentConfig);
this->targetControllerThread = std::thread(
&TargetController::run,
std::ref(this->targetController)
);
auto tcStateChangeEvent = this->applicationEventListener->waitForEvent<Events::TargetControllerStateChanged>();
if (!tcStateChangeEvent.has_value() || tcStateChangeEvent->get()->getState() != ThreadState::READY) {
throw Exception("TargetController failed to startup.");
}
}
void Application::stopTargetController() {
auto targetControllerState = this->targetController.getState();
if (targetControllerState == ThreadState::STARTING || targetControllerState == ThreadState::READY) {
// this->applicationEventListener->clearEventsOfType(Events::TargetControllerStateChanged::name);
this->eventManager.triggerEvent(std::make_shared<Events::ShutdownTargetController>());
this->applicationEventListener->waitForEvent<Events::TargetControllerStateChanged>(
std::chrono::milliseconds(10000)
);
}
if (this->targetControllerThread.joinable()) {
Logger::debug("Joining TargetController thread");
this->targetControllerThread.join();
Logger::debug("TargetController thread joined");
}
}
void Application::startDebugServer() {
auto supportedDebugServers = this->getSupportedDebugServers();
if (!supportedDebugServers.contains(this->debugServerConfig.name)) {
throw Exceptions::InvalidConfig("DebugServer \"" + this->debugServerConfig.name + "\" not found.");
}
this->debugServer = supportedDebugServers.at(this->debugServerConfig.name)();
this->debugServer->setApplicationConfig(this->applicationConfig);
this->debugServer->setEnvironmentConfig(this->environmentConfig);
this->debugServer->setDebugServerConfig(this->debugServerConfig);
Logger::info("Selected DebugServer: " + this->debugServer->getName());
this->debugServerThread = std::thread(
&DebugServer::run,
this->debugServer.get()
);
auto dsStateChangeEvent = this->applicationEventListener->waitForEvent<Events::DebugServerStateChanged>();
if (!dsStateChangeEvent.has_value() || dsStateChangeEvent->get()->getState() != ThreadState::READY) {
throw Exception("DebugServer failed to startup.");
}
}
void Application::stopDebugServer() {
if (this->debugServer == nullptr) {
// DebugServer hasn't been resolved yet.
return;
}
auto debugServerState = this->debugServer->getState();
if (debugServerState == ThreadState::STARTING || debugServerState == ThreadState::READY) {
this->eventManager.triggerEvent(std::make_shared<Events::ShutdownDebugServer>());
this->applicationEventListener->waitForEvent<Events::DebugServerStateChanged>(
std::chrono::milliseconds(5000)
);
}
if (this->debugServerThread.joinable()) {
Logger::debug("Joining DebugServer thread");
this->debugServerThread.join();
Logger::debug("DebugServer thread joined");
}
}
void Application::handleTargetControllerStateChangedEvent(EventPointer<Events::TargetControllerStateChanged> event) {
if (event->getState() == ThreadState::STOPPED || event->getState() == ThreadState::SHUTDOWN_INITIATED) {
// TargetController has unexpectedly shutdown - it must have encountered a fatal error.
this->shutdown();
}
}
void Application::handleShutdownApplicationEvent(EventPointer<Events::ShutdownApplication>) {
Logger::debug("ShutdownApplication event received.");
this->shutdown();
}
void Application::onDebugServerStateChanged(EventPointer<Events::DebugServerStateChanged> event) {
if (event->getState() == ThreadState::STOPPED || event->getState() == ThreadState::SHUTDOWN_INITIATED) {
// DebugServer has unexpectedly shutdown - it must have encountered a fatal error.
this->shutdown();
}
}
std::string Application::getApplicationDirPath() {
auto pathCharArray = std::array<char, PATH_MAX>();
if (readlink("/proc/self/exe", pathCharArray.data(), PATH_MAX) < 0) {
throw Exception("Failed to obtain application directory path.");
}
return std::filesystem::path(std::string(pathCharArray.begin(), pathCharArray.end())).parent_path();
}
std::string Application::getResourcesDirPath() {
return Application::getApplicationDirPath() + "/../resources/";
}
bool Application::isRunningAsRoot() {
return geteuid() == 0;
}

183
src/Application.hpp Normal file
View File

@@ -0,0 +1,183 @@
#pragma once
#include <memory>
#include <map>
#include <string>
#include <functional>
#include <QtCore/QtCore>
#include <thread>
#include "src/SignalHandler/SignalHandler.hpp"
#include "src/TargetController/TargetController.hpp"
#include "src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp"
#include "src/Insight/Insight.hpp"
#include "src/Helpers/Thread.hpp"
#include "src/Logger/Logger.hpp"
#include "src/ApplicationConfig.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/EventManager/EventListener.hpp"
#include "src/EventManager/Events/Events.hpp"
namespace Bloom
{
using namespace DebugServers;
class Application: public Thread
{
public:
static const inline std::string VERSION_STR = "0.0.1";
private:
/**
* This is the application wide event manager. It should be the only instance of the EventManager class at
* any given time.
*
* It should be injected (by reference) into any other instances of classes that require use
* of the EventManager.
*/
EventManager eventManager = EventManager();
EventListenerPointer applicationEventListener = std::make_shared<EventListener>("ApplicationEventListener");
SignalHandler signalHandler = SignalHandler(this->eventManager);
std::thread signalHandlerThread;
TargetController targetController = TargetController(this->eventManager);
std::thread targetControllerThread;
std::unique_ptr<DebugServer> debugServer = nullptr;
std::thread debugServerThread;
Insight insight = Insight(this->eventManager);
ApplicationConfig applicationConfig;
EnvironmentConfig environmentConfig;
DebugServerConfig debugServerConfig;
InsightConfig insightConfig;
std::optional<std::string> selectedEnvironmentName;
auto getCommandToCallbackMapping() {
return std::map<std::string, std::function<int()>> {
{
"--help",
std::bind(&Application::presentHelpText, this)
},
{
"-h",
std::bind(&Application::presentHelpText, this)
},
{
"--version",
std::bind(&Application::presentVersionText, this)
},
{
"-v",
std::bind(&Application::presentVersionText, this)
},
{
"init",
std::bind(&Application::initProject, this)
},
};
}
/**
* Extracts config from the user's JSON config file and generates an ApplicationConfig object.
*
* @see ApplicationConfig declaration for more on this.
* @return
*/
static ApplicationConfig extractConfig();
/**
* Presents application help text to user.
*
* @return
*/
int presentHelpText();
/**
* Presents the current Bloom version number to user.
*
* @return
*/
int presentVersionText();
/**
* Initialises a project in the user's working directory.
*
* @return
*/
int initProject();
/**
* Kicks off the application.
*
* Will start the TargetController and DebugServer. And register the main application's event handlers.
*/
void startup();
/**
* Will cleanly shutdown the application. This should never fail.
*/
void shutdown();
/**
* Prepares a dedicated thread for the TargetController and kicks it off.
*/
void startTargetController();
/**
* Invokes a clean shutdown of the TargetController. The TargetController should disconnect from the target
* and debug tool in a clean and safe manner, ensuring that both are left in a sensible state.
*
* This will join the TargetController thread.
*/
void stopTargetController();
void startDebugServer();
void stopDebugServer();
public:
explicit Application() = default;
auto getSupportedDebugServers() {
return std::map<std::string, std::function<std::unique_ptr<DebugServer>()>> {
{
"avr-gdb-rsp",
[this]() -> std::unique_ptr<DebugServer> {
return std::make_unique<DebugServers::Gdb::AvrGdbRsp>(this->eventManager);
}
},
};
};
int run(const std::vector<std::string>& arguments);
void handleTargetControllerStateChangedEvent(EventPointer<Events::TargetControllerStateChanged> event);
void onDebugServerStateChanged(EventPointer<Events::DebugServerStateChanged> event);
void handleShutdownApplicationEvent(EventPointer<Events::ShutdownApplication>);
/**
* Returns the path to the directory in which the Bloom binary resides.
*
* @return
*/
static std::string getApplicationDirPath();
/**
* Returns the path to the Resources directory, located in the application directory.
*
* @return
*/
static std::string getResourcesDirPath();
/**
* Checks if the current effective user running Bloom has root privileges.
*
* @return
*/
static bool isRunningAsRoot();
};
}

88
src/ApplicationConfig.cpp Normal file
View File

@@ -0,0 +1,88 @@
#include <src/Logger/Logger.hpp>
#include <src/Exceptions/InvalidConfig.hpp>
#include "ApplicationConfig.hpp"
using namespace Bloom;
void ApplicationConfig::init(QJsonObject jsonObject) {
if (!jsonObject.contains("environments")) {
throw Exceptions::InvalidConfig("No environments found.");
}
// Extract all environment objects from JSON config.
auto environments = jsonObject.find("environments").value().toObject();
for (auto environmentIt = environments.begin(); environmentIt != environments.end(); environmentIt++) {
auto environmentName = environmentIt.key().toStdString();
try {
auto environmentConfig = EnvironmentConfig();
environmentConfig.init(environmentName, environmentIt.value().toObject());
this->environments.insert(
std::pair<std::string, EnvironmentConfig>(environmentName, environmentConfig)
);
} catch (Exceptions::InvalidConfig& exception) {
Logger::error("Invalid environment config for environment '" + environmentName + "': "
+ exception.getMessage() + " Environment will be ignored.");
}
}
if (jsonObject.contains("debugServer")) {
auto debugServerConfig = DebugServerConfig();
debugServerConfig.init(jsonObject.find("debugServer")->toObject());
this->debugServerConfig = debugServerConfig;
}
if (jsonObject.contains("insight")) {
this->insightConfig.init(jsonObject.find("insight")->toObject());
}
if (jsonObject.contains("debugLoggingEnabled")) {
this->debugLoggingEnabled = jsonObject.find("debugLoggingEnabled").value().toBool();
}
}
void InsightConfig::init(QJsonObject jsonObject) {
if (jsonObject.contains("enabled")) {
this->insightEnabled = jsonObject.find("enabled").value().toBool();
}
}
void EnvironmentConfig::init(std::string name, QJsonObject jsonObject) {
this->name = name;
this->debugToolName = jsonObject.find("debugToolName")->toString().toLower().toStdString();
// Extract target data
if (!jsonObject.contains("target")) {
// Environment has no target data - ignore
throw Exceptions::InvalidConfig("No target configuration provided");
}
this->targetConfig.init(jsonObject.find("target")->toObject());
if (jsonObject.contains("debugServer")) {
auto debugServerConfig = DebugServerConfig();
debugServerConfig.init(jsonObject.find("debugServer")->toObject());
this->debugServerConfig = debugServerConfig;
}
if (jsonObject.contains("insight")) {
auto insightConfig = InsightConfig();
insightConfig.init(jsonObject.find("insight")->toObject());
this->insightConfig = insightConfig;
}
}
void TargetConfig::init(QJsonObject jsonObject) {
if (!jsonObject.contains("name")) {
throw Exceptions::InvalidConfig("No target name found.");
}
this->name = jsonObject.find("name")->toString().toLower().toStdString();
this->jsonObject = jsonObject;
}
void DebugServerConfig::init(QJsonObject jsonObject) {
this->name = jsonObject.find("name")->toString().toLower().toStdString();
this->jsonObject = jsonObject;
}

154
src/ApplicationConfig.hpp Normal file
View File

@@ -0,0 +1,154 @@
#pragma once
#include <memory>
#include <map>
#include <string>
#include <QJsonObject>
namespace Bloom
{
/*
* Currently, all user configuration is stored in a JSON file (bloom.json), in the user's project directory.
*
* The JSON config file should define debugging environment objects. A debugging environment object is just
* a user defined JSON object that holds parameters relating to a specific debugging environment (like the
* name of the DebugTool, Target configuration and any debug server config). Because a config file
* can define multiple debugging environments, each object should be assigned a key in the config file. We use this
* key to allow users to select different debugging environments between debugging sessions.
*
* On application startup, we extract the config from this JSON file and generate an ApplicationConfig object.
* See Application::extractConfig() for more on this.
*
* Some config parameters are specific to certain entities within Bloom, but have no significance across the
* rest of the application. For example, AVR8 targets require 'physicalInterface' and 'configVariant' parameters.
* These are used to configure the AVR8 target, but have no significance across the rest of the application.
* This is why some configuration structs (like TargetConfig) include a QJsonObject member, typically named jsonObject.
* When instances of these structs are passed to the appropriate entities, any configuration required by those
* entities is extracted from the jsonObject member. This means we don't have to worry about any entity specific
* config parameters at the application level. We can simply extract what we need at an entity level and the rest
* of the application can remain oblivious. For an example on extracting entity specific config, see AVR8::configure().
*/
/**
* Configuration relating to a specific target.
*
* Please don't define any target specific configuration here, unless it applies to *all* targets across
* the application. If a target requires specific config, it should be extracted from the jsonObject member.
* This should be done in Target::preActivationConfigure(), to which an instance of TargetConfig is passed.
* See the comment above on entity specific config for more on this.
*/
struct TargetConfig
{
/**
* Obtains config parameters from JSON object.
*
* @param jsonObject
*/
void init(QJsonObject jsonObject);
std::string name;
QJsonObject jsonObject;
};
/**
* Debug server configuration.
*/
struct DebugServerConfig
{
/**
* Obtains config parameters from JSON object.
*
* @param jsonObject
*/
void init(QJsonObject jsonObject);
std::string name;
QJsonObject jsonObject;
};
struct InsightConfig
{
/**
* Obtains config parameters from JSON object.
*
* @param jsonObject
*/
void init(QJsonObject jsonObject);
bool insightEnabled = true;
};
/**
* Configuration relating to a specific user defined environment.
*
* An instance of this type will be instantiated for each environment defined in the user's config file.
* See Application::extractConfig() implementation for more on this.
*/
struct EnvironmentConfig
{
/**
* Obtains config parameters from JSON object.
*
* @param jsonObject
*/
void init(std::string name, QJsonObject jsonObject);
/**
* The environment name is stored as the key to the JSON object containing the environment parameters.
*
* Environment names must be unique.
*/
std::string name;
/**
* Name of the selected debug tool for this environment.
*/
std::string debugToolName;
/**
* Configuration for the environment's selected target.
*
* Each environment can consist of only one target.
*/
TargetConfig targetConfig;
/**
* Configuration for the environment's debug server. Users can define this at the application level if
* they desire.
*/
std::optional<DebugServerConfig> debugServerConfig;
/**
* Insight configuration can be defined at an environment level as well as at an application level.
*/
std::optional<InsightConfig> insightConfig;
};
/**
* This holds all user provided application configuration.
*/
struct ApplicationConfig
{
/**
* Obtains config parameters from JSON object.
*
* @param jsonObject
*/
void init(QJsonObject jsonObject);
/**
* A mapping of environment names to EnvironmentConfig objects.
*/
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;
InsightConfig insightConfig;
bool debugLoggingEnabled = false;
};
}

View File

@@ -0,0 +1,233 @@
#include <variant>
#include <cstdint>
#include "DebugServer.hpp"
#include "src/Exceptions/InvalidConfig.hpp"
#include "src/Logger/Logger.hpp"
using namespace Bloom::DebugServers;
void DebugServer::run() {
try {
this->startup();
Logger::info("DebugServer ready");
while (this->getState() == ThreadState::READY) {
this->serve();
this->eventListener->dispatchCurrentEvents();
}
} catch (const std::exception& exception) {
Logger::error("DebugServer fatal error: " + std::string(exception.what()));
}
this->shutdown();
}
void DebugServer::startup() {
this->setName("DS");
Logger::info("Starting DebugServer");
this->eventManager.registerListener(this->eventListener);
this->interruptEventNotifier = std::make_shared<EventNotifier>();
this->interruptEventNotifier->init();
this->eventListener->setInterruptEventNotifier(this->interruptEventNotifier);
// Register event handlers
this->eventListener->registerCallbackForEventType<Events::ShutdownDebugServer>(
std::bind(&DebugServer::onShutdownDebugServerEvent, this, std::placeholders::_1)
);
this->init();
this->setStateAndEmitEvent(ThreadState::READY);
}
void DebugServer::shutdown() {
if (this->getState() == ThreadState::STOPPED || this->getState() == ThreadState::SHUTDOWN_INITIATED) {
return;
}
this->setState(ThreadState::SHUTDOWN_INITIATED);
Logger::info("Shutting down DebugServer");
this->close();
this->interruptEventNotifier->close();
this->setStateAndEmitEvent(ThreadState::STOPPED);
}
void DebugServer::onShutdownDebugServerEvent(EventPointer<Events::ShutdownDebugServer> event) {
this->shutdown();
}
void DebugServer::stopTargetExecution() {
auto stopTargetEvent = std::make_shared<Events::StopTargetExecution>();
this->eventManager.triggerEvent(stopTargetEvent);
auto responseEvent = this->eventListener->waitForEvent<
Events::TargetExecutionStopped,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), stopTargetEvent->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::TargetExecutionStopped>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}
void DebugServer::continueTargetExecution(std::optional<std::uint32_t> fromAddress) {
auto resumeExecutionEvent = std::make_shared<Events::ResumeTargetExecution>();
if (fromAddress.has_value()) {
resumeExecutionEvent->fromProgramCounter = fromAddress.value();
}
this->eventManager.triggerEvent(resumeExecutionEvent);
auto responseEvent = this->eventListener->waitForEvent<
Events::TargetExecutionResumed,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), resumeExecutionEvent->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::TargetExecutionResumed>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}
void DebugServer::stepTargetExecution(std::optional<std::uint32_t> fromAddress) {
auto stepExecutionEvent = std::make_shared<Events::StepTargetExecution>();
if (fromAddress.has_value()) {
stepExecutionEvent->fromProgramCounter = fromAddress.value();
}
this->eventManager.triggerEvent(stepExecutionEvent);
auto responseEvent = this->eventListener->waitForEvent<
Events::TargetExecutionResumed,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), stepExecutionEvent->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::TargetExecutionResumed>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}
TargetRegisters DebugServer::readGeneralRegistersFromTarget(TargetRegisterDescriptors descriptors) {
auto readRegistersEvent = std::make_shared<Events::RetrieveRegistersFromTarget>();
readRegistersEvent->descriptors = descriptors;
this->eventManager.triggerEvent(readRegistersEvent);
auto responseEvent = this->eventListener->waitForEvent<
Events::RegistersRetrievedFromTarget,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), readRegistersEvent->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::RegistersRetrievedFromTarget>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
auto retrievedRegistersEvent = std::get<EventPointer<Events::RegistersRetrievedFromTarget>>(responseEvent.value());
return retrievedRegistersEvent->registers;
}
void DebugServer::writeGeneralRegistersToTarget(TargetRegisters registers) {
auto event = std::make_shared<Events::WriteRegistersToTarget>();
event->registers = registers;
this->eventManager.triggerEvent(event);
auto responseEvent = this->eventListener->waitForEvent<
Events::RegistersWrittenToTarget,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), event->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::RegistersWrittenToTarget>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}
TargetMemoryBuffer DebugServer::readMemoryFromTarget(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes) {
auto readMemoryEvent = std::make_shared<Events::RetrieveMemoryFromTarget>();
readMemoryEvent->memoryType = memoryType;
readMemoryEvent->startAddress = startAddress;
readMemoryEvent->bytes = bytes;
this->eventManager.triggerEvent(readMemoryEvent);
auto responseEvent = this->eventListener->waitForEvent<
Events::MemoryRetrievedFromTarget,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), readMemoryEvent->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::MemoryRetrievedFromTarget>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
auto retrievedRegistersEvent = std::get<EventPointer<Events::MemoryRetrievedFromTarget>>(responseEvent.value());
return retrievedRegistersEvent->data;
}
void DebugServer::writeMemoryToTarget(
TargetMemoryType memoryType,
std::uint32_t startAddress,
const TargetMemoryBuffer& buffer
) {
auto writeMemoryEvent = std::make_shared<Events::WriteMemoryToTarget>();
writeMemoryEvent->memoryType = memoryType;
writeMemoryEvent->startAddress = startAddress;
writeMemoryEvent->buffer = buffer;
this->eventManager.triggerEvent(writeMemoryEvent);
auto responseEvent = this->eventListener->waitForEvent<
Events::MemoryWrittenToTarget,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), writeMemoryEvent->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::MemoryWrittenToTarget>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}
void DebugServer::setBreakpointOnTarget(TargetBreakpoint breakpoint) {
auto event = std::make_shared<Events::SetBreakpointOnTarget>();
event->breakpoint = breakpoint;
this->eventManager.triggerEvent(event);
auto responseEvent = this->eventListener->waitForEvent<
Events::BreakpointSetOnTarget,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), event->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::BreakpointSetOnTarget>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}
void DebugServer::removeBreakpointOnTarget(TargetBreakpoint breakpoint) {
auto event = std::make_shared<Events::RemoveBreakpointOnTarget>();
event->breakpoint = breakpoint;
this->eventManager.triggerEvent(event);
auto responseEvent = this->eventListener->waitForEvent<
Events::BreakpointRemovedOnTarget,
Events::TargetControllerErrorOccurred
>(std::chrono::milliseconds(5000), event->id);
if (!responseEvent.has_value()
|| !std::holds_alternative<EventPointer<Events::BreakpointRemovedOnTarget>>(responseEvent.value())
) {
throw Exception("Unexpected response from TargetController");
}
}

View File

@@ -0,0 +1,195 @@
#pragma once
#include <map>
#include <functional>
#include <cstdint>
#include "src/EventManager/Events/Events.hpp"
#include "src/EventManager/EventManager.hpp"
#include "src/Exceptions/DebugServerInterrupted.hpp"
#include "src/ApplicationConfig.hpp"
#include "src/Helpers/Thread.hpp"
#include "src/Targets/TargetRegister.hpp"
#include "src/Targets/TargetBreakpoint.hpp"
namespace Bloom::DebugServers
{
using Targets::TargetRegister;
using Targets::TargetRegisterDescriptor;
using Targets::TargetRegisters;
using Targets::TargetRegisterMap;
using Targets::TargetMemoryBuffer;
using Targets::TargetMemoryType;
using Targets::TargetBreakpoint;
/**
* The DebugServer exposes the connected target to third-party debugging software such as IDEs.
* The DebugServer runs on a dedicated thread which is kicked off shortly after the TargetController has been
* started.
*
* All supported DebugServers should be derived from this class.
*
* Bloom currently only supports one DebugServer - the GdbRspDebugServer.
*/
class DebugServer: public Thread
{
private:
/**
* Prepares the debug server thread and then calls init().
*
* Derived classes should not override this method - they should instead use init().
*/
void startup();
/**
* Calls close() and updates the thread state.
*
* As with startup(), derived classes should not override this method. They should use close() instead.
*/
void shutdown();
/**
* Updates the state of the DebugServer and emits a state changed event.
*
* @param state
* @param emitEvent
*/
void setStateAndEmitEvent(ThreadState state) {
Thread::setState(state);
this->eventManager.triggerEvent(
std::make_shared<Events::DebugServerStateChanged>(state)
);
};
/**
* Handles a shutdown request.
*
* @param event
*/
void onShutdownDebugServerEvent(EventPointer<Events::ShutdownDebugServer> event);
protected:
/**
* Application-wide instance to EventManager
*/
EventManager& eventManager;
EventListenerPointer eventListener = std::make_shared<EventListener>("DebugServerEventListener");
/**
* Enables the interruption of any blocking file IO.
*/
std::shared_ptr<EventNotifier> interruptEventNotifier = nullptr;
ApplicationConfig applicationConfig;
EnvironmentConfig environmentConfig;
DebugServerConfig debugServerConfig;
/**
* Called on startup of the DebugServer thread. Derived classes should implement any initialisation work here.
*/
virtual void init() = 0;
/**
* Called repeatedly in an infinite loop when the DebugServer is running.
*/
virtual void serve() = 0;
/**
* Called on shutdown of the debug server.
*/
virtual void close() = 0;
/**
* Requests the TargetController to halt execution on the target.
*/
void stopTargetExecution();
/**
* Requests the TargetController to continue execution on the target.
*
* @param fromAddress
*/
void continueTargetExecution(std::optional<std::uint32_t> fromAddress);
/**
* Requests the TargetController to step execution on the target.
*
* @param fromAddress
*/
void stepTargetExecution(std::optional<std::uint32_t> fromAddress);
/**
* Requests the TargetController to read register values from the target.
*
* @param descriptors
* Descriptors of the registers to read.
*
* @return
*/
TargetRegisters readGeneralRegistersFromTarget(TargetRegisterDescriptors descriptors);
/**
* Requests the TargetController to write register values to the target.
*
* @param registers
*/
void writeGeneralRegistersToTarget(TargetRegisters registers);
/**
* Requests the TargetController to read memory from the target.
*
* @param memoryType
* @param startAddress
* @param bytes
* @return
*/
TargetMemoryBuffer readMemoryFromTarget(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes);
/**
* Requests the TargetController to write memory to the target.
*
* @param memoryType
* @param startAddress
* @param buffer
*/
void writeMemoryToTarget(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer);
/**
* Requests the TargetController to set a breakpoint on the target.
*
* @param breakpoint
*/
void setBreakpointOnTarget(TargetBreakpoint breakpoint);
/**
* Requests the TargetController to remove a breakpoint from the target.
*
* @param breakpoint
*/
void removeBreakpointOnTarget(TargetBreakpoint breakpoint);
public:
DebugServer(EventManager& eventManager) : eventManager(eventManager) {};
void setApplicationConfig(const ApplicationConfig& applicationConfig) {
this->applicationConfig = applicationConfig;
}
void setEnvironmentConfig(const EnvironmentConfig& environmentConfig) {
this->environmentConfig = environmentConfig;
}
void setDebugServerConfig(const DebugServerConfig& debugServerConfig) {
this->debugServerConfig = debugServerConfig;
}
/**
* Entry point for the DebugServer. This must called from a dedicated thread.
*/
void run();
virtual std::string getName() const = 0;
virtual ~DebugServer() = default;
};
}

View 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";
}
};
}

View File

@@ -0,0 +1,11 @@
#pragma once
namespace Bloom::DebugServers::Gdb
{
enum class BreakpointType: int
{
UNKNOWN = 0,
SOFTWARE_BREAKPOINT = 1,
HARDWARE_BREAKPOINT = 2,
};
}

View 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);
}

View 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);
};
}

View 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;
}

View File

@@ -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);
};
}

View 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);
}

View 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;
};
}

View File

@@ -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);
}

View File

@@ -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;
};
}

View File

@@ -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);
}

View File

@@ -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;
};
}

View 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);
}

View 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;
};
}

View 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);
}

View 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;
};
}

View 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);
}

View 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;
};
}

View 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);
}

View 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;
};
}

View File

@@ -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);
}

View File

@@ -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;
};
}

View File

@@ -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);
}

View File

@@ -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;
};
}

View 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);
}

View 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;
};
}

View 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;
}

View 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;
}
};
}

View 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 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;
};
}

View 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;
};
}

View 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;
};
}

View 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"},
};
}
}

View 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(),
[&registerNumberToDescriptorMapping](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, &registerNumberToDescriptorMapping](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'}));
}
}

View 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);
};
}

View 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;
};
}

View File

@@ -0,0 +1,6 @@
#pragma once
namespace Bloom::DebugServers::Gdb
{
using GdbRegisterNumber = int;
}

View 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'};
}
};
}

View 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;
}
};
}

View File

@@ -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());
}

View File

@@ -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;
};
}

View 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());
}
};
}

View File

@@ -0,0 +1,10 @@
#pragma once
namespace Bloom::DebugServers::Gdb
{
enum class Signal: unsigned char
{
TRAP = 5,
INTERRUPTED = 2,
};
}

View 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"},
});
}
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include "TargetInterfaces/Microchip/AVR/AVR8/Avr8Interface.hpp"
namespace Bloom
{
using DebugToolDrivers::TargetInterfaces::Microchip::Avr::Avr8::Avr8Interface;
/**
* A debug tool can be any device that provides access to the connected target. Debug tools are usually connected
* to the host machine via USB.
*
* Each debug tool must implement this interface. Note that target specific driver code should not be placed here.
* Each target family will expect the debug tool to provide an interface for that particular group of targets.
* For an example, see the Avr8Interface class and the DebugTool::getAvr8Interface().
*/
class DebugTool
{
private:
bool initialised = false;
protected:
void setInitialised(bool initialised) {
this->initialised = initialised;
}
public:
bool isInitialised() const {
return this->initialised;
}
virtual void init() = 0;
virtual void close() = 0;
virtual std::string getName() = 0;
virtual std::string getSerialNumber() = 0;
/**
* All debug tools that support AVR8 targets must provide an implementation of the Avr8Interface
* class, via this method.
*
* For debug tools that do not support AVR8 targets, this method should return a nullptr.
*
* @return
*/
virtual Avr8Interface* getAvr8Interface() {
return nullptr;
};
virtual ~DebugTool() = default;
};
}

View File

@@ -0,0 +1,5 @@
#pragma once
#include "DebugTool.hpp"
#include "src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp"
#include "src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp"

View File

@@ -0,0 +1,87 @@
#include <stdexcept>
#include "AtmelIce.hpp"
#include "src/Exceptions/Exception.hpp"
using namespace Bloom::DebugToolDrivers;
using namespace Protocols::CmsisDap::Edbg::Avr;
using namespace Bloom::Exceptions;
void AtmelIce::init() {
UsbDevice::init();
// TODO: Move away from hard-coding the CMSIS-DAP/EDBG interface number
auto& usbHidInterface = this->getEdbgInterface().getUsbHidInterface();
usbHidInterface.setNumber(0);
usbHidInterface.setUSBDevice(this->getLibUsbDevice());
usbHidInterface.setVendorId(this->getVendorId());
usbHidInterface.setProductId(this->getProductId());
if (!usbHidInterface.isInitialised()) {
usbHidInterface.init();
}
/**
* The Atmel-ICE EDBG/CMSIS-DAP interface doesn't operate properly when sending commands too quickly.
*
* Because of this, we have to enforce a minimum time gap between commands. See comment
* in CmsisDapInterface class declaration for more info.
*/
this->getEdbgInterface().setMinimumCommandTimeGap(35);
// We don't need to claim the CMSISDAP interface here as the HIDAPI will have already done so.
if (!this->sessionStarted) {
this->startSession();
}
this->edbgAvr8Interface = std::make_unique<EdbgAvr8Interface>(this->edbgInterface);
this->setInitialised(true);
}
void AtmelIce::close() {
if (this->sessionStarted) {
this->endSession();
}
this->getEdbgInterface().getUsbHidInterface().close();
UsbDevice::close();
}
std::string AtmelIce::getSerialNumber() {
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
CommandFrames::Discovery::Query(CommandFrames::Discovery::QueryContext::SERIAL_NUMBER)
);
if (response.getResponseId() != CommandFrames::Discovery::ResponseId::OK) {
throw Exception("Failed to fetch serial number from device - invalid Discovery Protocol response ID.");
}
auto data = response.getPayloadData();
return std::string(data.begin(), data.end());
}
void AtmelIce::startSession() {
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
CommandFrames::HouseKeeping::StartSession()
);
if (response.getResponseId() == CommandFrames::HouseKeeping::ResponseId::FAILED) {
// Failed response returned!
throw Exception("Failed to start session with Atmel-ICE!");
}
this->sessionStarted = true;
}
void AtmelIce::endSession() {
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
CommandFrames::HouseKeeping::EndSession()
);
if (response.getResponseId() == CommandFrames::HouseKeeping::ResponseId::FAILED) {
// Failed response returned!
throw Exception("Failed to end session with Atmel-ICE!");
}
this->sessionStarted = false;
}

View File

@@ -0,0 +1,104 @@
#pragma once
#include <cstdint>
#include <vector>
#include <memory>
#include "src/DebugToolDrivers/DebugTool.hpp"
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/EdbgInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrames.hpp"
namespace Bloom::DebugToolDrivers
{
using namespace Protocols::CmsisDap;
using Protocols::CmsisDap::Edbg::EdbgInterface;
using Protocols::CmsisDap::Edbg::Avr::EdbgAvr8Interface;
/**
* The Atmel-ICE device is an EDBG (Embedded Debugger) device. It implements the CMSIS-DAP layer as well
* as an Atmel Data Gateway Interface (DGI).
*
* Communication:
* Using the Atmel-ICE device for AVR debugging/programming requires the AVR communication protocol, which
* is a sub-protocol of the CMSIS-DAP (AVR communication protocol commands are wrapped in CMSIS-DAP vendor
* commands). AVR communication protocol commands contain AVR frames, in which commands for numerous
* sub-protocols are enveloped.
*
* So to summarise, issuing an AVR command to the EDBG device, involves:
* Actual command data -> AVR sub-protocol -> AVR Frames -> CMSIS-DAP Vendor commands -> EDBG Device
*
* For more information on protocols and sub-protocols employed by Microchip EDBG devices, see
* the 'Embedded Debugger-Based Tools Protocols User's Guide' document by Microchip.
* @link http://ww1.microchip.com/downloads/en/DeviceDoc/50002630A.pdf
*
* USB Setup:
* Vendor ID: 0x03eb (1003)
* Product ID: 0x2141 (8513)
*
* The Atmel-ICE consists of two USB interfaces. One HID interface for CMSIS-DAP and one vendor specific
* interface for the DGI. We only work with the CMSIS-DAP interface, for now.
*/
class AtmelIce: public DebugTool, public Usb::UsbDevice
{
private:
/**
* The EDBG interface implements additional functionality via vendor specific CMSIS-DAP commands.
* In other words, all EDBG commands are just CMSIS-DAP vendor commands that allow the debug tool
* to support additional functionality, like AVR programming and debugging.
*
* Any non-EDBG CMSIS-DAP commands for the Atmel-ICE can be sent through the EdbgInterface (as the
* EdbgInterface extends the CmsisDapInterface).
*/
EdbgInterface edbgInterface = EdbgInterface();
/**
* The Atmel-ICE employs the EDBG AVR8 Generic protocol, for debugging AVR8 targets. This protocol is
* implemented in EdbgAvr8Interface. See the EdbgAvr8Interface class for more information.
*/
std::unique_ptr<EdbgAvr8Interface> edbgAvr8Interface = nullptr;
bool sessionStarted = false;
public:
static const std::uint16_t USB_VENDOR_ID = 1003;
static const std::uint16_t USB_PRODUCT_ID = 8513;
AtmelIce() : UsbDevice(AtmelIce::USB_VENDOR_ID, AtmelIce::USB_PRODUCT_ID) {}
void init() override;
void close() override;
EdbgInterface& getEdbgInterface() {
return this->edbgInterface;
}
Avr8Interface* getAvr8Interface() override {
return this->edbgAvr8Interface.get();
}
std::string getName() override {
return "Atmel-ICE";
};
/**
* Retrieves the device serial number via the Discovery Protocol.
*
* @return
*/
std::string getSerialNumber() override;
/**
* Starts a session with the EDBG-based tool using the housekeeping protocol.
*/
void startSession();
/**
* Ends the active session with the debug tool.
*/
void endSession();
};
}

View File

@@ -0,0 +1,87 @@
#include <stdexcept>
#include "PowerDebugger.hpp"
#include "src/Exceptions/Exception.hpp"
using namespace Bloom::DebugToolDrivers;
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
using namespace Bloom::Exceptions;
void PowerDebugger::init() {
UsbDevice::init();
// TODO: Move away from hard-coding the CMSIS-DAP/EDBG interface number
auto& usbHidInterface = this->getEdbgInterface().getUsbHidInterface();
usbHidInterface.setNumber(0);
usbHidInterface.setUSBDevice(this->getLibUsbDevice());
usbHidInterface.setVendorId(this->getVendorId());
usbHidInterface.setProductId(this->getProductId());
if (!usbHidInterface.isInitialised()) {
usbHidInterface.init();
}
/**
* The Power Debugger EDBG/CMSIS-DAP interface doesn't operate properly when sending commands too quickly.
*
* Because of this, we have to enforce a minimum time gap between commands. See comment in
* CmsisDapInterface class declaration for more info.
*/
this->getEdbgInterface().setMinimumCommandTimeGap(35);
// We don't need to claim the CMSISDAP interface here as the HIDAPI will have already done so.
if (!this->sessionStarted) {
this->startSession();
}
this->edbgAvr8Interface = std::make_unique<EdbgAvr8Interface>(this->edbgInterface);
this->setInitialised(true);
}
void PowerDebugger::close() {
if (this->sessionStarted) {
this->endSession();
}
this->getEdbgInterface().getUsbHidInterface().close();
UsbDevice::close();
}
std::string PowerDebugger::getSerialNumber() {
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
CommandFrames::Discovery::Query(Discovery::QueryContext::SERIAL_NUMBER)
);
if (response.getResponseId() != Discovery::ResponseId::OK) {
throw Exception("Failed to fetch serial number from device - invalid Discovery Protocol response ID.");
}
auto data = response.getPayloadData();
return std::string(data.begin(), data.end());
}
void PowerDebugger::startSession() {
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
CommandFrames::HouseKeeping::StartSession()
);
if (response.getResponseId() == HouseKeeping::ResponseId::FAILED) {
// Failed response returned!
throw Exception("Failed to start session with the Power Debugger - device returned failed response ID");
}
this->sessionStarted = true;
}
void PowerDebugger::endSession() {
auto response = this->getEdbgInterface().sendAvrCommandFrameAndWaitForResponseFrame(
CommandFrames::HouseKeeping::EndSession()
);
if (response.getResponseId() == HouseKeeping::ResponseId::FAILED) {
// Failed response returned!
throw Exception("Failed to end session with the Power Debugger - device returned failed response ID");
}
this->sessionStarted = false;
}

View File

@@ -0,0 +1,96 @@
#pragma once
#include <cstdint>
#include <vector>
#include <memory>
#include "src/DebugToolDrivers/DebugTool.hpp"
#include "src/DebugToolDrivers/USB/UsbDevice.hpp"
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/EdbgInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrames.hpp"
namespace Bloom::DebugToolDrivers
{
using namespace Protocols::CmsisDap;
using namespace Protocols::CmsisDap::Edbg::Avr::CommandFrames;
using namespace Protocols::CmsisDap::Edbg::Avr::CommandFrames::Discovery;
using namespace Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping;
using Protocols::CmsisDap::Edbg::EdbgInterface;
using Protocols::CmsisDap::Edbg::Avr::EdbgAvr8Interface;
/**
* The Power Debugger device is very similar to the Atmel-ICE. It implements the CMSIS-DAP layer as well
* as an Atmel Data Gateway Interface (DGI).
*
* Communication:
* Like the Atmel-ICE, using the Power Debugger device for AVR debugging/programming requires the AVR
* communication protocol, which is a sub-protocol of the CMSIS-DAP.
*
* For more information on the communication protocol, see the DebugToolDrivers::AtmelIce class.
*
* USB Setup:
* Vendor ID: 0x03eb (1003)
* Product ID: 0x2141 (8513)
*/
class PowerDebugger: public DebugTool, public Usb::UsbDevice
{
private:
/**
* The EDBG interface implements additional functionality via vendor specific CMSIS-DAP commands.
* In other words, all EDBG commands are just CMSIS-DAP vendor commands that allow the debug tool
* to support additional functionality, like AVR programming and debugging.
*
* Any non-EDBG CMSIS-DAP commands for the Power Debugger can be sent through the EDBGInterface (as the
* EdbgInterface extends the CmsisDapInterface).
*/
EdbgInterface edbgInterface = EdbgInterface();
/**
* The Power Debugger employs the EDBG AVR8Generic protocol for interfacing with AVR8 targets.
*/
std::unique_ptr<EdbgAvr8Interface> edbgAvr8Interface;
bool sessionStarted = false;
public:
static const std::uint16_t USB_VENDOR_ID = 1003;
static const std::uint16_t USB_PRODUCT_ID = 8516;
PowerDebugger() : UsbDevice(PowerDebugger::USB_VENDOR_ID, PowerDebugger::USB_PRODUCT_ID) {}
void init() override;
void close() override;
EdbgInterface& getEdbgInterface() {
return this->edbgInterface;
}
Avr8Interface* getAvr8Interface() override {
return this->edbgAvr8Interface.get();
}
std::string getName() override {
return "Power Debugger";
};
/**
* Retrieves the device serial number via the Discovery Protocol.
*
* @return
*/
std::string getSerialNumber() override;
/**
* Starts a session with the EDBG-based tool using the housekeeping protocol.
*/
void startSession();
/**
* Ends the active session with the debug tool.
*/
void endSession();
};
}

View File

@@ -0,0 +1,51 @@
#include <memory>
#include <chrono>
#include <thread>
#include "CmsisDapInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
#include "src/Exceptions/Exception.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap;
using namespace Bloom::Exceptions;
void CmsisDapInterface::sendCommand(const Command& cmsisDapCommand) {
if (this->msSendCommandDelay > 0) {
using namespace std::chrono;
long now = duration_cast<milliseconds>(high_resolution_clock::now().time_since_epoch()).count();
long difference = (now - this->lastCommandSentTimeStamp);
if (difference < this->msSendCommandDelay) {
std::this_thread::sleep_for(milliseconds(this->msSendCommandDelay - difference));
}
this->lastCommandSentTimeStamp = now;
}
this->getUsbHidInterface().write(static_cast<std::vector<unsigned char>>(cmsisDapCommand));
}
std::unique_ptr<Response> CmsisDapInterface::getResponse() {
auto rawResponse = this->getUsbHidInterface().read(5000);
if (rawResponse.size() == 0) {
throw Exception("Empty CMSIS-DAP response received");
}
auto response = std::make_unique<Response>(Response());
response->init(rawResponse);
return response;
}
std::unique_ptr<Response> CmsisDapInterface::sendCommandAndWaitForResponse(const Command& cmsisDapCommand) {
this->sendCommand(cmsisDapCommand);
auto response = this->getResponse();
if (response->getResponseId() != cmsisDapCommand.getCommandId()) {
// This response is not what we were expecting
throw Exception("Unexpected response to CMSIS-DAP command.");
}
return response;
}

View File

@@ -0,0 +1,91 @@
#pragma once
#include <memory>
#include <chrono>
#include "src/DebugToolDrivers/USB/HID/HidInterface.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp"
using namespace Bloom::DebugToolDrivers;
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{
/**
* The CmsisDapInterface class implements the CMSIS-DAP protocol.
*
* See https://www.keil.com/support/man/docs/dapdebug/dapdebug_introduction.htm for more on the CMSIS-DAP protocol.
*/
class CmsisDapInterface
{
private:
/**
* All CMSIS-DAP devices employ the USB HID interface for communication.
*
* For many CMSIS-DAP devices, the USB HID interface parameters (interface number, endpoint config, etc) vary
* amongst devices, so we'll need to be able to preActivationConfigure the CMSISDAPInterface from a
* higher level. For an example, see the constructor of the AtmelIce device class.
*/
Usb::HidInterface usbHidInterface = Usb::HidInterface();
/**
* Some CMSIS-DAP debug tools fail to operate properly when we send commands too quickly. Even if we've
* received a response from every previous command.
*
* Because of this, we may need to enforce a minimum time gap between sending CMSIS commands.
* Setting msSendCommandDelay to any value above 0 will enforce an x millisecond gap between each command
* being sent, where x is the value of msSendCommandDelay.
*/
int msSendCommandDelay = 0;
long lastCommandSentTimeStamp;
public:
explicit CmsisDapInterface() = default;
Usb::HidInterface& getUsbHidInterface() {
return this->usbHidInterface;
}
void setUsbHidInterface(Usb::HidInterface& usbHidInterface) {
this->usbHidInterface = usbHidInterface;
}
size_t getUsbHidInputReportSize() {
return this->usbHidInterface.getInputReportSize();
}
void setMinimumCommandTimeGap(int millisecondTimeGap) {
this->msSendCommandDelay = millisecondTimeGap;
}
/**
* Sends a CMSIS-DAP command to the device.
*
* @param cmsisDapCommand
*/
virtual void sendCommand(const Protocols::CmsisDap::Command& cmsisDapCommand);
/**
* Listens for a CMSIS-DAP response from the device.
*
* @TODO: There is a hard-coded timeout in this method. Review.
*
* @return
* The parsed response.
*/
virtual std::unique_ptr<Protocols::CmsisDap::Response> getResponse();
/**
* Sends a CMSIS-DAP command and waits for a response.
*
* @param cmsisDapCommand
*
* @return
* The parsed response.
*/
virtual std::unique_ptr<Protocols::CmsisDap::Response> sendCommandAndWaitForResponse(
const Protocols::CmsisDap::Command& cmsisDapCommand
);
};
}

View File

@@ -0,0 +1,12 @@
#include "Command.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap;
Command::operator std::vector<unsigned char> () const
{
auto rawCommand = std::vector<unsigned char>(1, this->getCommandId());
auto commandData = this->getData();
rawCommand.insert(rawCommand.end(), commandData.begin(), commandData.end());
return rawCommand;
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{
class Command
{
private:
unsigned char commandId = 0x00;
std::vector<unsigned char> data;
public:
unsigned char getCommandId() const {
return this->commandId;
}
void setCommandId(unsigned char commandId) {
this->commandId = commandId;
}
virtual std::vector<unsigned char> getData() const {
return this->data;
}
void setData(const std::vector<unsigned char>& data) {
this->data = data;
}
[[nodiscard]] int getCommandSize() const {
// +1 for the command ID
return (int) (1 + this->getData().size());
}
std::uint16_t getDataSize() const {
return (std::uint16_t) this->getData().size();
}
/**
* Converts instance of a CMSIS Command to a vector of unsigned char (buffer), for sending
* to the debug tool.
*
* @return
*/
explicit virtual operator std::vector<unsigned char>() const;
virtual ~Command() = default;
};
}

View File

@@ -0,0 +1,24 @@
#include <cstdint>
#include "src/Exceptions/Exception.hpp"
#include "Response.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap;
void Response::init(unsigned char* response, std::size_t length)
{
if (length == 0) {
throw Exceptions::Exception("Failed to process CMSIS-DAP response - invalid response");
}
this->setResponseId(response[0]);
std::vector<unsigned char> data = this->getData();
// TODO: use insert with iterators here
for (std::size_t i = 1; i < length; i++) {
data.push_back(response[i]);
}
this->setData(data);
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{
class Response
{
private:
unsigned char responseId = 0x00;
std::vector<unsigned char> data;
protected:
void setResponseId(unsigned char commandId) {
this->responseId = commandId;
}
void setData(const std::vector<unsigned char>& data) {
this->data = data;
}
public:
Response() = default;
virtual void init(unsigned char* response, std::size_t length);
virtual void init(std::vector<unsigned char> response) {
this->init(response.data(), response.size());
}
unsigned char getResponseId() const {
return this->responseId;
}
virtual const std::vector<unsigned char>& getData() const {
return this->data;
}
virtual ~Response() = default;
};
}

View File

@@ -0,0 +1,146 @@
#pragma once
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{
struct Avr8EdbgParameter
{
unsigned char context = 0x00;
unsigned char id = 0x00;
constexpr Avr8EdbgParameter() = default;
constexpr Avr8EdbgParameter(unsigned char context, unsigned char id)
: context(context), id(id) {};
};
struct Avr8EdbgParameters
{
constexpr static Avr8EdbgParameter CONFIG_VARIANT {0x00, 0x00};
constexpr static Avr8EdbgParameter CONFIG_FUNCTION {0x00, 0x01};
constexpr static Avr8EdbgParameter PHYSICAL_INTERFACE {0x01, 0x00};
constexpr static Avr8EdbgParameter DW_CLOCK_DIVISION_FACTOR {0x01, 0x10};
constexpr static Avr8EdbgParameter XMEGA_PDI_CLOCK {0x01, 0x31};
// debugWire and JTAG parameters
constexpr static Avr8EdbgParameter DEVICE_BOOT_START_ADDR {0x02, 0x0A};
constexpr static Avr8EdbgParameter DEVICE_FLASH_BASE {0x02, 0x06};
constexpr static Avr8EdbgParameter DEVICE_SRAM_START {0x02, 0x0E};
constexpr static Avr8EdbgParameter DEVICE_EEPROM_SIZE {0x02, 0x10};
constexpr static Avr8EdbgParameter DEVICE_EEPROM_PAGE_SIZE {0x02, 0x12};
constexpr static Avr8EdbgParameter DEVICE_FLASH_PAGE_SIZE {0x02, 0x00};
constexpr static Avr8EdbgParameter DEVICE_FLASH_SIZE {0x02, 0x02};
constexpr static Avr8EdbgParameter DEVICE_OCD_REVISION {0x02, 0x13};
constexpr static Avr8EdbgParameter DEVICE_OCD_DATA_REGISTER {0x02, 0x18};
constexpr static Avr8EdbgParameter DEVICE_SPMCR_REGISTER {0x02, 0x1D};
constexpr static Avr8EdbgParameter DEVICE_OSCCAL_ADDR {0x02, 0x1E};
constexpr static Avr8EdbgParameter DEVICE_EEARH_ADDR {0x02, 0x19};
constexpr static Avr8EdbgParameter DEVICE_EEARL_ADDR {0x02, 0x1A};
constexpr static Avr8EdbgParameter DEVICE_EECR_ADDR {0x02, 0x1B};
constexpr static Avr8EdbgParameter DEVICE_EEDR_ADDR {0x02, 0x1C};
// PDI/XMega device parameters
constexpr static Avr8EdbgParameter DEVICE_XMEGA_APPL_BASE_ADDR {0x02, 0x00};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_BOOT_BASE_ADDR {0x02, 0x04};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_EEPROM_BASE_ADDR {0x02, 0x08};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_FUSE_BASE_ADDR {0x02, 0x0C};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_LOCKBIT_BASE_ADDR {0x02, 0x10};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_USER_SIGN_BASE_ADDR {0x02, 0x14};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_PROD_SIGN_BASE_ADDR {0x02, 0x18};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_DATA_BASE_ADDR {0x02, 0x1C};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_APPLICATION_BYTES {0x02, 0x20};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_BOOT_BYTES {0x02, 0x24};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_NVM_BASE {0x02, 0x2B};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_SIGNATURE_OFFSET {0x02, 0x2D};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_FLASH_PAGE_BYTES {0x02, 0x26};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_EEPROM_SIZE {0x02, 0x28};
constexpr static Avr8EdbgParameter DEVICE_XMEGA_EEPROM_PAGE_SIZE {0x02, 0x2A};
constexpr static Avr8EdbgParameter RUN_TIMERS_WHILST_STOPPED {0x03, 0x00};
};
enum class Avr8ConfigVariant: unsigned char
{
LOOPBACK = 0x00,
NONE = 0xff,
DEBUG_WIRE = 0x01,
MEGAJTAG = 0x02,
XMEGA = 0x03,
UPDI = 0x05,
};
enum class Avr8ConfigFunction: unsigned char
{
NONE = 0x00,
PROGRAMMING = 0x01,
DEBUGGING = 0x02,
};
enum class Avr8PhysicalInterface: unsigned char
{
NONE = 0x00,
JTAG = 0x04,
DEBUG_WIRE = 0x05,
PDI = 0x06,
PDI_1W = 0x08,
};
enum class Avr8MemoryType: unsigned char
{
/**
* The SRAM memory type can be used to read &write to internal memory on the target.
*
* It's available with all of the config variants in debugging mode.
*/
SRAM = 0x20,
/**
* The EEPROM memory type can be used to read &write to EEPROM memory on the target.
*
* It's available with all of the config variants, in debugging mode.
*/
EEPROM = 0x22,
/**
* The FLASH_PAGE memory type can be used to read &write full flash pages on the target.
*
* Only available with the JTAG and debugWire config variants.
*
* This memory type is not available with the JTAG config variant in debugging mode. Programming mode will need
* to be enabled before it can be used with JTAG targets. With the debugWire variant, this memory type *can* be
* used whilst in debugging mode.
*/
FLASH_PAGE = 0xB0,
/**
* The APPL_FLASH memory type can be used to read/write flash memory on the target.
*
* Only available with the XMEGA (PDI) and UPDI (PDI_1W) config variants.
*
* When in debugging mode, only read access is permitted. Programming mode will need to be enabled before
* any attempts of writing data.
*/
APPL_FLASH = 0xC0,
/**
* The SPM memory type can be used to read memory from the target whilst in debugging mode.
*
* Only available with JTAG and debugWire config variants.
*/
SPM = 0xA0,
/**
* The REGISTER_FILE memory type can be used to read &write to general purpose registers.
*
* Only available in debugging mode and with XMEGA and UPDI config variants. The SRAM memory type can be used
* to access general purpose registers when other variants are in use.
*/
REGISTER_FILE = 0xB8,
};
enum class Avr8ResponseId: unsigned char
{
OK = 0x80,
DATA = 0x84,
FAILED = 0xA0,
};
}

View File

@@ -0,0 +1,26 @@
#include "AvrCommand.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
std::vector<unsigned char> AvrCommand::getData() const
{
std::vector<unsigned char> data;
auto commandPacket = this->getCommandPacket();
std::size_t commandPacketSize = commandPacket.size();
data.resize(3 + commandPacketSize);
// FragmentInfo byte
data[0] = static_cast<unsigned char>((this->getFragmentNumber() << 4) | this->getFragmentCount());
// Size byte
data[1] = (unsigned char) (commandPacketSize >> 8);
data[2] = (unsigned char) (commandPacketSize & 0xFF);
if (commandPacketSize > 0) {
for (std::size_t index = 0; index <= commandPacketSize - 1; index++) {
data[3 + index] = commandPacket[index];
}
}
return data;
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include <vector>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{
class AvrCommand: public Command
{
private:
size_t fragmentNumber = 1;
size_t fragmentCount = 1;
std::vector<unsigned char> commandPacket;
public:
AvrCommand() {
this->setCommandId(0x80);
}
/**
* Constructs raw command data on the fly.
*
* @return
*/
std::vector<unsigned char> getData() const override;
size_t getFragmentNumber() const {
return this->fragmentNumber;
}
void setFragmentNumber(size_t fragmentNumber) {
this->fragmentNumber = fragmentNumber;
}
size_t getFragmentCount() const {
return this->fragmentCount;
}
void setFragmentCount(size_t fragmentCount) {
this->fragmentCount = fragmentCount;
}
const std::vector<unsigned char>& getCommandPacket() const {
return this->commandPacket;
}
void setCommandPacket(const std::vector<unsigned char>& commandPacket) {
this->commandPacket = commandPacket;
}
};
}

View File

@@ -0,0 +1,53 @@
#include <stdexcept>
#include <iostream>
#include "AvrEvent.hpp"
#include "src/Exceptions/Exception.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
using namespace Bloom::Exceptions;
void AvrEvent::init(unsigned char* response, size_t length)
{
Response::init(response, length);
if (this->getResponseId() != 0x82) {
throw Exception("Failed to construct AvrEvent object - invalid response ID.");
}
std::vector<unsigned char> responseData = this->getData();
if (responseData.size() < 2) {
// All AVR_EVT responses should consist of at least two bytes (excluding the AVR_EVT ID)
throw Exception("Failed to construct AvrEvent object - AVR_EVT response "
"returned no additional data.");
}
// Response size is two bytes, MSB
size_t responsePacketSize = static_cast<size_t>((responseData[0] << 8) | responseData[1]);
if (responseData.size() < 2) {
// All AVR_EVT responses should consist of at least two bytes (excluding the AVR_EVT ID)
throw Exception("Failed to construct AvrEvent object - AVR_EVT response "
"contained invalid event data size.");
}
std::vector<unsigned char> eventData;
/*
* Ignore the SOF, protocol version &handler id and sequence ID (with all make up 5 bytes in total, 7 when
* you include the two size bytes)
*/
eventData.insert(
eventData.end(),
responseData.begin() + 7,
responseData.begin() + 7 + static_cast<long>(responsePacketSize)
);
this->setEventData(eventData);
if (eventData.size() >= 1) {
this->eventId = eventData[0];
}
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include <vector>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/Edbg.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{
using Edbg::ProtocolHandlerId;
enum class AvrEventId : unsigned char
{
AVR8_BREAK_EVENT = 0x40,
};
inline bool operator==(unsigned char rawId, AvrEventId id) {
return static_cast<unsigned char>(id) == rawId;
}
inline bool operator==(AvrEventId id, unsigned char rawId) {
return rawId == id;
}
class AvrEvent: public Response
{
private:
unsigned char eventId;
std::vector<unsigned char> eventData;
protected:
void setEventData(const std::vector<unsigned char>& eventData) {
this->eventData = eventData;
}
public:
AvrEvent() = default;
/**
* Construct an AVRResponse object from a Response object.
*
* @param response
*/
void init(const Response& response) {
auto rawData = response.getData();
rawData.insert(rawData.begin(), response.getResponseId());
this->init(rawData.data(), rawData.size());
}
void init(unsigned char* response, size_t length) override;
const std::vector<unsigned char>& getEventData() const {
return this->eventData;
}
size_t getEventDataSize() const {
return this->eventData.size();
}
AvrEventId getEventId() {
return static_cast<AvrEventId>(this->eventId);
}
};
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <vector>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap
{
class AvrEventCommand: public Command
{
public:
AvrEventCommand() {
this->setCommandId(0x82);
}
};
}

View File

@@ -0,0 +1,44 @@
#include <cstdint>
#include "AvrResponse.hpp"
#include "src/Exceptions/Exception.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
using namespace Bloom::Exceptions;
void AvrResponse::init(unsigned char* response, std::size_t length)
{
Response::init(response, length);
if (this->getResponseId() != 0x81) {
throw Exception("Failed to construct AvrResponse object - invalid response ID.");
}
std::vector<unsigned char> responseData = this->getData();
if (responseData.empty()) {
// All AVR responses should contain at least one byte (the fragment info byte)
throw Exception("Failed to construct AvrResponse object - AVR_RSP response "
"returned no additional data");
}
if (responseData[0] == 0x00) {
// This AVR Response contains no data (the device had no data to send), so we can stop here.
return;
}
this->setFragmentCount(static_cast<std::uint8_t>(responseData[0] & 0x0Fu));
this->setFragmentNumber(static_cast<std::uint8_t>(responseData[0] >> 4));
// Response size is two bytes, MSB
std::size_t responsePacketSize = static_cast<std::size_t>((responseData[1] << 8u) + responseData[2]);
std::vector<unsigned char> responsePacket;
responsePacket.resize(responsePacketSize);
for (std::size_t i = 0; i < responsePacketSize; i++) {
responsePacket[i] = responseData[i + 3];
}
this->setResponsePacket(responsePacket);
}

View File

@@ -0,0 +1,59 @@
#pragma once
#include <cstdint>
#include <vector>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{
class AvrResponse: public Response
{
private:
std::uint8_t fragmentNumber = 0;
std::uint8_t fragmentCount = 0;
std::vector<unsigned char> responsePacket;
protected:
void setFragmentNumber(std::uint8_t fragmentNumber) {
this->fragmentNumber = fragmentNumber;
}
void setFragmentCount(std::uint8_t fragmentCount) {
this->fragmentCount = fragmentCount;
}
void setResponsePacket(const std::vector<unsigned char>& responsePacket) {
this->responsePacket = responsePacket;
}
public:
AvrResponse() = default;
/**
* Construct an AVRResponse object from a Response object.
*
* @param response
*/
void init(const Response& response) {
auto rawData = response.getData();
rawData.insert(rawData.begin(), response.getResponseId());
this->init(rawData.data(), rawData.size());
}
void init(unsigned char* response, std::size_t length) override;
std::uint8_t getFragmentNumber() const {
return this->fragmentNumber;
}
std::uint8_t getFragmentCount() const {
return this->fragmentCount;
}
const std::vector<unsigned char>& getResponsePacket() const {
return this->responsePacket;
}
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include <vector>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{
/**
* All AVR commands result in an automatic response, but that is just a response to confirm
* receipt of the AVR Command. It is *not* a response to the AVR command.
*
* Responses to AVR commands are not automatically sent from the device, they must be requested from the device.
*
* An AvrResponseCommand is a CMSIS-DAP command, used to request a response for an AVR command. In response to an
* AvrResponseCommand, the device will send over an AVRResponse, which will contain the response to the AVR command.
*
* For more information on this, see the 'Embedded Debugger-Based Tools Protocols User's Guide'
* document, by Microchip. Link provided in the AtmelICE device class declaration.
*
* An AvrResponseCommand is very simple - it consists of a command ID and nothing more.
*/
class AvrResponseCommand: public Command
{
public:
AvrResponseCommand() {
this->setCommandId(0x81);
}
};
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class ActivatePhysical: public Avr8GenericCommandFrame
{
private:
bool reset = false;
public:
ActivatePhysical() = default;
ActivatePhysical(bool reset) : reset(reset) {};
void setReset(bool reset) {
this->reset = reset;
}
std::vector<unsigned char> getPayload() const override {
/*
* The activate physical command consists of 3 bytes:
* 1. Command ID (0x10)
* 2. Version (0x00)
* 3. Reset flag (to apply external reset)
*/
auto output = std::vector<unsigned char>(3, 0x00);
output[0] = 0x10;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->reset);
return output;
}
};
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class Attach: public Avr8GenericCommandFrame
{
private:
bool breakAfterAttach = false;
public:
Attach() = default;
Attach(bool breakAfterAttach) : breakAfterAttach(breakAfterAttach) {};
void setBreadAfterAttach(bool breakAfterAttach) {
this->breakAfterAttach = breakAfterAttach;
}
std::vector<unsigned char> getPayload() const override {
/*
* The attach command consists of 3 bytes:
* 1. Command ID (0x13)
* 2. Version (0x00)
* 3. Break (stop) after attach flag
*/
auto output = std::vector<unsigned char>(3, 0x00);
output[0] = 0x13;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->breakAfterAttach);
return output;
}
};
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "src/Exceptions/Exception.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Avr8Generic.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrame.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AVR8Generic/Avr8GenericResponseFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
using namespace DebugToolDrivers::Protocols::CmsisDap;
using namespace DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
class Avr8GenericCommandFrame: public AvrCommandFrame
{
public:
using ResponseFrameType = ResponseFrames::Avr8Generic::Avr8GenericResponseFrame;
Avr8GenericCommandFrame() {
this->setProtocolHandlerId(ProtocolHandlerId::Avr8Generic);
}
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class ClearAllSoftwareBreakpoints: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The clear all software breakpoints command consists of 2 bytes:
* 1. Command ID (0x45)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x45;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
ClearAllSoftwareBreakpoints() {
init();
};
};
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class ClearSoftwareBreakpoints: public Avr8GenericCommandFrame
{
private:
std::vector<std::uint32_t> addresses;
public:
ClearSoftwareBreakpoints() = default;
ClearSoftwareBreakpoints(const std::vector<std::uint32_t>& addresses) : addresses(addresses) {}
void setAddresses(const std::vector<std::uint32_t>& addresses) {
this->addresses = addresses;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The clear software breakpoints command consists of 2 bytes + 4*n bytes, where n is the number
* of breakpoints to clear:
*
* 1. Command ID (0x44)
* 2. Version (0x00)
* ... addresses
*/
auto output = std::vector<unsigned char>(2, 0x00);
output[0] = 0x44;
output[1] = 0x00;
for (const auto& address : this->addresses) {
output.push_back(static_cast<unsigned char>(address));
output.push_back(static_cast<unsigned char>(address >> 8));
output.push_back(static_cast<unsigned char>(address >> 16));
output.push_back(static_cast<unsigned char>(address >> 24));
}
return output;
}
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class DeactivatePhysical: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The deactivate physical command consists of 2 bytes:
* 1. Command ID (0x11)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x11;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
DeactivatePhysical() {
init();
};
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class Detach: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The detach command consists of 2 bytes:
* 1. Command ID (0x11)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x11;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
Detach() {
init();
};
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class DisableDebugWire: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The disable debugWire command consists of 2 bytes:
* 1. Command ID (0x17)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x17;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
DisableDebugWire() {
init();
};
};
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
#include "../../ResponseFrames/AVR8Generic/GetDeviceId.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class GetDeviceId: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The get device ID command consists of 2 bytes:
* 1. Command ID (0x12)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x12;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
using ResponseFrameType = ResponseFrames::Avr8Generic::GetDeviceId;
GetDeviceId() {
init();
};
};
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class GetParameter: public Avr8GenericCommandFrame
{
private:
Avr8EdbgParameter parameter;
std::uint8_t size;
public:
GetParameter() = default;
GetParameter(const Avr8EdbgParameter& parameter) {
this->setParameter(parameter);
}
GetParameter(const Avr8EdbgParameter& parameter, std::uint8_t size) : GetParameter(parameter) {
this->setSize(size);
}
void setParameter(const Avr8EdbgParameter& parameter) {
this->parameter = parameter;
}
void setSize(std::uint8_t size) {
this->size = size;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The get param command consists of 5 bytes:
* 1. Command ID (0x02)
* 2. Version (0x00)
* 3. Param context (Avr8Parameter::context)
* 4. Param ID (Avr8Parameter::id)
* 5. Param value length (this->size)
*/
auto output = std::vector<unsigned char>(5, 0x00);
output[0] = 0x02;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->parameter.context);
output[3] = static_cast<unsigned char>(this->parameter.id);
output[4] = static_cast<unsigned char>(this->size);
return output;
}
};
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
#include "../../ResponseFrames/AVR8Generic/GetProgramCounter.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class GetProgramCounter: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The PC Read command consists of 2 bytes:
* 1. Command ID (0x35)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x35;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
using ResponseFrameType = ResponseFrames::Avr8Generic::GetProgramCounter;
GetProgramCounter() {
init();
};
};
}

View File

@@ -0,0 +1,62 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
#include "../../ResponseFrames/AVR8Generic/ReadMemory.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
using namespace Exceptions;
class ReadMemory: public Avr8GenericCommandFrame
{
private:
Avr8MemoryType type;
std::uint32_t address = 0;
std::uint32_t bytes = 0;
public:
using ResponseFrameType = ResponseFrames::Avr8Generic::ReadMemory;
ReadMemory() = default;
void setType(const Avr8MemoryType& type) {
this->type = type;
}
void setAddress(std::uint32_t address) {
this->address = address;
}
void setBytes(std::uint32_t bytes) {
this->bytes = bytes;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The read memory command consists of 11 bytes:
* 1. Command ID (0x21)
* 2. Version (0x00)
* 3. Memory type (Avr8MemoryType)
* 4. Start address (4 bytes)
* 5. Number of bytes to read (4 bytes)
*/
auto output = std::vector<unsigned char>(11, 0x00);
output[0] = 0x21;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->type);
output[3] = static_cast<unsigned char>(this->address);
output[4] = static_cast<unsigned char>(this->address >> 8);
output[5] = static_cast<unsigned char>(this->address >> 16);
output[6] = static_cast<unsigned char>(this->address >> 24);
output[7] = static_cast<unsigned char>(this->bytes);
output[8] = static_cast<unsigned char>(this->bytes >> 8);
output[9] = static_cast<unsigned char>(this->bytes >> 16);
output[10] = static_cast<unsigned char>(this->bytes >> 24);
return output;
}
};
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class Reset: public Avr8GenericCommandFrame
{
private:
bool stopAtMainAddress = false;
public:
Reset() = default;
Reset(bool stopAtMainAddress) : stopAtMainAddress(stopAtMainAddress) {};
void setStopAtMainAddress(bool stopAtMainAddress) {
this->stopAtMainAddress = stopAtMainAddress;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The reset command consists of 3 bytes:
* 1. Command ID (0x30)
* 2. Version (0x00)
* 3. "Level" (0x01 to stop at boot reset vector or 0x02 to stop at main address)
*/
auto output = std::vector<unsigned char>(3, 0x00);
output[0] = 0x30;
output[1] = 0x00;
output[2] = this->stopAtMainAddress ? 0x02 : 0x01;
return output;
}
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class Run: public Avr8GenericCommandFrame
{
private:
void init() {
/*
* The run command consists of 2 bytes:
* 1. Command ID (0x32)
* 2. Version (0x00)
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x32;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
Run() {
init();
};
};
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class RunTo: public Avr8GenericCommandFrame
{
private:
std::uint32_t address;
public:
RunTo() = default;
RunTo(const std::uint32_t& address) : address(address) {}
void setAddress(const std::uint32_t& address) {
this->address = address;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The run-to command consists of 6 bytes:
*
* 1. Command ID (0x33)
* 2. Version (0x00)
* 3. Address to run to (4 bytes)
*/
auto output = std::vector<unsigned char>(6, 0x00);
output[0] = 0x33;
output[1] = 0x00;
// The address to run to needs to be the 16-bit word address, not the byte address.
auto wordAddress = this->address / 2;
output[2] = (static_cast<unsigned char>(wordAddress));
output[3] = (static_cast<unsigned char>(wordAddress >> 8));
output[4] = (static_cast<unsigned char>(wordAddress >> 16));
output[5] = (static_cast<unsigned char>(wordAddress >> 24));
return output;
}
};
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
using namespace Exceptions;
class SetParameter: public Avr8GenericCommandFrame
{
private:
Avr8EdbgParameter parameter;
std::vector<unsigned char> value;
public:
SetParameter() = default;
SetParameter(const Avr8EdbgParameter& parameter) {
this->setParameter(parameter);
}
SetParameter(const Avr8EdbgParameter& parameter, const std::vector<unsigned char>& value) : SetParameter(parameter) {
this->setValue(value);
}
SetParameter(const Avr8EdbgParameter& parameter, unsigned char value) : SetParameter(parameter) {
this->setValue(value);
}
void setParameter(const Avr8EdbgParameter& parameter) {
this->parameter = parameter;
}
void setValue(const std::vector<unsigned char>& value) {
this->value = value;
}
void setValue(unsigned char value) {
this->value.resize(1, value);
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The set param command consists of this->value.size() + 5 bytes. The first five bytes consist of:
* 1. Command ID (0x01)
* 2. Version (0x00)
* 3. Param context (Avr8Parameter::context)
* 4. Param ID (Avr8Parameter::id)
* 5. Param value length (this->value.size()) - this is only one byte in size, so its value should
* never exceed 255.
*/
auto output = std::vector<unsigned char>(this->value.size() + 5, 0x00);
output[0] = 0x01;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->parameter.context);
output[3] = static_cast<unsigned char>(this->parameter.id);
output[4] = static_cast<unsigned char>(this->value.size());
output.insert(output.begin() + 5, this->value.begin(), this->value.end());
return output;
}
};
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
using namespace Exceptions;
class SetProgramCounter: public Avr8GenericCommandFrame
{
private:
std::uint32_t programCounter = 0;
public:
SetProgramCounter(std::uint32_t programCounter) : programCounter(programCounter) {}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The PC write command consists of 6 bytes:
* 1. Command ID (0x01)
* 2. Version (0x00)
* 3. PC (4 bytes, LSB)
*/
auto output = std::vector<unsigned char>(6, 0x00);
output[0] = 0x36;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->programCounter);
output[3] = static_cast<unsigned char>(this->programCounter >> 8);
output[4] = static_cast<unsigned char>(this->programCounter >> 16);
output[5] = static_cast<unsigned char>(this->programCounter >> 24);
return output;
}
};
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class SetSoftwareBreakpoints: public Avr8GenericCommandFrame
{
private:
std::vector<std::uint32_t> addresses;
public:
SetSoftwareBreakpoints() = default;
SetSoftwareBreakpoints(const std::vector<std::uint32_t>& addresses) : addresses(addresses) {}
void setAddresses(const std::vector<std::uint32_t>& addresses) {
this->addresses = addresses;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The set software breakpoint command consists of 2 bytes + 4*n bytes, where n is the number
* of breakpoints to set:
*
* 1. Command ID (0x43)
* 2. Version (0x00)
* ... addresses
*/
auto output = std::vector<unsigned char>(2, 0x00);
output[0] = 0x43;
output[1] = 0x00;
for (const auto& address : this->addresses) {
output.push_back(static_cast<unsigned char>(address));
output.push_back(static_cast<unsigned char>(address >> 8));
output.push_back(static_cast<unsigned char>(address >> 16));
output.push_back(static_cast<unsigned char>(address >> 24));
}
return output;
}
};
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class SetXmegaSoftwareBreakpoint: public Avr8GenericCommandFrame
{
private:
std::uint32_t address;
public:
SetXmegaSoftwareBreakpoint() = default;
SetXmegaSoftwareBreakpoint(std::uint32_t address) : address(address) {}
void setAddress(std::uint32_t address) {
this->address = address;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The set software breakpoint command consists of 6 bytes bytes
*
* 1. Command ID (0x42)
* 2. Version (0x00)
* 3. Address (4 bytes)
*/
auto output = std::vector<unsigned char>(15, 0x00);
output[0] = 0x42;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(address);
output[3] = static_cast<unsigned char>(address >> 8);
output[4] = static_cast<unsigned char>(address >> 16);
output[5] = static_cast<unsigned char>(address >> 24);
output[13] = 0x01; // One breakpoint
output[14] = 0x00;
return output;
}
};
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class Step: public Avr8GenericCommandFrame
{
public:
Step() = default;
virtual std::vector<unsigned char> getPayload() const override {
/*
* The step command consists of 4 bytes:
* 1. Command ID (0x34)
* 2. Version (0x00)
* 3. Level (0x01 for instruction level step, 0x02 for statement level step)
* 4. Mode (0x00 for step over, 0x01 for step into, 0x02 for step out)
*/
auto output = std::vector<unsigned char>(4, 0x00);
output[0] = 0x34;
output[1] = 0x00;
output[2] = 0x01;
output[3] = 0x01;
return output;
}
};
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include "Avr8GenericCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
class Stop: public Avr8GenericCommandFrame
{
private:
bool stopImmediately = true;
public:
Stop() = default;
Stop(bool stopImmediately) : stopImmediately(stopImmediately) {};
void setStopImmediately(bool stopImmediately) {
this->stopImmediately = stopImmediately;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The stop command consists of 3 bytes:
* 1. Command ID (0x31)
* 2. Version (0x00)
* 3. Stop immediately (0x01 to stop immediately or 0x02 to stop at the next symbol)
*/
auto output = std::vector<unsigned char>(3, 0x00);
output[0] = 0x31;
output[1] = 0x00;
output[2] = this->stopImmediately ? 0x01 : 0x02;
return output;
}
};
}

View File

@@ -0,0 +1,70 @@
#pragma once
#include <cstdint>
#include "Avr8GenericCommandFrame.hpp"
#include "src/Targets/TargetMemory.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Avr8Generic
{
using namespace Exceptions;
using Bloom::Targets::TargetMemoryBuffer;
class WriteMemory: public Avr8GenericCommandFrame
{
private:
Avr8MemoryType type;
std::uint32_t address = 0;
TargetMemoryBuffer buffer;
public:
WriteMemory() = default;
void setType(const Avr8MemoryType& type) {
this->type = type;
}
void setAddress(std::uint32_t address) {
this->address = address;
}
void setBuffer(const TargetMemoryBuffer& buffer) {
this->buffer = buffer;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The write memory command consists of 12 bytes + the buffer size:
* 1. Command ID (0x23)
* 2. Version (0x00)
* 3. Memory type (Avr8MemoryType)
* 4. Start address (4 bytes)
* 5. Number of bytes to write (4 bytes)
* 6. Asynchronous flag (0x00 for "write first, then reply" and 0x01 for "reply first, then write")
* 7. Buffer
*/
auto output = std::vector<unsigned char>(12, 0x00);
output[0] = 0x23;
output[1] = 0x00;
output[2] = static_cast<unsigned char>(this->type);
output[3] = static_cast<unsigned char>(this->address);
output[4] = static_cast<unsigned char>(this->address >> 8);
output[5] = static_cast<unsigned char>(this->address >> 16);
output[6] = static_cast<unsigned char>(this->address >> 24);
auto bytesToWrite = static_cast<std::uint32_t>(this->buffer.size());
output[7] = static_cast<unsigned char>(bytesToWrite);
output[8] = static_cast<unsigned char>(bytesToWrite >> 8);
output[9] = static_cast<unsigned char>(bytesToWrite >> 16);
output[10] = static_cast<unsigned char>(bytesToWrite >> 24);
// We always set the async flag to 0x00 ("write first, then reply")
output[11] = 0x00;
output.insert(output.end(), this->buffer.begin(), this->buffer.end());
return output;
}
};
}

View File

@@ -0,0 +1,61 @@
#include <math.h>
#include "AvrCommandFrame.hpp"
using namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr;
std::vector<AvrCommand> AvrCommandFrame::generateAvrCommands(std::size_t maximumCommandPacketSize) const {
auto rawCommandFrame = static_cast<std::vector<unsigned char>>(*this);
std::size_t commandFrameSize = rawCommandFrame.size();
std::size_t commandsRequired = static_cast<std::size_t>(ceil(static_cast<float>(commandFrameSize) / static_cast<float>(maximumCommandPacketSize)));
std::vector<AvrCommand> avrCommands;
std::size_t copiedPacketSize = 0;
for (std::size_t i = 0; i < commandsRequired; i++) {
AvrCommand avrCommand;
avrCommand.setFragmentCount(commandsRequired);
avrCommand.setFragmentNumber(i + 1);
auto commandPacket = avrCommand.getCommandPacket();
// If we're on the last packet, the packet size will be what ever is left of the AvrCommandFrame
std::size_t commandPacketSize = ((i + 1) != commandsRequired) ? maximumCommandPacketSize
: (commandFrameSize - (maximumCommandPacketSize * i));
commandPacket.insert(
commandPacket.end(),
rawCommandFrame.begin() + static_cast<long>(copiedPacketSize),
rawCommandFrame.begin() + static_cast<long>(copiedPacketSize + commandPacketSize)
);
avrCommand.setCommandPacket(commandPacket);
avrCommands.push_back(avrCommand);
copiedPacketSize += commandPacketSize;
}
return avrCommands;
}
AvrCommandFrame::operator std::vector<unsigned char> () const {
auto data = this->getPayload();
auto dataSize = data.size();
auto rawCommand = std::vector<unsigned char>(5);
rawCommand[0] = this->SOF;
rawCommand[1] = this->getProtocolVersion();
rawCommand[2] = static_cast<unsigned char>(this->getSequenceId());
rawCommand[3] = static_cast<unsigned char>(this->getSequenceId() >> 8);
rawCommand[4] = static_cast<unsigned char>(this->getProtocolHandlerId());
if (dataSize > 0) {
rawCommand.insert(
rawCommand.end(),
data.begin(),
data.end()
);
}
return rawCommand;
}

View File

@@ -0,0 +1,110 @@
#pragma once
#include <cstdint>
#include <vector>
#include <memory>
#include <limits>
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/Edbg.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.hpp"
#include "src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AvrResponseFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr
{
using namespace Edbg;
using namespace DebugToolDrivers::Protocols::CmsisDap;
class AvrCommandFrame
{
private:
unsigned char SOF = 0x0E;
unsigned char protocolVersion = 0x00;
/**
* Incrementing from 0x00
*/
std::uint16_t sequenceId = 0;
inline static std::uint16_t lastSequenceId = 0;
/**
* Destination sub-protocol handler ID
*/
ProtocolHandlerId protocolHandlerID;
std::vector<unsigned char> payload;
public:
using ResponseFrameType = AvrResponseFrame;
AvrCommandFrame() {
if (this->lastSequenceId < std::numeric_limits<decltype(this->lastSequenceId)>::max()) {
this->sequenceId = ++(this->lastSequenceId);
} else {
this->sequenceId = 0;
this->lastSequenceId = 0;
}
};
unsigned char getProtocolVersion() const {
return this->protocolVersion;
}
void setProtocolVersion(unsigned char protocolVersion) {
this->protocolVersion = protocolVersion;
}
std::uint16_t getSequenceId() const {
return this->sequenceId;
}
ProtocolHandlerId getProtocolHandlerId() const {
return this->protocolHandlerID;
}
void setProtocolHandlerId(ProtocolHandlerId protocolHandlerId) {
this->protocolHandlerID = protocolHandlerId;
}
void setProtocolHandlerId(unsigned char protocolHandlerId) {
this->protocolHandlerID = static_cast<ProtocolHandlerId>(protocolHandlerId);
}
virtual std::vector<unsigned char> getPayload() const {
return this->payload;
}
void setPayload(const std::vector<unsigned char>& payload) {
this->payload = payload;
}
/**
* AvrCommandFrames are sent to the device via AVRCommands (CMSIS-DAP vendor commands).
*
* If the size of an AvrCommandFrame exceeds the maximum packet size of an AVRCommand, it will need to
* be split into multiple AVRCommands before being sent to the device.
*
* This methods generates AVR commands from an AvrCommandFrame. The number of AVRCommands generated depends
* on the size of the AvrCommandFrame and the passed maximumCommandPacketSize.
*
* @param maximumCommandPacketSize
* The maximum size of an AVRCommand command packet. This is usually the REPORT_SIZE of the HID
* endpoint, minus a few bytes to account for other AVRCommand fields. The maximumCommandPacketSize is used to
* determine the number of AVRCommands to be generated.
*
* @return
* A vector of sequenced AVRCommands, each containing a segment of the AvrCommandFrame.
*/
std::vector<AvrCommand> generateAvrCommands(std::size_t maximumCommandPacketSize) const;
/**
* Converts instance of a CMSIS Command to an unsigned char, for sending to the Atmel ICE device.
*
* @return
*/
explicit virtual operator std::vector<unsigned char> () const;
};
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "AvrCommandFrame.hpp"
#include "Discovery/DiscoveryCommandFrame.hpp"
#include "Discovery/Query.hpp"
#include "HouseKeeping/HouseKeepingCommandFrame.hpp"
#include "HouseKeeping/StartSession.hpp"
#include "HouseKeeping/EndSession.hpp"
#include "AVR8Generic/Avr8GenericCommandFrame.hpp"

View File

@@ -0,0 +1,47 @@
#pragma once
#include <src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/DiscoveryResponseFrame.hpp>
#include "../AvrCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Discovery
{
using namespace DebugToolDrivers::Protocols::CmsisDap;
/**
* Discovery commands can only return two responses; A LIST response and a failure.
*/
enum class ResponseId : unsigned char
{
/*
* According to the protocol docs, response ID 0x81 is for a LIST response, but this doesn't seem to be
* well defined. So just going to use a generic name.
*/
OK = 0x81,
FAILED = 0xA0,
};
inline bool operator==(unsigned char rawId, ResponseId id) {
return static_cast<unsigned char>(id) == rawId;
}
inline bool operator==(ResponseId id, unsigned char rawId) {
return static_cast<unsigned char>(id) == rawId;
}
inline bool operator!=(unsigned char rawId, ResponseId id) {
return static_cast<unsigned char>(id) != rawId;
}
inline bool operator!=(ResponseId id, unsigned char rawId) {
return static_cast<unsigned char>(id) != rawId;
}
class DiscoveryCommandFrame: public AvrCommandFrame
{
public:
using ResponseFrameType = ResponseFrames::DiscoveryResponseFrame;
DiscoveryCommandFrame() {
this->setProtocolHandlerId(ProtocolHandlerId::Discovery);
}
};
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include "DiscoveryCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::Discovery
{
/**
* The query context is the type of query to execute.
*/
enum class QueryContext : unsigned char
{
COMMAND_HANDLERS = 0x00,
TOOL_NAME = 0x80,
SERIAL_NUMBER = 0x81,
MANUFACTURE_DATE = 0x82,
};
/**
* The Discovery protocol handler only supports one command; the Query command. This command is used to
* query information from the device, such as device capabilities, manufacture date, serial number, etc.
*/
class Query: public DiscoveryCommandFrame
{
private:
QueryContext context;
public:
Query() : DiscoveryCommandFrame() {}
Query(QueryContext context) : DiscoveryCommandFrame() {
this->setContext(context);
}
void setContext(QueryContext context) {
this->context = context;
}
virtual std::vector<unsigned char> getPayload() const override {
/*
* The payload for the Query command consists of three bytes. A command ID (0x00), version (0x00) and a
* query context.
*/
auto output = std::vector<unsigned char>(3, 0x00);
output[2] = static_cast<unsigned char>(this->context);
return output;
}
};
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "HouseKeepingCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping
{
/**
* The End Session command ends the active session with the tool.
*/
class EndSession: public HouseKeepingCommandFrame
{
private:
void init() {
/*
* The payload for the End Session command consists of three bytes. A command ID byte (0x11), a
* version byte (0x00) and a reset flag (0x00 for no reset, 0x01 for tool reset).
*/
auto payload = std::vector<unsigned char>(3);
payload[0] = 0x11;
payload[1] = 0x00;
payload[2] = 0x00;
this->setPayload(payload);
}
public:
EndSession() : HouseKeepingCommandFrame() {
this->init();
}
};
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "../AvrCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping
{
enum class ResponseId : unsigned char
{
OK = 0x80,
LIST = 0x81,
DATA = 0x84,
FAILED = 0xA0,
FAILED_WITH_DATA = 0xA1
};
inline bool operator==(unsigned char rawId, ResponseId id) {
return static_cast<unsigned char>(id) == rawId;
}
inline bool operator==(ResponseId id, unsigned char rawId) {
return rawId == id;
}
class HouseKeepingCommandFrame: public AvrCommandFrame
{
public:
HouseKeepingCommandFrame() {
this->setProtocolHandlerId(ProtocolHandlerId::HouseKeeping);
}
};
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "HouseKeepingCommandFrame.hpp"
namespace Bloom::DebugToolDrivers::Protocols::CmsisDap::Edbg::Avr::CommandFrames::HouseKeeping
{
/**
* The Start Session command begins a session with the tool.
*/
class StartSession: public HouseKeepingCommandFrame
{
private:
void init() {
/*
* The payload for the Start Session command consists of two bytes. A command ID byte (0x10) and a
* version byte (0x00).
*/
auto payload = std::vector<unsigned char>(2);
payload[0] = 0x10;
payload[1] = 0x00;
this->setPayload(payload);
}
public:
StartSession() : HouseKeepingCommandFrame() {
this->init();
}
};
}

Some files were not shown because too many files have changed in this diff Show More