Initial commit
This commit is contained in:
344
src/Application.cpp
Normal file
344
src/Application.cpp
Normal 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
183
src/Application.hpp
Normal 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
88
src/ApplicationConfig.cpp
Normal 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
154
src/ApplicationConfig.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
233
src/DebugServers/DebugServer.cpp
Normal file
233
src/DebugServers/DebugServer.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
195
src/DebugServers/DebugServer.hpp
Normal file
195
src/DebugServers/DebugServer.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
121
src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp
Normal file
121
src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "src/DebugServers/GdbRsp/Register.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
/**
|
||||
* The AVR GDB client (avr-gdb) defines a set of parameters relating to AVR targets. These parameters are
|
||||
* hardcoded in the AVR GDB source code. The client expects all compatible GDB RSP servers to be aware of
|
||||
* these parameters.
|
||||
*
|
||||
* An example of these hardcoded parameters is target registers and the order in which they are supplied; AVR GDB
|
||||
* clients expect 35 registers to be accessible via the server. 32 of these registers are general purpose CPU
|
||||
* registers. The GP registers are expected to be followed by the status register (SREG), stack pointer
|
||||
* register (SPH & SPL) and the program counter. These must all be given in a specific order, which is
|
||||
* pre-determined by the AVR GDB client. See AvrGdbRsp::getRegisterNumberToDescriptorMapping() for more.
|
||||
*
|
||||
* For more on this, see the AVR GDB source code at https://github.com/bminor/binutils-gdb/blob/master/gdb/avr-tdep.c
|
||||
*
|
||||
* The AvrGdpRsp class extends the generic GDB RSP debug server and implements these AVR specific parameters.
|
||||
*/
|
||||
class AvrGdbRsp: public GdbRspDebugServer
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The mask used by the AVR GDB client to encode the memory type into memory addresses.
|
||||
* See AvrGdbRsp::getMemoryTypeFromGdbAddress() for more.
|
||||
*/
|
||||
unsigned int gdbInternalMemoryMask = 0xFE0000u;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* For AVR targets, avr-gdb defines 35 registers in total:
|
||||
*
|
||||
* Register number 0 through 31 are general purpose registers
|
||||
* Register number 32 is the status register (SREG)
|
||||
* Register number 33 is the stack pointer register
|
||||
* Register number 34 is the program counter register
|
||||
*
|
||||
* Only general purpose registers have register IDs. The others do not require an ID.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() override {
|
||||
static BiMap<GdbRegisterNumber, TargetRegisterDescriptor> mapping = {
|
||||
{0, TargetRegisterDescriptor(0, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{1, TargetRegisterDescriptor(1, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{2, TargetRegisterDescriptor(2, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{3, TargetRegisterDescriptor(3, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{4, TargetRegisterDescriptor(4, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{5, TargetRegisterDescriptor(5, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{6, TargetRegisterDescriptor(6, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{7, TargetRegisterDescriptor(7, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{8, TargetRegisterDescriptor(8, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{9, TargetRegisterDescriptor(9, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{10, TargetRegisterDescriptor(10, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{11, TargetRegisterDescriptor(11, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{12, TargetRegisterDescriptor(12, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{13, TargetRegisterDescriptor(13, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{14, TargetRegisterDescriptor(14, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{15, TargetRegisterDescriptor(15, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{16, TargetRegisterDescriptor(16, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{17, TargetRegisterDescriptor(17, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{18, TargetRegisterDescriptor(18, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{19, TargetRegisterDescriptor(19, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{20, TargetRegisterDescriptor(20, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{21, TargetRegisterDescriptor(21, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{22, TargetRegisterDescriptor(22, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{23, TargetRegisterDescriptor(23, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{24, TargetRegisterDescriptor(24, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{25, TargetRegisterDescriptor(25, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{26, TargetRegisterDescriptor(26, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{27, TargetRegisterDescriptor(27, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{28, TargetRegisterDescriptor(28, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{29, TargetRegisterDescriptor(29, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{30, TargetRegisterDescriptor(30, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{31, TargetRegisterDescriptor(31, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{32, TargetRegisterDescriptor(TargetRegisterType::STATUS_REGISTER)},
|
||||
{33, TargetRegisterDescriptor(TargetRegisterType::STACK_POINTER)},
|
||||
{34, TargetRegisterDescriptor(TargetRegisterType::PROGRAM_COUNTER)},
|
||||
};
|
||||
|
||||
return mapping;
|
||||
};
|
||||
|
||||
/**
|
||||
* avr-gdb uses the most significant 15 bits in memory addresses to indicate the type of memory being
|
||||
* addressed.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) override {
|
||||
if (address & this->gdbInternalMemoryMask) {
|
||||
return TargetMemoryType::RAM;
|
||||
}
|
||||
|
||||
return TargetMemoryType::FLASH;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strips the most significant 15 bits from a GDB memory address.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) override {
|
||||
return address & this->gdbInternalMemoryMask ? (address & ~(this->gdbInternalMemoryMask)) : address;
|
||||
};
|
||||
|
||||
public:
|
||||
AvrGdbRsp(EventManager& eventManager) : GdbRspDebugServer(eventManager) {};
|
||||
|
||||
std::string getName() const override {
|
||||
return "AVR GDB Remote Serial Protocol Debug Server";
|
||||
}
|
||||
};
|
||||
}
|
||||
11
src/DebugServers/GdbRsp/BreakpointType.hpp
Normal file
11
src/DebugServers/GdbRsp/BreakpointType.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class BreakpointType: int
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
SOFTWARE_BREAKPOINT = 1,
|
||||
HARDWARE_BREAKPOINT = 2,
|
||||
};
|
||||
}
|
||||
8
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
Normal file
8
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void CommandPacket::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
51
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp
Normal file
51
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
class GdbRspDebugServer;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* GDB RSP command packets are sent to the server, from the GDB client. These packets carry instructions that the
|
||||
* server is expected to carry out. Upon completion, the server is expected to respond to the client with
|
||||
* a ResponsePacket.
|
||||
*
|
||||
* For some command packets, we define a specific data structure by extending this CommandPacket class. These
|
||||
* classes extend the data structure to include fields for data which may be specific to the command.
|
||||
* They also implement additional methods that allow us to easily access the additional data. An example
|
||||
* of this would be the SupportedFeaturesQuery class. It extends the CommandPacket class and provides access
|
||||
* to additional data fields that are specific to the command (in this case, a set of GDB features reported to be
|
||||
* supported by the GDB client).
|
||||
*
|
||||
* Typically, command packets that require specific data structures are handled in a dedicated handler method
|
||||
* in the GdbRspDebugServer. This is done by double dispatching the packet object to the appropriate handler.
|
||||
* See CommandPacket::dispatchToHandler(), GdbRspDebugServer::serve() and the overloads
|
||||
* for GdbRspDebugServer::handleGdbPacket() for more on this.
|
||||
*
|
||||
* Some command packets are so simple they do not require a dedicated data structure. An example of this is
|
||||
* the halt reason packet, which contains nothing more than an ? character in the packet body. These packets are
|
||||
* typically handled in the generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) method.
|
||||
*
|
||||
* See the Packet class for information on how the raw packets are formatted.
|
||||
*/
|
||||
class CommandPacket: public Packet
|
||||
{
|
||||
public:
|
||||
CommandPacket(const std::vector<unsigned char>& rawPacket) : Packet(rawPacket) {}
|
||||
|
||||
/**
|
||||
* Double dispatches the packet to the appropriate overload of handleGdbPacket(), within the passed instance of
|
||||
* GdbRspDebugServer. If there is no overload defined for a specific CommandPacket-inherited type, the
|
||||
* generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) is used.
|
||||
*
|
||||
* @param gdbRspDebugServer
|
||||
*/
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer);
|
||||
};
|
||||
}
|
||||
129
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
Normal file
129
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
#include "CommandPacketFactory.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
std::unique_ptr<CommandPacket> CommandPacketFactory::create(std::vector<unsigned char> rawPacket) {
|
||||
|
||||
if (rawPacket.size() == 5 && rawPacket[1] == 0x03) {
|
||||
// This is an interrupt request - create a fake packet for it
|
||||
return std::make_unique<CommandPackets::InterruptExecution>(rawPacket);
|
||||
}
|
||||
|
||||
auto rawPacketString = std::string(rawPacket.begin(), rawPacket.end());
|
||||
|
||||
if (rawPacketString.size() >= 2) {
|
||||
/*
|
||||
* First byte of the raw packet will be 0x24 ('$'), so find() should return 1, not 0, when
|
||||
* looking for a command identifier string.
|
||||
*/
|
||||
if (rawPacketString.find("qSupported") == 1) {
|
||||
return std::make_unique<CommandPackets::SupportedFeaturesQuery>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'g' || rawPacketString[1] == 'p') {
|
||||
return std::make_unique<CommandPackets::ReadGeneralRegisters>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'G' || rawPacketString[1] == 'P') {
|
||||
return std::make_unique<CommandPackets::WriteGeneralRegisters>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'c') {
|
||||
return std::make_unique<CommandPackets::ContinueExecution>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 's') {
|
||||
return std::make_unique<CommandPackets::StepExecution>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'm') {
|
||||
return std::make_unique<CommandPackets::ReadMemory>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'M') {
|
||||
return std::make_unique<CommandPackets::WriteMemory>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'Z') {
|
||||
return std::make_unique<CommandPackets::SetBreakpoint>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'z') {
|
||||
return std::make_unique<CommandPackets::RemoveBreakpoint>(rawPacket);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<CommandPacket>(rawPacket);
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned char>> CommandPacketFactory::extractRawPackets(std::vector<unsigned char> buffer) {
|
||||
std::vector<std::vector<unsigned char>> output;
|
||||
|
||||
std::size_t bufferIndex;
|
||||
std::size_t bufferSize = buffer.size();
|
||||
unsigned char byte;
|
||||
for (bufferIndex = 0; bufferIndex < bufferSize; bufferIndex++) {
|
||||
byte = buffer[bufferIndex];
|
||||
|
||||
if (byte == 0x03) {
|
||||
/*
|
||||
* This is an interrupt packet - it doesn't carry any of the usual packet frame bytes, so we'll just
|
||||
* add them here, in order to keep things consistent.
|
||||
*
|
||||
* Because we're effectively faking the packet frame, we can use any value for the checksum.
|
||||
*/
|
||||
output.push_back({'$', byte, '#', 'F', 'F'});
|
||||
|
||||
} else if (byte == '$') {
|
||||
// Beginning of packet
|
||||
std::vector<unsigned char> rawPacket;
|
||||
rawPacket.push_back('$');
|
||||
|
||||
auto packetIndex = bufferIndex;
|
||||
bool validPacket = false;
|
||||
bool isByteEscaped = false;
|
||||
|
||||
for (packetIndex++; packetIndex < bufferSize; packetIndex++) {
|
||||
byte = buffer[packetIndex];
|
||||
|
||||
if (byte == '}' && !isByteEscaped) {
|
||||
isByteEscaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (byte == '$' && !isByteEscaped) {
|
||||
// Unexpected end of packet
|
||||
validPacket = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (byte == '#' && !isByteEscaped) {
|
||||
// End of packet data
|
||||
if ((bufferSize - 1) < (packetIndex + 2)) {
|
||||
// There should be at least two more bytes in the buffer, for the checksum.
|
||||
break;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
|
||||
// Add the checksum bytes and break the loop
|
||||
rawPacket.push_back(buffer[++packetIndex]);
|
||||
rawPacket.push_back(buffer[++packetIndex]);
|
||||
validPacket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isByteEscaped) {
|
||||
// Escaped bytes are XOR'd with a 0x20 mask.
|
||||
byte ^= 0x20;
|
||||
isByteEscaped = false;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
}
|
||||
|
||||
if (validPacket) {
|
||||
output.push_back(rawPacket);
|
||||
bufferIndex = packetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Command packets
|
||||
#include "CommandPacket.hpp"
|
||||
#include "InterruptExecution.hpp"
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
#include "ReadGeneralRegisters.hpp"
|
||||
#include "WriteGeneralRegisters.hpp"
|
||||
#include "ReadMemory.hpp"
|
||||
#include "WriteMemory.hpp"
|
||||
#include "StepExecution.hpp"
|
||||
#include "ContinueExecution.hpp"
|
||||
#include "SetBreakpoint.hpp"
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using namespace CommandPackets;
|
||||
|
||||
/**
|
||||
* The CommandPacketFactory class provides a means for extracting raw packet data from a raw buffer, and
|
||||
* constructing the appropriate CommandPacket objects.
|
||||
*/
|
||||
class CommandPacketFactory
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Extracts raw GDB RSP packets from buffer.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer from which to extract the raw GDB RSP packets.
|
||||
*
|
||||
* @return
|
||||
* A vector of raw packets.
|
||||
*/
|
||||
static std::vector<std::vector<unsigned char>> extractRawPackets(std::vector<unsigned char> buffer);
|
||||
|
||||
/**
|
||||
* Constructs the appropriate CommandPacket object from a single raw GDB RSP packet.
|
||||
*
|
||||
* @param rawPacket
|
||||
* The raw GDB RSP packet from which to construct the CommandPacket object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::unique_ptr<CommandPacket> create(std::vector<unsigned char> rawPacket);
|
||||
};
|
||||
}
|
||||
18
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
Normal file
18
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "ContinueExecution.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void ContinueExecution::init() {
|
||||
if (this->data.size() > 1) {
|
||||
this->fromProgramCounter = static_cast<std::uint32_t>(
|
||||
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ContinueExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.hpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ContinueExecution class implements a structure for "c" packets. These packets instruct the server
|
||||
* to continue execution on the target.
|
||||
*
|
||||
* See @link https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets for more on this.
|
||||
*/
|
||||
class ContinueExecution: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The "c" packet can contain an address which defines the point from which the execution should be resumed on
|
||||
* the target.
|
||||
*
|
||||
* Although the packet *can* contain this address, it is not required, hence the optional.
|
||||
*/
|
||||
std::optional<std::uint32_t> fromProgramCounter;
|
||||
|
||||
ContinueExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "InterruptExecution.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void InterruptExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The InterruptException class represents interrupt command packets. Upon receiving an interrupt packet, the
|
||||
* server is expected to interrupt execution on the target.
|
||||
*
|
||||
* Technically, interrupts are not sent by the client in the form of a typical GDP RSP packet. Instead, they're
|
||||
* just sent as a single byte from the client. We fake the packet on our end, to save us the headache of dealing
|
||||
* with this inconsistency. We do this in CommandPacketFactory::extractRawPackets().
|
||||
*/
|
||||
class InterruptExecution: public CommandPacket
|
||||
{
|
||||
public:
|
||||
InterruptExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ReadGeneralRegisters.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void ReadGeneralRegisters::init() {
|
||||
if (this->data.size() >= 2 && this->data.front() == 'p') {
|
||||
// This command packet is requesting a specific register
|
||||
this->registerNumber = static_cast<size_t>(std::stoi(std::string(this->data.begin() + 1, this->data.end())));
|
||||
}
|
||||
}
|
||||
|
||||
void ReadGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ReadGeneralRegisters class implements a structure for "g" and "p" command packets. In response to these
|
||||
* packets, the server is expected to send register values for all registers (for "g" packets) or for a single
|
||||
* register (for "p" packets).
|
||||
*/
|
||||
class ReadGeneralRegisters: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* "p" packets include a register number to indicate which register is requested for reading. When this is set,
|
||||
* the server is expected to respond with only the value of the requested register.
|
||||
*
|
||||
* If the register number is not supplied (as is the case with "g" packets), the server is expected to respond
|
||||
* with values for all registers.
|
||||
*/
|
||||
std::optional<int> registerNumber;
|
||||
|
||||
ReadGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
43
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
Normal file
43
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "ReadMemory.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void ReadMemory::init() {
|
||||
if (this->data.size() < 4) {
|
||||
throw Exception("Invalid packet length");
|
||||
}
|
||||
|
||||
auto packetString = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 1),
|
||||
static_cast<int>(this->data.size() - 1)
|
||||
);
|
||||
|
||||
/*
|
||||
* The read memory ('m') packet consists of two segments, an address and a number of bytes to read.
|
||||
* These are separated by a comma character.
|
||||
*/
|
||||
auto packetSegments = packetString.split(",");
|
||||
|
||||
if (packetSegments.size() != 2) {
|
||||
throw Exception("Unexpected number of segments in packet data: " + std::to_string(packetSegments.size()));
|
||||
}
|
||||
|
||||
bool conversionStatus = false;
|
||||
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse start address from read memory packet data");
|
||||
}
|
||||
|
||||
this->bytes = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse read length from read memory packet data");
|
||||
}
|
||||
}
|
||||
|
||||
void ReadMemory::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
42
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.hpp
Normal file
42
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ReadMemory class implements a structure for "m" packets. Upon receiving these packets, the server is
|
||||
* expected to read memory from the target and send it the client.
|
||||
*/
|
||||
class ReadMemory: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The startAddress sent from the GDB client may include additional bits used to indicate the memory type.
|
||||
* These bits have to be removed from the address before it can be used as a start address. This is not done
|
||||
* here, as it's target specific.
|
||||
*
|
||||
* For an example of where GDB does this, see the AvrGdbRsp class.
|
||||
*/
|
||||
std::uint32_t startAddress;
|
||||
|
||||
/**
|
||||
* Number of bytes to read.
|
||||
*/
|
||||
std::uint32_t bytes;
|
||||
|
||||
ReadMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void RemoveBreakpoint::init() {
|
||||
if (data.size() < 6) {
|
||||
throw Exception("Unexpected RemoveBreakpoint packet size");
|
||||
}
|
||||
|
||||
// z0 = SW breakpoint, z1 = HW breakpoint
|
||||
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
|
||||
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
|
||||
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 2),
|
||||
static_cast<int>(this->data.size() - 2)
|
||||
);
|
||||
|
||||
auto packetSegments = packetData.split(",");
|
||||
if (packetSegments.size() < 3) {
|
||||
throw Exception("Unexpected number of packet segments in RemoveBreakpoint packet");
|
||||
}
|
||||
|
||||
bool conversionStatus = true;
|
||||
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to convert address hex value from RemoveBreakpoint packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveBreakpoint::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
44
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.hpp
Normal file
44
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../BreakpointType.hpp"
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature: int;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The RemoveBreakpoint class implements the structure for "z" command packets. Upon receiving this command, the
|
||||
* server is expected to remove a breakpoint at the specified address.
|
||||
*/
|
||||
class RemoveBreakpoint: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address;
|
||||
|
||||
RemoveBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
39
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
Normal file
39
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "SetBreakpoint.hpp"
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void SetBreakpoint::init() {
|
||||
if (data.size() < 6) {
|
||||
throw Exception("Unexpected SetBreakpoint packet size");
|
||||
}
|
||||
|
||||
// Z0 = SW breakpoint, Z1 = HW breakpoint
|
||||
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
|
||||
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
|
||||
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 2),
|
||||
static_cast<int>(this->data.size() - 2)
|
||||
);
|
||||
|
||||
auto packetSegments = packetData.split(",");
|
||||
if (packetSegments.size() < 3) {
|
||||
throw Exception("Unexpected number of packet segments in SetBreakpoint packet");
|
||||
}
|
||||
|
||||
bool conversionStatus = true;
|
||||
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to convert address hex value from SetBreakpoint packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SetBreakpoint::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
44
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.hpp
Normal file
44
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../BreakpointType.hpp"
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature: int;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The SetBreakpoint class implements the structure for "Z" command packets. Upon receiving this command, the
|
||||
* server is expected to set a breakpoint at the specified address.
|
||||
*/
|
||||
class SetBreakpoint: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address;
|
||||
|
||||
SetBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
18
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
Normal file
18
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "StepExecution.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void StepExecution::init() {
|
||||
if (this->data.size() > 1) {
|
||||
this->fromProgramCounter = static_cast<std::uint32_t>(
|
||||
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void StepExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
32
src/DebugServers/GdbRsp/CommandPackets/StepExecution.hpp
Normal file
32
src/DebugServers/GdbRsp/CommandPackets/StepExecution.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The StepExecution class implements the structure for "s" command packets. Upon receiving this command, the
|
||||
* server is expected to step execution on the target.
|
||||
*/
|
||||
class StepExecution: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The address from which to begin the step.
|
||||
*/
|
||||
std::optional<size_t> fromProgramCounter;
|
||||
|
||||
StepExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void SupportedFeaturesQuery::init() {
|
||||
/*
|
||||
* For qSupported packets, supported and unsupported GDB features are reported in the packet
|
||||
* data, where each GDB feature is separated by a semicolon.
|
||||
*/
|
||||
|
||||
// The "qSupported:" prefix occupies 11 bytes
|
||||
if (data.size() > 11) {
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 11),
|
||||
static_cast<int>(this->data.size() - 11)
|
||||
);
|
||||
|
||||
auto featureList = packetData.split(";");
|
||||
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
|
||||
|
||||
for (int i = 0; i < featureList.size(); i++) {
|
||||
auto featureString = featureList.at(i);
|
||||
|
||||
// We only care about supported features. Supported features will precede a '+' character.
|
||||
if (featureString[featureString.size() - 1] == "+") {
|
||||
featureString.remove("+");
|
||||
|
||||
auto feature = gdbFeatureMapping.valueAt(featureString.toStdString());
|
||||
if (feature.has_value()) {
|
||||
this->supportedFeatures.insert(static_cast<decltype(feature)::value_type>(feature.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SupportedFeaturesQuery::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The SupportedFeaturesQuery command packet is a query from the GDB client, requesting a list of GDB features
|
||||
* supported by the GDB server. The body of this packet also contains a list GDB features that are supported or
|
||||
* unsupported by the GDB client.
|
||||
*
|
||||
* The command packet is identified by its 'qSupported' prefix in the command packet data. Following the prefix is
|
||||
* a list of GDB features that are supported/unsupported by the client. For more info on this command
|
||||
* packet, see the GDP RSP documentation.
|
||||
*
|
||||
* Responses to this command packet should take the form of a ResponsePackets::SupportedFeaturesResponse.
|
||||
*/
|
||||
class SupportedFeaturesQuery: public CommandPacket
|
||||
{
|
||||
private:
|
||||
std::set<Feature> supportedFeatures;
|
||||
|
||||
void init();
|
||||
|
||||
public:
|
||||
SupportedFeaturesQuery(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
bool isFeatureSupported(const Feature& feature) const {
|
||||
return this->supportedFeatures.find(feature) != this->supportedFeatures.end();
|
||||
}
|
||||
|
||||
const std::set<Feature>& getSupportedFeatures() const {
|
||||
return this->supportedFeatures;
|
||||
}
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "WriteGeneralRegisters.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void WriteGeneralRegisters::init() {
|
||||
// The P packet updates a single register
|
||||
auto packet = std::string(this->data.begin(), this->data.end());
|
||||
|
||||
if (packet.size() < 6) {
|
||||
throw Exception("Invalid P command packet - insufficient data in packet.");
|
||||
}
|
||||
|
||||
if (packet.find("=") == std::string::npos) {
|
||||
throw Exception("Invalid P command packet - unexpected format");
|
||||
}
|
||||
|
||||
auto packetSegments = QString::fromStdString(packet).split("=");
|
||||
this->registerNumber = packetSegments.front().mid(1).toUInt(nullptr, 16);
|
||||
this->registerValue = this->hexToData(packetSegments.back().toStdString());
|
||||
std::reverse(this->registerValue.begin(), this->registerValue.end());
|
||||
}
|
||||
|
||||
void WriteGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using Bloom::Targets::TargetRegister;
|
||||
using Bloom::Targets::TargetRegisterMap;
|
||||
|
||||
/**
|
||||
* The WriteGeneralRegisters class implements the structure for "G" and "P" packets. Upon receiving this packet,
|
||||
* server is expected to write register values to the target.
|
||||
*/
|
||||
class WriteGeneralRegisters: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
TargetRegisterMap registerMap;
|
||||
int registerNumber;
|
||||
std::vector<unsigned char> registerValue;
|
||||
|
||||
WriteGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
52
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
Normal file
52
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "WriteMemory.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void WriteMemory::init() {
|
||||
if (this->data.size() < 4) {
|
||||
throw Exception("Invalid packet length");
|
||||
}
|
||||
|
||||
auto packetString = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 1),
|
||||
static_cast<int>(this->data.size() - 1)
|
||||
);
|
||||
|
||||
/*
|
||||
* The write memory ('M') packet consists of three segments, an address, a length and a buffer.
|
||||
* The address and length are separated by a comma character, and the buffer proceeds a colon character.
|
||||
*/
|
||||
auto packetSegments = packetString.split(",");
|
||||
if (packetSegments.size() != 2) {
|
||||
throw Exception("Unexpected number of segments in packet data: " + std::to_string(packetSegments.size()));
|
||||
}
|
||||
|
||||
bool conversionStatus = false;
|
||||
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse start address from write memory packet data");
|
||||
}
|
||||
|
||||
auto lengthAndBufferSegments = packetSegments.at(1).split(":");
|
||||
if (lengthAndBufferSegments.size() != 2) {
|
||||
throw Exception("Unexpected number of segments in packet data: " + std::to_string(lengthAndBufferSegments.size()));
|
||||
}
|
||||
|
||||
auto bufferSize = lengthAndBufferSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse write length from write memory packet data");
|
||||
}
|
||||
|
||||
this->buffer = this->hexToData(lengthAndBufferSegments.at(1).toStdString());
|
||||
|
||||
if (this->buffer.size() != bufferSize) {
|
||||
throw Exception("Buffer size does not match length value given in write memory packet");
|
||||
}
|
||||
}
|
||||
|
||||
void WriteMemory::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.hpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using Bloom::Targets::TargetMemoryBuffer;
|
||||
|
||||
/**
|
||||
* The WriteMemory class implements the structure for "M" packets. Upon receiving this packet, the server is
|
||||
* expected to write data to the target's memory, at the specified start address.
|
||||
*/
|
||||
class WriteMemory: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Like with the ReadMemory command packet, the start address carries additional bits that indicate
|
||||
* the memory type.
|
||||
*/
|
||||
std::uint32_t startAddress;
|
||||
|
||||
TargetMemoryBuffer buffer;
|
||||
|
||||
WriteMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
208
src/DebugServers/GdbRsp/Connection.cpp
Normal file
208
src/DebugServers/GdbRsp/Connection.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
#include "Connection.hpp"
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "CommandPackets/CommandPacketFactory.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using namespace Bloom::DebugServers::Gdb::Exceptions;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void Connection::accept(int serverSocketFileDescriptor) {
|
||||
int socketAddressLength = sizeof(this->socketAddress);
|
||||
|
||||
this->socketFileDescriptor = ::accept(
|
||||
serverSocketFileDescriptor,
|
||||
(sockaddr*)& (this->socketAddress),
|
||||
(socklen_t*)& socketAddressLength
|
||||
);
|
||||
|
||||
if (this->socketFileDescriptor == -1) {
|
||||
throw Exception("Failed to accept GDB Remote Serial Protocol connection");
|
||||
}
|
||||
|
||||
::fcntl(
|
||||
this->socketFileDescriptor,
|
||||
F_SETFL,
|
||||
fcntl(this->socketFileDescriptor, F_GETFL, 0) | O_NONBLOCK
|
||||
);
|
||||
|
||||
// Create event FD
|
||||
this->eventFileDescriptor = ::epoll_create(2);
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = this->socketFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->socketFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed to create event FD for GDB client connection - could not add client connection "
|
||||
"socket FD to epoll FD");
|
||||
}
|
||||
|
||||
this->enableReadInterrupts();
|
||||
}
|
||||
|
||||
void Connection::disableReadInterrupts() {
|
||||
if (::epoll_ctl(
|
||||
this->eventFileDescriptor,
|
||||
EPOLL_CTL_DEL,
|
||||
this->interruptEventNotifier->getFileDescriptor(),
|
||||
NULL) != 0
|
||||
) {
|
||||
throw Exception("Failed to disable GDB client connection read interrupts - epoll_ctl failed");
|
||||
}
|
||||
|
||||
this->readInterruptEnabled = false;
|
||||
}
|
||||
|
||||
void Connection::enableReadInterrupts() {
|
||||
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = interruptFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed to enable GDB client connection read interrupts - epoll_ctl failed");
|
||||
}
|
||||
|
||||
this->readInterruptEnabled = true;
|
||||
}
|
||||
|
||||
void Connection::close() noexcept {
|
||||
if (this->socketFileDescriptor > 0) {
|
||||
::close(this->socketFileDescriptor);
|
||||
this->socketFileDescriptor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::write(const std::vector<unsigned char>& buffer) {
|
||||
Logger::debug("Writing packet: " + std::string(buffer.begin(), buffer.end()));
|
||||
if (::write(this->socketFileDescriptor, buffer.data(), buffer.size()) == -1) {
|
||||
if (errno == EPIPE || errno == ECONNRESET) {
|
||||
// Connection was closed
|
||||
throw ClientDisconnected();
|
||||
|
||||
} else {
|
||||
throw ClientCommunicationError("Failed to write " + std::to_string(buffer.size())
|
||||
+ " bytes to GDP client socket - error no: " + std::to_string(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::writePacket(const ResponsePacket& packet) {
|
||||
// Write the packet repeatedly until the GDB client acknowledges it.
|
||||
int attempts = 0;
|
||||
auto rawPacket = packet.toRawPacket();
|
||||
|
||||
do {
|
||||
if (attempts > 10) {
|
||||
throw ClientCommunicationError("Failed to write GDB response packet - client failed to "
|
||||
"acknowledge receipt - retry limit reached");
|
||||
}
|
||||
|
||||
this->write(rawPacket);
|
||||
attempts++;
|
||||
|
||||
} while(this->readSingleByte(false).value_or(0) != '+');
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Connection::read(size_t bytes, bool interruptible, std::optional<int> msTimeout) {
|
||||
auto output = std::vector<unsigned char>();
|
||||
constexpr size_t bufferSize = 1024;
|
||||
std::array<unsigned char, bufferSize> buffer;
|
||||
ssize_t bytesRead;
|
||||
|
||||
if (interruptible) {
|
||||
if (this->readInterruptEnabled != interruptible) {
|
||||
this->enableReadInterrupts();
|
||||
} else {
|
||||
// Clear any previous interrupts that are still hanging around
|
||||
this->interruptEventNotifier->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->readInterruptEnabled != interruptible && !interruptible) {
|
||||
this->disableReadInterrupts();
|
||||
}
|
||||
|
||||
std::array<struct epoll_event, 1> events = {};
|
||||
|
||||
int eventCount = ::epoll_wait(
|
||||
this->eventFileDescriptor,
|
||||
events.data(),
|
||||
1,
|
||||
msTimeout.value_or(-1)
|
||||
);
|
||||
|
||||
if (eventCount > 0) {
|
||||
for (size_t i = 0; i < eventCount; i++) {
|
||||
auto fileDescriptor = events[i].data.fd;
|
||||
|
||||
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
|
||||
// Interrupted
|
||||
this->interruptEventNotifier->clear();
|
||||
throw DebugServerInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
size_t bytesToRead = (bytes > bufferSize || bytes == 0) ? bufferSize : bytes;
|
||||
while (bytesToRead > 0 && (bytesRead = ::read(this->socketFileDescriptor, buffer.data(), bytesToRead)) > 0) {
|
||||
output.insert(output.end(), buffer.begin(), buffer.begin() + bytesRead);
|
||||
|
||||
if (bytesRead < bytesToRead) {
|
||||
// No more data available
|
||||
break;
|
||||
}
|
||||
|
||||
bytesToRead = ((bytes - output.size()) > bufferSize || bytes == 0) ? bufferSize : (bytes - output.size());
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
// EOF means the client has disconnected
|
||||
throw ClientDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::optional<unsigned char> Connection::readSingleByte(bool interruptible) {
|
||||
auto bytes = this->read(1, interruptible, 300);
|
||||
|
||||
if (!bytes.empty()) {
|
||||
return bytes.front();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<CommandPacket>> Connection::readPackets() {
|
||||
auto buffer = this->read();
|
||||
Logger::debug("GDB client data received (" + std::to_string(buffer.size()) + " bytes): " + std::string(buffer.begin(), buffer.end()));
|
||||
|
||||
auto rawPackets = CommandPacketFactory::extractRawPackets(buffer);
|
||||
std::vector<std::unique_ptr<CommandPacket>> output;
|
||||
|
||||
for (const auto& rawPacket : rawPackets) {
|
||||
try {
|
||||
output.push_back(CommandPacketFactory::create(rawPacket));
|
||||
this->write({'+'});
|
||||
|
||||
} catch (const ClientDisconnected& exception) {
|
||||
throw exception;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to parse GDB packet - " + exception.getMessage());
|
||||
this->write({'-'});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
137
src/DebugServers/GdbRsp/Connection.hpp
Normal file
137
src/DebugServers/GdbRsp/Connection.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <netinet/in.h>
|
||||
#include <queue>
|
||||
#include <array>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp"
|
||||
#include "src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using namespace CommandPackets;
|
||||
using namespace ResponsePackets;
|
||||
|
||||
/**
|
||||
* The Connection class represents an active connection between the GDB RSP server and client.
|
||||
*
|
||||
* All interfacing with the GDB client should take place here.
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
private:
|
||||
int socketFileDescriptor = -1;
|
||||
int eventFileDescriptor = -1;
|
||||
|
||||
struct sockaddr_in socketAddress = {};
|
||||
int maxPacketSize = 1024;
|
||||
|
||||
/**
|
||||
* The interruptEventNotifier allows us to interrupt blocking IO calls on the GDB debug server.
|
||||
* Under the hood, this is just a wrapper for a Linux event notifier. See the EventNotifier class for more.
|
||||
*/
|
||||
std::shared_ptr<EventNotifier> interruptEventNotifier = nullptr;
|
||||
bool readInterruptEnabled = false;
|
||||
|
||||
/**
|
||||
* Reads data from the client into a raw buffer.
|
||||
*
|
||||
* @param bytes
|
||||
* Number of bytes to read.
|
||||
*
|
||||
* @param interruptible
|
||||
* If this flag is set to false, no other component within Bloom will be able to gracefully interrupt
|
||||
* the read (via means of this->interruptEventNotifier). This flag has no affect if this->readInterruptEnabled
|
||||
* is false.
|
||||
*
|
||||
* @param msTimeout
|
||||
* The timeout in milliseconds. If not supplied, no timeout will be applied.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> read(std::size_t bytes = 0, bool interruptible = true, std::optional<int> msTimeout = std::nullopt);
|
||||
|
||||
/**
|
||||
* Does the same as Connection::read(), but only reads a single byte.
|
||||
*
|
||||
* @param interruptible
|
||||
* See Connection::read().
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::optional<unsigned char> readSingleByte(bool interruptible = true);
|
||||
|
||||
/**
|
||||
* Writes data from a raw buffer to the client connection.
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void write(const std::vector<unsigned char>& buffer);
|
||||
|
||||
void disableReadInterrupts();
|
||||
|
||||
void enableReadInterrupts();
|
||||
|
||||
public:
|
||||
/**
|
||||
* When the GDB client is waiting for the target to reach a breakpoint, this is set to true so we know when to
|
||||
* notify the client.
|
||||
*
|
||||
* @TODO: This is pretty gross. Consider rethinking it.
|
||||
*/
|
||||
bool waitingForBreak = false;
|
||||
|
||||
Connection(std::shared_ptr<EventNotifier> interruptEventNotifier)
|
||||
: interruptEventNotifier(interruptEventNotifier) {};
|
||||
|
||||
/**
|
||||
* Accepts a connection on serverSocketFileDescriptor.
|
||||
*
|
||||
* @param serverSocketFileDescriptor
|
||||
*/
|
||||
void accept(int serverSocketFileDescriptor);
|
||||
|
||||
/**
|
||||
* Closes the connection with the client.
|
||||
*/
|
||||
void close() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the human readable IP address of the connected client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string getIpAddress() {
|
||||
std::array<char, INET_ADDRSTRLEN> ipAddress;
|
||||
|
||||
if (::inet_ntop(AF_INET, &(socketAddress.sin_addr), ipAddress.data(), INET_ADDRSTRLEN) == nullptr) {
|
||||
throw Exceptions::Exception("Failed to convert client IP address to text form.");
|
||||
}
|
||||
|
||||
return std::string(ipAddress.data());
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for incoming data from the client and returns any received command packets.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<std::unique_ptr<CommandPacket>> readPackets();
|
||||
|
||||
/**
|
||||
* Sends a response packet to the client.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
void writePacket(const ResponsePacket& packet);
|
||||
|
||||
int getMaxPacketSize() {
|
||||
return this->maxPacketSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
/**
|
||||
* In the event that communication between the GDB RSP client and Bloom fails, a ClientCommunicationFailure
|
||||
* exception should be thrown. The GDB debug server handles this by severing the connection.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientCommunicationError: public Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientCommunicationError(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError() = default;
|
||||
};
|
||||
}
|
||||
29
src/DebugServers/GdbRsp/Exceptions/ClientDisconnected.hpp
Normal file
29
src/DebugServers/GdbRsp/Exceptions/ClientDisconnected.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
/**
|
||||
* When a GDB RSP client unexpectedly drops the connection in the middle of an IO operation, a ClientDisconnected
|
||||
* exception should be thrown. The GDB debug server handles this by clearing the connection and waiting for a new
|
||||
* one.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientDisconnected: public Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientDisconnected(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientDisconnected(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientDisconnected() = default;
|
||||
};
|
||||
}
|
||||
28
src/DebugServers/GdbRsp/Exceptions/ClientNotSupported.hpp
Normal file
28
src/DebugServers/GdbRsp/Exceptions/ClientNotSupported.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
/**
|
||||
* In the event that the GDB debug server determines that the connected client cannot be served,
|
||||
* the ClientNotSupported exception should be thrown.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientNotSupported: public Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientNotSupported(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientNotSupported(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientNotSupported() = default;
|
||||
};
|
||||
}
|
||||
23
src/DebugServers/GdbRsp/Feature.hpp
Normal file
23
src/DebugServers/GdbRsp/Feature.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class Feature: int
|
||||
{
|
||||
SOFTWARE_BREAKPOINTS,
|
||||
HARDWARE_BREAKPOINTS,
|
||||
PACKET_SIZE,
|
||||
MEMORY_MAP_READ,
|
||||
};
|
||||
|
||||
static inline BiMap<Feature, std::string> getGdbFeatureToNameMapping() {
|
||||
return BiMap<Feature, std::string>{
|
||||
{Feature::HARDWARE_BREAKPOINTS, "hwbreak"},
|
||||
{Feature::SOFTWARE_BREAKPOINTS, "swbreak"},
|
||||
{Feature::PACKET_SIZE, "PacketSize"},
|
||||
{Feature::MEMORY_MAP_READ, "qXfer:memory-map:read"},
|
||||
};
|
||||
}
|
||||
}
|
||||
404
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
Normal file
404
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
Normal file
@@ -0,0 +1,404 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "GdbRspDebugServer.hpp"
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientNotSupported.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using namespace Exceptions;
|
||||
|
||||
void GdbRspDebugServer::init() {
|
||||
auto ipAddress = this->debugServerConfig.jsonObject.find("ipAddress")->toString().toStdString();
|
||||
auto port = static_cast<std::uint16_t>(this->debugServerConfig.jsonObject.find("port")->toInt());
|
||||
|
||||
if (!ipAddress.empty()) {
|
||||
this->listeningAddress = ipAddress;
|
||||
}
|
||||
|
||||
if (port > 0) {
|
||||
this->listeningPortNumber = port;
|
||||
}
|
||||
|
||||
this->socketAddress.sin_family = AF_INET;
|
||||
this->socketAddress.sin_port = htons(this->listeningPortNumber);
|
||||
|
||||
if (::inet_pton(AF_INET, this->listeningAddress.c_str(), &(this->socketAddress.sin_addr)) == 0) {
|
||||
// Invalid IP address
|
||||
throw InvalidConfig("Invalid IP address provided in config file: (\"" + this->listeningAddress + "\")");
|
||||
}
|
||||
|
||||
int socketFileDescriptor;
|
||||
|
||||
if ((socketFileDescriptor = ::socket(AF_INET, SOCK_STREAM, 0)) == 0) {
|
||||
throw Exception("Failed to create socket file descriptor.");
|
||||
}
|
||||
|
||||
if (::setsockopt(
|
||||
socketFileDescriptor,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
&this->enableReuseAddressSocketOption,
|
||||
sizeof(this->enableReuseAddressSocketOption)) < 0
|
||||
) {
|
||||
Logger::error("Failed to set socket SO_REUSEADDR option.");
|
||||
}
|
||||
|
||||
if (::bind(
|
||||
socketFileDescriptor,
|
||||
reinterpret_cast<const sockaddr*>(&(this->socketAddress)),
|
||||
sizeof(this->socketAddress)
|
||||
) < 0
|
||||
) {
|
||||
throw Exception("Failed to bind address.");
|
||||
}
|
||||
|
||||
this->serverSocketFileDescriptor = socketFileDescriptor;
|
||||
|
||||
this->eventFileDescriptor = ::epoll_create(2);
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = this->serverSocketFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->serverSocketFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed epoll_ctl server socket");
|
||||
}
|
||||
|
||||
if (this->interruptEventNotifier != nullptr) {
|
||||
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = interruptFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed epoll_ctl interrupt event fd");
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info("GDB RSP address: " + this->listeningAddress);
|
||||
Logger::info("GDB RSP port: " + std::to_string(this->listeningPortNumber));
|
||||
|
||||
this->eventListener->registerCallbackForEventType<Events::TargetExecutionStopped>(
|
||||
std::bind(&GdbRspDebugServer::onTargetExecutionStopped, this, std::placeholders::_1)
|
||||
);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::serve() {
|
||||
try {
|
||||
if (!this->clientConnection.has_value()) {
|
||||
Logger::info("Waiting for GDB RSP connection");
|
||||
this->waitForConnection();
|
||||
}
|
||||
|
||||
auto packets = this->clientConnection->readPackets();
|
||||
|
||||
for (auto& packet : packets) {
|
||||
// Double-dispatch to appropriate handler
|
||||
packet->dispatchToHandler(*this);
|
||||
}
|
||||
|
||||
} catch (const ClientDisconnected&) {
|
||||
Logger::info("GDB RSP client disconnected");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const ClientCommunicationError& exception) {
|
||||
Logger::error("GDB client communication error - " + exception.getMessage() + " - closing connection");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const ClientNotSupported& exception) {
|
||||
Logger::error("Invalid GDB client - " + exception.getMessage() + " - closing connection");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const DebugServerInterrupted&) {
|
||||
// Server was interrupted
|
||||
Logger::debug("GDB RSP interrupted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::waitForConnection() {
|
||||
if (::listen(this->serverSocketFileDescriptor, 3) != 0) {
|
||||
throw Exception("Failed to listen on server socket");
|
||||
}
|
||||
|
||||
std::array<struct epoll_event, 5> events = {};
|
||||
int eventCount = ::epoll_wait(
|
||||
this->eventFileDescriptor,
|
||||
events.data(),
|
||||
5,
|
||||
-1
|
||||
);
|
||||
|
||||
if (eventCount > 0) {
|
||||
for (size_t i = 0; i < eventCount; i++) {
|
||||
auto fileDescriptor = events[i].data.fd;
|
||||
|
||||
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
|
||||
// Interrupted
|
||||
this->interruptEventNotifier->clear();
|
||||
throw DebugServerInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
this->clientConnection = Connection(this->interruptEventNotifier);
|
||||
this->clientConnection->accept(this->serverSocketFileDescriptor);
|
||||
|
||||
Logger::info("Accepted GDP RSP connection from " + this->clientConnection->getIpAddress());
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionStarted>());
|
||||
} else {
|
||||
// This method should not return until a connection has been established (or an exception is thrown)
|
||||
return this->waitForConnection();
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::close() {
|
||||
this->closeClientConnection();
|
||||
|
||||
if (this->serverSocketFileDescriptor > 0) {
|
||||
::close(this->serverSocketFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPacket& packet) {
|
||||
auto packetData = packet.getData();
|
||||
auto packetString = std::string(packetData.begin(), packetData.end());
|
||||
|
||||
if (packetString[0] == '?') {
|
||||
// Status report
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
|
||||
|
||||
} else if (packetString.find("qAttached") == 0) {
|
||||
Logger::debug("Handling qAttached");
|
||||
this->clientConnection->writePacket(ResponsePacket({1}));
|
||||
|
||||
} else {
|
||||
Logger::debug("Unknown GDB RSP packet: " + packetString + " - returning empty response");
|
||||
|
||||
// Respond with an empty packet
|
||||
this->clientConnection->writePacket(ResponsePacket({0}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>) {
|
||||
if (this->clientConnection.has_value() && this->clientConnection->waitingForBreak) {
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
|
||||
this->clientConnection->waitingForBreak = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet) {
|
||||
Logger::debug("Handling QuerySupport packet");
|
||||
|
||||
if (!packet.isFeatureSupported(Feature::HARDWARE_BREAKPOINTS)
|
||||
&& !packet.isFeatureSupported(Feature::SOFTWARE_BREAKPOINTS)) {
|
||||
// All GDB clients are expected to support breakpoints!
|
||||
throw ClientNotSupported("GDB client does not support HW or SW breakpoints");
|
||||
}
|
||||
|
||||
// Respond with a SupportedFeaturesResponse packet, listing all supported GDB features by Bloom
|
||||
auto response = ResponsePackets::SupportedFeaturesResponse({
|
||||
{Feature::SOFTWARE_BREAKPOINTS, std::nullopt},
|
||||
{Feature::PACKET_SIZE, std::to_string(this->clientConnection->getMaxPacketSize())},
|
||||
});
|
||||
|
||||
this->clientConnection->writePacket(response);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet) {
|
||||
Logger::debug("Handling ReadGeneralRegisters packet");
|
||||
|
||||
try {
|
||||
auto descriptors = TargetRegisterDescriptors();
|
||||
|
||||
if (packet.registerNumber.has_value()) {
|
||||
Logger::debug("Reading register number: " + std::to_string(packet.registerNumber.value()));
|
||||
descriptors.push_back(this->getRegisterDescriptorFromNumber(packet.registerNumber.value()));
|
||||
|
||||
} else {
|
||||
// Read all descriptors
|
||||
auto descriptorMapping = this->getRegisterNumberToDescriptorMapping();
|
||||
for (auto& descriptor : descriptorMapping.getMap()) {
|
||||
descriptors.push_back(descriptor.second);
|
||||
}
|
||||
}
|
||||
|
||||
auto registerSet = this->readGeneralRegistersFromTarget(descriptors);
|
||||
auto registerNumberToDescriptorMapping = this->getRegisterNumberToDescriptorMapping();
|
||||
|
||||
/*
|
||||
* Remove any registers that are not mapped to GDB register numbers (as we won't know where to place
|
||||
* them in our response to GDB). All registers that are expected from the GDB client should be mapped
|
||||
* to register numbers.
|
||||
*
|
||||
* Registers that are not mapped to a GDB register number are presumed to be unknown to GDB, so GDB shouldn't
|
||||
* complain about not receiving them.
|
||||
*/
|
||||
registerSet.erase(
|
||||
std::remove_if(
|
||||
registerSet.begin(),
|
||||
registerSet.end(),
|
||||
[®isterNumberToDescriptorMapping](const TargetRegister& reg) {
|
||||
return !registerNumberToDescriptorMapping.contains(reg.descriptor);
|
||||
}
|
||||
),
|
||||
registerSet.end()
|
||||
);
|
||||
|
||||
/*
|
||||
* Sort each register by their respective GDB register number - this will leave us with a collection of
|
||||
* registers in the order expected by the GDB client.
|
||||
*/
|
||||
std::sort(
|
||||
registerSet.begin(),
|
||||
registerSet.end(),
|
||||
[this, ®isterNumberToDescriptorMapping](const TargetRegister& registerA, const TargetRegister& registerB) {
|
||||
return registerNumberToDescriptorMapping.valueAt(registerA.descriptor) <
|
||||
registerNumberToDescriptorMapping.valueAt(registerB.descriptor);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Finally, implode the register values, convert to hexadecimal form and send to the GDB client.
|
||||
*/
|
||||
auto registers = std::vector<unsigned char>();
|
||||
for (const auto& reg : registerSet) {
|
||||
registers.insert(registers.end(), reg.value.begin(), reg.value.end());
|
||||
}
|
||||
|
||||
auto responseRegisters = Packet::dataToHex(registers);
|
||||
this->clientConnection->writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(responseRegisters.begin(), responseRegisters.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read general registers - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet) {
|
||||
Logger::debug("Handling WriteGeneralRegisters packet");
|
||||
|
||||
try {
|
||||
auto registerDescriptor = this->getRegisterDescriptorFromNumber(packet.registerNumber);
|
||||
this->writeGeneralRegistersToTarget({TargetRegister(registerDescriptor, packet.registerValue)});
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write general registers - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ContinueExecution& packet) {
|
||||
Logger::debug("Handling ContinueExecution packet");
|
||||
|
||||
try {
|
||||
this->continueTargetExecution(packet.fromProgramCounter);
|
||||
this->clientConnection->waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to continue execution on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::StepExecution& packet) {
|
||||
Logger::debug("Handling StepExecution packet");
|
||||
|
||||
try {
|
||||
this->stepTargetExecution(packet.fromProgramCounter);
|
||||
this->clientConnection->waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to step execution on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadMemory& packet) {
|
||||
Logger::debug("Handling ReadMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
|
||||
auto memoryBuffer = this->readMemoryFromTarget(memoryType, startAddress, packet.bytes);
|
||||
|
||||
auto hexMemoryBuffer = Packet::dataToHex(memoryBuffer);
|
||||
this->clientConnection->writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(hexMemoryBuffer.begin(), hexMemoryBuffer.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read memory from target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteMemory& packet) {
|
||||
Logger::debug("Handling WriteMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
|
||||
this->writeMemoryToTarget(memoryType, startAddress, packet.buffer);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write memory two target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SetBreakpoint& packet) {
|
||||
Logger::debug("Handling SetBreakpoint packet");
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = packet.address;
|
||||
this->setBreakpointOnTarget(breakpoint);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to set breakpoint on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::RemoveBreakpoint& packet) {
|
||||
Logger::debug("Removing breakpoint at address " + std::to_string(packet.address));
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = packet.address;
|
||||
this->removeBreakpointOnTarget(breakpoint);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to remove breakpoint on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::InterruptExecution& packet) {
|
||||
Logger::debug("Handling InterruptExecution packet");
|
||||
|
||||
try {
|
||||
this->stopTargetExecution();
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::INTERRUPTED));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to interrupt execution - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
257
src/DebugServers/GdbRsp/GdbRspDebugServer.hpp
Normal file
257
src/DebugServers/GdbRsp/GdbRspDebugServer.hpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../DebugServer.hpp"
|
||||
#include "Connection.hpp"
|
||||
#include "Signal.hpp"
|
||||
#include "Register.hpp"
|
||||
#include "Feature.hpp"
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
#include "CommandPackets/CommandPacketFactory.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
// Response packets
|
||||
#include "ResponsePackets/SupportedFeaturesResponse.hpp"
|
||||
#include "ResponsePackets/TargetStopped.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterType;
|
||||
using Bloom::Targets::TargetRegisterDescriptor;
|
||||
|
||||
/**
|
||||
* The GdbRspDebugServer is an implementation of a GDB server using the GDB Remote Serial Protocol.
|
||||
*
|
||||
* This DebugServer employs TCP/IP sockets to interface with GDB clients. The listening address can be configured
|
||||
* in the user's project config file.
|
||||
*
|
||||
* See https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html for more info on the GDB Remote
|
||||
* Serial Protocol.
|
||||
*
|
||||
* @TODO: This could do with some cleaning.
|
||||
*/
|
||||
class GdbRspDebugServer: public DebugServer
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* The port number for the GDB server to listen on.
|
||||
*
|
||||
* This will be pulled from the user's project configuration, if set. Otherwise it will default to whatever is
|
||||
* set here.
|
||||
*/
|
||||
std::uint16_t listeningPortNumber = 1055;
|
||||
|
||||
/**
|
||||
* The address for the GDB server to listen on.
|
||||
*
|
||||
* Like the port number, this can also be pulled from the user's project configuration.
|
||||
*/
|
||||
std::string listeningAddress = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* Listening socket address
|
||||
*/
|
||||
struct sockaddr_in socketAddress = {};
|
||||
|
||||
/**
|
||||
* Listening socket file descriptor
|
||||
*/
|
||||
int serverSocketFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* We don't listen on the this->serverSocketFileDescriptor directly. Instead, we add it to an epoll set, along
|
||||
* with the this->interruptEventNotifier FD. This allows us to interrupt any blocking socket IO calls when
|
||||
* we have other things to do.
|
||||
*
|
||||
* See GdbRspDebugServer::init()
|
||||
* See DebugServer::interruptEventNotifier
|
||||
* See EventNotifier
|
||||
*/
|
||||
int eventFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* SO_REUSEADDR option value for listening socket.
|
||||
*/
|
||||
int enableReuseAddressSocketOption = 1;
|
||||
|
||||
/**
|
||||
* The current active GDB client connection, if any.
|
||||
*/
|
||||
std::optional<Connection> clientConnection;
|
||||
|
||||
/**
|
||||
* Prepares the GDB server for listing on the selected address and port.
|
||||
*/
|
||||
void init() override;
|
||||
|
||||
/**
|
||||
* Closes any client connection as well as the listening socket file descriptor.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* See DebugServer::serve()
|
||||
*/
|
||||
void serve() override;
|
||||
|
||||
/**
|
||||
* Waits for a GDB client to connect on the listening socket. Accepts the connection and
|
||||
* sets this->clientConnection.
|
||||
*/
|
||||
void waitForConnection();
|
||||
|
||||
void closeClientConnection() {
|
||||
if (this->clientConnection.has_value()) {
|
||||
this->clientConnection->close();
|
||||
this->clientConnection = std::nullopt;
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionFinished>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GDB clients encode memory type information (flash, ram, eeprom, etc) in memory addresses. This is typically
|
||||
* hardcoded in the GDB client source. This method extracts memory type information from a given memory address.
|
||||
* The specifics of the encoding may vary with targets, which is why this method is virtual. For an example,
|
||||
* see the implementation of this method in AvrGdbRsp.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
virtual TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Removes memory type information from memory address.
|
||||
* See comment for GdbRspDebugServer::getMemoryTypeFromGdbAddress()
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
virtual std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Like with the method of encoding memory type information onto memory addresses, GDB clients also expect
|
||||
* a pre-defined set of registers. The defined set being dependant on the target. This is hardcoded in the the
|
||||
* GDB client source. The order of the registers is also pre-defined in the GDB client.
|
||||
*
|
||||
* For an example, see the implementation of this method in the AvrGdbRsp class.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() = 0;
|
||||
|
||||
/**
|
||||
* Obtains the appropriate register descriptor from a register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegisterDescriptor getRegisterDescriptorFromNumber(GdbRegisterNumber number) {
|
||||
auto mapping = this->getRegisterNumberToDescriptorMapping();
|
||||
|
||||
if (!mapping.contains(number)) {
|
||||
throw Exception("Unknown register from GDB - register number (" + std::to_string(number)
|
||||
+ ") not mapped to any register descriptor.");
|
||||
}
|
||||
|
||||
return mapping.valueAt(number).value();
|
||||
}
|
||||
|
||||
public:
|
||||
GdbRspDebugServer(EventManager& eventManager) : DebugServer(eventManager) {};
|
||||
|
||||
std::string getName() const override {
|
||||
return "GDB Remote Serial Protocol DebugServer";
|
||||
};
|
||||
|
||||
/**
|
||||
* If the GDB client is currently waiting for the target execution to stop, this event handler will issue
|
||||
* a "stop reply" packet to the client once the target execution stops.
|
||||
*/
|
||||
void onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>);
|
||||
|
||||
/**
|
||||
* Handles any other GDB command packet that has not been promoted to a more specific type.
|
||||
* This would be packets like "?" and "qAttached".
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPacket& packet);
|
||||
|
||||
/**
|
||||
* Handles the supported features query ("qSupported") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet);
|
||||
|
||||
/**
|
||||
* Handles the read registers ("g" and "p") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet);
|
||||
|
||||
/**
|
||||
* Handles the write general registers ("G" and "P") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet);
|
||||
|
||||
/**
|
||||
* Handles the continue execution ("c") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ContinueExecution& packet);
|
||||
|
||||
/**
|
||||
* Handles the step execution ("s") packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::StepExecution& packet);
|
||||
|
||||
/**
|
||||
* Handles the read memory ("m") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ReadMemory& packet);
|
||||
|
||||
/**
|
||||
* Handles the write memory ("M") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::WriteMemory& packet);
|
||||
|
||||
/**
|
||||
* Handles the set breakpoint ("Z") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::SetBreakpoint& packet);
|
||||
|
||||
/**
|
||||
* Handles the remove breakpoint ("z") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::RemoveBreakpoint& packet);
|
||||
|
||||
/**
|
||||
* Handles the interrupt command packet.
|
||||
* Will attempt to halt execution on the target. Should respond with a "stop reply" packet, or an error code.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::InterruptExecution& packet);
|
||||
};
|
||||
}
|
||||
123
src/DebugServers/GdbRsp/Packet.hpp
Normal file
123
src/DebugServers/GdbRsp/Packet.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <QString>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
/**
|
||||
* The Packet class implements the data structure for GDB RSP packets.
|
||||
*
|
||||
* Fore more information on the packet data structure, see https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
|
||||
*/
|
||||
class Packet
|
||||
{
|
||||
protected:
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
void init(const std::vector<unsigned char>& rawPacket) {
|
||||
this->data.insert(
|
||||
this->data.begin(),
|
||||
rawPacket.begin() + 1,
|
||||
rawPacket.end() - 3
|
||||
);
|
||||
}
|
||||
public:
|
||||
Packet() = default;
|
||||
Packet(const std::vector<unsigned char>& rawPacket) {
|
||||
this->init(rawPacket);
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getData() const {
|
||||
return this->data;
|
||||
}
|
||||
|
||||
void setData(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a raw packet.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> toRawPacket() const {
|
||||
std::vector<unsigned char> packet = {'$'};
|
||||
auto data = this->getData();
|
||||
|
||||
for (const auto& byte : data) {
|
||||
// Escape $ and # characters
|
||||
switch (byte) {
|
||||
case '$':
|
||||
case '#': {
|
||||
packet.push_back('}');
|
||||
packet.push_back(byte ^ 0x20);
|
||||
}
|
||||
default: {
|
||||
packet.push_back(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto dataSum = std::accumulate(packet.begin() + 1, packet.end(), 0);
|
||||
packet.push_back('#');
|
||||
auto checkSum = QStringLiteral("%1").arg(dataSum % 256, 2, 16, QLatin1Char('0')).toStdString();
|
||||
|
||||
if (checkSum.size() < 2) {
|
||||
packet.push_back('0');
|
||||
|
||||
if (checkSum.size() < 1) {
|
||||
packet.push_back('0');
|
||||
} else {
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[0]));
|
||||
}
|
||||
}
|
||||
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[0]));
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[1]));
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw data to hexadecimal form, the form in which responses are expected to be delivered from the
|
||||
* server.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
static std::string dataToHex(std::vector<unsigned char> data) {
|
||||
std::stringstream stream;
|
||||
stream << std::hex << std::setfill('0');
|
||||
|
||||
for (const auto& byte : data) {
|
||||
stream << std::setw(2) << static_cast<unsigned int>(byte);
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts data in hexadecimal form to raw data.
|
||||
*
|
||||
* @param hexData
|
||||
* @return
|
||||
*/
|
||||
static std::vector<unsigned char> hexToData(const std::string& hexData) {
|
||||
std::vector<unsigned char> output;
|
||||
|
||||
for (auto i = 0; i < hexData.size(); i += 2) {
|
||||
auto hexByte = std::string((hexData.begin() + i), (hexData.begin() + i + 2));
|
||||
output.push_back(static_cast<unsigned char>(std::stoi(hexByte, nullptr, 16)));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
virtual ~Packet() = default;
|
||||
};
|
||||
}
|
||||
6
src/DebugServers/GdbRsp/Register.hpp
Normal file
6
src/DebugServers/GdbRsp/Register.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using GdbRegisterNumber = int;
|
||||
}
|
||||
25
src/DebugServers/GdbRsp/ResponsePackets/Ok.hpp
Normal file
25
src/DebugServers/GdbRsp/ResponsePackets/Ok.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* OK response packet expected by the GDB client, in response to certain commands.
|
||||
*/
|
||||
class Ok: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
Ok() = default;
|
||||
|
||||
std::vector<unsigned char> getData() const override {
|
||||
return {'O', 'K'};
|
||||
}
|
||||
};
|
||||
}
|
||||
22
src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp
Normal file
22
src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* Upon receiving a CommandPacket from the connected GDB RSP client, the server is expected to respond with a
|
||||
* response packet.
|
||||
*/
|
||||
class ResponsePacket: public Packet
|
||||
{
|
||||
public:
|
||||
ResponsePacket() = default;
|
||||
explicit ResponsePacket(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "SupportedFeaturesResponse.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::ResponsePackets;
|
||||
|
||||
std::vector<unsigned char> SupportedFeaturesResponse::getData() const {
|
||||
std::string output = "qSupported:";
|
||||
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
|
||||
|
||||
for (const auto& supportedFeature : this->supportedFeatures) {
|
||||
auto featureString = gdbFeatureMapping.valueAt(supportedFeature.first);
|
||||
|
||||
if (featureString.has_value()) {
|
||||
if (supportedFeature.second.has_value()) {
|
||||
output.append(featureString.value() + "=" + supportedFeature.second.value() + ";");
|
||||
} else {
|
||||
output.append(featureString.value() + "+;");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* The SupportedFeaturesResponse class implements the response packet structure for the "qSupported" command.
|
||||
*/
|
||||
class SupportedFeaturesResponse: public ResponsePacket
|
||||
{
|
||||
protected:
|
||||
std::set<std::pair<Feature, std::optional<std::string>>> supportedFeatures;
|
||||
|
||||
public:
|
||||
SupportedFeaturesResponse() = default;
|
||||
SupportedFeaturesResponse(const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures)
|
||||
: supportedFeatures(supportedFeatures) {};
|
||||
|
||||
std::vector<unsigned char> getData() const override;
|
||||
};
|
||||
}
|
||||
47
src/DebugServers/GdbRsp/ResponsePackets/TargetStopped.hpp
Normal file
47
src/DebugServers/GdbRsp/ResponsePackets/TargetStopped.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Signal.hpp"
|
||||
#include "../StopReason.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterMap;
|
||||
|
||||
/**
|
||||
* The TargetStopped class implements the response packet structure for any commands that expect a "StopReply"
|
||||
* packet in response.
|
||||
*/
|
||||
class TargetStopped: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
Signal signal;
|
||||
std::optional<TargetRegisterMap> registerMap;
|
||||
std::optional<StopReason> stopReason;
|
||||
|
||||
TargetStopped(Signal signal) : signal(signal) {}
|
||||
|
||||
std::vector<unsigned char> getData() const override {
|
||||
std::string output = "T" + this->dataToHex({static_cast<unsigned char>(this->signal)});
|
||||
|
||||
if (this->stopReason.has_value()) {
|
||||
auto stopReasonMapping = getStopReasonToNameMapping();
|
||||
auto stopReasonName = stopReasonMapping.valueAt(this->stopReason.value());
|
||||
|
||||
if (stopReasonName.has_value()) {
|
||||
output += stopReasonName.value() + ":;";
|
||||
}
|
||||
}
|
||||
|
||||
if (this->registerMap.has_value()) {
|
||||
for (const auto& [registerId, registerValue] : this->registerMap.value()) {
|
||||
output += this->dataToHex({static_cast<unsigned char>(registerId)});
|
||||
output += ":" + this->dataToHex(registerValue.value) + ";";
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
};
|
||||
}
|
||||
10
src/DebugServers/GdbRsp/Signal.hpp
Normal file
10
src/DebugServers/GdbRsp/Signal.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class Signal: unsigned char
|
||||
{
|
||||
TRAP = 5,
|
||||
INTERRUPTED = 2,
|
||||
};
|
||||
}
|
||||
19
src/DebugServers/GdbRsp/StopReason.hpp
Normal file
19
src/DebugServers/GdbRsp/StopReason.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class StopReason: int
|
||||
{
|
||||
SOFTWARE_BREAKPOINT = 0,
|
||||
HARDWARE_BREAKPOINT = 1,
|
||||
};
|
||||
|
||||
static inline BiMap<StopReason, std::string> getStopReasonToNameMapping() {
|
||||
return BiMap<StopReason, std::string>({
|
||||
{StopReason::HARDWARE_BREAKPOINT, "hwbreak"},
|
||||
{StopReason::SOFTWARE_BREAKPOINT, "swbreak"},
|
||||
});
|
||||
}
|
||||
}
|
||||
54
src/DebugToolDrivers/DebugTool.hpp
Normal file
54
src/DebugToolDrivers/DebugTool.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
5
src/DebugToolDrivers/DebugTools.hpp
Normal file
5
src/DebugToolDrivers/DebugTools.hpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "DebugTool.hpp"
|
||||
#include "src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp"
|
||||
#include "src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.hpp"
|
||||
87
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.cpp
Normal file
87
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.cpp
Normal 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;
|
||||
}
|
||||
104
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp
Normal file
104
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.hpp
Normal 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();
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
12
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.cpp
Normal file
12
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.cpp
Normal 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;
|
||||
}
|
||||
52
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp
Normal file
52
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.hpp
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
24
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.cpp
Normal file
24
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.cpp
Normal 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);
|
||||
}
|
||||
|
||||
41
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp
Normal file
41
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user