New GenerateSvd GDB monitor command
This commit is contained in:
@@ -4,6 +4,10 @@ Supported Bloom commands:
|
|||||||
version Outputs Bloom's version information.
|
version Outputs Bloom's version information.
|
||||||
version machine Outputs Bloom's version information in JSON format.
|
version machine Outputs Bloom's version information in JSON format.
|
||||||
|
|
||||||
|
svd Generates the System View Description (SVD) XML for the current target and saves it into a
|
||||||
|
file located in the current project directory.
|
||||||
|
svd --out Generates the System View Description (SVD) XML for the current target and sends it to GDB, as
|
||||||
|
command output.
|
||||||
target-info machine Outputs information on the connected target, in JSON format.
|
target-info machine Outputs information on the connected target, in JSON format.
|
||||||
|
|
||||||
reset Resets the target and holds it in a stopped state.
|
reset Resets the target and holds it in a stopped state.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ target_sources(
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/BloomVersion.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/BloomVersion.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/BloomVersionMachine.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/BloomVersionMachine.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/TargetInfoMachine.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/TargetInfoMachine.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/CommandPackets/GenerateSvd.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/ResponsePackets/SupportedFeaturesResponse.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/Gdb/ResponsePackets/SupportedFeaturesResponse.cpp
|
||||||
|
|
||||||
# AVR GDB RSP Server
|
# AVR GDB RSP Server
|
||||||
|
|||||||
227
src/DebugServer/Gdb/CommandPackets/GenerateSvd.cpp
Normal file
227
src/DebugServer/Gdb/CommandPackets/GenerateSvd.cpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#include "GenerateSvd.hpp"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
#include "src/DebugServer/Gdb/ResponsePackets/ResponsePacket.hpp"
|
||||||
|
#include "src/DebugServer/Gdb/ResponsePackets/ErrorResponsePacket.hpp"
|
||||||
|
|
||||||
|
#include "src/Targets/TargetMemory.hpp"
|
||||||
|
|
||||||
|
#include "src/Application.hpp"
|
||||||
|
#include "src/Helpers/Paths.hpp"
|
||||||
|
#include "src/Exceptions/Exception.hpp"
|
||||||
|
#include "src/Logger/Logger.hpp"
|
||||||
|
|
||||||
|
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||||
|
{
|
||||||
|
using TargetController::TargetControllerConsole;
|
||||||
|
|
||||||
|
using ResponsePackets::ResponsePacket;
|
||||||
|
using ResponsePackets::ErrorResponsePacket;
|
||||||
|
using Exceptions::Exception;
|
||||||
|
|
||||||
|
GenerateSvd::GenerateSvd(Monitor&& monitorPacket)
|
||||||
|
: Monitor(std::move(monitorPacket))
|
||||||
|
, sendOutput(this->command.find("--out") != std::string::npos)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void GenerateSvd::handle(DebugSession& debugSession, TargetControllerConsole&) {
|
||||||
|
Logger::debug("Handling GenerateSvd packet");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Logger::info("Generating SVD XML for current target");
|
||||||
|
|
||||||
|
const auto& targetDescriptor = debugSession.gdbTargetDescriptor.targetDescriptor;
|
||||||
|
|
||||||
|
const auto svdXml = this->generateSvd(
|
||||||
|
targetDescriptor,
|
||||||
|
debugSession.gdbTargetDescriptor.getMemoryOffset(Targets::TargetMemoryType::RAM)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this->sendOutput) {
|
||||||
|
debugSession.connection.writePacket(ResponsePacket(Packet::toHex(svdXml.toString().toStdString())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto svdOutputFilePath = Paths::projectDirPath() + "/" + targetDescriptor.name + ".svd";
|
||||||
|
auto outputFile = QFile(QString::fromStdString(svdOutputFilePath));
|
||||||
|
|
||||||
|
if (!outputFile.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
|
||||||
|
throw Exception(
|
||||||
|
"Failed to open/create SVD output file (" + svdOutputFilePath + "). Check file permissions."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFile.write(svdXml.toByteArray());
|
||||||
|
outputFile.close();
|
||||||
|
|
||||||
|
debugSession.connection.writePacket(ResponsePacket(Packet::toHex(
|
||||||
|
"SVD output saved to " + svdOutputFilePath + "\n"
|
||||||
|
)));
|
||||||
|
|
||||||
|
Logger::info("SVD output saved to " + svdOutputFilePath);
|
||||||
|
|
||||||
|
} catch (const Exception& exception) {
|
||||||
|
Logger::error(exception.getMessage());
|
||||||
|
debugSession.connection.writePacket(ErrorResponsePacket());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QDomDocument GenerateSvd::generateSvd(
|
||||||
|
const Targets::TargetDescriptor& targetDescriptor,
|
||||||
|
std::uint32_t baseAddressOffset
|
||||||
|
) {
|
||||||
|
auto document = QDomDocument();
|
||||||
|
|
||||||
|
const auto createElement = [&document] (const QString& tagName, const QString& value) {
|
||||||
|
auto element = document.createElement(tagName);
|
||||||
|
auto textNode = document.createTextNode(value);
|
||||||
|
element.appendChild(textNode);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.appendChild(document.createComment(
|
||||||
|
" This SVD was generated by Bloom (https://bloom.oscillate.io/). "
|
||||||
|
"Please report any issues via https://bloom.oscillate.io/report-issue "
|
||||||
|
));
|
||||||
|
|
||||||
|
if (baseAddressOffset != 0) {
|
||||||
|
document.appendChild(document.createComment(
|
||||||
|
" Base addresses in this SVD have been offset by 0x" + QString::number(baseAddressOffset, 16)
|
||||||
|
+ ". This offset is required for access via avr-gdb. "
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deviceElement = document.createElement("device");
|
||||||
|
|
||||||
|
deviceElement.setAttribute("schemaVersion", "1.3");
|
||||||
|
deviceElement.setAttribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema-instance");
|
||||||
|
deviceElement.setAttribute(
|
||||||
|
"xs:noNamespaceSchemaLocation",
|
||||||
|
QString::fromStdString(Paths::homeDomainName() + "/assets/svd-schema.xsd")
|
||||||
|
);
|
||||||
|
|
||||||
|
deviceElement.appendChild(createElement("vendor", QString::fromStdString(targetDescriptor.vendorName)));
|
||||||
|
deviceElement.appendChild(createElement("name", QString::fromStdString(targetDescriptor.name)));
|
||||||
|
|
||||||
|
deviceElement.appendChild(document.createComment(
|
||||||
|
" The version number below is that of the Bloom binary which generated this SVD. "
|
||||||
|
));
|
||||||
|
deviceElement.appendChild(createElement("version", QString::fromStdString(Application::VERSION.toString())));
|
||||||
|
|
||||||
|
deviceElement.appendChild(
|
||||||
|
createElement(
|
||||||
|
"description",
|
||||||
|
QString::fromStdString(targetDescriptor.name) + " from "
|
||||||
|
+ QString::fromStdString(targetDescriptor.vendorName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: These values should be part of the TargetDescriptor, but given that Bloom only supports 8-bit AVRs,
|
||||||
|
* it really doesn't matter ATM. Will fix it later (lol no I won't).
|
||||||
|
*/
|
||||||
|
deviceElement.appendChild(createElement("addressUnitBits", "8"));
|
||||||
|
deviceElement.appendChild(createElement("width", "8"));
|
||||||
|
deviceElement.appendChild(createElement("size", "8"));
|
||||||
|
|
||||||
|
deviceElement.appendChild(createElement("access", "read-only"));
|
||||||
|
|
||||||
|
struct Peripheral {
|
||||||
|
QString name;
|
||||||
|
std::uint32_t baseAddress;
|
||||||
|
|
||||||
|
Targets::TargetRegisterDescriptors registerDescriptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto peripheralsByName = std::map<std::string, Peripheral>();
|
||||||
|
|
||||||
|
for (const auto& [registerType, registerDescriptors] : targetDescriptor.registerDescriptorsByType) {
|
||||||
|
if (registerDescriptors.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& registerDescriptor : registerDescriptors) {
|
||||||
|
if (
|
||||||
|
!registerDescriptor.startAddress.has_value()
|
||||||
|
|| !registerDescriptor.name.has_value()
|
||||||
|
|| registerDescriptor.name->empty()
|
||||||
|
|| !registerDescriptor.groupName.has_value()
|
||||||
|
|| (
|
||||||
|
registerDescriptor.type != Targets::TargetRegisterType::GENERAL_PURPOSE_REGISTER
|
||||||
|
&& registerDescriptor.type != Targets::TargetRegisterType::OTHER
|
||||||
|
&& registerDescriptor.type != Targets::TargetRegisterType::PORT_REGISTER
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto peripheralIt = peripheralsByName.find(*registerDescriptor.groupName);
|
||||||
|
|
||||||
|
if (peripheralIt == peripheralsByName.end()) {
|
||||||
|
auto peripheral = Peripheral{
|
||||||
|
.name = QString::fromStdString(
|
||||||
|
*registerDescriptor.groupName
|
||||||
|
).replace(QChar(' '), QChar('_')).toUpper(),
|
||||||
|
.baseAddress = baseAddressOffset
|
||||||
|
};
|
||||||
|
|
||||||
|
peripheralIt = peripheralsByName.insert(std::pair(*registerDescriptor.groupName, peripheral)).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
peripheralIt->second.registerDescriptors.insert(registerDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto peripheralsElement = document.createElement("peripherals");
|
||||||
|
|
||||||
|
for (const auto& [peripheralName, peripheral] : peripheralsByName) {
|
||||||
|
auto peripheralElement = document.createElement("peripheral");
|
||||||
|
|
||||||
|
peripheralElement.appendChild(createElement("name", peripheral.name));
|
||||||
|
peripheralElement.appendChild(createElement("baseAddress", "0x" + QString::number(peripheral.baseAddress, 16)));
|
||||||
|
|
||||||
|
auto registersElement = document.createElement("registers");
|
||||||
|
|
||||||
|
for (const auto& registerDescriptor : peripheral.registerDescriptors) {
|
||||||
|
auto registerElement = document.createElement("register");
|
||||||
|
|
||||||
|
registerElement.appendChild(
|
||||||
|
createElement(
|
||||||
|
"name",
|
||||||
|
QString::fromStdString(*registerDescriptor.name).replace(QChar(' '), QChar('_')).toUpper()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (registerDescriptor.description.has_value()) {
|
||||||
|
registerElement.appendChild(
|
||||||
|
createElement("description", QString::fromStdString(*registerDescriptor.description))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerElement.appendChild(
|
||||||
|
createElement("addressOffset", "0x" + QString::number(*registerDescriptor.startAddress, 16))
|
||||||
|
);
|
||||||
|
|
||||||
|
registerElement.appendChild(
|
||||||
|
createElement("size", QString::number(registerDescriptor.size * 8))
|
||||||
|
);
|
||||||
|
|
||||||
|
registerElement.appendChild(
|
||||||
|
createElement("access", registerDescriptor.writable ? "read-write" : "read-only")
|
||||||
|
);
|
||||||
|
|
||||||
|
registersElement.appendChild(registerElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
peripheralElement.appendChild(registersElement);
|
||||||
|
peripheralsElement.appendChild(peripheralElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceElement.appendChild(peripheralsElement);
|
||||||
|
document.appendChild(deviceElement);
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/DebugServer/Gdb/CommandPackets/GenerateSvd.hpp
Normal file
35
src/DebugServer/Gdb/CommandPackets/GenerateSvd.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "Monitor.hpp"
|
||||||
|
|
||||||
|
#include "src/Targets/TargetDescriptor.hpp"
|
||||||
|
|
||||||
|
namespace Bloom::DebugServer::Gdb::CommandPackets
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The GenerateSvd class implements a structure for the "monitor svd" GDB command.
|
||||||
|
*
|
||||||
|
* This command generates XML conforming to the CMSIS-SVD schema, for the connected target. Will output the XML to
|
||||||
|
* a file or send it to GDB.
|
||||||
|
*/
|
||||||
|
class GenerateSvd: public Monitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit GenerateSvd(Monitor&& monitorPacket);
|
||||||
|
|
||||||
|
void handle(
|
||||||
|
DebugSession& debugSession,
|
||||||
|
TargetController::TargetControllerConsole& targetControllerConsole
|
||||||
|
) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool sendOutput = false;
|
||||||
|
|
||||||
|
QDomDocument generateSvd(
|
||||||
|
const Targets::TargetDescriptor& targetDescriptor,
|
||||||
|
std::uint32_t baseAddressOffset = 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "CommandPackets/BloomVersion.hpp"
|
#include "CommandPackets/BloomVersion.hpp"
|
||||||
#include "CommandPackets/BloomVersionMachine.hpp"
|
#include "CommandPackets/BloomVersionMachine.hpp"
|
||||||
#include "CommandPackets/TargetInfoMachine.hpp"
|
#include "CommandPackets/TargetInfoMachine.hpp"
|
||||||
|
#include "CommandPackets/GenerateSvd.hpp"
|
||||||
|
|
||||||
// Response packets
|
// Response packets
|
||||||
#include "ResponsePackets/TargetStopped.hpp"
|
#include "ResponsePackets/TargetStopped.hpp"
|
||||||
@@ -318,6 +319,9 @@ namespace Bloom::DebugServer::Gdb
|
|||||||
if (monitorCommand->command == "target-info machine") {
|
if (monitorCommand->command == "target-info machine") {
|
||||||
return std::make_unique<CommandPackets::TargetInfoMachine>(std::move(*(monitorCommand.release())));
|
return std::make_unique<CommandPackets::TargetInfoMachine>(std::move(*(monitorCommand.release())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (monitorCommand->command.find("svd") == 0) {
|
||||||
|
return std::make_unique<CommandPackets::GenerateSvd>(std::move(*(monitorCommand.release())));
|
||||||
}
|
}
|
||||||
|
|
||||||
return monitorCommand;
|
return monitorCommand;
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ namespace Bloom::Targets::Microchip::Avr::Avr8Bit
|
|||||||
auto descriptor = TargetDescriptor();
|
auto descriptor = TargetDescriptor();
|
||||||
descriptor.id = this->getHumanReadableId();
|
descriptor.id = this->getHumanReadableId();
|
||||||
descriptor.name = this->getName();
|
descriptor.name = this->getName();
|
||||||
|
descriptor.vendorName = std::string("Microchip");
|
||||||
descriptor.programMemoryType = Targets::TargetMemoryType::FLASH;
|
descriptor.programMemoryType = Targets::TargetMemoryType::FLASH;
|
||||||
descriptor.registerDescriptorsByType = this->targetRegisterDescriptorsByType;
|
descriptor.registerDescriptorsByType = this->targetRegisterDescriptorsByType;
|
||||||
descriptor.memoryDescriptorsByType = this->targetMemoryDescriptorsByType;
|
descriptor.memoryDescriptorsByType = this->targetMemoryDescriptorsByType;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace Bloom::Targets
|
|||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string id;
|
std::string id;
|
||||||
|
std::string vendorName;
|
||||||
std::map<TargetMemoryType, TargetMemoryDescriptor> memoryDescriptorsByType;
|
std::map<TargetMemoryType, TargetMemoryDescriptor> memoryDescriptorsByType;
|
||||||
std::map<TargetRegisterType, TargetRegisterDescriptors> registerDescriptorsByType;
|
std::map<TargetRegisterType, TargetRegisterDescriptors> registerDescriptorsByType;
|
||||||
std::vector<TargetVariant> variants;
|
std::vector<TargetVariant> variants;
|
||||||
|
|||||||
Reference in New Issue
Block a user