Initial commit

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

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.idea/
release
_CPack_Packages
build/cmake-build-debug
build/cmake-build-release
build/resources
build/bin/bloom
build/bin/bloom.json
*.deb

222
CMakeLists.txt Executable file
View File

@@ -0,0 +1,222 @@
cmake_minimum_required(VERSION 3.10)
project(Bloom LANGUAGES CXX VERSION 0.1)
set(CMAKE_VERBOSE_MAKEFILE off)
# Create directory for generated sources
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/Generated)
set(CMAKE_CXX_STANDARD 20)
set(ENABLE_SANITIZERS off)
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
# The HIDAPI library lives here
link_directories(/usr/local/lib)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(AUTOGEN_BUILD_DIR ../build/)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build/bin")
find_package(Qt5Core)
find_package(Qt5Gui)
find_package(Qt5Widgets)
find_package(Qt5Xml)
find_package(Qt5Svg)
find_package(Qt5UiTools)
set(CMAKE_SKIP_BUILD_RPATH true)
set(CMAKE_BUILD_RPATH_USE_ORIGIN true)
set(CMAKE_INSTALL_RPATH "\$ORIGIN/../lib")
set(CMAKE_BUILD_RPATH ${CMAKE_INSTALL_RPATH})
add_executable(Bloom
src/main.cpp
src/Generated/resources.cpp
src/ApplicationConfig.cpp
src/Logger/Logger.cpp
src/SignalHandler/SignalHandler.cpp
src/DebugToolDrivers/USB/Interface.cpp
src/DebugToolDrivers/USB/HID/HidInterface.cpp
src/DebugToolDrivers/Microchip/AtmelICE/AtmelIce.cpp
src/DebugToolDrivers/Microchip/PowerDebugger/PowerDebugger.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/Command.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/Response.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrCommand.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/CommandFrames/AvrCommandFrame.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrResponse.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/ResponseFrames/AvrResponseFrame.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/AvrEvent.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/Events/AVR8Generic/BreakEvent.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/CmsisDapInterface.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/EdbgInterface.cpp
src/DebugToolDrivers/Protocols/CMSIS-DAP/VendorSpecific/EDBG/AVR/EdbgAvr8Interface.cpp
src/Targets/Target.cpp
src/Targets/Microchip/AVR/AVR8/Avr8.cpp
src/Targets/Microchip/AVR/AVR8/Mega/Mega.cpp
src/Targets/Microchip/AVR/AVR8/PartDescription/PartDescriptionFile.cpp
src/Application.cpp
src/DebugToolDrivers/USB/UsbDevice.cpp
src/TargetController/TargetController.cpp
src/EventManager/EventListener.cpp
src/EventManager/EventManager.cpp
src/DebugServers/DebugServer.cpp
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
src/DebugServers/GdbRsp/CommandPackets/SupportedFeaturesQuery.cpp
src/DebugServers/GdbRsp/CommandPackets/ReadGeneralRegisters.cpp
src/DebugServers/GdbRsp/CommandPackets/WriteGeneralRegisters.cpp
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
src/DebugServers/GdbRsp/CommandPackets/InterruptExecution.cpp
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
src/DebugServers/GdbRsp/ResponsePackets/SupportedFeaturesResponse.cpp
src/DebugServers/GdbRsp/Connection.cpp
src/Insight/Insight.cpp
src/Insight/InsightWorker.cpp
src/Insight/UserInterfaces/InsightWindow/InsightWindow.cpp
src/Insight/UserInterfaces/InsightWindow/AboutWindow.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/TargetPackageWidget.hpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/TargetPinWidget.hpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/DIP/DualInlinePackageWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/DIP/PinWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/DIP/PinBodyWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/DIP/BodyWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/QFP/QuadFlatPackageWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/QFP/PinWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/QFP/PinBodyWidget.cpp
src/Insight/UserInterfaces/InsightWindow/TargetWidgets/QFP/BodyWidget.cpp
build/resources/TargetPartDescriptions/AVR/Mapping.json
)
set_target_properties(Bloom PROPERTIES OUTPUT_NAME bloom)
target_include_directories(Bloom PUBLIC ./)
# Construct JSON mapping of part description files.
add_custom_command(
OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/build/resources/TargetPartDescriptions/AVR/Mapping.json
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/build/scripts/CopyAvrPartFilesAndCreateMapping.php
COMMAND echo 'Processing AVR target description files.'
COMMAND
php ${CMAKE_CURRENT_SOURCE_DIR}/build/scripts/CopyAvrPartFilesAndCreateMapping.php
)
# Compile resources
add_custom_command(
OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/resources.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/resources_fake.cpp
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/src/resources.qrc
COMMAND echo 'Compiling QT resources. |${CMAKE_BUILD_TYPE}|'
COMMAND
rcc -o ${CMAKE_CURRENT_SOURCE_DIR}/src/Generated/resources.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/resources.qrc
)
target_link_libraries(Bloom -static-libgcc -static-libstdc++)
target_link_libraries(Bloom -lstdc++fs)
target_link_libraries(Bloom -lpthread)
target_link_libraries(Bloom -lusb-1.0)
target_link_libraries(Bloom -lhidapi-libusb)
target_link_libraries(Bloom Qt5::Core)
target_link_libraries(Bloom Qt5::Gui)
target_link_libraries(Bloom Qt5::UiTools)
target_link_libraries(Bloom Qt5::Widgets)
target_link_libraries(Bloom Qt5::Xml)
target_link_libraries(Bloom Qt5::Svg)
target_compile_options(
Bloom
PUBLIC -std=c++2a
PUBLIC -pedantic
PUBLIC -Wconversion
PUBLIC -fno-sized-deallocation
PUBLIC $<$<CONFIG:DEBUG>:-g>
PUBLIC $<$<CONFIG:DEBUG>:-Os>
PUBLIC $<$<CONFIG:RELEASE>:-O2>
PUBLIC $<$<CONFIG:DEBUG>:-fno-inline>
PUBLIC $<$<CONFIG:DEBUG>:-fkeep-static-functions>
)
target_link_options(
Bloom
PUBLIC [=[-Wl,--disable-new-dtags]=] #,--verbose
)
if (${ENABLE_SANITIZERS})
message(WARNING "Sanitizers have been enabled")
# For TSAN, see ThreadSanitizerSuppression.txt
# Some sanitizers are not compatible with each other.
target_compile_options(
Bloom
PUBLIC "-fsanitize=address"
#PUBLIC "-fsanitize=undefined"
# PUBLIC "-fsanitize=thread"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=address>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=undefined>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=integer-divide-by-zero>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=unreachable>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=vla-bound>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=null>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=return>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=signed-integer-overflow>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=bounds>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=alignment>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=object-size>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=float-divide-by-zero>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=float-cast-overflow>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=nonnull-attribute>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=returns-nonnull-attribute>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=bool>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=enum>"
# PUBLIC "$<$<BOOL:${ENABLE_SANITIZERS}>:-fsanitize=vptr>"
)
target_link_libraries(
Bloom
"-fsanitize=address"
# "-fsanitize=undefined"
# "-fsanitize=thread"
)
endif()
# Installation configuration
set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/release/")
install(TARGETS Bloom DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
install(FILES build/bin/bloom.json DESTINATION bin PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ) # Remove this
install(DIRECTORY build/bin/plugins DESTINATION "bin" DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
install(DIRECTORY build/bin/platforms DESTINATION "bin" DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
install(DIRECTORY build/resources DESTINATION "." DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
install(DIRECTORY build/lib DESTINATION "." DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
# Debian package configuration
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_NAME "Bloom")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "")
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/resources/packaging/description.txt CPACK_DEBIAN_PACKAGE_DESCRIPTION)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A Linux-based debug interface for embedded systems development")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Bloom Support <support@bloom.oscillate.io>")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://bloom.oscillate.io")
set(CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/bloom")
set(
CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
${CMAKE_CURRENT_SOURCE_DIR}/resources/packaging/postinst;
${CMAKE_CURRENT_SOURCE_DIR}/resources/packaging/postrm
)
include(CPack)

165
COPYING.LESSER Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

12
LICENSE.txt Normal file
View File

@@ -0,0 +1,12 @@
Bloom is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bloom is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program (COPYING.LESSER). If not, see <https://www.gnu.org/licenses/>.

51
README.md Normal file
View File

@@ -0,0 +1,51 @@
<img alt="" src="/src/Insight/UserInterfaces/InsightWindow/Images/BloomIcon.svg"/>
## Bloom
Bloom is a Linux-based debug platform for microcontrollers. This is the official repository for Bloom's source code.
For information on how to use Bloom, please visit https://bloom.oscillate.io.
Bloom implements a number of user-space device drivers, enabling support for many debug tools (such as the Atmel-ICE,
Power Debugger, MPLAB SNAP* and the MPLAB PICkit 4*). Bloom exposes an interface to the connected target, via a GDB
RSP server. This allows any IDE with GDB RSP client capabilities to interface with Bloom and gain full
access to the target.
Currently, Bloom only supports AVR8 targets from Microchip. Bloom was designed to accommodate targets from different
families and architectures. Support for other target families will be considered as and when requested.
*These debug tools are not yet officially supported by Bloom, but will be soon.
### License
Bloom is released under the LGPLv3 license. See LICENSE.txt
### Bloom Architecture
Bloom is a multithreaded event-driven program written in C++. It consists of four components:
- TargetController
- DebugServer
- Insight
- SignalHandler
##### TargetController
The TargetController possesses full control of the connected debug tool and target. Execution of user-space
device drivers takes place here. All interaction with the connected hardware goes through the TargetController.
It exposes an interface to the connected hardware via events. The TargetController runs on a dedicated thread.
##### DebugServer
The DebugServer exposes an interface to the connected target, for third-party programs such as IDEs. Currently, Bloom
only supports one DebugServer - the GDB RSP server. With this server, any IDE with GDB RSP support can interface with
Bloom and thus the connected target. The DebugServer runs on a dedicated thread.
##### Insight
Insight is a graphical user interface that provides insight of the target's GPIO pin states. It also enables GPIO
pin manipulation. Insight occupies Bloom's main thread and employs a single worker thread for background tasks.
Unlike other components within Bloom, Insight relies heavily on the Qt framework for its GUI capabilities and
other useful utilities.
##### SignalHandler
The SignalHandler is responsible for handling any UNIX signals issued to Bloom. It runs on a dedicated thread. All
other threads within Bloom do not accept any UNIX signals.
#### Inter-component communication
The components described above interact with each other using an event-based mechanism.
More documentation to follow.

View File

@@ -0,0 +1,5 @@
# Suppression file can be specified via:
# TSAN_OPTIONS="suppressions=../../../ThreadSanitizerSuppression.txt" bloom
race:libusb-1.0
race:libglib

BIN
build/bin/platforms/libqxcb.so Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
build/lib/libQt5Core.so.5 Symbolic link
View File

@@ -0,0 +1 @@
./libQt5Core.so.5.12.10

Binary file not shown.

1
build/lib/libQt5DBus.so.5 Symbolic link
View File

@@ -0,0 +1 @@
./libQt5DBus.so.5.12.10

BIN
build/lib/libQt5DBus.so.5.12.10 Executable file

Binary file not shown.

1
build/lib/libQt5Gui.so.5 Symbolic link
View File

@@ -0,0 +1 @@
./libQt5Gui.so.5.12.10

Binary file not shown.

1
build/lib/libQt5Svg.so.5 Symbolic link
View File

@@ -0,0 +1 @@
./libQt5Svg.so.5.12.10

BIN
build/lib/libQt5Svg.so.5.12.10 Executable file

Binary file not shown.

View File

@@ -0,0 +1 @@
./libQt5Widgets.so.5.12.10

Binary file not shown.

1
build/lib/libQt5XcbQpa.so.5 Symbolic link
View File

@@ -0,0 +1 @@
./libQt5XcbQpa.so.5.12.10

Binary file not shown.

1
build/lib/libQt5Xml.so.5 Symbolic link
View File

@@ -0,0 +1 @@
./libQt5Xml.so.5.12.10

Binary file not shown.

Binary file not shown.

1
build/lib/libicudata.so.56 Symbolic link
View File

@@ -0,0 +1 @@
./libicudata.so.56.1

Binary file not shown.

1
build/lib/libicui18n.so.56 Symbolic link
View File

@@ -0,0 +1 @@
./libicui18n.so.56.1

Binary file not shown.

1
build/lib/libicuuc.so.56 Symbolic link
View File

@@ -0,0 +1 @@
./libicuuc.so.56.1

BIN
build/lib/libicuuc.so.56.1 Normal file

Binary file not shown.

BIN
build/lib/libqxcb.so Executable file

Binary file not shown.

1
build/lib/libqxcb.so.1 Symbolic link
View File

@@ -0,0 +1 @@
./libqxcb.so

BIN
build/lib/libusb-1.0.so.0 Normal file

Binary file not shown.

View File

@@ -0,0 +1,17 @@
#!/bin/bash
cd /home/nav/Projects/Bloom || exit;
rm -fr build/cmake-build-debug/*; rm -fr build/cmake-build-release/*;
rm -fr release;
rm -fr Bloom-*.deb;
rm -fr "_CPack_Packages";
export CMAKE_PREFIX_PATH=/opt/Qt/5.12.10/gcc_64/
cd build/cmake-build-debug/ && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=/usr/bin/g++-9 /home/nav/Projects/Bloom/
cd /home/nav/Projects/Bloom/ && cmake --build /home/nav/Projects/Bloom/build/cmake-build-debug --target clean
cmake --build /home/nav/Projects/Bloom/build/cmake-build-debug --target Bloom
cmake --install /home/nav/Projects/Bloom/build/cmake-build-debug --target Bloom

View File

@@ -0,0 +1,178 @@
<?php
/*
* Parses Microchip AVR Part Description (.atdf/.xml) files and creates a JSON mapping of target IDs to file paths.
* Also copies the files over to the Distribution directory.
*
* The JSON mapping is compiled as a Qt resource and used for looking-up target description file paths, by target ID.
*
* This script should be run as part of the build process.
*/
CONST AVR_PD_FILE_PATH = __DIR__ . "/../../src/Targets/Microchip/AVR/PartDescriptionFiles";
CONST TARGET_PD_DEST_FILE_PATH = __DIR__ . "/../resources/TargetPartDescriptions/AVR";
CONST TARGET_PD_DEST_RELATIVE_FILE_PATH = "../resources/TargetPartDescriptions/AVR";
CONST TARGET_PD_MAPPING_FILE_PATH = TARGET_PD_DEST_FILE_PATH . "/Mapping.json";
CONST PD_COMMENT = "\n<!-- This is an automatically generated file. Any changes made to it will likely be lost. -->\n";
// Empty destination directory
if (file_exists(TARGET_PD_DEST_FILE_PATH)) {
// There is no PHP function to delete a non-empty directory and I can't be arsed to write one. Bite me
exec("rm -r " . TARGET_PD_DEST_FILE_PATH);
}
if (file_exists(TARGET_PD_MAPPING_FILE_PATH)) {
unlink(TARGET_PD_MAPPING_FILE_PATH);
}
mkdir(TARGET_PD_DEST_FILE_PATH, 0777, true);
class TargetDescription implements JsonSerializable
{
public ?string $targetId;
public ?string $targetName;
public ?string $originalFilePath;
public ?string $destinationFilePath;
public ?string $relativeDestinationFilePath;
public function jsonSerialize() {
return [
'targetName' => $this->targetName,
'targetDescriptionFilePath' => $this->relativeDestinationFilePath,
];
}
}
$processedFileCount = 0;
$failedFileCount = 0;
/**
* Will read all .atdf and .xml files in $path and attempt to parse them as AVR target description files.
* This function is recursive - it will call itself if it stumbles upon a directory within $path.
*
* @param $path
* @return TargetDescription[][]
* A mapping of target IDs to an array of TargetDescription objects. Note: target IDs are not
* always unique to targets. That is why each ID is mapped to an array of TargetDescription objects.
*/
function processAvrPartFiles($path) : array {
global $processedFileCount, $failedFileCount;
/** @var TargetDescription[][] $output */
$output = [];
foreach (glob($path . "/*") as $file) {
if (is_dir($file)) {
$output = array_merge($output, processAvrPartFiles($file));
continue;
}
if (strstr($file, '.atdf') === false && strstr($file, '.xml') === false) {
// Unknown file type
continue;
}
$fileContents = file_get_contents($file);
$pdXml = simplexml_load_string($fileContents);
if ($pdXml === false) {
print "Invalid XML in \"" . $file . "\"\n";
$failedFileCount++;
continue;
}
$device = $pdXml->devices[0]->device;
$partDescriptionXml = new TargetDescription();
$partDescriptionXml->originalFilePath = $file;
$partDescriptionXml->destinationFilePath = TARGET_PD_DEST_FILE_PATH . "/";
$partDescriptionXml->relativeDestinationFilePath = TARGET_PD_DEST_RELATIVE_FILE_PATH . "/";
if (!empty($device['architecture'])
&& in_array($device['architecture'], ['AVR8', 'AVR32', 'AVR8X', 'AVR8L', 'AVR8_XMEGA'])
) {
// This is an AVR device.
if (!empty($device['architecture'])) {
// Group by architecture
$partDescriptionXml->destinationFilePath .= strtoupper((string) $device['architecture']) . "/";
$partDescriptionXml->relativeDestinationFilePath .= strtoupper((string) $device['architecture']) . "/";
}
if (!empty($device['family'])) {
// Group by family
$partDescriptionXml->destinationFilePath .= str_replace([' '] , '_', strtoupper((string) $device['family']));
$partDescriptionXml->relativeDestinationFilePath .= str_replace([' '] , '_', strtoupper((string) $device['family']));
}
$partDescriptionXml->targetName = str_replace([' '], '', (string) $device['name']);
if (!empty(($signatures = $device->{'property-groups'}
->xpath('property-group[@name="SIGNATURES"]')[0]))
&& !empty($signatures->xpath('property[@name="SIGNATURE0"]'))
&& !empty($signatures->xpath('property[@name="SIGNATURE1"]'))
&& !empty($signatures->xpath('property[@name="SIGNATURE2"]'))
) {
$partDescriptionXml->targetId = (string) $signatures->xpath('property[@name="SIGNATURE0"]')[0]['value']
. str_replace('0x', '', (string) $signatures->xpath('property[@name="SIGNATURE1"]')[0]['value'])
. str_replace('0x', '', (string) $signatures->xpath('property[@name="SIGNATURE2"]')[0]['value'])
;
$partDescriptionXml->targetId = strtolower($partDescriptionXml->targetId);
}
}
if (empty($partDescriptionXml->destinationFilePath)
|| empty($partDescriptionXml->targetName)
|| empty($partDescriptionXml->targetId)
) {
print "Failed to parse file {$file}\n";
$failedFileCount++;
continue;
}
if (!file_exists($partDescriptionXml->destinationFilePath)) {
mkdir($partDescriptionXml->destinationFilePath, 0777, true);
}
$partDescriptionXml->destinationFilePath .= "/" . strtoupper($partDescriptionXml->targetName) . ".xml";
$partDescriptionXml->relativeDestinationFilePath .= "/" . strtoupper($partDescriptionXml->targetName) . ".xml";
if (strstr($fileContents, '<avr-tools-device-file ') !== false) {
// Prefix auto gen comment
// This approach could be better
$fileContents = str_replace(
'<avr-tools-device-file ',
PD_COMMENT . "<avr-tools-device-file ",
$fileContents
);
}
if (file_put_contents($partDescriptionXml->destinationFilePath, $fileContents) === false) {
print "FATAL ERROR: Failed to write data to " . $partDescriptionXml->destinationFilePath . "\n";
print "Aborting\n";
exit(1);
}
$output[$partDescriptionXml->targetId][] = $partDescriptionXml;
echo "Target Part Description File Processed: \"" . substr($partDescriptionXml->originalFilePath, strlen(AVR_PD_FILE_PATH)) . "\"\n"
. "Target Name: \"" . $partDescriptionXml->targetName . "\" Target ID: \"" . $partDescriptionXml->targetId
. "\" Destination: \"" . substr($partDescriptionXml->destinationFilePath, strlen(TARGET_PD_DEST_FILE_PATH))
. "\"\n\n"
;
$processedFileCount++;
}
return $output;
}
print "Processing files in " . AVR_PD_FILE_PATH . "\n\n";
$targetDescriptions = processAvrPartFiles(AVR_PD_FILE_PATH);
if (file_put_contents(TARGET_PD_MAPPING_FILE_PATH, json_encode($targetDescriptions, JSON_PRETTY_PRINT)) === false) {
print "FATAL ERROR: Failed to create JSON mapping of target IDs to target description file paths\n";
exit(1);
}
print "\nCreated JSON mapping of target IDs to target description file paths: " . TARGET_PD_MAPPING_FILE_PATH . "\n";
print "\nProcessed " . $processedFileCount . " files. Failures: " . $failedFileCount . "\n";
print "Done\n";

View File

@@ -0,0 +1,20 @@
{
"environments": {
"default": {
"debugToolName": "atmel-ice",
"target": {
"name": "avr8",
"physicalInterface": "debugWire"
},
"debugServer": {
"name": "avr-gdb-rsp"
}
}
},
"insight": {
"enabled": true
}
}

14
resources/help.txt Normal file
View File

@@ -0,0 +1,14 @@
A Linux-based debug interface for embedded systems development.
Usage:
bloom [ENVIRONMENT_NAME/COMMAND]
If no environment name or command is provided, Bloom will fallback to the environment named "default".
If no such environment exists, Bloom will exit.
Commands:
--help, -h Displays this help text.
--version, -v Displays the current version number.
init Creates a new Bloom project configuration file (bloom.json) in the working directory.
For more information on getting started with Bloom, please visit https://bloom.oscillate.io/getting-started.

View File

@@ -0,0 +1 @@
Bloom is a Linux-based debug interface for embedded systems development. Bloom supports most Microchip AVR8 targets, along with numerous EDBG-based debug tools.

15
resources/packaging/postinst Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
BLOOM_UDEV_FILE_PATH=/etc/udev/rules.d/
if [ ! -f "${BLOOM_UDEV_FILE_PATH}/99-bloom.rules" ]; then
sudo ln -s /opt/bloom/resources/UDevRules/99-bloom.rules "$BLOOM_UDEV_FILE_PATH";
fi
if [ -f "/usr/bin/bloom" ]; then
sudo rm /usr/bin/bloom;
fi
sudo chmod u=rwx,g=rwx,o=rx -R /opt/bloom
ln -s /opt/bloom/bin/bloom /usr/bin/bloom;

11
resources/packaging/postrm Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
BLOOM_UDEV_FILE_PATH=/etc/udev/rules.d/
if [ -f "${BLOOM_UDEV_FILE_PATH}/99-bloom.rules" ]; then
sudo rm "$BLOOM_UDEV_FILE_PATH/99-bloom.rules";
fi
if [ -f "/usr/bin/bloom" ]; then
sudo rm /usr/bin/bloom;
fi

344
src/Application.cpp Normal file
View File

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

183
src/Application.hpp Normal file
View File

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

88
src/ApplicationConfig.cpp Normal file
View File

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

154
src/ApplicationConfig.hpp Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,121 @@
#pragma once
#include <cstdint>
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
#include "src/DebugServers/GdbRsp/Register.hpp"
namespace Bloom::DebugServers::Gdb
{
/**
* The AVR GDB client (avr-gdb) defines a set of parameters relating to AVR targets. These parameters are
* hardcoded in the AVR GDB source code. The client expects all compatible GDB RSP servers to be aware of
* these parameters.
*
* An example of these hardcoded parameters is target registers and the order in which they are supplied; AVR GDB
* clients expect 35 registers to be accessible via the server. 32 of these registers are general purpose CPU
* registers. The GP registers are expected to be followed by the status register (SREG), stack pointer
* register (SPH & SPL) and the program counter. These must all be given in a specific order, which is
* pre-determined by the AVR GDB client. See AvrGdbRsp::getRegisterNumberToDescriptorMapping() for more.
*
* For more on this, see the AVR GDB source code at https://github.com/bminor/binutils-gdb/blob/master/gdb/avr-tdep.c
*
* The AvrGdpRsp class extends the generic GDB RSP debug server and implements these AVR specific parameters.
*/
class AvrGdbRsp: public GdbRspDebugServer
{
private:
/**
* The mask used by the AVR GDB client to encode the memory type into memory addresses.
* See AvrGdbRsp::getMemoryTypeFromGdbAddress() for more.
*/
unsigned int gdbInternalMemoryMask = 0xFE0000u;
protected:
/**
* For AVR targets, avr-gdb defines 35 registers in total:
*
* Register number 0 through 31 are general purpose registers
* Register number 32 is the status register (SREG)
* Register number 33 is the stack pointer register
* Register number 34 is the program counter register
*
* Only general purpose registers have register IDs. The others do not require an ID.
*
* @return
*/
BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() override {
static BiMap<GdbRegisterNumber, TargetRegisterDescriptor> mapping = {
{0, TargetRegisterDescriptor(0, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{1, TargetRegisterDescriptor(1, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{2, TargetRegisterDescriptor(2, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{3, TargetRegisterDescriptor(3, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{4, TargetRegisterDescriptor(4, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{5, TargetRegisterDescriptor(5, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{6, TargetRegisterDescriptor(6, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{7, TargetRegisterDescriptor(7, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{8, TargetRegisterDescriptor(8, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{9, TargetRegisterDescriptor(9, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{10, TargetRegisterDescriptor(10, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{11, TargetRegisterDescriptor(11, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{12, TargetRegisterDescriptor(12, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{13, TargetRegisterDescriptor(13, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{14, TargetRegisterDescriptor(14, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{15, TargetRegisterDescriptor(15, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{16, TargetRegisterDescriptor(16, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{17, TargetRegisterDescriptor(17, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{18, TargetRegisterDescriptor(18, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{19, TargetRegisterDescriptor(19, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{20, TargetRegisterDescriptor(20, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{21, TargetRegisterDescriptor(21, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{22, TargetRegisterDescriptor(22, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{23, TargetRegisterDescriptor(23, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{24, TargetRegisterDescriptor(24, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{25, TargetRegisterDescriptor(25, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{26, TargetRegisterDescriptor(26, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{27, TargetRegisterDescriptor(27, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{28, TargetRegisterDescriptor(28, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{29, TargetRegisterDescriptor(29, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{30, TargetRegisterDescriptor(30, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{31, TargetRegisterDescriptor(31, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
{32, TargetRegisterDescriptor(TargetRegisterType::STATUS_REGISTER)},
{33, TargetRegisterDescriptor(TargetRegisterType::STACK_POINTER)},
{34, TargetRegisterDescriptor(TargetRegisterType::PROGRAM_COUNTER)},
};
return mapping;
};
/**
* avr-gdb uses the most significant 15 bits in memory addresses to indicate the type of memory being
* addressed.
*
* @param address
* @return
*/
TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) override {
if (address & this->gdbInternalMemoryMask) {
return TargetMemoryType::RAM;
}
return TargetMemoryType::FLASH;
};
/**
* Strips the most significant 15 bits from a GDB memory address.
*
* @param address
* @return
*/
std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) override {
return address & this->gdbInternalMemoryMask ? (address & ~(this->gdbInternalMemoryMask)) : address;
};
public:
AvrGdbRsp(EventManager& eventManager) : GdbRspDebugServer(eventManager) {};
std::string getName() const override {
return "AVR GDB Remote Serial Protocol Debug Server";
}
};
}

View File

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

View File

@@ -0,0 +1,8 @@
#include "CommandPacket.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void CommandPacket::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <memory>
#include "src/DebugServers/GdbRsp/Packet.hpp"
namespace Bloom::DebugServers::Gdb {
class GdbRspDebugServer;
}
namespace Bloom::DebugServers::Gdb::CommandPackets
{
/**
* GDB RSP command packets are sent to the server, from the GDB client. These packets carry instructions that the
* server is expected to carry out. Upon completion, the server is expected to respond to the client with
* a ResponsePacket.
*
* For some command packets, we define a specific data structure by extending this CommandPacket class. These
* classes extend the data structure to include fields for data which may be specific to the command.
* They also implement additional methods that allow us to easily access the additional data. An example
* of this would be the SupportedFeaturesQuery class. It extends the CommandPacket class and provides access
* to additional data fields that are specific to the command (in this case, a set of GDB features reported to be
* supported by the GDB client).
*
* Typically, command packets that require specific data structures are handled in a dedicated handler method
* in the GdbRspDebugServer. This is done by double dispatching the packet object to the appropriate handler.
* See CommandPacket::dispatchToHandler(), GdbRspDebugServer::serve() and the overloads
* for GdbRspDebugServer::handleGdbPacket() for more on this.
*
* Some command packets are so simple they do not require a dedicated data structure. An example of this is
* the halt reason packet, which contains nothing more than an ? character in the packet body. These packets are
* typically handled in the generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) method.
*
* See the Packet class for information on how the raw packets are formatted.
*/
class CommandPacket: public Packet
{
public:
CommandPacket(const std::vector<unsigned char>& rawPacket) : Packet(rawPacket) {}
/**
* Double dispatches the packet to the appropriate overload of handleGdbPacket(), within the passed instance of
* GdbRspDebugServer. If there is no overload defined for a specific CommandPacket-inherited type, the
* generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) is used.
*
* @param gdbRspDebugServer
*/
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer);
};
}

View File

@@ -0,0 +1,129 @@
#include <vector>
#include <memory>
#include <map>
#include "CommandPacketFactory.hpp"
using namespace Bloom::DebugServers::Gdb;
std::unique_ptr<CommandPacket> CommandPacketFactory::create(std::vector<unsigned char> rawPacket) {
if (rawPacket.size() == 5 && rawPacket[1] == 0x03) {
// This is an interrupt request - create a fake packet for it
return std::make_unique<CommandPackets::InterruptExecution>(rawPacket);
}
auto rawPacketString = std::string(rawPacket.begin(), rawPacket.end());
if (rawPacketString.size() >= 2) {
/*
* First byte of the raw packet will be 0x24 ('$'), so find() should return 1, not 0, when
* looking for a command identifier string.
*/
if (rawPacketString.find("qSupported") == 1) {
return std::make_unique<CommandPackets::SupportedFeaturesQuery>(rawPacket);
} else if (rawPacketString[1] == 'g' || rawPacketString[1] == 'p') {
return std::make_unique<CommandPackets::ReadGeneralRegisters>(rawPacket);
} else if (rawPacketString[1] == 'G' || rawPacketString[1] == 'P') {
return std::make_unique<CommandPackets::WriteGeneralRegisters>(rawPacket);
} else if (rawPacketString[1] == 'c') {
return std::make_unique<CommandPackets::ContinueExecution>(rawPacket);
} else if (rawPacketString[1] == 's') {
return std::make_unique<CommandPackets::StepExecution>(rawPacket);
} else if (rawPacketString[1] == 'm') {
return std::make_unique<CommandPackets::ReadMemory>(rawPacket);
} else if (rawPacketString[1] == 'M') {
return std::make_unique<CommandPackets::WriteMemory>(rawPacket);
} else if (rawPacketString[1] == 'Z') {
return std::make_unique<CommandPackets::SetBreakpoint>(rawPacket);
} else if (rawPacketString[1] == 'z') {
return std::make_unique<CommandPackets::RemoveBreakpoint>(rawPacket);
}
}
return std::make_unique<CommandPacket>(rawPacket);
}
std::vector<std::vector<unsigned char>> CommandPacketFactory::extractRawPackets(std::vector<unsigned char> buffer) {
std::vector<std::vector<unsigned char>> output;
std::size_t bufferIndex;
std::size_t bufferSize = buffer.size();
unsigned char byte;
for (bufferIndex = 0; bufferIndex < bufferSize; bufferIndex++) {
byte = buffer[bufferIndex];
if (byte == 0x03) {
/*
* This is an interrupt packet - it doesn't carry any of the usual packet frame bytes, so we'll just
* add them here, in order to keep things consistent.
*
* Because we're effectively faking the packet frame, we can use any value for the checksum.
*/
output.push_back({'$', byte, '#', 'F', 'F'});
} else if (byte == '$') {
// Beginning of packet
std::vector<unsigned char> rawPacket;
rawPacket.push_back('$');
auto packetIndex = bufferIndex;
bool validPacket = false;
bool isByteEscaped = false;
for (packetIndex++; packetIndex < bufferSize; packetIndex++) {
byte = buffer[packetIndex];
if (byte == '}' && !isByteEscaped) {
isByteEscaped = true;
continue;
}
if (byte == '$' && !isByteEscaped) {
// Unexpected end of packet
validPacket = false;
break;
}
if (byte == '#' && !isByteEscaped) {
// End of packet data
if ((bufferSize - 1) < (packetIndex + 2)) {
// There should be at least two more bytes in the buffer, for the checksum.
break;
}
rawPacket.push_back(byte);
// Add the checksum bytes and break the loop
rawPacket.push_back(buffer[++packetIndex]);
rawPacket.push_back(buffer[++packetIndex]);
validPacket = true;
break;
}
if (isByteEscaped) {
// Escaped bytes are XOR'd with a 0x20 mask.
byte ^= 0x20;
isByteEscaped = false;
}
rawPacket.push_back(byte);
}
if (validPacket) {
output.push_back(rawPacket);
bufferIndex = packetIndex;
}
}
}
return output;
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <memory>
// Command packets
#include "CommandPacket.hpp"
#include "InterruptExecution.hpp"
#include "SupportedFeaturesQuery.hpp"
#include "ReadGeneralRegisters.hpp"
#include "WriteGeneralRegisters.hpp"
#include "ReadMemory.hpp"
#include "WriteMemory.hpp"
#include "StepExecution.hpp"
#include "ContinueExecution.hpp"
#include "SetBreakpoint.hpp"
#include "RemoveBreakpoint.hpp"
namespace Bloom::DebugServers::Gdb
{
using namespace CommandPackets;
/**
* The CommandPacketFactory class provides a means for extracting raw packet data from a raw buffer, and
* constructing the appropriate CommandPacket objects.
*/
class CommandPacketFactory
{
public:
/**
* Extracts raw GDB RSP packets from buffer.
*
* @param buffer
* The buffer from which to extract the raw GDB RSP packets.
*
* @return
* A vector of raw packets.
*/
static std::vector<std::vector<unsigned char>> extractRawPackets(std::vector<unsigned char> buffer);
/**
* Constructs the appropriate CommandPacket object from a single raw GDB RSP packet.
*
* @param rawPacket
* The raw GDB RSP packet from which to construct the CommandPacket object.
*
* @return
*/
static std::unique_ptr<CommandPacket> create(std::vector<unsigned char> rawPacket);
};
}

View File

@@ -0,0 +1,18 @@
#include <cstdint>
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
#include "ContinueExecution.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void ContinueExecution::init() {
if (this->data.size() > 1) {
this->fromProgramCounter = static_cast<std::uint32_t>(
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
);
}
}
void ContinueExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <cstdint>
#include <optional>
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The ContinueExecution class implements a structure for "c" packets. These packets instruct the server
* to continue execution on the target.
*
* See @link https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets for more on this.
*/
class ContinueExecution: public CommandPacket
{
private:
void init();
public:
/**
* The "c" packet can contain an address which defines the point from which the execution should be resumed on
* the target.
*
* Although the packet *can* contain this address, it is not required, hence the optional.
*/
std::optional<std::uint32_t> fromProgramCounter;
ContinueExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,8 @@
#include "InterruptExecution.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void InterruptExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The InterruptException class represents interrupt command packets. Upon receiving an interrupt packet, the
* server is expected to interrupt execution on the target.
*
* Technically, interrupts are not sent by the client in the form of a typical GDP RSP packet. Instead, they're
* just sent as a single byte from the client. We fake the packet on our end, to save us the headache of dealing
* with this inconsistency. We do this in CommandPacketFactory::extractRawPackets().
*/
class InterruptExecution: public CommandPacket
{
public:
InterruptExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,15 @@
#include "ReadGeneralRegisters.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void ReadGeneralRegisters::init() {
if (this->data.size() >= 2 && this->data.front() == 'p') {
// This command packet is requesting a specific register
this->registerNumber = static_cast<size_t>(std::stoi(std::string(this->data.begin() + 1, this->data.end())));
}
}
void ReadGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <optional>
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The ReadGeneralRegisters class implements a structure for "g" and "p" command packets. In response to these
* packets, the server is expected to send register values for all registers (for "g" packets) or for a single
* register (for "p" packets).
*/
class ReadGeneralRegisters: public CommandPacket
{
private:
void init();
public:
/**
* "p" packets include a register number to indicate which register is requested for reading. When this is set,
* the server is expected to respond with only the value of the requested register.
*
* If the register number is not supplied (as is the case with "g" packets), the server is expected to respond
* with values for all registers.
*/
std::optional<int> registerNumber;
ReadGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,43 @@
#include "ReadMemory.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
using namespace Bloom::Exceptions;
void ReadMemory::init() {
if (this->data.size() < 4) {
throw Exception("Invalid packet length");
}
auto packetString = QString::fromLocal8Bit(
reinterpret_cast<const char*>(this->data.data() + 1),
static_cast<int>(this->data.size() - 1)
);
/*
* The read memory ('m') packet consists of two segments, an address and a number of bytes to read.
* These are separated by a comma character.
*/
auto packetSegments = packetString.split(",");
if (packetSegments.size() != 2) {
throw Exception("Unexpected number of segments in packet data: " + std::to_string(packetSegments.size()));
}
bool conversionStatus = false;
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
if (!conversionStatus) {
throw Exception("Failed to parse start address from read memory packet data");
}
this->bytes = packetSegments.at(1).toUInt(&conversionStatus, 16);
if (!conversionStatus) {
throw Exception("Failed to parse read length from read memory packet data");
}
}
void ReadMemory::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <optional>
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The ReadMemory class implements a structure for "m" packets. Upon receiving these packets, the server is
* expected to read memory from the target and send it the client.
*/
class ReadMemory: public CommandPacket
{
private:
void init();
public:
/**
* The startAddress sent from the GDB client may include additional bits used to indicate the memory type.
* These bits have to be removed from the address before it can be used as a start address. This is not done
* here, as it's target specific.
*
* For an example of where GDB does this, see the AvrGdbRsp class.
*/
std::uint32_t startAddress;
/**
* Number of bytes to read.
*/
std::uint32_t bytes;
ReadMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,38 @@
#include <QtCore/QString>
#include "RemoveBreakpoint.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
using namespace Bloom::Exceptions;
void RemoveBreakpoint::init() {
if (data.size() < 6) {
throw Exception("Unexpected RemoveBreakpoint packet size");
}
// z0 = SW breakpoint, z1 = HW breakpoint
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
auto packetData = QString::fromLocal8Bit(
reinterpret_cast<const char*>(this->data.data() + 2),
static_cast<int>(this->data.size() - 2)
);
auto packetSegments = packetData.split(",");
if (packetSegments.size() < 3) {
throw Exception("Unexpected number of packet segments in RemoveBreakpoint packet");
}
bool conversionStatus = true;
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
if (!conversionStatus) {
throw Exception("Failed to convert address hex value from RemoveBreakpoint packet.");
}
}
void RemoveBreakpoint::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <cstdint>
#include <string>
#include <set>
#include "../BreakpointType.hpp"
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb {
enum class Feature: int;
}
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The RemoveBreakpoint class implements the structure for "z" command packets. Upon receiving this command, the
* server is expected to remove a breakpoint at the specified address.
*/
class RemoveBreakpoint: public CommandPacket
{
private:
void init();
public:
/**
* Breakpoint type (Software or Hardware)
*/
BreakpointType type = BreakpointType::UNKNOWN;
/**
* Address at which the breakpoint should be located.
*/
std::uint32_t address;
RemoveBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
this->init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,39 @@
#include "SetBreakpoint.hpp"
#include <QtCore/QString>
#include <QtCore/QStringList>
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
using namespace Bloom::Exceptions;
void SetBreakpoint::init() {
if (data.size() < 6) {
throw Exception("Unexpected SetBreakpoint packet size");
}
// Z0 = SW breakpoint, Z1 = HW breakpoint
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
auto packetData = QString::fromLocal8Bit(
reinterpret_cast<const char*>(this->data.data() + 2),
static_cast<int>(this->data.size() - 2)
);
auto packetSegments = packetData.split(",");
if (packetSegments.size() < 3) {
throw Exception("Unexpected number of packet segments in SetBreakpoint packet");
}
bool conversionStatus = true;
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
if (!conversionStatus) {
throw Exception("Failed to convert address hex value from SetBreakpoint packet.");
}
}
void SetBreakpoint::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <cstdint>
#include <string>
#include <set>
#include "../BreakpointType.hpp"
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb {
enum class Feature: int;
}
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The SetBreakpoint class implements the structure for "Z" command packets. Upon receiving this command, the
* server is expected to set a breakpoint at the specified address.
*/
class SetBreakpoint: public CommandPacket
{
private:
void init();
public:
/**
* Breakpoint type (Software or Hardware)
*/
BreakpointType type = BreakpointType::UNKNOWN;
/**
* Address at which the breakpoint should be located.
*/
std::uint32_t address;
SetBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
this->init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,18 @@
#include <cstdint>
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
#include "StepExecution.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void StepExecution::init() {
if (this->data.size() > 1) {
this->fromProgramCounter = static_cast<std::uint32_t>(
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
);
}
}
void StepExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <optional>
#include "CommandPacket.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The StepExecution class implements the structure for "s" command packets. Upon receiving this command, the
* server is expected to step execution on the target.
*/
class StepExecution: public CommandPacket
{
private:
void init();
public:
/**
* The address from which to begin the step.
*/
std::optional<size_t> fromProgramCounter;
StepExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,43 @@
#include "SupportedFeaturesQuery.hpp"
#include <QtCore/QString>
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
#include "../Feature.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void SupportedFeaturesQuery::init() {
/*
* For qSupported packets, supported and unsupported GDB features are reported in the packet
* data, where each GDB feature is separated by a semicolon.
*/
// The "qSupported:" prefix occupies 11 bytes
if (data.size() > 11) {
auto packetData = QString::fromLocal8Bit(
reinterpret_cast<const char*>(this->data.data() + 11),
static_cast<int>(this->data.size() - 11)
);
auto featureList = packetData.split(";");
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
for (int i = 0; i < featureList.size(); i++) {
auto featureString = featureList.at(i);
// We only care about supported features. Supported features will precede a '+' character.
if (featureString[featureString.size() - 1] == "+") {
featureString.remove("+");
auto feature = gdbFeatureMapping.valueAt(featureString.toStdString());
if (feature.has_value()) {
this->supportedFeatures.insert(static_cast<decltype(feature)::value_type>(feature.value()));
}
}
}
}
}
void SupportedFeaturesQuery::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <string>
#include <set>
#include "CommandPacket.hpp"
#include "../Feature.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
/**
* The SupportedFeaturesQuery command packet is a query from the GDB client, requesting a list of GDB features
* supported by the GDB server. The body of this packet also contains a list GDB features that are supported or
* unsupported by the GDB client.
*
* The command packet is identified by its 'qSupported' prefix in the command packet data. Following the prefix is
* a list of GDB features that are supported/unsupported by the client. For more info on this command
* packet, see the GDP RSP documentation.
*
* Responses to this command packet should take the form of a ResponsePackets::SupportedFeaturesResponse.
*/
class SupportedFeaturesQuery: public CommandPacket
{
private:
std::set<Feature> supportedFeatures;
void init();
public:
SupportedFeaturesQuery(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
this->init();
};
bool isFeatureSupported(const Feature& feature) const {
return this->supportedFeatures.find(feature) != this->supportedFeatures.end();
}
const std::set<Feature>& getSupportedFeatures() const {
return this->supportedFeatures;
}
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,26 @@
#include "WriteGeneralRegisters.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
void WriteGeneralRegisters::init() {
// The P packet updates a single register
auto packet = std::string(this->data.begin(), this->data.end());
if (packet.size() < 6) {
throw Exception("Invalid P command packet - insufficient data in packet.");
}
if (packet.find("=") == std::string::npos) {
throw Exception("Invalid P command packet - unexpected format");
}
auto packetSegments = QString::fromStdString(packet).split("=");
this->registerNumber = packetSegments.front().mid(1).toUInt(nullptr, 16);
this->registerValue = this->hexToData(packetSegments.back().toStdString());
std::reverse(this->registerValue.begin(), this->registerValue.end());
}
void WriteGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <optional>
#include "CommandPacket.hpp"
#include "src/Targets/TargetRegister.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
using Bloom::Targets::TargetRegister;
using Bloom::Targets::TargetRegisterMap;
/**
* The WriteGeneralRegisters class implements the structure for "G" and "P" packets. Upon receiving this packet,
* server is expected to write register values to the target.
*/
class WriteGeneralRegisters: public CommandPacket
{
private:
void init();
public:
TargetRegisterMap registerMap;
int registerNumber;
std::vector<unsigned char> registerValue;
WriteGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,52 @@
#include "WriteMemory.hpp"
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
using namespace Bloom::DebugServers::Gdb::CommandPackets;
using namespace Bloom::Exceptions;
void WriteMemory::init() {
if (this->data.size() < 4) {
throw Exception("Invalid packet length");
}
auto packetString = QString::fromLocal8Bit(
reinterpret_cast<const char*>(this->data.data() + 1),
static_cast<int>(this->data.size() - 1)
);
/*
* The write memory ('M') packet consists of three segments, an address, a length and a buffer.
* The address and length are separated by a comma character, and the buffer proceeds a colon character.
*/
auto packetSegments = packetString.split(",");
if (packetSegments.size() != 2) {
throw Exception("Unexpected number of segments in packet data: " + std::to_string(packetSegments.size()));
}
bool conversionStatus = false;
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
if (!conversionStatus) {
throw Exception("Failed to parse start address from write memory packet data");
}
auto lengthAndBufferSegments = packetSegments.at(1).split(":");
if (lengthAndBufferSegments.size() != 2) {
throw Exception("Unexpected number of segments in packet data: " + std::to_string(lengthAndBufferSegments.size()));
}
auto bufferSize = lengthAndBufferSegments.at(0).toUInt(&conversionStatus, 16);
if (!conversionStatus) {
throw Exception("Failed to parse write length from write memory packet data");
}
this->buffer = this->hexToData(lengthAndBufferSegments.at(1).toStdString());
if (this->buffer.size() != bufferSize) {
throw Exception("Buffer size does not match length value given in write memory packet");
}
}
void WriteMemory::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
gdbRspDebugServer.handleGdbPacket(*this);
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <cstdint>
#include <optional>
#include "CommandPacket.hpp"
#include "src/Targets/TargetMemory.hpp"
namespace Bloom::DebugServers::Gdb::CommandPackets
{
using namespace Bloom::DebugServers::Gdb;
using Bloom::Targets::TargetMemoryBuffer;
/**
* The WriteMemory class implements the structure for "M" packets. Upon receiving this packet, the server is
* expected to write data to the target's memory, at the specified start address.
*/
class WriteMemory: public CommandPacket
{
private:
void init();
public:
/**
* Like with the ReadMemory command packet, the start address carries additional bits that indicate
* the memory type.
*/
std::uint32_t startAddress;
TargetMemoryBuffer buffer;
WriteMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
init();
};
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
};
}

View File

@@ -0,0 +1,208 @@
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#include "src/Logger/Logger.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Exceptions/DebugServerInterrupted.hpp"
#include "Connection.hpp"
#include "Exceptions/ClientDisconnected.hpp"
#include "Exceptions/ClientCommunicationError.hpp"
#include "CommandPackets/CommandPacketFactory.hpp"
using namespace Bloom::DebugServers::Gdb;
using namespace Bloom::DebugServers::Gdb::Exceptions;
using namespace Bloom::Exceptions;
void Connection::accept(int serverSocketFileDescriptor) {
int socketAddressLength = sizeof(this->socketAddress);
this->socketFileDescriptor = ::accept(
serverSocketFileDescriptor,
(sockaddr*)& (this->socketAddress),
(socklen_t*)& socketAddressLength
);
if (this->socketFileDescriptor == -1) {
throw Exception("Failed to accept GDB Remote Serial Protocol connection");
}
::fcntl(
this->socketFileDescriptor,
F_SETFL,
fcntl(this->socketFileDescriptor, F_GETFL, 0) | O_NONBLOCK
);
// Create event FD
this->eventFileDescriptor = ::epoll_create(2);
struct epoll_event event = {};
event.events = EPOLLIN;
event.data.fd = this->socketFileDescriptor;
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->socketFileDescriptor, &event) != 0) {
throw Exception("Failed to create event FD for GDB client connection - could not add client connection "
"socket FD to epoll FD");
}
this->enableReadInterrupts();
}
void Connection::disableReadInterrupts() {
if (::epoll_ctl(
this->eventFileDescriptor,
EPOLL_CTL_DEL,
this->interruptEventNotifier->getFileDescriptor(),
NULL) != 0
) {
throw Exception("Failed to disable GDB client connection read interrupts - epoll_ctl failed");
}
this->readInterruptEnabled = false;
}
void Connection::enableReadInterrupts() {
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
struct epoll_event event = {};
event.events = EPOLLIN;
event.data.fd = interruptFileDescriptor;
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
throw Exception("Failed to enable GDB client connection read interrupts - epoll_ctl failed");
}
this->readInterruptEnabled = true;
}
void Connection::close() noexcept {
if (this->socketFileDescriptor > 0) {
::close(this->socketFileDescriptor);
this->socketFileDescriptor = 0;
}
}
void Connection::write(const std::vector<unsigned char>& buffer) {
Logger::debug("Writing packet: " + std::string(buffer.begin(), buffer.end()));
if (::write(this->socketFileDescriptor, buffer.data(), buffer.size()) == -1) {
if (errno == EPIPE || errno == ECONNRESET) {
// Connection was closed
throw ClientDisconnected();
} else {
throw ClientCommunicationError("Failed to write " + std::to_string(buffer.size())
+ " bytes to GDP client socket - error no: " + std::to_string(errno));
}
}
}
void Connection::writePacket(const ResponsePacket& packet) {
// Write the packet repeatedly until the GDB client acknowledges it.
int attempts = 0;
auto rawPacket = packet.toRawPacket();
do {
if (attempts > 10) {
throw ClientCommunicationError("Failed to write GDB response packet - client failed to "
"acknowledge receipt - retry limit reached");
}
this->write(rawPacket);
attempts++;
} while(this->readSingleByte(false).value_or(0) != '+');
}
std::vector<unsigned char> Connection::read(size_t bytes, bool interruptible, std::optional<int> msTimeout) {
auto output = std::vector<unsigned char>();
constexpr size_t bufferSize = 1024;
std::array<unsigned char, bufferSize> buffer;
ssize_t bytesRead;
if (interruptible) {
if (this->readInterruptEnabled != interruptible) {
this->enableReadInterrupts();
} else {
// Clear any previous interrupts that are still hanging around
this->interruptEventNotifier->clear();
}
}
if (this->readInterruptEnabled != interruptible && !interruptible) {
this->disableReadInterrupts();
}
std::array<struct epoll_event, 1> events = {};
int eventCount = ::epoll_wait(
this->eventFileDescriptor,
events.data(),
1,
msTimeout.value_or(-1)
);
if (eventCount > 0) {
for (size_t i = 0; i < eventCount; i++) {
auto fileDescriptor = events[i].data.fd;
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
// Interrupted
this->interruptEventNotifier->clear();
throw DebugServerInterrupted();
}
}
size_t bytesToRead = (bytes > bufferSize || bytes == 0) ? bufferSize : bytes;
while (bytesToRead > 0 && (bytesRead = ::read(this->socketFileDescriptor, buffer.data(), bytesToRead)) > 0) {
output.insert(output.end(), buffer.begin(), buffer.begin() + bytesRead);
if (bytesRead < bytesToRead) {
// No more data available
break;
}
bytesToRead = ((bytes - output.size()) > bufferSize || bytes == 0) ? bufferSize : (bytes - output.size());
}
if (output.empty()) {
// EOF means the client has disconnected
throw ClientDisconnected();
}
}
return output;
}
std::optional<unsigned char> Connection::readSingleByte(bool interruptible) {
auto bytes = this->read(1, interruptible, 300);
if (!bytes.empty()) {
return bytes.front();
}
return std::nullopt;
}
std::vector<std::unique_ptr<CommandPacket>> Connection::readPackets() {
auto buffer = this->read();
Logger::debug("GDB client data received (" + std::to_string(buffer.size()) + " bytes): " + std::string(buffer.begin(), buffer.end()));
auto rawPackets = CommandPacketFactory::extractRawPackets(buffer);
std::vector<std::unique_ptr<CommandPacket>> output;
for (const auto& rawPacket : rawPackets) {
try {
output.push_back(CommandPacketFactory::create(rawPacket));
this->write({'+'});
} catch (const ClientDisconnected& exception) {
throw exception;
} catch (const Exception& exception) {
Logger::error("Failed to parse GDB packet - " + exception.getMessage());
this->write({'-'});
}
}
return output;
}

View File

@@ -0,0 +1,137 @@
#pragma once
#include <cstdint>
#include <vector>
#include <netinet/in.h>
#include <queue>
#include <array>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "src/Helpers/EventNotifier.hpp"
#include "src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp"
#include "src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp"
namespace Bloom::DebugServers::Gdb
{
using namespace CommandPackets;
using namespace ResponsePackets;
/**
* The Connection class represents an active connection between the GDB RSP server and client.
*
* All interfacing with the GDB client should take place here.
*/
class Connection
{
private:
int socketFileDescriptor = -1;
int eventFileDescriptor = -1;
struct sockaddr_in socketAddress = {};
int maxPacketSize = 1024;
/**
* The interruptEventNotifier allows us to interrupt blocking IO calls on the GDB debug server.
* Under the hood, this is just a wrapper for a Linux event notifier. See the EventNotifier class for more.
*/
std::shared_ptr<EventNotifier> interruptEventNotifier = nullptr;
bool readInterruptEnabled = false;
/**
* Reads data from the client into a raw buffer.
*
* @param bytes
* Number of bytes to read.
*
* @param interruptible
* If this flag is set to false, no other component within Bloom will be able to gracefully interrupt
* the read (via means of this->interruptEventNotifier). This flag has no affect if this->readInterruptEnabled
* is false.
*
* @param msTimeout
* The timeout in milliseconds. If not supplied, no timeout will be applied.
*
* @return
*/
std::vector<unsigned char> read(std::size_t bytes = 0, bool interruptible = true, std::optional<int> msTimeout = std::nullopt);
/**
* Does the same as Connection::read(), but only reads a single byte.
*
* @param interruptible
* See Connection::read().
*
* @return
*/
std::optional<unsigned char> readSingleByte(bool interruptible = true);
/**
* Writes data from a raw buffer to the client connection.
*
* @param buffer
*/
void write(const std::vector<unsigned char>& buffer);
void disableReadInterrupts();
void enableReadInterrupts();
public:
/**
* When the GDB client is waiting for the target to reach a breakpoint, this is set to true so we know when to
* notify the client.
*
* @TODO: This is pretty gross. Consider rethinking it.
*/
bool waitingForBreak = false;
Connection(std::shared_ptr<EventNotifier> interruptEventNotifier)
: interruptEventNotifier(interruptEventNotifier) {};
/**
* Accepts a connection on serverSocketFileDescriptor.
*
* @param serverSocketFileDescriptor
*/
void accept(int serverSocketFileDescriptor);
/**
* Closes the connection with the client.
*/
void close() noexcept;
/**
* Obtains the human readable IP address of the connected client.
*
* @return
*/
std::string getIpAddress() {
std::array<char, INET_ADDRSTRLEN> ipAddress;
if (::inet_ntop(AF_INET, &(socketAddress.sin_addr), ipAddress.data(), INET_ADDRSTRLEN) == nullptr) {
throw Exceptions::Exception("Failed to convert client IP address to text form.");
}
return std::string(ipAddress.data());
};
/**
* Waits for incoming data from the client and returns any received command packets.
*
* @return
*/
std::vector<std::unique_ptr<CommandPacket>> readPackets();
/**
* Sends a response packet to the client.
*
* @param packet
*/
void writePacket(const ResponsePacket& packet);
int getMaxPacketSize() {
return this->maxPacketSize;
}
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "src/Exceptions/Exception.hpp"
namespace Bloom::DebugServers::Gdb::Exceptions
{
using namespace Bloom::Exceptions;
/**
* In the event that communication between the GDB RSP client and Bloom fails, a ClientCommunicationFailure
* exception should be thrown. The GDB debug server handles this by severing the connection.
*
* See GdbRspDebugServer::serve() for handling code.
*/
class ClientCommunicationError: public Exception
{
public:
explicit ClientCommunicationError(const std::string& message) : Exception(message) {
this->message = message;
}
explicit ClientCommunicationError(const char* message) : Exception(message) {
this->message = std::string(message);
}
explicit ClientCommunicationError() = default;
};
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "src/Exceptions/Exception.hpp"
namespace Bloom::DebugServers::Gdb::Exceptions
{
using namespace Bloom::Exceptions;
/**
* When a GDB RSP client unexpectedly drops the connection in the middle of an IO operation, a ClientDisconnected
* exception should be thrown. The GDB debug server handles this by clearing the connection and waiting for a new
* one.
*
* See GdbRspDebugServer::serve() for handling code.
*/
class ClientDisconnected: public Exception
{
public:
explicit ClientDisconnected(const std::string& message) : Exception(message) {
this->message = message;
}
explicit ClientDisconnected(const char* message) : Exception(message) {
this->message = std::string(message);
}
explicit ClientDisconnected() = default;
};
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "src/Exceptions/Exception.hpp"
namespace Bloom::DebugServers::Gdb::Exceptions
{
using namespace Bloom::Exceptions;
/**
* In the event that the GDB debug server determines that the connected client cannot be served,
* the ClientNotSupported exception should be thrown.
*
* See GdbRspDebugServer::serve() for handling code.
*/
class ClientNotSupported: public Exception
{
public:
explicit ClientNotSupported(const std::string& message) : Exception(message) {
this->message = message;
}
explicit ClientNotSupported(const char* message) : Exception(message) {
this->message = std::string(message);
}
explicit ClientNotSupported() = default;
};
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "src/Helpers/BiMap.hpp"
namespace Bloom::DebugServers::Gdb
{
enum class Feature: int
{
SOFTWARE_BREAKPOINTS,
HARDWARE_BREAKPOINTS,
PACKET_SIZE,
MEMORY_MAP_READ,
};
static inline BiMap<Feature, std::string> getGdbFeatureToNameMapping() {
return BiMap<Feature, std::string>{
{Feature::HARDWARE_BREAKPOINTS, "hwbreak"},
{Feature::SOFTWARE_BREAKPOINTS, "swbreak"},
{Feature::PACKET_SIZE, "PacketSize"},
{Feature::MEMORY_MAP_READ, "qXfer:memory-map:read"},
};
}
}

View File

@@ -0,0 +1,404 @@
#include <sys/socket.h>
#include <sys/epoll.h>
#include <cstdint>
#include "GdbRspDebugServer.hpp"
#include "Exceptions/ClientDisconnected.hpp"
#include "Exceptions/ClientNotSupported.hpp"
#include "Exceptions/ClientCommunicationError.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Exceptions/InvalidConfig.hpp"
#include "src/Logger/Logger.hpp"
using namespace Bloom::DebugServers::Gdb;
using namespace Exceptions;
void GdbRspDebugServer::init() {
auto ipAddress = this->debugServerConfig.jsonObject.find("ipAddress")->toString().toStdString();
auto port = static_cast<std::uint16_t>(this->debugServerConfig.jsonObject.find("port")->toInt());
if (!ipAddress.empty()) {
this->listeningAddress = ipAddress;
}
if (port > 0) {
this->listeningPortNumber = port;
}
this->socketAddress.sin_family = AF_INET;
this->socketAddress.sin_port = htons(this->listeningPortNumber);
if (::inet_pton(AF_INET, this->listeningAddress.c_str(), &(this->socketAddress.sin_addr)) == 0) {
// Invalid IP address
throw InvalidConfig("Invalid IP address provided in config file: (\"" + this->listeningAddress + "\")");
}
int socketFileDescriptor;
if ((socketFileDescriptor = ::socket(AF_INET, SOCK_STREAM, 0)) == 0) {
throw Exception("Failed to create socket file descriptor.");
}
if (::setsockopt(
socketFileDescriptor,
SOL_SOCKET,
SO_REUSEADDR,
&this->enableReuseAddressSocketOption,
sizeof(this->enableReuseAddressSocketOption)) < 0
) {
Logger::error("Failed to set socket SO_REUSEADDR option.");
}
if (::bind(
socketFileDescriptor,
reinterpret_cast<const sockaddr*>(&(this->socketAddress)),
sizeof(this->socketAddress)
) < 0
) {
throw Exception("Failed to bind address.");
}
this->serverSocketFileDescriptor = socketFileDescriptor;
this->eventFileDescriptor = ::epoll_create(2);
struct epoll_event event = {};
event.events = EPOLLIN;
event.data.fd = this->serverSocketFileDescriptor;
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->serverSocketFileDescriptor, &event) != 0) {
throw Exception("Failed epoll_ctl server socket");
}
if (this->interruptEventNotifier != nullptr) {
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
event.events = EPOLLIN;
event.data.fd = interruptFileDescriptor;
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
throw Exception("Failed epoll_ctl interrupt event fd");
}
}
Logger::info("GDB RSP address: " + this->listeningAddress);
Logger::info("GDB RSP port: " + std::to_string(this->listeningPortNumber));
this->eventListener->registerCallbackForEventType<Events::TargetExecutionStopped>(
std::bind(&GdbRspDebugServer::onTargetExecutionStopped, this, std::placeholders::_1)
);
}
void GdbRspDebugServer::serve() {
try {
if (!this->clientConnection.has_value()) {
Logger::info("Waiting for GDB RSP connection");
this->waitForConnection();
}
auto packets = this->clientConnection->readPackets();
for (auto& packet : packets) {
// Double-dispatch to appropriate handler
packet->dispatchToHandler(*this);
}
} catch (const ClientDisconnected&) {
Logger::info("GDB RSP client disconnected");
this->closeClientConnection();
return;
} catch (const ClientCommunicationError& exception) {
Logger::error("GDB client communication error - " + exception.getMessage() + " - closing connection");
this->closeClientConnection();
return;
} catch (const ClientNotSupported& exception) {
Logger::error("Invalid GDB client - " + exception.getMessage() + " - closing connection");
this->closeClientConnection();
return;
} catch (const DebugServerInterrupted&) {
// Server was interrupted
Logger::debug("GDB RSP interrupted");
return;
}
}
void GdbRspDebugServer::waitForConnection() {
if (::listen(this->serverSocketFileDescriptor, 3) != 0) {
throw Exception("Failed to listen on server socket");
}
std::array<struct epoll_event, 5> events = {};
int eventCount = ::epoll_wait(
this->eventFileDescriptor,
events.data(),
5,
-1
);
if (eventCount > 0) {
for (size_t i = 0; i < eventCount; i++) {
auto fileDescriptor = events[i].data.fd;
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
// Interrupted
this->interruptEventNotifier->clear();
throw DebugServerInterrupted();
}
}
this->clientConnection = Connection(this->interruptEventNotifier);
this->clientConnection->accept(this->serverSocketFileDescriptor);
Logger::info("Accepted GDP RSP connection from " + this->clientConnection->getIpAddress());
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionStarted>());
} else {
// This method should not return until a connection has been established (or an exception is thrown)
return this->waitForConnection();
}
}
void GdbRspDebugServer::close() {
this->closeClientConnection();
if (this->serverSocketFileDescriptor > 0) {
::close(this->serverSocketFileDescriptor);
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPacket& packet) {
auto packetData = packet.getData();
auto packetString = std::string(packetData.begin(), packetData.end());
if (packetString[0] == '?') {
// Status report
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
} else if (packetString.find("qAttached") == 0) {
Logger::debug("Handling qAttached");
this->clientConnection->writePacket(ResponsePacket({1}));
} else {
Logger::debug("Unknown GDB RSP packet: " + packetString + " - returning empty response");
// Respond with an empty packet
this->clientConnection->writePacket(ResponsePacket({0}));
}
}
void GdbRspDebugServer::onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>) {
if (this->clientConnection.has_value() && this->clientConnection->waitingForBreak) {
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
this->clientConnection->waitingForBreak = false;
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet) {
Logger::debug("Handling QuerySupport packet");
if (!packet.isFeatureSupported(Feature::HARDWARE_BREAKPOINTS)
&& !packet.isFeatureSupported(Feature::SOFTWARE_BREAKPOINTS)) {
// All GDB clients are expected to support breakpoints!
throw ClientNotSupported("GDB client does not support HW or SW breakpoints");
}
// Respond with a SupportedFeaturesResponse packet, listing all supported GDB features by Bloom
auto response = ResponsePackets::SupportedFeaturesResponse({
{Feature::SOFTWARE_BREAKPOINTS, std::nullopt},
{Feature::PACKET_SIZE, std::to_string(this->clientConnection->getMaxPacketSize())},
});
this->clientConnection->writePacket(response);
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet) {
Logger::debug("Handling ReadGeneralRegisters packet");
try {
auto descriptors = TargetRegisterDescriptors();
if (packet.registerNumber.has_value()) {
Logger::debug("Reading register number: " + std::to_string(packet.registerNumber.value()));
descriptors.push_back(this->getRegisterDescriptorFromNumber(packet.registerNumber.value()));
} else {
// Read all descriptors
auto descriptorMapping = this->getRegisterNumberToDescriptorMapping();
for (auto& descriptor : descriptorMapping.getMap()) {
descriptors.push_back(descriptor.second);
}
}
auto registerSet = this->readGeneralRegistersFromTarget(descriptors);
auto registerNumberToDescriptorMapping = this->getRegisterNumberToDescriptorMapping();
/*
* Remove any registers that are not mapped to GDB register numbers (as we won't know where to place
* them in our response to GDB). All registers that are expected from the GDB client should be mapped
* to register numbers.
*
* Registers that are not mapped to a GDB register number are presumed to be unknown to GDB, so GDB shouldn't
* complain about not receiving them.
*/
registerSet.erase(
std::remove_if(
registerSet.begin(),
registerSet.end(),
[&registerNumberToDescriptorMapping](const TargetRegister& reg) {
return !registerNumberToDescriptorMapping.contains(reg.descriptor);
}
),
registerSet.end()
);
/*
* Sort each register by their respective GDB register number - this will leave us with a collection of
* registers in the order expected by the GDB client.
*/
std::sort(
registerSet.begin(),
registerSet.end(),
[this, &registerNumberToDescriptorMapping](const TargetRegister& registerA, const TargetRegister& registerB) {
return registerNumberToDescriptorMapping.valueAt(registerA.descriptor) <
registerNumberToDescriptorMapping.valueAt(registerB.descriptor);
}
);
/*
* Finally, implode the register values, convert to hexadecimal form and send to the GDB client.
*/
auto registers = std::vector<unsigned char>();
for (const auto& reg : registerSet) {
registers.insert(registers.end(), reg.value.begin(), reg.value.end());
}
auto responseRegisters = Packet::dataToHex(registers);
this->clientConnection->writePacket(
ResponsePacket(std::vector<unsigned char>(responseRegisters.begin(), responseRegisters.end()))
);
} catch (const Exception& exception) {
Logger::error("Failed to read general registers - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet) {
Logger::debug("Handling WriteGeneralRegisters packet");
try {
auto registerDescriptor = this->getRegisterDescriptorFromNumber(packet.registerNumber);
this->writeGeneralRegistersToTarget({TargetRegister(registerDescriptor, packet.registerValue)});
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
} catch (const Exception& exception) {
Logger::error("Failed to write general registers - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ContinueExecution& packet) {
Logger::debug("Handling ContinueExecution packet");
try {
this->continueTargetExecution(packet.fromProgramCounter);
this->clientConnection->waitingForBreak = true;
} catch (const Exception& exception) {
Logger::error("Failed to continue execution on target - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::StepExecution& packet) {
Logger::debug("Handling StepExecution packet");
try {
this->stepTargetExecution(packet.fromProgramCounter);
this->clientConnection->waitingForBreak = true;
} catch (const Exception& exception) {
Logger::error("Failed to step execution on target - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadMemory& packet) {
Logger::debug("Handling ReadMemory packet");
try {
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
auto memoryBuffer = this->readMemoryFromTarget(memoryType, startAddress, packet.bytes);
auto hexMemoryBuffer = Packet::dataToHex(memoryBuffer);
this->clientConnection->writePacket(
ResponsePacket(std::vector<unsigned char>(hexMemoryBuffer.begin(), hexMemoryBuffer.end()))
);
} catch (const Exception& exception) {
Logger::error("Failed to read memory from target - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteMemory& packet) {
Logger::debug("Handling WriteMemory packet");
try {
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
this->writeMemoryToTarget(memoryType, startAddress, packet.buffer);
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
} catch (const Exception& exception) {
Logger::error("Failed to write memory two target - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SetBreakpoint& packet) {
Logger::debug("Handling SetBreakpoint packet");
try {
auto breakpoint = TargetBreakpoint();
breakpoint.address = packet.address;
this->setBreakpointOnTarget(breakpoint);
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
} catch (const Exception& exception) {
Logger::error("Failed to set breakpoint on target - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::RemoveBreakpoint& packet) {
Logger::debug("Removing breakpoint at address " + std::to_string(packet.address));
try {
auto breakpoint = TargetBreakpoint();
breakpoint.address = packet.address;
this->removeBreakpointOnTarget(breakpoint);
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
} catch (const Exception& exception) {
Logger::error("Failed to remove breakpoint on target - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}
void GdbRspDebugServer::handleGdbPacket(CommandPackets::InterruptExecution& packet) {
Logger::debug("Handling InterruptExecution packet");
try {
this->stopTargetExecution();
this->clientConnection->writePacket(TargetStopped(Signal::INTERRUPTED));
} catch (const Exception& exception) {
Logger::error("Failed to interrupt execution - " + exception.getMessage());
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
}
}

View File

@@ -0,0 +1,257 @@
#pragma once
#include <netinet/in.h>
#include <arpa/inet.h>
#include <vector>
#include <queue>
#include <cstdint>
#include "../DebugServer.hpp"
#include "Connection.hpp"
#include "Signal.hpp"
#include "Register.hpp"
#include "Feature.hpp"
#include "src/Helpers/EventNotifier.hpp"
#include "src/Helpers/BiMap.hpp"
#include "CommandPackets/CommandPacketFactory.hpp"
#include "src/Targets/TargetRegister.hpp"
// Response packets
#include "ResponsePackets/SupportedFeaturesResponse.hpp"
#include "ResponsePackets/TargetStopped.hpp"
namespace Bloom::DebugServers::Gdb
{
using Bloom::Targets::TargetRegisterType;
using Bloom::Targets::TargetRegisterDescriptor;
/**
* The GdbRspDebugServer is an implementation of a GDB server using the GDB Remote Serial Protocol.
*
* This DebugServer employs TCP/IP sockets to interface with GDB clients. The listening address can be configured
* in the user's project config file.
*
* See https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html for more info on the GDB Remote
* Serial Protocol.
*
* @TODO: This could do with some cleaning.
*/
class GdbRspDebugServer: public DebugServer
{
protected:
/**
* The port number for the GDB server to listen on.
*
* This will be pulled from the user's project configuration, if set. Otherwise it will default to whatever is
* set here.
*/
std::uint16_t listeningPortNumber = 1055;
/**
* The address for the GDB server to listen on.
*
* Like the port number, this can also be pulled from the user's project configuration.
*/
std::string listeningAddress = "127.0.0.1";
/**
* Listening socket address
*/
struct sockaddr_in socketAddress = {};
/**
* Listening socket file descriptor
*/
int serverSocketFileDescriptor = -1;
/**
* We don't listen on the this->serverSocketFileDescriptor directly. Instead, we add it to an epoll set, along
* with the this->interruptEventNotifier FD. This allows us to interrupt any blocking socket IO calls when
* we have other things to do.
*
* See GdbRspDebugServer::init()
* See DebugServer::interruptEventNotifier
* See EventNotifier
*/
int eventFileDescriptor = -1;
/**
* SO_REUSEADDR option value for listening socket.
*/
int enableReuseAddressSocketOption = 1;
/**
* The current active GDB client connection, if any.
*/
std::optional<Connection> clientConnection;
/**
* Prepares the GDB server for listing on the selected address and port.
*/
void init() override;
/**
* Closes any client connection as well as the listening socket file descriptor.
*/
void close() override;
/**
* See DebugServer::serve()
*/
void serve() override;
/**
* Waits for a GDB client to connect on the listening socket. Accepts the connection and
* sets this->clientConnection.
*/
void waitForConnection();
void closeClientConnection() {
if (this->clientConnection.has_value()) {
this->clientConnection->close();
this->clientConnection = std::nullopt;
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionFinished>());
}
}
/**
* GDB clients encode memory type information (flash, ram, eeprom, etc) in memory addresses. This is typically
* hardcoded in the GDB client source. This method extracts memory type information from a given memory address.
* The specifics of the encoding may vary with targets, which is why this method is virtual. For an example,
* see the implementation of this method in AvrGdbRsp.
*
* @param address
* @return
*/
virtual TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) = 0;
/**
* Removes memory type information from memory address.
* See comment for GdbRspDebugServer::getMemoryTypeFromGdbAddress()
*
* @param address
* @return
*/
virtual std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) = 0;
/**
* Like with the method of encoding memory type information onto memory addresses, GDB clients also expect
* a pre-defined set of registers. The defined set being dependant on the target. This is hardcoded in the the
* GDB client source. The order of the registers is also pre-defined in the GDB client.
*
* For an example, see the implementation of this method in the AvrGdbRsp class.
*
* @return
*/
virtual BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() = 0;
/**
* Obtains the appropriate register descriptor from a register number.
*
* @param number
* @return
*/
virtual TargetRegisterDescriptor getRegisterDescriptorFromNumber(GdbRegisterNumber number) {
auto mapping = this->getRegisterNumberToDescriptorMapping();
if (!mapping.contains(number)) {
throw Exception("Unknown register from GDB - register number (" + std::to_string(number)
+ ") not mapped to any register descriptor.");
}
return mapping.valueAt(number).value();
}
public:
GdbRspDebugServer(EventManager& eventManager) : DebugServer(eventManager) {};
std::string getName() const override {
return "GDB Remote Serial Protocol DebugServer";
};
/**
* If the GDB client is currently waiting for the target execution to stop, this event handler will issue
* a "stop reply" packet to the client once the target execution stops.
*/
void onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>);
/**
* Handles any other GDB command packet that has not been promoted to a more specific type.
* This would be packets like "?" and "qAttached".
*
* @param packet
*/
virtual void handleGdbPacket(CommandPacket& packet);
/**
* Handles the supported features query ("qSupported") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet);
/**
* Handles the read registers ("g" and "p") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet);
/**
* Handles the write general registers ("G" and "P") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet);
/**
* Handles the continue execution ("c") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::ContinueExecution& packet);
/**
* Handles the step execution ("s") packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::StepExecution& packet);
/**
* Handles the read memory ("m") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::ReadMemory& packet);
/**
* Handles the write memory ("M") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::WriteMemory& packet);
/**
* Handles the set breakpoint ("Z") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::SetBreakpoint& packet);
/**
* Handles the remove breakpoint ("z") command packet.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::RemoveBreakpoint& packet);
/**
* Handles the interrupt command packet.
* Will attempt to halt execution on the target. Should respond with a "stop reply" packet, or an error code.
*
* @param packet
*/
virtual void handleGdbPacket(CommandPackets::InterruptExecution& packet);
};
}

View File

@@ -0,0 +1,123 @@
#pragma once
#include <vector>
#include <memory>
#include <numeric>
#include <QString>
#include <sstream>
#include <iomanip>
namespace Bloom::DebugServers::Gdb
{
/**
* The Packet class implements the data structure for GDB RSP packets.
*
* Fore more information on the packet data structure, see https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
*/
class Packet
{
protected:
std::vector<unsigned char> data;
void init(const std::vector<unsigned char>& rawPacket) {
this->data.insert(
this->data.begin(),
rawPacket.begin() + 1,
rawPacket.end() - 3
);
}
public:
Packet() = default;
Packet(const std::vector<unsigned char>& rawPacket) {
this->init(rawPacket);
}
virtual std::vector<unsigned char> getData() const {
return this->data;
}
void setData(const std::vector<unsigned char>& data) {
this->data = data;
}
/**
* Generates a raw packet.
*
* @return
*/
std::vector<unsigned char> toRawPacket() const {
std::vector<unsigned char> packet = {'$'};
auto data = this->getData();
for (const auto& byte : data) {
// Escape $ and # characters
switch (byte) {
case '$':
case '#': {
packet.push_back('}');
packet.push_back(byte ^ 0x20);
}
default: {
packet.push_back(byte);
}
}
}
auto dataSum = std::accumulate(packet.begin() + 1, packet.end(), 0);
packet.push_back('#');
auto checkSum = QStringLiteral("%1").arg(dataSum % 256, 2, 16, QLatin1Char('0')).toStdString();
if (checkSum.size() < 2) {
packet.push_back('0');
if (checkSum.size() < 1) {
packet.push_back('0');
} else {
packet.push_back(static_cast<unsigned char>(checkSum[0]));
}
}
packet.push_back(static_cast<unsigned char>(checkSum[0]));
packet.push_back(static_cast<unsigned char>(checkSum[1]));
return packet;
}
/**
* Converts raw data to hexadecimal form, the form in which responses are expected to be delivered from the
* server.
*
* @param data
* @return
*/
static std::string dataToHex(std::vector<unsigned char> data) {
std::stringstream stream;
stream << std::hex << std::setfill('0');
for (const auto& byte : data) {
stream << std::setw(2) << static_cast<unsigned int>(byte);
}
return stream.str();
}
/**
* Converts data in hexadecimal form to raw data.
*
* @param hexData
* @return
*/
static std::vector<unsigned char> hexToData(const std::string& hexData) {
std::vector<unsigned char> output;
for (auto i = 0; i < hexData.size(); i += 2) {
auto hexByte = std::string((hexData.begin() + i), (hexData.begin() + i + 2));
output.push_back(static_cast<unsigned char>(std::stoi(hexByte, nullptr, 16)));
}
return output;
}
virtual ~Packet() = default;
};
}

View File

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

View File

@@ -0,0 +1,25 @@
#pragma once
#include <set>
#include "ResponsePacket.hpp"
namespace Bloom::DebugServers::Gdb {
enum class Feature;
}
namespace Bloom::DebugServers::Gdb::ResponsePackets
{
/**
* OK response packet expected by the GDB client, in response to certain commands.
*/
class Ok: public ResponsePacket
{
public:
Ok() = default;
std::vector<unsigned char> getData() const override {
return {'O', 'K'};
}
};
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include <memory>
#include "src/DebugServers/GdbRsp/Packet.hpp"
namespace Bloom::DebugServers::Gdb::ResponsePackets
{
/**
* Upon receiving a CommandPacket from the connected GDB RSP client, the server is expected to respond with a
* response packet.
*/
class ResponsePacket: public Packet
{
public:
ResponsePacket() = default;
explicit ResponsePacket(const std::vector<unsigned char>& data) {
this->data = data;
}
};
}

View File

@@ -0,0 +1,23 @@
#include "SupportedFeaturesResponse.hpp"
using namespace Bloom::DebugServers::Gdb::ResponsePackets;
std::vector<unsigned char> SupportedFeaturesResponse::getData() const {
std::string output = "qSupported:";
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
for (const auto& supportedFeature : this->supportedFeatures) {
auto featureString = gdbFeatureMapping.valueAt(supportedFeature.first);
if (featureString.has_value()) {
if (supportedFeature.second.has_value()) {
output.append(featureString.value() + "=" + supportedFeature.second.value() + ";");
} else {
output.append(featureString.value() + "+;");
}
}
}
return std::vector<unsigned char>(output.begin(), output.end());
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <set>
#include "ResponsePacket.hpp"
#include "../Feature.hpp"
namespace Bloom::DebugServers::Gdb::ResponsePackets
{
/**
* The SupportedFeaturesResponse class implements the response packet structure for the "qSupported" command.
*/
class SupportedFeaturesResponse: public ResponsePacket
{
protected:
std::set<std::pair<Feature, std::optional<std::string>>> supportedFeatures;
public:
SupportedFeaturesResponse() = default;
SupportedFeaturesResponse(const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures)
: supportedFeatures(supportedFeatures) {};
std::vector<unsigned char> getData() const override;
};
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include "ResponsePacket.hpp"
#include "../Signal.hpp"
#include "../StopReason.hpp"
#include "src/Targets/TargetRegister.hpp"
namespace Bloom::DebugServers::Gdb::ResponsePackets
{
using Bloom::Targets::TargetRegisterMap;
/**
* The TargetStopped class implements the response packet structure for any commands that expect a "StopReply"
* packet in response.
*/
class TargetStopped: public ResponsePacket
{
public:
Signal signal;
std::optional<TargetRegisterMap> registerMap;
std::optional<StopReason> stopReason;
TargetStopped(Signal signal) : signal(signal) {}
std::vector<unsigned char> getData() const override {
std::string output = "T" + this->dataToHex({static_cast<unsigned char>(this->signal)});
if (this->stopReason.has_value()) {
auto stopReasonMapping = getStopReasonToNameMapping();
auto stopReasonName = stopReasonMapping.valueAt(this->stopReason.value());
if (stopReasonName.has_value()) {
output += stopReasonName.value() + ":;";
}
}
if (this->registerMap.has_value()) {
for (const auto& [registerId, registerValue] : this->registerMap.value()) {
output += this->dataToHex({static_cast<unsigned char>(registerId)});
output += ":" + this->dataToHex(registerValue.value) + ";";
}
}
return std::vector<unsigned char>(output.begin(), output.end());
}
};
}

View File

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

View File

@@ -0,0 +1,19 @@
#pragma once
#include "src/Helpers/BiMap.hpp"
namespace Bloom::DebugServers::Gdb
{
enum class StopReason: int
{
SOFTWARE_BREAKPOINT = 0,
HARDWARE_BREAKPOINT = 1,
};
static inline BiMap<StopReason, std::string> getStopReasonToNameMapping() {
return BiMap<StopReason, std::string>({
{StopReason::HARDWARE_BREAKPOINT, "hwbreak"},
{StopReason::SOFTWARE_BREAKPOINT, "swbreak"},
});
}
}

View File

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

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