Initial commit
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal 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
222
CMakeLists.txt
Executable 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
165
COPYING.LESSER
Normal 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
12
LICENSE.txt
Normal 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
51
README.md
Normal 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.
|
||||
5
ThreadSanitizerSuppression.txt
Normal file
5
ThreadSanitizerSuppression.txt
Normal 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
BIN
build/bin/platforms/libqxcb.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/iconengines/libqsvgicon.so
Executable file
BIN
build/bin/plugins/iconengines/libqsvgicon.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqgif.so
Executable file
BIN
build/bin/plugins/imageformats/libqgif.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqicns.so
Executable file
BIN
build/bin/plugins/imageformats/libqicns.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqico.so
Executable file
BIN
build/bin/plugins/imageformats/libqico.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqjpeg.so
Executable file
BIN
build/bin/plugins/imageformats/libqjpeg.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqsvg.so
Executable file
BIN
build/bin/plugins/imageformats/libqsvg.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqtga.so
Executable file
BIN
build/bin/plugins/imageformats/libqtga.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqtiff.so
Executable file
BIN
build/bin/plugins/imageformats/libqtiff.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqwbmp.so
Executable file
BIN
build/bin/plugins/imageformats/libqwbmp.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/imageformats/libqwebp.so
Executable file
BIN
build/bin/plugins/imageformats/libqwebp.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/xcbglintegrations/libqxcb-egl-integration.so
Executable file
BIN
build/bin/plugins/xcbglintegrations/libqxcb-egl-integration.so
Executable file
Binary file not shown.
BIN
build/bin/plugins/xcbglintegrations/libqxcb-glx-integration.so
Executable file
BIN
build/bin/plugins/xcbglintegrations/libqxcb-glx-integration.so
Executable file
Binary file not shown.
1
build/lib/libQt5Core.so.5
Symbolic link
1
build/lib/libQt5Core.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5Core.so.5.12.10
|
||||
BIN
build/lib/libQt5Core.so.5.12.10
Normal file
BIN
build/lib/libQt5Core.so.5.12.10
Normal file
Binary file not shown.
1
build/lib/libQt5DBus.so.5
Symbolic link
1
build/lib/libQt5DBus.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5DBus.so.5.12.10
|
||||
BIN
build/lib/libQt5DBus.so.5.12.10
Executable file
BIN
build/lib/libQt5DBus.so.5.12.10
Executable file
Binary file not shown.
1
build/lib/libQt5Gui.so.5
Symbolic link
1
build/lib/libQt5Gui.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5Gui.so.5.12.10
|
||||
BIN
build/lib/libQt5Gui.so.5.12.10
Normal file
BIN
build/lib/libQt5Gui.so.5.12.10
Normal file
Binary file not shown.
1
build/lib/libQt5Svg.so.5
Symbolic link
1
build/lib/libQt5Svg.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5Svg.so.5.12.10
|
||||
BIN
build/lib/libQt5Svg.so.5.12.10
Executable file
BIN
build/lib/libQt5Svg.so.5.12.10
Executable file
Binary file not shown.
1
build/lib/libQt5Widgets.so.5
Symbolic link
1
build/lib/libQt5Widgets.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5Widgets.so.5.12.10
|
||||
BIN
build/lib/libQt5Widgets.so.5.12.10
Normal file
BIN
build/lib/libQt5Widgets.so.5.12.10
Normal file
Binary file not shown.
1
build/lib/libQt5XcbQpa.so.5
Symbolic link
1
build/lib/libQt5XcbQpa.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5XcbQpa.so.5.12.10
|
||||
BIN
build/lib/libQt5XcbQpa.so.5.12.10
Normal file
BIN
build/lib/libQt5XcbQpa.so.5.12.10
Normal file
Binary file not shown.
1
build/lib/libQt5Xml.so.5
Symbolic link
1
build/lib/libQt5Xml.so.5
Symbolic link
@@ -0,0 +1 @@
|
||||
./libQt5Xml.so.5.12.10
|
||||
BIN
build/lib/libQt5Xml.so.5.12.10
Normal file
BIN
build/lib/libQt5Xml.so.5.12.10
Normal file
Binary file not shown.
BIN
build/lib/libhidapi-libusb.so.0
Normal file
BIN
build/lib/libhidapi-libusb.so.0
Normal file
Binary file not shown.
1
build/lib/libicudata.so.56
Symbolic link
1
build/lib/libicudata.so.56
Symbolic link
@@ -0,0 +1 @@
|
||||
./libicudata.so.56.1
|
||||
BIN
build/lib/libicudata.so.56.1
Normal file
BIN
build/lib/libicudata.so.56.1
Normal file
Binary file not shown.
1
build/lib/libicui18n.so.56
Symbolic link
1
build/lib/libicui18n.so.56
Symbolic link
@@ -0,0 +1 @@
|
||||
./libicui18n.so.56.1
|
||||
BIN
build/lib/libicui18n.so.56.1
Normal file
BIN
build/lib/libicui18n.so.56.1
Normal file
Binary file not shown.
1
build/lib/libicuuc.so.56
Symbolic link
1
build/lib/libicuuc.so.56
Symbolic link
@@ -0,0 +1 @@
|
||||
./libicuuc.so.56.1
|
||||
BIN
build/lib/libicuuc.so.56.1
Normal file
BIN
build/lib/libicuuc.so.56.1
Normal file
Binary file not shown.
BIN
build/lib/libqxcb.so
Executable file
BIN
build/lib/libqxcb.so
Executable file
Binary file not shown.
1
build/lib/libqxcb.so.1
Symbolic link
1
build/lib/libqxcb.so.1
Symbolic link
@@ -0,0 +1 @@
|
||||
./libqxcb.so
|
||||
BIN
build/lib/libusb-1.0.so.0
Normal file
BIN
build/lib/libusb-1.0.so.0
Normal file
Binary file not shown.
17
build/scripts/CleanBuild.sh
Normal file
17
build/scripts/CleanBuild.sh
Normal 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
|
||||
178
build/scripts/CopyAvrPartFilesAndCreateMapping.php
Normal file
178
build/scripts/CopyAvrPartFilesAndCreateMapping.php
Normal 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";
|
||||
20
resources/bloom.template.json
Normal file
20
resources/bloom.template.json
Normal 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
14
resources/help.txt
Normal 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.
|
||||
1
resources/packaging/description.txt
Normal file
1
resources/packaging/description.txt
Normal 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
15
resources/packaging/postinst
Executable 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
11
resources/packaging/postrm
Executable 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
344
src/Application.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
#include <iostream>
|
||||
#include <csignal>
|
||||
#include <QtCore>
|
||||
#include <thread>
|
||||
#include <QJsonDocument>
|
||||
#include <QCoreApplication>
|
||||
#include <unistd.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "SignalHandler/SignalHandler.hpp"
|
||||
#include "Exceptions/InvalidConfig.hpp"
|
||||
|
||||
using namespace Bloom;
|
||||
using namespace Bloom::Events;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
int Application::run(const std::vector<std::string>& arguments) {
|
||||
try {
|
||||
this->setName("Bloom");
|
||||
|
||||
if (!arguments.empty()) {
|
||||
auto firstArg = arguments.front();
|
||||
auto commandsToCallbackMapping = this->getCommandToCallbackMapping();
|
||||
|
||||
if (commandsToCallbackMapping.contains(firstArg)) {
|
||||
// User has passed an argument that maps to a command callback - invoke the callback and shutdown
|
||||
auto returnValue = commandsToCallbackMapping.at(firstArg)();
|
||||
|
||||
this->shutdown();
|
||||
return returnValue;
|
||||
|
||||
} else {
|
||||
// If the first argument didn't map to a command, we assume it's an environment name
|
||||
this->selectedEnvironmentName = firstArg;
|
||||
}
|
||||
}
|
||||
|
||||
this->startup();
|
||||
|
||||
if (this->insightConfig.insightEnabled) {
|
||||
this->insight.setApplicationConfig(this->applicationConfig);
|
||||
this->insight.setEnvironmentConfig(this->environmentConfig);
|
||||
this->insight.run();
|
||||
Logger::debug("Insight closed");
|
||||
this->shutdown();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Main event loop
|
||||
while (Thread::getState() == ThreadState::READY) {
|
||||
this->applicationEventListener->waitAndDispatch();
|
||||
}
|
||||
|
||||
} catch (const InvalidConfig& exception) {
|
||||
Logger::error(exception.getMessage());
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error(exception.getMessage());
|
||||
}
|
||||
|
||||
this->shutdown();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Application::startup() {
|
||||
auto applicationEventListener = this->applicationEventListener;
|
||||
this->eventManager.registerListener(applicationEventListener);
|
||||
applicationEventListener->registerCallbackForEventType<Events::ShutdownApplication>(
|
||||
std::bind(&Application::handleShutdownApplicationEvent, this, std::placeholders::_1)
|
||||
);
|
||||
|
||||
this->applicationConfig = this->extractConfig();
|
||||
Logger::configure(this->applicationConfig);
|
||||
|
||||
// Start signal handler
|
||||
this->blockAllSignalsOnCurrentThread();
|
||||
this->signalHandlerThread = std::thread(&SignalHandler::run, std::ref(this->signalHandler));
|
||||
|
||||
auto environmentName = this->selectedEnvironmentName.value_or("default");
|
||||
Logger::info("Selected environment: " + environmentName);
|
||||
Logger::debug("Number of environments extracted from config: "
|
||||
+ std::to_string(this->applicationConfig.environments.size()));
|
||||
|
||||
// Validate the selected environment
|
||||
if (!applicationConfig.environments.contains(environmentName)) {
|
||||
throw InvalidConfig("Environment (\"" + environmentName + "\") not found in configuration.");
|
||||
}
|
||||
|
||||
this->environmentConfig = applicationConfig.environments.at(environmentName);
|
||||
this->insightConfig = this->environmentConfig.insightConfig.value_or(this->applicationConfig.insightConfig);
|
||||
|
||||
if (this->environmentConfig.debugServerConfig.has_value()) {
|
||||
this->debugServerConfig = this->environmentConfig.debugServerConfig.value();
|
||||
|
||||
} else if (this->applicationConfig.debugServerConfig.has_value()) {
|
||||
this->debugServerConfig = this->applicationConfig.debugServerConfig.value();
|
||||
|
||||
} else {
|
||||
throw InvalidConfig("Debug server configuration missing.");
|
||||
}
|
||||
|
||||
applicationEventListener->registerCallbackForEventType<Events::TargetControllerStateChanged>(
|
||||
std::bind(&Application::handleTargetControllerStateChangedEvent, this, std::placeholders::_1)
|
||||
);
|
||||
|
||||
applicationEventListener->registerCallbackForEventType<Events::DebugServerStateChanged>(
|
||||
std::bind(&Application::onDebugServerStateChanged, this, std::placeholders::_1)
|
||||
);
|
||||
|
||||
this->startTargetController();
|
||||
this->startDebugServer();
|
||||
|
||||
Thread::setState(ThreadState::READY);
|
||||
}
|
||||
|
||||
ApplicationConfig Application::extractConfig() {
|
||||
auto appConfig = ApplicationConfig();
|
||||
auto currentPath = std::filesystem::current_path().string();
|
||||
auto jsonConfigFile = QFile(QString::fromStdString(currentPath + "/bloom.json"));
|
||||
|
||||
if (!jsonConfigFile.exists()) {
|
||||
throw InvalidConfig("Bloom configuration file (bloom.json) not found. Working directory: "
|
||||
+ currentPath);
|
||||
}
|
||||
|
||||
if (!jsonConfigFile.open(QIODevice::ReadOnly)) {
|
||||
throw InvalidConfig("Failed to load Bloom configuration file (bloom.json) Working directory: "
|
||||
+ currentPath);
|
||||
}
|
||||
|
||||
auto jsonObject = QJsonDocument::fromJson(jsonConfigFile.readAll()).object();
|
||||
appConfig.init(jsonObject);
|
||||
|
||||
jsonConfigFile.close();
|
||||
return appConfig;
|
||||
}
|
||||
|
||||
int Application::presentHelpText() {
|
||||
/*
|
||||
* Silence all logging here, as we're just to display the help text and then exit the application. Any
|
||||
* further logging will just be noise.
|
||||
*/
|
||||
Logger::silence();
|
||||
|
||||
// The file help.txt is included in the binary image as a resource. See src/resource.qrc
|
||||
auto helpFile = QFile(":/compiled/resources/help.txt");
|
||||
|
||||
if (!helpFile.open(QIODevice::ReadOnly)) {
|
||||
// This should never happen - if it does, something has gone very wrong
|
||||
throw Exception("Failed to open help file - please report this issue at https://bloom.oscillate.io/report-issue");
|
||||
}
|
||||
|
||||
std::cout << "Bloom v" << Application::VERSION_STR << std::endl;
|
||||
std::cout << QTextStream(&helpFile).readAll().toUtf8().constData() << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int Application::presentVersionText() {
|
||||
Logger::silence();
|
||||
|
||||
std::cout << "Bloom v" << Application::VERSION_STR << "\n";
|
||||
std::cout << "https://bloom.oscillate.io/" << "\n";
|
||||
std::cout << "Nav Mohammed" << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int Application::initProject() {
|
||||
auto configFile = QFile(QString::fromStdString(std::filesystem::current_path().string() + "/bloom.json"));
|
||||
|
||||
if (configFile.exists()) {
|
||||
throw Exception("Bloom configuration file (bloom.json) already exists in working directory.");
|
||||
}
|
||||
|
||||
/*
|
||||
* The file bloom.template.json is just a template Bloom config file that is included in the binary image as
|
||||
* a resource. See src/resource.qrc
|
||||
*
|
||||
* We simply copy the template file into the user's working directory.
|
||||
*/
|
||||
auto templateConfigFile = QFile(":/compiled/resources/bloom.template.json");
|
||||
|
||||
if (!templateConfigFile.open(QIODevice::ReadOnly)) {
|
||||
throw Exception("Failed to open template configuration file - please report this issue at https://bloom.oscillate.io/report-issue");
|
||||
}
|
||||
|
||||
if (!configFile.open(QIODevice::ReadWrite)) {
|
||||
throw Exception("Failed to create Bloom configuration file (bloom.json)");
|
||||
}
|
||||
|
||||
configFile.write(templateConfigFile.readAll());
|
||||
|
||||
configFile.close();
|
||||
templateConfigFile.close();
|
||||
|
||||
Logger::info("Bloom configuration file (bloom.json) created in working directory.");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
auto appState = Thread::getState();
|
||||
if (appState == ThreadState::STOPPED || appState == ThreadState::SHUTDOWN_INITIATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
Thread::setState(ThreadState::SHUTDOWN_INITIATED);
|
||||
Logger::info("Shutting down Bloom");
|
||||
|
||||
this->stopDebugServer();
|
||||
this->stopTargetController();
|
||||
|
||||
if (this->signalHandler.getState() == ThreadState::READY) {
|
||||
// Signal handler is still running
|
||||
this->signalHandler.triggerShutdown();
|
||||
|
||||
// Send meaningless signal to the SignalHandler thread to have it shutdown.
|
||||
pthread_kill(this->signalHandlerThread.native_handle(), SIGUSR1);
|
||||
}
|
||||
|
||||
if (this->signalHandlerThread.joinable()) {
|
||||
Logger::debug("Joining SignalHandler thread");
|
||||
this->signalHandlerThread.join();
|
||||
Logger::debug("SignalHandler thread joined");
|
||||
}
|
||||
|
||||
Thread::setState(ThreadState::STOPPED);
|
||||
}
|
||||
|
||||
void Application::startTargetController() {
|
||||
this->targetController.setApplicationConfig(this->applicationConfig);
|
||||
this->targetController.setEnvironmentConfig(this->environmentConfig);
|
||||
|
||||
this->targetControllerThread = std::thread(
|
||||
&TargetController::run,
|
||||
std::ref(this->targetController)
|
||||
);
|
||||
|
||||
auto tcStateChangeEvent = this->applicationEventListener->waitForEvent<Events::TargetControllerStateChanged>();
|
||||
|
||||
if (!tcStateChangeEvent.has_value() || tcStateChangeEvent->get()->getState() != ThreadState::READY) {
|
||||
throw Exception("TargetController failed to startup.");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::stopTargetController() {
|
||||
auto targetControllerState = this->targetController.getState();
|
||||
if (targetControllerState == ThreadState::STARTING || targetControllerState == ThreadState::READY) {
|
||||
// this->applicationEventListener->clearEventsOfType(Events::TargetControllerStateChanged::name);
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::ShutdownTargetController>());
|
||||
this->applicationEventListener->waitForEvent<Events::TargetControllerStateChanged>(
|
||||
std::chrono::milliseconds(10000)
|
||||
);
|
||||
}
|
||||
|
||||
if (this->targetControllerThread.joinable()) {
|
||||
Logger::debug("Joining TargetController thread");
|
||||
this->targetControllerThread.join();
|
||||
Logger::debug("TargetController thread joined");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::startDebugServer() {
|
||||
auto supportedDebugServers = this->getSupportedDebugServers();
|
||||
if (!supportedDebugServers.contains(this->debugServerConfig.name)) {
|
||||
throw Exceptions::InvalidConfig("DebugServer \"" + this->debugServerConfig.name + "\" not found.");
|
||||
}
|
||||
|
||||
this->debugServer = supportedDebugServers.at(this->debugServerConfig.name)();
|
||||
this->debugServer->setApplicationConfig(this->applicationConfig);
|
||||
this->debugServer->setEnvironmentConfig(this->environmentConfig);
|
||||
this->debugServer->setDebugServerConfig(this->debugServerConfig);
|
||||
|
||||
Logger::info("Selected DebugServer: " + this->debugServer->getName());
|
||||
|
||||
this->debugServerThread = std::thread(
|
||||
&DebugServer::run,
|
||||
this->debugServer.get()
|
||||
);
|
||||
|
||||
auto dsStateChangeEvent = this->applicationEventListener->waitForEvent<Events::DebugServerStateChanged>();
|
||||
|
||||
if (!dsStateChangeEvent.has_value() || dsStateChangeEvent->get()->getState() != ThreadState::READY) {
|
||||
throw Exception("DebugServer failed to startup.");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::stopDebugServer() {
|
||||
if (this->debugServer == nullptr) {
|
||||
// DebugServer hasn't been resolved yet.
|
||||
return;
|
||||
}
|
||||
|
||||
auto debugServerState = this->debugServer->getState();
|
||||
if (debugServerState == ThreadState::STARTING || debugServerState == ThreadState::READY) {
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::ShutdownDebugServer>());
|
||||
this->applicationEventListener->waitForEvent<Events::DebugServerStateChanged>(
|
||||
std::chrono::milliseconds(5000)
|
||||
);
|
||||
}
|
||||
|
||||
if (this->debugServerThread.joinable()) {
|
||||
Logger::debug("Joining DebugServer thread");
|
||||
this->debugServerThread.join();
|
||||
Logger::debug("DebugServer thread joined");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleTargetControllerStateChangedEvent(EventPointer<Events::TargetControllerStateChanged> event) {
|
||||
if (event->getState() == ThreadState::STOPPED || event->getState() == ThreadState::SHUTDOWN_INITIATED) {
|
||||
// TargetController has unexpectedly shutdown - it must have encountered a fatal error.
|
||||
this->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleShutdownApplicationEvent(EventPointer<Events::ShutdownApplication>) {
|
||||
Logger::debug("ShutdownApplication event received.");
|
||||
this->shutdown();
|
||||
}
|
||||
|
||||
void Application::onDebugServerStateChanged(EventPointer<Events::DebugServerStateChanged> event) {
|
||||
if (event->getState() == ThreadState::STOPPED || event->getState() == ThreadState::SHUTDOWN_INITIATED) {
|
||||
// DebugServer has unexpectedly shutdown - it must have encountered a fatal error.
|
||||
this->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Application::getApplicationDirPath() {
|
||||
auto pathCharArray = std::array<char, PATH_MAX>();
|
||||
|
||||
if (readlink("/proc/self/exe", pathCharArray.data(), PATH_MAX) < 0) {
|
||||
throw Exception("Failed to obtain application directory path.");
|
||||
}
|
||||
|
||||
return std::filesystem::path(std::string(pathCharArray.begin(), pathCharArray.end())).parent_path();
|
||||
}
|
||||
|
||||
std::string Application::getResourcesDirPath() {
|
||||
return Application::getApplicationDirPath() + "/../resources/";
|
||||
}
|
||||
|
||||
bool Application::isRunningAsRoot() {
|
||||
return geteuid() == 0;
|
||||
}
|
||||
183
src/Application.hpp
Normal file
183
src/Application.hpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <QtCore/QtCore>
|
||||
#include <thread>
|
||||
|
||||
#include "src/SignalHandler/SignalHandler.hpp"
|
||||
#include "src/TargetController/TargetController.hpp"
|
||||
#include "src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp"
|
||||
#include "src/Insight/Insight.hpp"
|
||||
#include "src/Helpers/Thread.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/ApplicationConfig.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/EventManager/EventListener.hpp"
|
||||
#include "src/EventManager/Events/Events.hpp"
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
using namespace DebugServers;
|
||||
|
||||
class Application: public Thread
|
||||
{
|
||||
public:
|
||||
static const inline std::string VERSION_STR = "0.0.1";
|
||||
|
||||
private:
|
||||
/**
|
||||
* This is the application wide event manager. It should be the only instance of the EventManager class at
|
||||
* any given time.
|
||||
*
|
||||
* It should be injected (by reference) into any other instances of classes that require use
|
||||
* of the EventManager.
|
||||
*/
|
||||
EventManager eventManager = EventManager();
|
||||
EventListenerPointer applicationEventListener = std::make_shared<EventListener>("ApplicationEventListener");
|
||||
|
||||
SignalHandler signalHandler = SignalHandler(this->eventManager);
|
||||
std::thread signalHandlerThread;
|
||||
|
||||
TargetController targetController = TargetController(this->eventManager);
|
||||
std::thread targetControllerThread;
|
||||
|
||||
std::unique_ptr<DebugServer> debugServer = nullptr;
|
||||
std::thread debugServerThread;
|
||||
|
||||
Insight insight = Insight(this->eventManager);
|
||||
|
||||
ApplicationConfig applicationConfig;
|
||||
EnvironmentConfig environmentConfig;
|
||||
DebugServerConfig debugServerConfig;
|
||||
InsightConfig insightConfig;
|
||||
|
||||
std::optional<std::string> selectedEnvironmentName;
|
||||
|
||||
auto getCommandToCallbackMapping() {
|
||||
return std::map<std::string, std::function<int()>> {
|
||||
{
|
||||
"--help",
|
||||
std::bind(&Application::presentHelpText, this)
|
||||
},
|
||||
{
|
||||
"-h",
|
||||
std::bind(&Application::presentHelpText, this)
|
||||
},
|
||||
{
|
||||
"--version",
|
||||
std::bind(&Application::presentVersionText, this)
|
||||
},
|
||||
{
|
||||
"-v",
|
||||
std::bind(&Application::presentVersionText, this)
|
||||
},
|
||||
{
|
||||
"init",
|
||||
std::bind(&Application::initProject, this)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts config from the user's JSON config file and generates an ApplicationConfig object.
|
||||
*
|
||||
* @see ApplicationConfig declaration for more on this.
|
||||
* @return
|
||||
*/
|
||||
static ApplicationConfig extractConfig();
|
||||
|
||||
/**
|
||||
* Presents application help text to user.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int presentHelpText();
|
||||
|
||||
/**
|
||||
* Presents the current Bloom version number to user.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int presentVersionText();
|
||||
|
||||
/**
|
||||
* Initialises a project in the user's working directory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int initProject();
|
||||
|
||||
/**
|
||||
* Kicks off the application.
|
||||
*
|
||||
* Will start the TargetController and DebugServer. And register the main application's event handlers.
|
||||
*/
|
||||
void startup();
|
||||
|
||||
/**
|
||||
* Will cleanly shutdown the application. This should never fail.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Prepares a dedicated thread for the TargetController and kicks it off.
|
||||
*/
|
||||
void startTargetController();
|
||||
|
||||
/**
|
||||
* Invokes a clean shutdown of the TargetController. The TargetController should disconnect from the target
|
||||
* and debug tool in a clean and safe manner, ensuring that both are left in a sensible state.
|
||||
*
|
||||
* This will join the TargetController thread.
|
||||
*/
|
||||
void stopTargetController();
|
||||
|
||||
void startDebugServer();
|
||||
|
||||
void stopDebugServer();
|
||||
|
||||
public:
|
||||
explicit Application() = default;
|
||||
|
||||
auto getSupportedDebugServers() {
|
||||
return std::map<std::string, std::function<std::unique_ptr<DebugServer>()>> {
|
||||
{
|
||||
"avr-gdb-rsp",
|
||||
[this]() -> std::unique_ptr<DebugServer> {
|
||||
return std::make_unique<DebugServers::Gdb::AvrGdbRsp>(this->eventManager);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
int run(const std::vector<std::string>& arguments);
|
||||
|
||||
void handleTargetControllerStateChangedEvent(EventPointer<Events::TargetControllerStateChanged> event);
|
||||
void onDebugServerStateChanged(EventPointer<Events::DebugServerStateChanged> event);
|
||||
void handleShutdownApplicationEvent(EventPointer<Events::ShutdownApplication>);
|
||||
|
||||
/**
|
||||
* Returns the path to the directory in which the Bloom binary resides.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::string getApplicationDirPath();
|
||||
|
||||
/**
|
||||
* Returns the path to the Resources directory, located in the application directory.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::string getResourcesDirPath();
|
||||
|
||||
/**
|
||||
* Checks if the current effective user running Bloom has root privileges.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static bool isRunningAsRoot();
|
||||
};
|
||||
}
|
||||
88
src/ApplicationConfig.cpp
Normal file
88
src/ApplicationConfig.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <src/Logger/Logger.hpp>
|
||||
#include <src/Exceptions/InvalidConfig.hpp>
|
||||
#include "ApplicationConfig.hpp"
|
||||
|
||||
using namespace Bloom;
|
||||
|
||||
void ApplicationConfig::init(QJsonObject jsonObject) {
|
||||
if (!jsonObject.contains("environments")) {
|
||||
throw Exceptions::InvalidConfig("No environments found.");
|
||||
}
|
||||
|
||||
// Extract all environment objects from JSON config.
|
||||
auto environments = jsonObject.find("environments").value().toObject();
|
||||
for (auto environmentIt = environments.begin(); environmentIt != environments.end(); environmentIt++) {
|
||||
auto environmentName = environmentIt.key().toStdString();
|
||||
|
||||
try {
|
||||
auto environmentConfig = EnvironmentConfig();
|
||||
environmentConfig.init(environmentName, environmentIt.value().toObject());
|
||||
|
||||
this->environments.insert(
|
||||
std::pair<std::string, EnvironmentConfig>(environmentName, environmentConfig)
|
||||
);
|
||||
} catch (Exceptions::InvalidConfig& exception) {
|
||||
Logger::error("Invalid environment config for environment '" + environmentName + "': "
|
||||
+ exception.getMessage() + " Environment will be ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonObject.contains("debugServer")) {
|
||||
auto debugServerConfig = DebugServerConfig();
|
||||
debugServerConfig.init(jsonObject.find("debugServer")->toObject());
|
||||
this->debugServerConfig = debugServerConfig;
|
||||
}
|
||||
|
||||
if (jsonObject.contains("insight")) {
|
||||
this->insightConfig.init(jsonObject.find("insight")->toObject());
|
||||
}
|
||||
|
||||
if (jsonObject.contains("debugLoggingEnabled")) {
|
||||
this->debugLoggingEnabled = jsonObject.find("debugLoggingEnabled").value().toBool();
|
||||
}
|
||||
}
|
||||
|
||||
void InsightConfig::init(QJsonObject jsonObject) {
|
||||
if (jsonObject.contains("enabled")) {
|
||||
this->insightEnabled = jsonObject.find("enabled").value().toBool();
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentConfig::init(std::string name, QJsonObject jsonObject) {
|
||||
this->name = name;
|
||||
this->debugToolName = jsonObject.find("debugToolName")->toString().toLower().toStdString();
|
||||
|
||||
// Extract target data
|
||||
if (!jsonObject.contains("target")) {
|
||||
// Environment has no target data - ignore
|
||||
throw Exceptions::InvalidConfig("No target configuration provided");
|
||||
}
|
||||
|
||||
this->targetConfig.init(jsonObject.find("target")->toObject());
|
||||
|
||||
if (jsonObject.contains("debugServer")) {
|
||||
auto debugServerConfig = DebugServerConfig();
|
||||
debugServerConfig.init(jsonObject.find("debugServer")->toObject());
|
||||
this->debugServerConfig = debugServerConfig;
|
||||
}
|
||||
|
||||
if (jsonObject.contains("insight")) {
|
||||
auto insightConfig = InsightConfig();
|
||||
insightConfig.init(jsonObject.find("insight")->toObject());
|
||||
this->insightConfig = insightConfig;
|
||||
}
|
||||
}
|
||||
|
||||
void TargetConfig::init(QJsonObject jsonObject) {
|
||||
if (!jsonObject.contains("name")) {
|
||||
throw Exceptions::InvalidConfig("No target name found.");
|
||||
}
|
||||
|
||||
this->name = jsonObject.find("name")->toString().toLower().toStdString();
|
||||
this->jsonObject = jsonObject;
|
||||
}
|
||||
|
||||
void DebugServerConfig::init(QJsonObject jsonObject) {
|
||||
this->name = jsonObject.find("name")->toString().toLower().toStdString();
|
||||
this->jsonObject = jsonObject;
|
||||
}
|
||||
154
src/ApplicationConfig.hpp
Normal file
154
src/ApplicationConfig.hpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
/*
|
||||
* Currently, all user configuration is stored in a JSON file (bloom.json), in the user's project directory.
|
||||
*
|
||||
* The JSON config file should define debugging environment objects. A debugging environment object is just
|
||||
* a user defined JSON object that holds parameters relating to a specific debugging environment (like the
|
||||
* name of the DebugTool, Target configuration and any debug server config). Because a config file
|
||||
* can define multiple debugging environments, each object should be assigned a key in the config file. We use this
|
||||
* key to allow users to select different debugging environments between debugging sessions.
|
||||
*
|
||||
* On application startup, we extract the config from this JSON file and generate an ApplicationConfig object.
|
||||
* See Application::extractConfig() for more on this.
|
||||
*
|
||||
* Some config parameters are specific to certain entities within Bloom, but have no significance across the
|
||||
* rest of the application. For example, AVR8 targets require 'physicalInterface' and 'configVariant' parameters.
|
||||
* These are used to configure the AVR8 target, but have no significance across the rest of the application.
|
||||
* This is why some configuration structs (like TargetConfig) include a QJsonObject member, typically named jsonObject.
|
||||
* When instances of these structs are passed to the appropriate entities, any configuration required by those
|
||||
* entities is extracted from the jsonObject member. This means we don't have to worry about any entity specific
|
||||
* config parameters at the application level. We can simply extract what we need at an entity level and the rest
|
||||
* of the application can remain oblivious. For an example on extracting entity specific config, see AVR8::configure().
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configuration relating to a specific target.
|
||||
*
|
||||
* Please don't define any target specific configuration here, unless it applies to *all* targets across
|
||||
* the application. If a target requires specific config, it should be extracted from the jsonObject member.
|
||||
* This should be done in Target::preActivationConfigure(), to which an instance of TargetConfig is passed.
|
||||
* See the comment above on entity specific config for more on this.
|
||||
*/
|
||||
struct TargetConfig
|
||||
{
|
||||
/**
|
||||
* Obtains config parameters from JSON object.
|
||||
*
|
||||
* @param jsonObject
|
||||
*/
|
||||
void init(QJsonObject jsonObject);
|
||||
|
||||
std::string name;
|
||||
QJsonObject jsonObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Debug server configuration.
|
||||
*/
|
||||
struct DebugServerConfig
|
||||
{
|
||||
/**
|
||||
* Obtains config parameters from JSON object.
|
||||
*
|
||||
* @param jsonObject
|
||||
*/
|
||||
void init(QJsonObject jsonObject);
|
||||
|
||||
std::string name;
|
||||
QJsonObject jsonObject;
|
||||
};
|
||||
|
||||
struct InsightConfig
|
||||
{
|
||||
/**
|
||||
* Obtains config parameters from JSON object.
|
||||
*
|
||||
* @param jsonObject
|
||||
*/
|
||||
void init(QJsonObject jsonObject);
|
||||
|
||||
bool insightEnabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration relating to a specific user defined environment.
|
||||
*
|
||||
* An instance of this type will be instantiated for each environment defined in the user's config file.
|
||||
* See Application::extractConfig() implementation for more on this.
|
||||
*/
|
||||
struct EnvironmentConfig
|
||||
{
|
||||
/**
|
||||
* Obtains config parameters from JSON object.
|
||||
*
|
||||
* @param jsonObject
|
||||
*/
|
||||
void init(std::string name, QJsonObject jsonObject);
|
||||
|
||||
/**
|
||||
* The environment name is stored as the key to the JSON object containing the environment parameters.
|
||||
*
|
||||
* Environment names must be unique.
|
||||
*/
|
||||
std::string name;
|
||||
|
||||
/**
|
||||
* Name of the selected debug tool for this environment.
|
||||
*/
|
||||
std::string debugToolName;
|
||||
|
||||
/**
|
||||
* Configuration for the environment's selected target.
|
||||
*
|
||||
* Each environment can consist of only one target.
|
||||
*/
|
||||
TargetConfig targetConfig;
|
||||
|
||||
/**
|
||||
* Configuration for the environment's debug server. Users can define this at the application level if
|
||||
* they desire.
|
||||
*/
|
||||
std::optional<DebugServerConfig> debugServerConfig;
|
||||
|
||||
/**
|
||||
* Insight configuration can be defined at an environment level as well as at an application level.
|
||||
*/
|
||||
std::optional<InsightConfig> insightConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* This holds all user provided application configuration.
|
||||
*/
|
||||
struct ApplicationConfig
|
||||
{
|
||||
/**
|
||||
* Obtains config parameters from JSON object.
|
||||
*
|
||||
* @param jsonObject
|
||||
*/
|
||||
void init(QJsonObject jsonObject);
|
||||
|
||||
/**
|
||||
* A mapping of environment names to EnvironmentConfig objects.
|
||||
*/
|
||||
std::map<std::string, EnvironmentConfig> environments;
|
||||
|
||||
/**
|
||||
* Application level debug server configuration. We use this as a fallback if no debug server config is
|
||||
* provided at the environment level.
|
||||
*/
|
||||
std::optional<DebugServerConfig> debugServerConfig;
|
||||
|
||||
InsightConfig insightConfig;
|
||||
|
||||
bool debugLoggingEnabled = false;
|
||||
};
|
||||
}
|
||||
233
src/DebugServers/DebugServer.cpp
Normal file
233
src/DebugServers/DebugServer.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
#include <variant>
|
||||
#include <cstdint>
|
||||
|
||||
#include "DebugServer.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers;
|
||||
|
||||
void DebugServer::run() {
|
||||
try {
|
||||
this->startup();
|
||||
|
||||
Logger::info("DebugServer ready");
|
||||
|
||||
while (this->getState() == ThreadState::READY) {
|
||||
this->serve();
|
||||
this->eventListener->dispatchCurrentEvents();
|
||||
}
|
||||
} catch (const std::exception& exception) {
|
||||
Logger::error("DebugServer fatal error: " + std::string(exception.what()));
|
||||
}
|
||||
|
||||
this->shutdown();
|
||||
}
|
||||
|
||||
void DebugServer::startup() {
|
||||
this->setName("DS");
|
||||
Logger::info("Starting DebugServer");
|
||||
|
||||
this->eventManager.registerListener(this->eventListener);
|
||||
|
||||
this->interruptEventNotifier = std::make_shared<EventNotifier>();
|
||||
this->interruptEventNotifier->init();
|
||||
this->eventListener->setInterruptEventNotifier(this->interruptEventNotifier);
|
||||
|
||||
// Register event handlers
|
||||
this->eventListener->registerCallbackForEventType<Events::ShutdownDebugServer>(
|
||||
std::bind(&DebugServer::onShutdownDebugServerEvent, this, std::placeholders::_1)
|
||||
);
|
||||
|
||||
this->init();
|
||||
this->setStateAndEmitEvent(ThreadState::READY);
|
||||
}
|
||||
|
||||
void DebugServer::shutdown() {
|
||||
if (this->getState() == ThreadState::STOPPED || this->getState() == ThreadState::SHUTDOWN_INITIATED) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->setState(ThreadState::SHUTDOWN_INITIATED);
|
||||
Logger::info("Shutting down DebugServer");
|
||||
this->close();
|
||||
this->interruptEventNotifier->close();
|
||||
this->setStateAndEmitEvent(ThreadState::STOPPED);
|
||||
}
|
||||
|
||||
void DebugServer::onShutdownDebugServerEvent(EventPointer<Events::ShutdownDebugServer> event) {
|
||||
this->shutdown();
|
||||
}
|
||||
|
||||
void DebugServer::stopTargetExecution() {
|
||||
auto stopTargetEvent = std::make_shared<Events::StopTargetExecution>();
|
||||
this->eventManager.triggerEvent(stopTargetEvent);
|
||||
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::TargetExecutionStopped,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), stopTargetEvent->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::TargetExecutionStopped>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
void DebugServer::continueTargetExecution(std::optional<std::uint32_t> fromAddress) {
|
||||
auto resumeExecutionEvent = std::make_shared<Events::ResumeTargetExecution>();
|
||||
|
||||
if (fromAddress.has_value()) {
|
||||
resumeExecutionEvent->fromProgramCounter = fromAddress.value();
|
||||
}
|
||||
|
||||
this->eventManager.triggerEvent(resumeExecutionEvent);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::TargetExecutionResumed,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), resumeExecutionEvent->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::TargetExecutionResumed>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
void DebugServer::stepTargetExecution(std::optional<std::uint32_t> fromAddress) {
|
||||
auto stepExecutionEvent = std::make_shared<Events::StepTargetExecution>();
|
||||
|
||||
if (fromAddress.has_value()) {
|
||||
stepExecutionEvent->fromProgramCounter = fromAddress.value();
|
||||
}
|
||||
|
||||
this->eventManager.triggerEvent(stepExecutionEvent);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::TargetExecutionResumed,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), stepExecutionEvent->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::TargetExecutionResumed>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
TargetRegisters DebugServer::readGeneralRegistersFromTarget(TargetRegisterDescriptors descriptors) {
|
||||
auto readRegistersEvent = std::make_shared<Events::RetrieveRegistersFromTarget>();
|
||||
readRegistersEvent->descriptors = descriptors;
|
||||
|
||||
this->eventManager.triggerEvent(readRegistersEvent);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::RegistersRetrievedFromTarget,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), readRegistersEvent->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::RegistersRetrievedFromTarget>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
|
||||
auto retrievedRegistersEvent = std::get<EventPointer<Events::RegistersRetrievedFromTarget>>(responseEvent.value());
|
||||
return retrievedRegistersEvent->registers;
|
||||
}
|
||||
|
||||
void DebugServer::writeGeneralRegistersToTarget(TargetRegisters registers) {
|
||||
auto event = std::make_shared<Events::WriteRegistersToTarget>();
|
||||
event->registers = registers;
|
||||
|
||||
this->eventManager.triggerEvent(event);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::RegistersWrittenToTarget,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), event->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::RegistersWrittenToTarget>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
TargetMemoryBuffer DebugServer::readMemoryFromTarget(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes) {
|
||||
auto readMemoryEvent = std::make_shared<Events::RetrieveMemoryFromTarget>();
|
||||
readMemoryEvent->memoryType = memoryType;
|
||||
readMemoryEvent->startAddress = startAddress;
|
||||
readMemoryEvent->bytes = bytes;
|
||||
|
||||
this->eventManager.triggerEvent(readMemoryEvent);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::MemoryRetrievedFromTarget,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), readMemoryEvent->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::MemoryRetrievedFromTarget>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
|
||||
auto retrievedRegistersEvent = std::get<EventPointer<Events::MemoryRetrievedFromTarget>>(responseEvent.value());
|
||||
return retrievedRegistersEvent->data;
|
||||
}
|
||||
|
||||
void DebugServer::writeMemoryToTarget(
|
||||
TargetMemoryType memoryType,
|
||||
std::uint32_t startAddress,
|
||||
const TargetMemoryBuffer& buffer
|
||||
) {
|
||||
auto writeMemoryEvent = std::make_shared<Events::WriteMemoryToTarget>();
|
||||
writeMemoryEvent->memoryType = memoryType;
|
||||
writeMemoryEvent->startAddress = startAddress;
|
||||
writeMemoryEvent->buffer = buffer;
|
||||
|
||||
this->eventManager.triggerEvent(writeMemoryEvent);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::MemoryWrittenToTarget,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), writeMemoryEvent->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::MemoryWrittenToTarget>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
void DebugServer::setBreakpointOnTarget(TargetBreakpoint breakpoint) {
|
||||
auto event = std::make_shared<Events::SetBreakpointOnTarget>();
|
||||
event->breakpoint = breakpoint;
|
||||
|
||||
this->eventManager.triggerEvent(event);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::BreakpointSetOnTarget,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), event->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::BreakpointSetOnTarget>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
void DebugServer::removeBreakpointOnTarget(TargetBreakpoint breakpoint) {
|
||||
auto event = std::make_shared<Events::RemoveBreakpointOnTarget>();
|
||||
event->breakpoint = breakpoint;
|
||||
|
||||
this->eventManager.triggerEvent(event);
|
||||
auto responseEvent = this->eventListener->waitForEvent<
|
||||
Events::BreakpointRemovedOnTarget,
|
||||
Events::TargetControllerErrorOccurred
|
||||
>(std::chrono::milliseconds(5000), event->id);
|
||||
|
||||
if (!responseEvent.has_value()
|
||||
|| !std::holds_alternative<EventPointer<Events::BreakpointRemovedOnTarget>>(responseEvent.value())
|
||||
) {
|
||||
throw Exception("Unexpected response from TargetController");
|
||||
}
|
||||
}
|
||||
|
||||
195
src/DebugServers/DebugServer.hpp
Normal file
195
src/DebugServers/DebugServer.hpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/EventManager/Events/Events.hpp"
|
||||
#include "src/EventManager/EventManager.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
#include "src/ApplicationConfig.hpp"
|
||||
#include "src/Helpers/Thread.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
#include "src/Targets/TargetBreakpoint.hpp"
|
||||
|
||||
namespace Bloom::DebugServers
|
||||
{
|
||||
using Targets::TargetRegister;
|
||||
using Targets::TargetRegisterDescriptor;
|
||||
using Targets::TargetRegisters;
|
||||
using Targets::TargetRegisterMap;
|
||||
using Targets::TargetMemoryBuffer;
|
||||
using Targets::TargetMemoryType;
|
||||
using Targets::TargetBreakpoint;
|
||||
|
||||
/**
|
||||
* The DebugServer exposes the connected target to third-party debugging software such as IDEs.
|
||||
* The DebugServer runs on a dedicated thread which is kicked off shortly after the TargetController has been
|
||||
* started.
|
||||
*
|
||||
* All supported DebugServers should be derived from this class.
|
||||
*
|
||||
* Bloom currently only supports one DebugServer - the GdbRspDebugServer.
|
||||
*/
|
||||
class DebugServer: public Thread
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* Prepares the debug server thread and then calls init().
|
||||
*
|
||||
* Derived classes should not override this method - they should instead use init().
|
||||
*/
|
||||
void startup();
|
||||
|
||||
/**
|
||||
* Calls close() and updates the thread state.
|
||||
*
|
||||
* As with startup(), derived classes should not override this method. They should use close() instead.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Updates the state of the DebugServer and emits a state changed event.
|
||||
*
|
||||
* @param state
|
||||
* @param emitEvent
|
||||
*/
|
||||
void setStateAndEmitEvent(ThreadState state) {
|
||||
Thread::setState(state);
|
||||
this->eventManager.triggerEvent(
|
||||
std::make_shared<Events::DebugServerStateChanged>(state)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a shutdown request.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
void onShutdownDebugServerEvent(EventPointer<Events::ShutdownDebugServer> event);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Application-wide instance to EventManager
|
||||
*/
|
||||
EventManager& eventManager;
|
||||
EventListenerPointer eventListener = std::make_shared<EventListener>("DebugServerEventListener");
|
||||
|
||||
/**
|
||||
* Enables the interruption of any blocking file IO.
|
||||
*/
|
||||
std::shared_ptr<EventNotifier> interruptEventNotifier = nullptr;
|
||||
|
||||
ApplicationConfig applicationConfig;
|
||||
EnvironmentConfig environmentConfig;
|
||||
DebugServerConfig debugServerConfig;
|
||||
|
||||
/**
|
||||
* Called on startup of the DebugServer thread. Derived classes should implement any initialisation work here.
|
||||
*/
|
||||
virtual void init() = 0;
|
||||
|
||||
/**
|
||||
* Called repeatedly in an infinite loop when the DebugServer is running.
|
||||
*/
|
||||
virtual void serve() = 0;
|
||||
|
||||
/**
|
||||
* Called on shutdown of the debug server.
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
|
||||
/**
|
||||
* Requests the TargetController to halt execution on the target.
|
||||
*/
|
||||
void stopTargetExecution();
|
||||
|
||||
/**
|
||||
* Requests the TargetController to continue execution on the target.
|
||||
*
|
||||
* @param fromAddress
|
||||
*/
|
||||
void continueTargetExecution(std::optional<std::uint32_t> fromAddress);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to step execution on the target.
|
||||
*
|
||||
* @param fromAddress
|
||||
*/
|
||||
void stepTargetExecution(std::optional<std::uint32_t> fromAddress);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to read register values from the target.
|
||||
*
|
||||
* @param descriptors
|
||||
* Descriptors of the registers to read.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
TargetRegisters readGeneralRegistersFromTarget(TargetRegisterDescriptors descriptors);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to write register values to the target.
|
||||
*
|
||||
* @param registers
|
||||
*/
|
||||
void writeGeneralRegistersToTarget(TargetRegisters registers);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to read memory from the target.
|
||||
*
|
||||
* @param memoryType
|
||||
* @param startAddress
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
TargetMemoryBuffer readMemoryFromTarget(TargetMemoryType memoryType, std::uint32_t startAddress, std::uint32_t bytes);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to write memory to the target.
|
||||
*
|
||||
* @param memoryType
|
||||
* @param startAddress
|
||||
* @param buffer
|
||||
*/
|
||||
void writeMemoryToTarget(TargetMemoryType memoryType, std::uint32_t startAddress, const TargetMemoryBuffer& buffer);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to set a breakpoint on the target.
|
||||
*
|
||||
* @param breakpoint
|
||||
*/
|
||||
void setBreakpointOnTarget(TargetBreakpoint breakpoint);
|
||||
|
||||
/**
|
||||
* Requests the TargetController to remove a breakpoint from the target.
|
||||
*
|
||||
* @param breakpoint
|
||||
*/
|
||||
void removeBreakpointOnTarget(TargetBreakpoint breakpoint);
|
||||
|
||||
public:
|
||||
DebugServer(EventManager& eventManager) : eventManager(eventManager) {};
|
||||
|
||||
void setApplicationConfig(const ApplicationConfig& applicationConfig) {
|
||||
this->applicationConfig = applicationConfig;
|
||||
}
|
||||
|
||||
void setEnvironmentConfig(const EnvironmentConfig& environmentConfig) {
|
||||
this->environmentConfig = environmentConfig;
|
||||
}
|
||||
|
||||
void setDebugServerConfig(const DebugServerConfig& debugServerConfig) {
|
||||
this->debugServerConfig = debugServerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for the DebugServer. This must called from a dedicated thread.
|
||||
*/
|
||||
void run();
|
||||
|
||||
virtual std::string getName() const = 0;
|
||||
|
||||
virtual ~DebugServer() = default;
|
||||
};
|
||||
}
|
||||
121
src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp
Normal file
121
src/DebugServers/GdbRsp/AvrGdbRsp/AvrGdbRsp.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "src/DebugServers/GdbRsp/Register.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
/**
|
||||
* The AVR GDB client (avr-gdb) defines a set of parameters relating to AVR targets. These parameters are
|
||||
* hardcoded in the AVR GDB source code. The client expects all compatible GDB RSP servers to be aware of
|
||||
* these parameters.
|
||||
*
|
||||
* An example of these hardcoded parameters is target registers and the order in which they are supplied; AVR GDB
|
||||
* clients expect 35 registers to be accessible via the server. 32 of these registers are general purpose CPU
|
||||
* registers. The GP registers are expected to be followed by the status register (SREG), stack pointer
|
||||
* register (SPH & SPL) and the program counter. These must all be given in a specific order, which is
|
||||
* pre-determined by the AVR GDB client. See AvrGdbRsp::getRegisterNumberToDescriptorMapping() for more.
|
||||
*
|
||||
* For more on this, see the AVR GDB source code at https://github.com/bminor/binutils-gdb/blob/master/gdb/avr-tdep.c
|
||||
*
|
||||
* The AvrGdpRsp class extends the generic GDB RSP debug server and implements these AVR specific parameters.
|
||||
*/
|
||||
class AvrGdbRsp: public GdbRspDebugServer
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The mask used by the AVR GDB client to encode the memory type into memory addresses.
|
||||
* See AvrGdbRsp::getMemoryTypeFromGdbAddress() for more.
|
||||
*/
|
||||
unsigned int gdbInternalMemoryMask = 0xFE0000u;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* For AVR targets, avr-gdb defines 35 registers in total:
|
||||
*
|
||||
* Register number 0 through 31 are general purpose registers
|
||||
* Register number 32 is the status register (SREG)
|
||||
* Register number 33 is the stack pointer register
|
||||
* Register number 34 is the program counter register
|
||||
*
|
||||
* Only general purpose registers have register IDs. The others do not require an ID.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() override {
|
||||
static BiMap<GdbRegisterNumber, TargetRegisterDescriptor> mapping = {
|
||||
{0, TargetRegisterDescriptor(0, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{1, TargetRegisterDescriptor(1, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{2, TargetRegisterDescriptor(2, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{3, TargetRegisterDescriptor(3, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{4, TargetRegisterDescriptor(4, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{5, TargetRegisterDescriptor(5, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{6, TargetRegisterDescriptor(6, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{7, TargetRegisterDescriptor(7, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{8, TargetRegisterDescriptor(8, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{9, TargetRegisterDescriptor(9, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{10, TargetRegisterDescriptor(10, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{11, TargetRegisterDescriptor(11, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{12, TargetRegisterDescriptor(12, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{13, TargetRegisterDescriptor(13, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{14, TargetRegisterDescriptor(14, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{15, TargetRegisterDescriptor(15, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{16, TargetRegisterDescriptor(16, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{17, TargetRegisterDescriptor(17, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{18, TargetRegisterDescriptor(18, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{19, TargetRegisterDescriptor(19, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{20, TargetRegisterDescriptor(20, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{21, TargetRegisterDescriptor(21, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{22, TargetRegisterDescriptor(22, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{23, TargetRegisterDescriptor(23, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{24, TargetRegisterDescriptor(24, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{25, TargetRegisterDescriptor(25, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{26, TargetRegisterDescriptor(26, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{27, TargetRegisterDescriptor(27, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{28, TargetRegisterDescriptor(28, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{29, TargetRegisterDescriptor(29, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{30, TargetRegisterDescriptor(30, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{31, TargetRegisterDescriptor(31, TargetRegisterType::GENERAL_PURPOSE_REGISTER)},
|
||||
{32, TargetRegisterDescriptor(TargetRegisterType::STATUS_REGISTER)},
|
||||
{33, TargetRegisterDescriptor(TargetRegisterType::STACK_POINTER)},
|
||||
{34, TargetRegisterDescriptor(TargetRegisterType::PROGRAM_COUNTER)},
|
||||
};
|
||||
|
||||
return mapping;
|
||||
};
|
||||
|
||||
/**
|
||||
* avr-gdb uses the most significant 15 bits in memory addresses to indicate the type of memory being
|
||||
* addressed.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) override {
|
||||
if (address & this->gdbInternalMemoryMask) {
|
||||
return TargetMemoryType::RAM;
|
||||
}
|
||||
|
||||
return TargetMemoryType::FLASH;
|
||||
};
|
||||
|
||||
/**
|
||||
* Strips the most significant 15 bits from a GDB memory address.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) override {
|
||||
return address & this->gdbInternalMemoryMask ? (address & ~(this->gdbInternalMemoryMask)) : address;
|
||||
};
|
||||
|
||||
public:
|
||||
AvrGdbRsp(EventManager& eventManager) : GdbRspDebugServer(eventManager) {};
|
||||
|
||||
std::string getName() const override {
|
||||
return "AVR GDB Remote Serial Protocol Debug Server";
|
||||
}
|
||||
};
|
||||
}
|
||||
11
src/DebugServers/GdbRsp/BreakpointType.hpp
Normal file
11
src/DebugServers/GdbRsp/BreakpointType.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class BreakpointType: int
|
||||
{
|
||||
UNKNOWN = 0,
|
||||
SOFTWARE_BREAKPOINT = 1,
|
||||
HARDWARE_BREAKPOINT = 2,
|
||||
};
|
||||
}
|
||||
8
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
Normal file
8
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void CommandPacket::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
51
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp
Normal file
51
src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
class GdbRspDebugServer;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
/**
|
||||
* GDB RSP command packets are sent to the server, from the GDB client. These packets carry instructions that the
|
||||
* server is expected to carry out. Upon completion, the server is expected to respond to the client with
|
||||
* a ResponsePacket.
|
||||
*
|
||||
* For some command packets, we define a specific data structure by extending this CommandPacket class. These
|
||||
* classes extend the data structure to include fields for data which may be specific to the command.
|
||||
* They also implement additional methods that allow us to easily access the additional data. An example
|
||||
* of this would be the SupportedFeaturesQuery class. It extends the CommandPacket class and provides access
|
||||
* to additional data fields that are specific to the command (in this case, a set of GDB features reported to be
|
||||
* supported by the GDB client).
|
||||
*
|
||||
* Typically, command packets that require specific data structures are handled in a dedicated handler method
|
||||
* in the GdbRspDebugServer. This is done by double dispatching the packet object to the appropriate handler.
|
||||
* See CommandPacket::dispatchToHandler(), GdbRspDebugServer::serve() and the overloads
|
||||
* for GdbRspDebugServer::handleGdbPacket() for more on this.
|
||||
*
|
||||
* Some command packets are so simple they do not require a dedicated data structure. An example of this is
|
||||
* the halt reason packet, which contains nothing more than an ? character in the packet body. These packets are
|
||||
* typically handled in the generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) method.
|
||||
*
|
||||
* See the Packet class for information on how the raw packets are formatted.
|
||||
*/
|
||||
class CommandPacket: public Packet
|
||||
{
|
||||
public:
|
||||
CommandPacket(const std::vector<unsigned char>& rawPacket) : Packet(rawPacket) {}
|
||||
|
||||
/**
|
||||
* Double dispatches the packet to the appropriate overload of handleGdbPacket(), within the passed instance of
|
||||
* GdbRspDebugServer. If there is no overload defined for a specific CommandPacket-inherited type, the
|
||||
* generic GdbRspDebugServer::handleGdbPacket(CommandPacket&) is used.
|
||||
*
|
||||
* @param gdbRspDebugServer
|
||||
*/
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer);
|
||||
};
|
||||
}
|
||||
129
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
Normal file
129
src/DebugServers/GdbRsp/CommandPackets/CommandPacketFactory.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
#include "CommandPacketFactory.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
std::unique_ptr<CommandPacket> CommandPacketFactory::create(std::vector<unsigned char> rawPacket) {
|
||||
|
||||
if (rawPacket.size() == 5 && rawPacket[1] == 0x03) {
|
||||
// This is an interrupt request - create a fake packet for it
|
||||
return std::make_unique<CommandPackets::InterruptExecution>(rawPacket);
|
||||
}
|
||||
|
||||
auto rawPacketString = std::string(rawPacket.begin(), rawPacket.end());
|
||||
|
||||
if (rawPacketString.size() >= 2) {
|
||||
/*
|
||||
* First byte of the raw packet will be 0x24 ('$'), so find() should return 1, not 0, when
|
||||
* looking for a command identifier string.
|
||||
*/
|
||||
if (rawPacketString.find("qSupported") == 1) {
|
||||
return std::make_unique<CommandPackets::SupportedFeaturesQuery>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'g' || rawPacketString[1] == 'p') {
|
||||
return std::make_unique<CommandPackets::ReadGeneralRegisters>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'G' || rawPacketString[1] == 'P') {
|
||||
return std::make_unique<CommandPackets::WriteGeneralRegisters>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'c') {
|
||||
return std::make_unique<CommandPackets::ContinueExecution>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 's') {
|
||||
return std::make_unique<CommandPackets::StepExecution>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'm') {
|
||||
return std::make_unique<CommandPackets::ReadMemory>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'M') {
|
||||
return std::make_unique<CommandPackets::WriteMemory>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'Z') {
|
||||
return std::make_unique<CommandPackets::SetBreakpoint>(rawPacket);
|
||||
|
||||
} else if (rawPacketString[1] == 'z') {
|
||||
return std::make_unique<CommandPackets::RemoveBreakpoint>(rawPacket);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<CommandPacket>(rawPacket);
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned char>> CommandPacketFactory::extractRawPackets(std::vector<unsigned char> buffer) {
|
||||
std::vector<std::vector<unsigned char>> output;
|
||||
|
||||
std::size_t bufferIndex;
|
||||
std::size_t bufferSize = buffer.size();
|
||||
unsigned char byte;
|
||||
for (bufferIndex = 0; bufferIndex < bufferSize; bufferIndex++) {
|
||||
byte = buffer[bufferIndex];
|
||||
|
||||
if (byte == 0x03) {
|
||||
/*
|
||||
* This is an interrupt packet - it doesn't carry any of the usual packet frame bytes, so we'll just
|
||||
* add them here, in order to keep things consistent.
|
||||
*
|
||||
* Because we're effectively faking the packet frame, we can use any value for the checksum.
|
||||
*/
|
||||
output.push_back({'$', byte, '#', 'F', 'F'});
|
||||
|
||||
} else if (byte == '$') {
|
||||
// Beginning of packet
|
||||
std::vector<unsigned char> rawPacket;
|
||||
rawPacket.push_back('$');
|
||||
|
||||
auto packetIndex = bufferIndex;
|
||||
bool validPacket = false;
|
||||
bool isByteEscaped = false;
|
||||
|
||||
for (packetIndex++; packetIndex < bufferSize; packetIndex++) {
|
||||
byte = buffer[packetIndex];
|
||||
|
||||
if (byte == '}' && !isByteEscaped) {
|
||||
isByteEscaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (byte == '$' && !isByteEscaped) {
|
||||
// Unexpected end of packet
|
||||
validPacket = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (byte == '#' && !isByteEscaped) {
|
||||
// End of packet data
|
||||
if ((bufferSize - 1) < (packetIndex + 2)) {
|
||||
// There should be at least two more bytes in the buffer, for the checksum.
|
||||
break;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
|
||||
// Add the checksum bytes and break the loop
|
||||
rawPacket.push_back(buffer[++packetIndex]);
|
||||
rawPacket.push_back(buffer[++packetIndex]);
|
||||
validPacket = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isByteEscaped) {
|
||||
// Escaped bytes are XOR'd with a 0x20 mask.
|
||||
byte ^= 0x20;
|
||||
isByteEscaped = false;
|
||||
}
|
||||
|
||||
rawPacket.push_back(byte);
|
||||
}
|
||||
|
||||
if (validPacket) {
|
||||
output.push_back(rawPacket);
|
||||
bufferIndex = packetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Command packets
|
||||
#include "CommandPacket.hpp"
|
||||
#include "InterruptExecution.hpp"
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
#include "ReadGeneralRegisters.hpp"
|
||||
#include "WriteGeneralRegisters.hpp"
|
||||
#include "ReadMemory.hpp"
|
||||
#include "WriteMemory.hpp"
|
||||
#include "StepExecution.hpp"
|
||||
#include "ContinueExecution.hpp"
|
||||
#include "SetBreakpoint.hpp"
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using namespace CommandPackets;
|
||||
|
||||
/**
|
||||
* The CommandPacketFactory class provides a means for extracting raw packet data from a raw buffer, and
|
||||
* constructing the appropriate CommandPacket objects.
|
||||
*/
|
||||
class CommandPacketFactory
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Extracts raw GDB RSP packets from buffer.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer from which to extract the raw GDB RSP packets.
|
||||
*
|
||||
* @return
|
||||
* A vector of raw packets.
|
||||
*/
|
||||
static std::vector<std::vector<unsigned char>> extractRawPackets(std::vector<unsigned char> buffer);
|
||||
|
||||
/**
|
||||
* Constructs the appropriate CommandPacket object from a single raw GDB RSP packet.
|
||||
*
|
||||
* @param rawPacket
|
||||
* The raw GDB RSP packet from which to construct the CommandPacket object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static std::unique_ptr<CommandPacket> create(std::vector<unsigned char> rawPacket);
|
||||
};
|
||||
}
|
||||
18
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
Normal file
18
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "ContinueExecution.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void ContinueExecution::init() {
|
||||
if (this->data.size() > 1) {
|
||||
this->fromProgramCounter = static_cast<std::uint32_t>(
|
||||
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ContinueExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.hpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/ContinueExecution.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ContinueExecution class implements a structure for "c" packets. These packets instruct the server
|
||||
* to continue execution on the target.
|
||||
*
|
||||
* See @link https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets for more on this.
|
||||
*/
|
||||
class ContinueExecution: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The "c" packet can contain an address which defines the point from which the execution should be resumed on
|
||||
* the target.
|
||||
*
|
||||
* Although the packet *can* contain this address, it is not required, hence the optional.
|
||||
*/
|
||||
std::optional<std::uint32_t> fromProgramCounter;
|
||||
|
||||
ContinueExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "InterruptExecution.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void InterruptExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The InterruptException class represents interrupt command packets. Upon receiving an interrupt packet, the
|
||||
* server is expected to interrupt execution on the target.
|
||||
*
|
||||
* Technically, interrupts are not sent by the client in the form of a typical GDP RSP packet. Instead, they're
|
||||
* just sent as a single byte from the client. We fake the packet on our end, to save us the headache of dealing
|
||||
* with this inconsistency. We do this in CommandPacketFactory::extractRawPackets().
|
||||
*/
|
||||
class InterruptExecution: public CommandPacket
|
||||
{
|
||||
public:
|
||||
InterruptExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ReadGeneralRegisters.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void ReadGeneralRegisters::init() {
|
||||
if (this->data.size() >= 2 && this->data.front() == 'p') {
|
||||
// This command packet is requesting a specific register
|
||||
this->registerNumber = static_cast<size_t>(std::stoi(std::string(this->data.begin() + 1, this->data.end())));
|
||||
}
|
||||
}
|
||||
|
||||
void ReadGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ReadGeneralRegisters class implements a structure for "g" and "p" command packets. In response to these
|
||||
* packets, the server is expected to send register values for all registers (for "g" packets) or for a single
|
||||
* register (for "p" packets).
|
||||
*/
|
||||
class ReadGeneralRegisters: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* "p" packets include a register number to indicate which register is requested for reading. When this is set,
|
||||
* the server is expected to respond with only the value of the requested register.
|
||||
*
|
||||
* If the register number is not supplied (as is the case with "g" packets), the server is expected to respond
|
||||
* with values for all registers.
|
||||
*/
|
||||
std::optional<int> registerNumber;
|
||||
|
||||
ReadGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
43
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
Normal file
43
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "ReadMemory.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void ReadMemory::init() {
|
||||
if (this->data.size() < 4) {
|
||||
throw Exception("Invalid packet length");
|
||||
}
|
||||
|
||||
auto packetString = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 1),
|
||||
static_cast<int>(this->data.size() - 1)
|
||||
);
|
||||
|
||||
/*
|
||||
* The read memory ('m') packet consists of two segments, an address and a number of bytes to read.
|
||||
* These are separated by a comma character.
|
||||
*/
|
||||
auto packetSegments = packetString.split(",");
|
||||
|
||||
if (packetSegments.size() != 2) {
|
||||
throw Exception("Unexpected number of segments in packet data: " + std::to_string(packetSegments.size()));
|
||||
}
|
||||
|
||||
bool conversionStatus = false;
|
||||
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse start address from read memory packet data");
|
||||
}
|
||||
|
||||
this->bytes = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse read length from read memory packet data");
|
||||
}
|
||||
}
|
||||
|
||||
void ReadMemory::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
42
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.hpp
Normal file
42
src/DebugServers/GdbRsp/CommandPackets/ReadMemory.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The ReadMemory class implements a structure for "m" packets. Upon receiving these packets, the server is
|
||||
* expected to read memory from the target and send it the client.
|
||||
*/
|
||||
class ReadMemory: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The startAddress sent from the GDB client may include additional bits used to indicate the memory type.
|
||||
* These bits have to be removed from the address before it can be used as a start address. This is not done
|
||||
* here, as it's target specific.
|
||||
*
|
||||
* For an example of where GDB does this, see the AvrGdbRsp class.
|
||||
*/
|
||||
std::uint32_t startAddress;
|
||||
|
||||
/**
|
||||
* Number of bytes to read.
|
||||
*/
|
||||
std::uint32_t bytes;
|
||||
|
||||
ReadMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "RemoveBreakpoint.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void RemoveBreakpoint::init() {
|
||||
if (data.size() < 6) {
|
||||
throw Exception("Unexpected RemoveBreakpoint packet size");
|
||||
}
|
||||
|
||||
// z0 = SW breakpoint, z1 = HW breakpoint
|
||||
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
|
||||
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
|
||||
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 2),
|
||||
static_cast<int>(this->data.size() - 2)
|
||||
);
|
||||
|
||||
auto packetSegments = packetData.split(",");
|
||||
if (packetSegments.size() < 3) {
|
||||
throw Exception("Unexpected number of packet segments in RemoveBreakpoint packet");
|
||||
}
|
||||
|
||||
bool conversionStatus = true;
|
||||
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to convert address hex value from RemoveBreakpoint packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveBreakpoint::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
44
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.hpp
Normal file
44
src/DebugServers/GdbRsp/CommandPackets/RemoveBreakpoint.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../BreakpointType.hpp"
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature: int;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The RemoveBreakpoint class implements the structure for "z" command packets. Upon receiving this command, the
|
||||
* server is expected to remove a breakpoint at the specified address.
|
||||
*/
|
||||
class RemoveBreakpoint: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address;
|
||||
|
||||
RemoveBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
39
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
Normal file
39
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "SetBreakpoint.hpp"
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void SetBreakpoint::init() {
|
||||
if (data.size() < 6) {
|
||||
throw Exception("Unexpected SetBreakpoint packet size");
|
||||
}
|
||||
|
||||
// Z0 = SW breakpoint, Z1 = HW breakpoint
|
||||
this->type = (data[1] == 0) ? BreakpointType::SOFTWARE_BREAKPOINT : (data[1] == 1) ?
|
||||
BreakpointType::HARDWARE_BREAKPOINT : BreakpointType::UNKNOWN;
|
||||
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 2),
|
||||
static_cast<int>(this->data.size() - 2)
|
||||
);
|
||||
|
||||
auto packetSegments = packetData.split(",");
|
||||
if (packetSegments.size() < 3) {
|
||||
throw Exception("Unexpected number of packet segments in SetBreakpoint packet");
|
||||
}
|
||||
|
||||
bool conversionStatus = true;
|
||||
this->address = packetSegments.at(1).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to convert address hex value from SetBreakpoint packet.");
|
||||
}
|
||||
}
|
||||
|
||||
void SetBreakpoint::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
44
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.hpp
Normal file
44
src/DebugServers/GdbRsp/CommandPackets/SetBreakpoint.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../BreakpointType.hpp"
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature: int;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The SetBreakpoint class implements the structure for "Z" command packets. Upon receiving this command, the
|
||||
* server is expected to set a breakpoint at the specified address.
|
||||
*/
|
||||
class SetBreakpoint: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Breakpoint type (Software or Hardware)
|
||||
*/
|
||||
BreakpointType type = BreakpointType::UNKNOWN;
|
||||
|
||||
/**
|
||||
* Address at which the breakpoint should be located.
|
||||
*/
|
||||
std::uint32_t address;
|
||||
|
||||
SetBreakpoint(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
18
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
Normal file
18
src/DebugServers/GdbRsp/CommandPackets/StepExecution.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "StepExecution.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void StepExecution::init() {
|
||||
if (this->data.size() > 1) {
|
||||
this->fromProgramCounter = static_cast<std::uint32_t>(
|
||||
std::stoi(std::string(this->data.begin(), this->data.end()), nullptr, 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void StepExecution::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
32
src/DebugServers/GdbRsp/CommandPackets/StepExecution.hpp
Normal file
32
src/DebugServers/GdbRsp/CommandPackets/StepExecution.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The StepExecution class implements the structure for "s" command packets. Upon receiving this command, the
|
||||
* server is expected to step execution on the target.
|
||||
*/
|
||||
class StepExecution: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* The address from which to begin the step.
|
||||
*/
|
||||
std::optional<size_t> fromProgramCounter;
|
||||
|
||||
StepExecution(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "SupportedFeaturesQuery.hpp"
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void SupportedFeaturesQuery::init() {
|
||||
/*
|
||||
* For qSupported packets, supported and unsupported GDB features are reported in the packet
|
||||
* data, where each GDB feature is separated by a semicolon.
|
||||
*/
|
||||
|
||||
// The "qSupported:" prefix occupies 11 bytes
|
||||
if (data.size() > 11) {
|
||||
auto packetData = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 11),
|
||||
static_cast<int>(this->data.size() - 11)
|
||||
);
|
||||
|
||||
auto featureList = packetData.split(";");
|
||||
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
|
||||
|
||||
for (int i = 0; i < featureList.size(); i++) {
|
||||
auto featureString = featureList.at(i);
|
||||
|
||||
// We only care about supported features. Supported features will precede a '+' character.
|
||||
if (featureString[featureString.size() - 1] == "+") {
|
||||
featureString.remove("+");
|
||||
|
||||
auto feature = gdbFeatureMapping.valueAt(featureString.toStdString());
|
||||
if (feature.has_value()) {
|
||||
this->supportedFeatures.insert(static_cast<decltype(feature)::value_type>(feature.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SupportedFeaturesQuery::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
|
||||
/**
|
||||
* The SupportedFeaturesQuery command packet is a query from the GDB client, requesting a list of GDB features
|
||||
* supported by the GDB server. The body of this packet also contains a list GDB features that are supported or
|
||||
* unsupported by the GDB client.
|
||||
*
|
||||
* The command packet is identified by its 'qSupported' prefix in the command packet data. Following the prefix is
|
||||
* a list of GDB features that are supported/unsupported by the client. For more info on this command
|
||||
* packet, see the GDP RSP documentation.
|
||||
*
|
||||
* Responses to this command packet should take the form of a ResponsePackets::SupportedFeaturesResponse.
|
||||
*/
|
||||
class SupportedFeaturesQuery: public CommandPacket
|
||||
{
|
||||
private:
|
||||
std::set<Feature> supportedFeatures;
|
||||
|
||||
void init();
|
||||
|
||||
public:
|
||||
SupportedFeaturesQuery(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
this->init();
|
||||
};
|
||||
|
||||
bool isFeatureSupported(const Feature& feature) const {
|
||||
return this->supportedFeatures.find(feature) != this->supportedFeatures.end();
|
||||
}
|
||||
|
||||
const std::set<Feature>& getSupportedFeatures() const {
|
||||
return this->supportedFeatures;
|
||||
}
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "WriteGeneralRegisters.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
|
||||
void WriteGeneralRegisters::init() {
|
||||
// The P packet updates a single register
|
||||
auto packet = std::string(this->data.begin(), this->data.end());
|
||||
|
||||
if (packet.size() < 6) {
|
||||
throw Exception("Invalid P command packet - insufficient data in packet.");
|
||||
}
|
||||
|
||||
if (packet.find("=") == std::string::npos) {
|
||||
throw Exception("Invalid P command packet - unexpected format");
|
||||
}
|
||||
|
||||
auto packetSegments = QString::fromStdString(packet).split("=");
|
||||
this->registerNumber = packetSegments.front().mid(1).toUInt(nullptr, 16);
|
||||
this->registerValue = this->hexToData(packetSegments.back().toStdString());
|
||||
std::reverse(this->registerValue.begin(), this->registerValue.end());
|
||||
}
|
||||
|
||||
void WriteGeneralRegisters::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using Bloom::Targets::TargetRegister;
|
||||
using Bloom::Targets::TargetRegisterMap;
|
||||
|
||||
/**
|
||||
* The WriteGeneralRegisters class implements the structure for "G" and "P" packets. Upon receiving this packet,
|
||||
* server is expected to write register values to the target.
|
||||
*/
|
||||
class WriteGeneralRegisters: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
TargetRegisterMap registerMap;
|
||||
int registerNumber;
|
||||
std::vector<unsigned char> registerValue;
|
||||
|
||||
WriteGeneralRegisters(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
52
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
Normal file
52
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "WriteMemory.hpp"
|
||||
#include "src/DebugServers/GdbRsp/GdbRspDebugServer.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::CommandPackets;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void WriteMemory::init() {
|
||||
if (this->data.size() < 4) {
|
||||
throw Exception("Invalid packet length");
|
||||
}
|
||||
|
||||
auto packetString = QString::fromLocal8Bit(
|
||||
reinterpret_cast<const char*>(this->data.data() + 1),
|
||||
static_cast<int>(this->data.size() - 1)
|
||||
);
|
||||
|
||||
/*
|
||||
* The write memory ('M') packet consists of three segments, an address, a length and a buffer.
|
||||
* The address and length are separated by a comma character, and the buffer proceeds a colon character.
|
||||
*/
|
||||
auto packetSegments = packetString.split(",");
|
||||
if (packetSegments.size() != 2) {
|
||||
throw Exception("Unexpected number of segments in packet data: " + std::to_string(packetSegments.size()));
|
||||
}
|
||||
|
||||
bool conversionStatus = false;
|
||||
this->startAddress = packetSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse start address from write memory packet data");
|
||||
}
|
||||
|
||||
auto lengthAndBufferSegments = packetSegments.at(1).split(":");
|
||||
if (lengthAndBufferSegments.size() != 2) {
|
||||
throw Exception("Unexpected number of segments in packet data: " + std::to_string(lengthAndBufferSegments.size()));
|
||||
}
|
||||
|
||||
auto bufferSize = lengthAndBufferSegments.at(0).toUInt(&conversionStatus, 16);
|
||||
if (!conversionStatus) {
|
||||
throw Exception("Failed to parse write length from write memory packet data");
|
||||
}
|
||||
|
||||
this->buffer = this->hexToData(lengthAndBufferSegments.at(1).toStdString());
|
||||
|
||||
if (this->buffer.size() != bufferSize) {
|
||||
throw Exception("Buffer size does not match length value given in write memory packet");
|
||||
}
|
||||
}
|
||||
|
||||
void WriteMemory::dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) {
|
||||
gdbRspDebugServer.handleGdbPacket(*this);
|
||||
}
|
||||
38
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.hpp
Normal file
38
src/DebugServers/GdbRsp/CommandPackets/WriteMemory.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "CommandPacket.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::CommandPackets
|
||||
{
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using Bloom::Targets::TargetMemoryBuffer;
|
||||
|
||||
/**
|
||||
* The WriteMemory class implements the structure for "M" packets. Upon receiving this packet, the server is
|
||||
* expected to write data to the target's memory, at the specified start address.
|
||||
*/
|
||||
class WriteMemory: public CommandPacket
|
||||
{
|
||||
private:
|
||||
void init();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Like with the ReadMemory command packet, the start address carries additional bits that indicate
|
||||
* the memory type.
|
||||
*/
|
||||
std::uint32_t startAddress;
|
||||
|
||||
TargetMemoryBuffer buffer;
|
||||
|
||||
WriteMemory(std::vector<unsigned char> rawPacket) : CommandPacket(rawPacket) {
|
||||
init();
|
||||
};
|
||||
|
||||
virtual void dispatchToHandler(Gdb::GdbRspDebugServer& gdbRspDebugServer) override;
|
||||
};
|
||||
}
|
||||
208
src/DebugServers/GdbRsp/Connection.cpp
Normal file
208
src/DebugServers/GdbRsp/Connection.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "src/Logger/Logger.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/DebugServerInterrupted.hpp"
|
||||
#include "Connection.hpp"
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "CommandPackets/CommandPacketFactory.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using namespace Bloom::DebugServers::Gdb::Exceptions;
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
void Connection::accept(int serverSocketFileDescriptor) {
|
||||
int socketAddressLength = sizeof(this->socketAddress);
|
||||
|
||||
this->socketFileDescriptor = ::accept(
|
||||
serverSocketFileDescriptor,
|
||||
(sockaddr*)& (this->socketAddress),
|
||||
(socklen_t*)& socketAddressLength
|
||||
);
|
||||
|
||||
if (this->socketFileDescriptor == -1) {
|
||||
throw Exception("Failed to accept GDB Remote Serial Protocol connection");
|
||||
}
|
||||
|
||||
::fcntl(
|
||||
this->socketFileDescriptor,
|
||||
F_SETFL,
|
||||
fcntl(this->socketFileDescriptor, F_GETFL, 0) | O_NONBLOCK
|
||||
);
|
||||
|
||||
// Create event FD
|
||||
this->eventFileDescriptor = ::epoll_create(2);
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = this->socketFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->socketFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed to create event FD for GDB client connection - could not add client connection "
|
||||
"socket FD to epoll FD");
|
||||
}
|
||||
|
||||
this->enableReadInterrupts();
|
||||
}
|
||||
|
||||
void Connection::disableReadInterrupts() {
|
||||
if (::epoll_ctl(
|
||||
this->eventFileDescriptor,
|
||||
EPOLL_CTL_DEL,
|
||||
this->interruptEventNotifier->getFileDescriptor(),
|
||||
NULL) != 0
|
||||
) {
|
||||
throw Exception("Failed to disable GDB client connection read interrupts - epoll_ctl failed");
|
||||
}
|
||||
|
||||
this->readInterruptEnabled = false;
|
||||
}
|
||||
|
||||
void Connection::enableReadInterrupts() {
|
||||
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = interruptFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed to enable GDB client connection read interrupts - epoll_ctl failed");
|
||||
}
|
||||
|
||||
this->readInterruptEnabled = true;
|
||||
}
|
||||
|
||||
void Connection::close() noexcept {
|
||||
if (this->socketFileDescriptor > 0) {
|
||||
::close(this->socketFileDescriptor);
|
||||
this->socketFileDescriptor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::write(const std::vector<unsigned char>& buffer) {
|
||||
Logger::debug("Writing packet: " + std::string(buffer.begin(), buffer.end()));
|
||||
if (::write(this->socketFileDescriptor, buffer.data(), buffer.size()) == -1) {
|
||||
if (errno == EPIPE || errno == ECONNRESET) {
|
||||
// Connection was closed
|
||||
throw ClientDisconnected();
|
||||
|
||||
} else {
|
||||
throw ClientCommunicationError("Failed to write " + std::to_string(buffer.size())
|
||||
+ " bytes to GDP client socket - error no: " + std::to_string(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::writePacket(const ResponsePacket& packet) {
|
||||
// Write the packet repeatedly until the GDB client acknowledges it.
|
||||
int attempts = 0;
|
||||
auto rawPacket = packet.toRawPacket();
|
||||
|
||||
do {
|
||||
if (attempts > 10) {
|
||||
throw ClientCommunicationError("Failed to write GDB response packet - client failed to "
|
||||
"acknowledge receipt - retry limit reached");
|
||||
}
|
||||
|
||||
this->write(rawPacket);
|
||||
attempts++;
|
||||
|
||||
} while(this->readSingleByte(false).value_or(0) != '+');
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Connection::read(size_t bytes, bool interruptible, std::optional<int> msTimeout) {
|
||||
auto output = std::vector<unsigned char>();
|
||||
constexpr size_t bufferSize = 1024;
|
||||
std::array<unsigned char, bufferSize> buffer;
|
||||
ssize_t bytesRead;
|
||||
|
||||
if (interruptible) {
|
||||
if (this->readInterruptEnabled != interruptible) {
|
||||
this->enableReadInterrupts();
|
||||
} else {
|
||||
// Clear any previous interrupts that are still hanging around
|
||||
this->interruptEventNotifier->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->readInterruptEnabled != interruptible && !interruptible) {
|
||||
this->disableReadInterrupts();
|
||||
}
|
||||
|
||||
std::array<struct epoll_event, 1> events = {};
|
||||
|
||||
int eventCount = ::epoll_wait(
|
||||
this->eventFileDescriptor,
|
||||
events.data(),
|
||||
1,
|
||||
msTimeout.value_or(-1)
|
||||
);
|
||||
|
||||
if (eventCount > 0) {
|
||||
for (size_t i = 0; i < eventCount; i++) {
|
||||
auto fileDescriptor = events[i].data.fd;
|
||||
|
||||
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
|
||||
// Interrupted
|
||||
this->interruptEventNotifier->clear();
|
||||
throw DebugServerInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
size_t bytesToRead = (bytes > bufferSize || bytes == 0) ? bufferSize : bytes;
|
||||
while (bytesToRead > 0 && (bytesRead = ::read(this->socketFileDescriptor, buffer.data(), bytesToRead)) > 0) {
|
||||
output.insert(output.end(), buffer.begin(), buffer.begin() + bytesRead);
|
||||
|
||||
if (bytesRead < bytesToRead) {
|
||||
// No more data available
|
||||
break;
|
||||
}
|
||||
|
||||
bytesToRead = ((bytes - output.size()) > bufferSize || bytes == 0) ? bufferSize : (bytes - output.size());
|
||||
}
|
||||
|
||||
if (output.empty()) {
|
||||
// EOF means the client has disconnected
|
||||
throw ClientDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
std::optional<unsigned char> Connection::readSingleByte(bool interruptible) {
|
||||
auto bytes = this->read(1, interruptible, 300);
|
||||
|
||||
if (!bytes.empty()) {
|
||||
return bytes.front();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<CommandPacket>> Connection::readPackets() {
|
||||
auto buffer = this->read();
|
||||
Logger::debug("GDB client data received (" + std::to_string(buffer.size()) + " bytes): " + std::string(buffer.begin(), buffer.end()));
|
||||
|
||||
auto rawPackets = CommandPacketFactory::extractRawPackets(buffer);
|
||||
std::vector<std::unique_ptr<CommandPacket>> output;
|
||||
|
||||
for (const auto& rawPacket : rawPackets) {
|
||||
try {
|
||||
output.push_back(CommandPacketFactory::create(rawPacket));
|
||||
this->write({'+'});
|
||||
|
||||
} catch (const ClientDisconnected& exception) {
|
||||
throw exception;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to parse GDB packet - " + exception.getMessage());
|
||||
this->write({'-'});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
137
src/DebugServers/GdbRsp/Connection.hpp
Normal file
137
src/DebugServers/GdbRsp/Connection.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <netinet/in.h>
|
||||
#include <queue>
|
||||
#include <array>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/DebugServers/GdbRsp/CommandPackets/CommandPacket.hpp"
|
||||
#include "src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using namespace CommandPackets;
|
||||
using namespace ResponsePackets;
|
||||
|
||||
/**
|
||||
* The Connection class represents an active connection between the GDB RSP server and client.
|
||||
*
|
||||
* All interfacing with the GDB client should take place here.
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
private:
|
||||
int socketFileDescriptor = -1;
|
||||
int eventFileDescriptor = -1;
|
||||
|
||||
struct sockaddr_in socketAddress = {};
|
||||
int maxPacketSize = 1024;
|
||||
|
||||
/**
|
||||
* The interruptEventNotifier allows us to interrupt blocking IO calls on the GDB debug server.
|
||||
* Under the hood, this is just a wrapper for a Linux event notifier. See the EventNotifier class for more.
|
||||
*/
|
||||
std::shared_ptr<EventNotifier> interruptEventNotifier = nullptr;
|
||||
bool readInterruptEnabled = false;
|
||||
|
||||
/**
|
||||
* Reads data from the client into a raw buffer.
|
||||
*
|
||||
* @param bytes
|
||||
* Number of bytes to read.
|
||||
*
|
||||
* @param interruptible
|
||||
* If this flag is set to false, no other component within Bloom will be able to gracefully interrupt
|
||||
* the read (via means of this->interruptEventNotifier). This flag has no affect if this->readInterruptEnabled
|
||||
* is false.
|
||||
*
|
||||
* @param msTimeout
|
||||
* The timeout in milliseconds. If not supplied, no timeout will be applied.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> read(std::size_t bytes = 0, bool interruptible = true, std::optional<int> msTimeout = std::nullopt);
|
||||
|
||||
/**
|
||||
* Does the same as Connection::read(), but only reads a single byte.
|
||||
*
|
||||
* @param interruptible
|
||||
* See Connection::read().
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::optional<unsigned char> readSingleByte(bool interruptible = true);
|
||||
|
||||
/**
|
||||
* Writes data from a raw buffer to the client connection.
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void write(const std::vector<unsigned char>& buffer);
|
||||
|
||||
void disableReadInterrupts();
|
||||
|
||||
void enableReadInterrupts();
|
||||
|
||||
public:
|
||||
/**
|
||||
* When the GDB client is waiting for the target to reach a breakpoint, this is set to true so we know when to
|
||||
* notify the client.
|
||||
*
|
||||
* @TODO: This is pretty gross. Consider rethinking it.
|
||||
*/
|
||||
bool waitingForBreak = false;
|
||||
|
||||
Connection(std::shared_ptr<EventNotifier> interruptEventNotifier)
|
||||
: interruptEventNotifier(interruptEventNotifier) {};
|
||||
|
||||
/**
|
||||
* Accepts a connection on serverSocketFileDescriptor.
|
||||
*
|
||||
* @param serverSocketFileDescriptor
|
||||
*/
|
||||
void accept(int serverSocketFileDescriptor);
|
||||
|
||||
/**
|
||||
* Closes the connection with the client.
|
||||
*/
|
||||
void close() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the human readable IP address of the connected client.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::string getIpAddress() {
|
||||
std::array<char, INET_ADDRSTRLEN> ipAddress;
|
||||
|
||||
if (::inet_ntop(AF_INET, &(socketAddress.sin_addr), ipAddress.data(), INET_ADDRSTRLEN) == nullptr) {
|
||||
throw Exceptions::Exception("Failed to convert client IP address to text form.");
|
||||
}
|
||||
|
||||
return std::string(ipAddress.data());
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for incoming data from the client and returns any received command packets.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<std::unique_ptr<CommandPacket>> readPackets();
|
||||
|
||||
/**
|
||||
* Sends a response packet to the client.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
void writePacket(const ResponsePacket& packet);
|
||||
|
||||
int getMaxPacketSize() {
|
||||
return this->maxPacketSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
/**
|
||||
* In the event that communication between the GDB RSP client and Bloom fails, a ClientCommunicationFailure
|
||||
* exception should be thrown. The GDB debug server handles this by severing the connection.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientCommunicationError: public Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientCommunicationError(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientCommunicationError() = default;
|
||||
};
|
||||
}
|
||||
29
src/DebugServers/GdbRsp/Exceptions/ClientDisconnected.hpp
Normal file
29
src/DebugServers/GdbRsp/Exceptions/ClientDisconnected.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
/**
|
||||
* When a GDB RSP client unexpectedly drops the connection in the middle of an IO operation, a ClientDisconnected
|
||||
* exception should be thrown. The GDB debug server handles this by clearing the connection and waiting for a new
|
||||
* one.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientDisconnected: public Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientDisconnected(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientDisconnected(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientDisconnected() = default;
|
||||
};
|
||||
}
|
||||
28
src/DebugServers/GdbRsp/Exceptions/ClientNotSupported.hpp
Normal file
28
src/DebugServers/GdbRsp/Exceptions/ClientNotSupported.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::Exceptions
|
||||
{
|
||||
using namespace Bloom::Exceptions;
|
||||
|
||||
/**
|
||||
* In the event that the GDB debug server determines that the connected client cannot be served,
|
||||
* the ClientNotSupported exception should be thrown.
|
||||
*
|
||||
* See GdbRspDebugServer::serve() for handling code.
|
||||
*/
|
||||
class ClientNotSupported: public Exception
|
||||
{
|
||||
public:
|
||||
explicit ClientNotSupported(const std::string& message) : Exception(message) {
|
||||
this->message = message;
|
||||
}
|
||||
|
||||
explicit ClientNotSupported(const char* message) : Exception(message) {
|
||||
this->message = std::string(message);
|
||||
}
|
||||
|
||||
explicit ClientNotSupported() = default;
|
||||
};
|
||||
}
|
||||
23
src/DebugServers/GdbRsp/Feature.hpp
Normal file
23
src/DebugServers/GdbRsp/Feature.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class Feature: int
|
||||
{
|
||||
SOFTWARE_BREAKPOINTS,
|
||||
HARDWARE_BREAKPOINTS,
|
||||
PACKET_SIZE,
|
||||
MEMORY_MAP_READ,
|
||||
};
|
||||
|
||||
static inline BiMap<Feature, std::string> getGdbFeatureToNameMapping() {
|
||||
return BiMap<Feature, std::string>{
|
||||
{Feature::HARDWARE_BREAKPOINTS, "hwbreak"},
|
||||
{Feature::SOFTWARE_BREAKPOINTS, "swbreak"},
|
||||
{Feature::PACKET_SIZE, "PacketSize"},
|
||||
{Feature::MEMORY_MAP_READ, "qXfer:memory-map:read"},
|
||||
};
|
||||
}
|
||||
}
|
||||
404
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
Normal file
404
src/DebugServers/GdbRsp/GdbRspDebugServer.cpp
Normal file
@@ -0,0 +1,404 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "GdbRspDebugServer.hpp"
|
||||
#include "Exceptions/ClientDisconnected.hpp"
|
||||
#include "Exceptions/ClientNotSupported.hpp"
|
||||
#include "Exceptions/ClientCommunicationError.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Exceptions/InvalidConfig.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb;
|
||||
using namespace Exceptions;
|
||||
|
||||
void GdbRspDebugServer::init() {
|
||||
auto ipAddress = this->debugServerConfig.jsonObject.find("ipAddress")->toString().toStdString();
|
||||
auto port = static_cast<std::uint16_t>(this->debugServerConfig.jsonObject.find("port")->toInt());
|
||||
|
||||
if (!ipAddress.empty()) {
|
||||
this->listeningAddress = ipAddress;
|
||||
}
|
||||
|
||||
if (port > 0) {
|
||||
this->listeningPortNumber = port;
|
||||
}
|
||||
|
||||
this->socketAddress.sin_family = AF_INET;
|
||||
this->socketAddress.sin_port = htons(this->listeningPortNumber);
|
||||
|
||||
if (::inet_pton(AF_INET, this->listeningAddress.c_str(), &(this->socketAddress.sin_addr)) == 0) {
|
||||
// Invalid IP address
|
||||
throw InvalidConfig("Invalid IP address provided in config file: (\"" + this->listeningAddress + "\")");
|
||||
}
|
||||
|
||||
int socketFileDescriptor;
|
||||
|
||||
if ((socketFileDescriptor = ::socket(AF_INET, SOCK_STREAM, 0)) == 0) {
|
||||
throw Exception("Failed to create socket file descriptor.");
|
||||
}
|
||||
|
||||
if (::setsockopt(
|
||||
socketFileDescriptor,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
&this->enableReuseAddressSocketOption,
|
||||
sizeof(this->enableReuseAddressSocketOption)) < 0
|
||||
) {
|
||||
Logger::error("Failed to set socket SO_REUSEADDR option.");
|
||||
}
|
||||
|
||||
if (::bind(
|
||||
socketFileDescriptor,
|
||||
reinterpret_cast<const sockaddr*>(&(this->socketAddress)),
|
||||
sizeof(this->socketAddress)
|
||||
) < 0
|
||||
) {
|
||||
throw Exception("Failed to bind address.");
|
||||
}
|
||||
|
||||
this->serverSocketFileDescriptor = socketFileDescriptor;
|
||||
|
||||
this->eventFileDescriptor = ::epoll_create(2);
|
||||
struct epoll_event event = {};
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = this->serverSocketFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, this->serverSocketFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed epoll_ctl server socket");
|
||||
}
|
||||
|
||||
if (this->interruptEventNotifier != nullptr) {
|
||||
auto interruptFileDescriptor = this->interruptEventNotifier->getFileDescriptor();
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = interruptFileDescriptor;
|
||||
|
||||
if (::epoll_ctl(this->eventFileDescriptor, EPOLL_CTL_ADD, interruptFileDescriptor, &event) != 0) {
|
||||
throw Exception("Failed epoll_ctl interrupt event fd");
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info("GDB RSP address: " + this->listeningAddress);
|
||||
Logger::info("GDB RSP port: " + std::to_string(this->listeningPortNumber));
|
||||
|
||||
this->eventListener->registerCallbackForEventType<Events::TargetExecutionStopped>(
|
||||
std::bind(&GdbRspDebugServer::onTargetExecutionStopped, this, std::placeholders::_1)
|
||||
);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::serve() {
|
||||
try {
|
||||
if (!this->clientConnection.has_value()) {
|
||||
Logger::info("Waiting for GDB RSP connection");
|
||||
this->waitForConnection();
|
||||
}
|
||||
|
||||
auto packets = this->clientConnection->readPackets();
|
||||
|
||||
for (auto& packet : packets) {
|
||||
// Double-dispatch to appropriate handler
|
||||
packet->dispatchToHandler(*this);
|
||||
}
|
||||
|
||||
} catch (const ClientDisconnected&) {
|
||||
Logger::info("GDB RSP client disconnected");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const ClientCommunicationError& exception) {
|
||||
Logger::error("GDB client communication error - " + exception.getMessage() + " - closing connection");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const ClientNotSupported& exception) {
|
||||
Logger::error("Invalid GDB client - " + exception.getMessage() + " - closing connection");
|
||||
this->closeClientConnection();
|
||||
return;
|
||||
|
||||
} catch (const DebugServerInterrupted&) {
|
||||
// Server was interrupted
|
||||
Logger::debug("GDB RSP interrupted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::waitForConnection() {
|
||||
if (::listen(this->serverSocketFileDescriptor, 3) != 0) {
|
||||
throw Exception("Failed to listen on server socket");
|
||||
}
|
||||
|
||||
std::array<struct epoll_event, 5> events = {};
|
||||
int eventCount = ::epoll_wait(
|
||||
this->eventFileDescriptor,
|
||||
events.data(),
|
||||
5,
|
||||
-1
|
||||
);
|
||||
|
||||
if (eventCount > 0) {
|
||||
for (size_t i = 0; i < eventCount; i++) {
|
||||
auto fileDescriptor = events[i].data.fd;
|
||||
|
||||
if (fileDescriptor == this->interruptEventNotifier->getFileDescriptor()) {
|
||||
// Interrupted
|
||||
this->interruptEventNotifier->clear();
|
||||
throw DebugServerInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
this->clientConnection = Connection(this->interruptEventNotifier);
|
||||
this->clientConnection->accept(this->serverSocketFileDescriptor);
|
||||
|
||||
Logger::info("Accepted GDP RSP connection from " + this->clientConnection->getIpAddress());
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionStarted>());
|
||||
} else {
|
||||
// This method should not return until a connection has been established (or an exception is thrown)
|
||||
return this->waitForConnection();
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::close() {
|
||||
this->closeClientConnection();
|
||||
|
||||
if (this->serverSocketFileDescriptor > 0) {
|
||||
::close(this->serverSocketFileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPacket& packet) {
|
||||
auto packetData = packet.getData();
|
||||
auto packetString = std::string(packetData.begin(), packetData.end());
|
||||
|
||||
if (packetString[0] == '?') {
|
||||
// Status report
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
|
||||
|
||||
} else if (packetString.find("qAttached") == 0) {
|
||||
Logger::debug("Handling qAttached");
|
||||
this->clientConnection->writePacket(ResponsePacket({1}));
|
||||
|
||||
} else {
|
||||
Logger::debug("Unknown GDB RSP packet: " + packetString + " - returning empty response");
|
||||
|
||||
// Respond with an empty packet
|
||||
this->clientConnection->writePacket(ResponsePacket({0}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>) {
|
||||
if (this->clientConnection.has_value() && this->clientConnection->waitingForBreak) {
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::TRAP));
|
||||
this->clientConnection->waitingForBreak = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet) {
|
||||
Logger::debug("Handling QuerySupport packet");
|
||||
|
||||
if (!packet.isFeatureSupported(Feature::HARDWARE_BREAKPOINTS)
|
||||
&& !packet.isFeatureSupported(Feature::SOFTWARE_BREAKPOINTS)) {
|
||||
// All GDB clients are expected to support breakpoints!
|
||||
throw ClientNotSupported("GDB client does not support HW or SW breakpoints");
|
||||
}
|
||||
|
||||
// Respond with a SupportedFeaturesResponse packet, listing all supported GDB features by Bloom
|
||||
auto response = ResponsePackets::SupportedFeaturesResponse({
|
||||
{Feature::SOFTWARE_BREAKPOINTS, std::nullopt},
|
||||
{Feature::PACKET_SIZE, std::to_string(this->clientConnection->getMaxPacketSize())},
|
||||
});
|
||||
|
||||
this->clientConnection->writePacket(response);
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet) {
|
||||
Logger::debug("Handling ReadGeneralRegisters packet");
|
||||
|
||||
try {
|
||||
auto descriptors = TargetRegisterDescriptors();
|
||||
|
||||
if (packet.registerNumber.has_value()) {
|
||||
Logger::debug("Reading register number: " + std::to_string(packet.registerNumber.value()));
|
||||
descriptors.push_back(this->getRegisterDescriptorFromNumber(packet.registerNumber.value()));
|
||||
|
||||
} else {
|
||||
// Read all descriptors
|
||||
auto descriptorMapping = this->getRegisterNumberToDescriptorMapping();
|
||||
for (auto& descriptor : descriptorMapping.getMap()) {
|
||||
descriptors.push_back(descriptor.second);
|
||||
}
|
||||
}
|
||||
|
||||
auto registerSet = this->readGeneralRegistersFromTarget(descriptors);
|
||||
auto registerNumberToDescriptorMapping = this->getRegisterNumberToDescriptorMapping();
|
||||
|
||||
/*
|
||||
* Remove any registers that are not mapped to GDB register numbers (as we won't know where to place
|
||||
* them in our response to GDB). All registers that are expected from the GDB client should be mapped
|
||||
* to register numbers.
|
||||
*
|
||||
* Registers that are not mapped to a GDB register number are presumed to be unknown to GDB, so GDB shouldn't
|
||||
* complain about not receiving them.
|
||||
*/
|
||||
registerSet.erase(
|
||||
std::remove_if(
|
||||
registerSet.begin(),
|
||||
registerSet.end(),
|
||||
[®isterNumberToDescriptorMapping](const TargetRegister& reg) {
|
||||
return !registerNumberToDescriptorMapping.contains(reg.descriptor);
|
||||
}
|
||||
),
|
||||
registerSet.end()
|
||||
);
|
||||
|
||||
/*
|
||||
* Sort each register by their respective GDB register number - this will leave us with a collection of
|
||||
* registers in the order expected by the GDB client.
|
||||
*/
|
||||
std::sort(
|
||||
registerSet.begin(),
|
||||
registerSet.end(),
|
||||
[this, ®isterNumberToDescriptorMapping](const TargetRegister& registerA, const TargetRegister& registerB) {
|
||||
return registerNumberToDescriptorMapping.valueAt(registerA.descriptor) <
|
||||
registerNumberToDescriptorMapping.valueAt(registerB.descriptor);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Finally, implode the register values, convert to hexadecimal form and send to the GDB client.
|
||||
*/
|
||||
auto registers = std::vector<unsigned char>();
|
||||
for (const auto& reg : registerSet) {
|
||||
registers.insert(registers.end(), reg.value.begin(), reg.value.end());
|
||||
}
|
||||
|
||||
auto responseRegisters = Packet::dataToHex(registers);
|
||||
this->clientConnection->writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(responseRegisters.begin(), responseRegisters.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read general registers - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet) {
|
||||
Logger::debug("Handling WriteGeneralRegisters packet");
|
||||
|
||||
try {
|
||||
auto registerDescriptor = this->getRegisterDescriptorFromNumber(packet.registerNumber);
|
||||
this->writeGeneralRegistersToTarget({TargetRegister(registerDescriptor, packet.registerValue)});
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write general registers - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ContinueExecution& packet) {
|
||||
Logger::debug("Handling ContinueExecution packet");
|
||||
|
||||
try {
|
||||
this->continueTargetExecution(packet.fromProgramCounter);
|
||||
this->clientConnection->waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to continue execution on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::StepExecution& packet) {
|
||||
Logger::debug("Handling StepExecution packet");
|
||||
|
||||
try {
|
||||
this->stepTargetExecution(packet.fromProgramCounter);
|
||||
this->clientConnection->waitingForBreak = true;
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to step execution on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::ReadMemory& packet) {
|
||||
Logger::debug("Handling ReadMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
|
||||
auto memoryBuffer = this->readMemoryFromTarget(memoryType, startAddress, packet.bytes);
|
||||
|
||||
auto hexMemoryBuffer = Packet::dataToHex(memoryBuffer);
|
||||
this->clientConnection->writePacket(
|
||||
ResponsePacket(std::vector<unsigned char>(hexMemoryBuffer.begin(), hexMemoryBuffer.end()))
|
||||
);
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to read memory from target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::WriteMemory& packet) {
|
||||
Logger::debug("Handling WriteMemory packet");
|
||||
|
||||
try {
|
||||
auto memoryType = this->getMemoryTypeFromGdbAddress(packet.startAddress);
|
||||
auto startAddress = this->removeMemoryTypeIndicatorFromGdbAddress(packet.startAddress);
|
||||
this->writeMemoryToTarget(memoryType, startAddress, packet.buffer);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to write memory two target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::SetBreakpoint& packet) {
|
||||
Logger::debug("Handling SetBreakpoint packet");
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = packet.address;
|
||||
this->setBreakpointOnTarget(breakpoint);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to set breakpoint on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::RemoveBreakpoint& packet) {
|
||||
Logger::debug("Removing breakpoint at address " + std::to_string(packet.address));
|
||||
|
||||
try {
|
||||
auto breakpoint = TargetBreakpoint();
|
||||
breakpoint.address = packet.address;
|
||||
this->removeBreakpointOnTarget(breakpoint);
|
||||
|
||||
this->clientConnection->writePacket(ResponsePacket({'O', 'K'}));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to remove breakpoint on target - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
|
||||
void GdbRspDebugServer::handleGdbPacket(CommandPackets::InterruptExecution& packet) {
|
||||
Logger::debug("Handling InterruptExecution packet");
|
||||
|
||||
try {
|
||||
this->stopTargetExecution();
|
||||
this->clientConnection->writePacket(TargetStopped(Signal::INTERRUPTED));
|
||||
|
||||
} catch (const Exception& exception) {
|
||||
Logger::error("Failed to interrupt execution - " + exception.getMessage());
|
||||
this->clientConnection->writePacket(ResponsePacket({'E', '0', '1'}));
|
||||
}
|
||||
}
|
||||
257
src/DebugServers/GdbRsp/GdbRspDebugServer.hpp
Normal file
257
src/DebugServers/GdbRsp/GdbRspDebugServer.hpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../DebugServer.hpp"
|
||||
#include "Connection.hpp"
|
||||
#include "Signal.hpp"
|
||||
#include "Register.hpp"
|
||||
#include "Feature.hpp"
|
||||
#include "src/Helpers/EventNotifier.hpp"
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
#include "CommandPackets/CommandPacketFactory.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
// Response packets
|
||||
#include "ResponsePackets/SupportedFeaturesResponse.hpp"
|
||||
#include "ResponsePackets/TargetStopped.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterType;
|
||||
using Bloom::Targets::TargetRegisterDescriptor;
|
||||
|
||||
/**
|
||||
* The GdbRspDebugServer is an implementation of a GDB server using the GDB Remote Serial Protocol.
|
||||
*
|
||||
* This DebugServer employs TCP/IP sockets to interface with GDB clients. The listening address can be configured
|
||||
* in the user's project config file.
|
||||
*
|
||||
* See https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html for more info on the GDB Remote
|
||||
* Serial Protocol.
|
||||
*
|
||||
* @TODO: This could do with some cleaning.
|
||||
*/
|
||||
class GdbRspDebugServer: public DebugServer
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* The port number for the GDB server to listen on.
|
||||
*
|
||||
* This will be pulled from the user's project configuration, if set. Otherwise it will default to whatever is
|
||||
* set here.
|
||||
*/
|
||||
std::uint16_t listeningPortNumber = 1055;
|
||||
|
||||
/**
|
||||
* The address for the GDB server to listen on.
|
||||
*
|
||||
* Like the port number, this can also be pulled from the user's project configuration.
|
||||
*/
|
||||
std::string listeningAddress = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* Listening socket address
|
||||
*/
|
||||
struct sockaddr_in socketAddress = {};
|
||||
|
||||
/**
|
||||
* Listening socket file descriptor
|
||||
*/
|
||||
int serverSocketFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* We don't listen on the this->serverSocketFileDescriptor directly. Instead, we add it to an epoll set, along
|
||||
* with the this->interruptEventNotifier FD. This allows us to interrupt any blocking socket IO calls when
|
||||
* we have other things to do.
|
||||
*
|
||||
* See GdbRspDebugServer::init()
|
||||
* See DebugServer::interruptEventNotifier
|
||||
* See EventNotifier
|
||||
*/
|
||||
int eventFileDescriptor = -1;
|
||||
|
||||
/**
|
||||
* SO_REUSEADDR option value for listening socket.
|
||||
*/
|
||||
int enableReuseAddressSocketOption = 1;
|
||||
|
||||
/**
|
||||
* The current active GDB client connection, if any.
|
||||
*/
|
||||
std::optional<Connection> clientConnection;
|
||||
|
||||
/**
|
||||
* Prepares the GDB server for listing on the selected address and port.
|
||||
*/
|
||||
void init() override;
|
||||
|
||||
/**
|
||||
* Closes any client connection as well as the listening socket file descriptor.
|
||||
*/
|
||||
void close() override;
|
||||
|
||||
/**
|
||||
* See DebugServer::serve()
|
||||
*/
|
||||
void serve() override;
|
||||
|
||||
/**
|
||||
* Waits for a GDB client to connect on the listening socket. Accepts the connection and
|
||||
* sets this->clientConnection.
|
||||
*/
|
||||
void waitForConnection();
|
||||
|
||||
void closeClientConnection() {
|
||||
if (this->clientConnection.has_value()) {
|
||||
this->clientConnection->close();
|
||||
this->clientConnection = std::nullopt;
|
||||
this->eventManager.triggerEvent(std::make_shared<Events::DebugSessionFinished>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GDB clients encode memory type information (flash, ram, eeprom, etc) in memory addresses. This is typically
|
||||
* hardcoded in the GDB client source. This method extracts memory type information from a given memory address.
|
||||
* The specifics of the encoding may vary with targets, which is why this method is virtual. For an example,
|
||||
* see the implementation of this method in AvrGdbRsp.
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
virtual TargetMemoryType getMemoryTypeFromGdbAddress(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Removes memory type information from memory address.
|
||||
* See comment for GdbRspDebugServer::getMemoryTypeFromGdbAddress()
|
||||
*
|
||||
* @param address
|
||||
* @return
|
||||
*/
|
||||
virtual std::uint32_t removeMemoryTypeIndicatorFromGdbAddress(std::uint32_t address) = 0;
|
||||
|
||||
/**
|
||||
* Like with the method of encoding memory type information onto memory addresses, GDB clients also expect
|
||||
* a pre-defined set of registers. The defined set being dependant on the target. This is hardcoded in the the
|
||||
* GDB client source. The order of the registers is also pre-defined in the GDB client.
|
||||
*
|
||||
* For an example, see the implementation of this method in the AvrGdbRsp class.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual BiMap<GdbRegisterNumber, TargetRegisterDescriptor> getRegisterNumberToDescriptorMapping() = 0;
|
||||
|
||||
/**
|
||||
* Obtains the appropriate register descriptor from a register number.
|
||||
*
|
||||
* @param number
|
||||
* @return
|
||||
*/
|
||||
virtual TargetRegisterDescriptor getRegisterDescriptorFromNumber(GdbRegisterNumber number) {
|
||||
auto mapping = this->getRegisterNumberToDescriptorMapping();
|
||||
|
||||
if (!mapping.contains(number)) {
|
||||
throw Exception("Unknown register from GDB - register number (" + std::to_string(number)
|
||||
+ ") not mapped to any register descriptor.");
|
||||
}
|
||||
|
||||
return mapping.valueAt(number).value();
|
||||
}
|
||||
|
||||
public:
|
||||
GdbRspDebugServer(EventManager& eventManager) : DebugServer(eventManager) {};
|
||||
|
||||
std::string getName() const override {
|
||||
return "GDB Remote Serial Protocol DebugServer";
|
||||
};
|
||||
|
||||
/**
|
||||
* If the GDB client is currently waiting for the target execution to stop, this event handler will issue
|
||||
* a "stop reply" packet to the client once the target execution stops.
|
||||
*/
|
||||
void onTargetExecutionStopped(EventPointer<Events::TargetExecutionStopped>);
|
||||
|
||||
/**
|
||||
* Handles any other GDB command packet that has not been promoted to a more specific type.
|
||||
* This would be packets like "?" and "qAttached".
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPacket& packet);
|
||||
|
||||
/**
|
||||
* Handles the supported features query ("qSupported") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::SupportedFeaturesQuery& packet);
|
||||
|
||||
/**
|
||||
* Handles the read registers ("g" and "p") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ReadGeneralRegisters& packet);
|
||||
|
||||
/**
|
||||
* Handles the write general registers ("G" and "P") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::WriteGeneralRegisters& packet);
|
||||
|
||||
/**
|
||||
* Handles the continue execution ("c") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ContinueExecution& packet);
|
||||
|
||||
/**
|
||||
* Handles the step execution ("s") packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::StepExecution& packet);
|
||||
|
||||
/**
|
||||
* Handles the read memory ("m") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::ReadMemory& packet);
|
||||
|
||||
/**
|
||||
* Handles the write memory ("M") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::WriteMemory& packet);
|
||||
|
||||
/**
|
||||
* Handles the set breakpoint ("Z") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::SetBreakpoint& packet);
|
||||
|
||||
/**
|
||||
* Handles the remove breakpoint ("z") command packet.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::RemoveBreakpoint& packet);
|
||||
|
||||
/**
|
||||
* Handles the interrupt command packet.
|
||||
* Will attempt to halt execution on the target. Should respond with a "stop reply" packet, or an error code.
|
||||
*
|
||||
* @param packet
|
||||
*/
|
||||
virtual void handleGdbPacket(CommandPackets::InterruptExecution& packet);
|
||||
};
|
||||
}
|
||||
123
src/DebugServers/GdbRsp/Packet.hpp
Normal file
123
src/DebugServers/GdbRsp/Packet.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <QString>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
/**
|
||||
* The Packet class implements the data structure for GDB RSP packets.
|
||||
*
|
||||
* Fore more information on the packet data structure, see https://sourceware.org/gdb/onlinedocs/gdb/Overview.html#Overview
|
||||
*/
|
||||
class Packet
|
||||
{
|
||||
protected:
|
||||
std::vector<unsigned char> data;
|
||||
|
||||
void init(const std::vector<unsigned char>& rawPacket) {
|
||||
this->data.insert(
|
||||
this->data.begin(),
|
||||
rawPacket.begin() + 1,
|
||||
rawPacket.end() - 3
|
||||
);
|
||||
}
|
||||
public:
|
||||
Packet() = default;
|
||||
Packet(const std::vector<unsigned char>& rawPacket) {
|
||||
this->init(rawPacket);
|
||||
}
|
||||
|
||||
virtual std::vector<unsigned char> getData() const {
|
||||
return this->data;
|
||||
}
|
||||
|
||||
void setData(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a raw packet.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
std::vector<unsigned char> toRawPacket() const {
|
||||
std::vector<unsigned char> packet = {'$'};
|
||||
auto data = this->getData();
|
||||
|
||||
for (const auto& byte : data) {
|
||||
// Escape $ and # characters
|
||||
switch (byte) {
|
||||
case '$':
|
||||
case '#': {
|
||||
packet.push_back('}');
|
||||
packet.push_back(byte ^ 0x20);
|
||||
}
|
||||
default: {
|
||||
packet.push_back(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto dataSum = std::accumulate(packet.begin() + 1, packet.end(), 0);
|
||||
packet.push_back('#');
|
||||
auto checkSum = QStringLiteral("%1").arg(dataSum % 256, 2, 16, QLatin1Char('0')).toStdString();
|
||||
|
||||
if (checkSum.size() < 2) {
|
||||
packet.push_back('0');
|
||||
|
||||
if (checkSum.size() < 1) {
|
||||
packet.push_back('0');
|
||||
} else {
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[0]));
|
||||
}
|
||||
}
|
||||
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[0]));
|
||||
packet.push_back(static_cast<unsigned char>(checkSum[1]));
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw data to hexadecimal form, the form in which responses are expected to be delivered from the
|
||||
* server.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
static std::string dataToHex(std::vector<unsigned char> data) {
|
||||
std::stringstream stream;
|
||||
stream << std::hex << std::setfill('0');
|
||||
|
||||
for (const auto& byte : data) {
|
||||
stream << std::setw(2) << static_cast<unsigned int>(byte);
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts data in hexadecimal form to raw data.
|
||||
*
|
||||
* @param hexData
|
||||
* @return
|
||||
*/
|
||||
static std::vector<unsigned char> hexToData(const std::string& hexData) {
|
||||
std::vector<unsigned char> output;
|
||||
|
||||
for (auto i = 0; i < hexData.size(); i += 2) {
|
||||
auto hexByte = std::string((hexData.begin() + i), (hexData.begin() + i + 2));
|
||||
output.push_back(static_cast<unsigned char>(std::stoi(hexByte, nullptr, 16)));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
virtual ~Packet() = default;
|
||||
};
|
||||
}
|
||||
6
src/DebugServers/GdbRsp/Register.hpp
Normal file
6
src/DebugServers/GdbRsp/Register.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
using GdbRegisterNumber = int;
|
||||
}
|
||||
25
src/DebugServers/GdbRsp/ResponsePackets/Ok.hpp
Normal file
25
src/DebugServers/GdbRsp/ResponsePackets/Ok.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb {
|
||||
enum class Feature;
|
||||
}
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* OK response packet expected by the GDB client, in response to certain commands.
|
||||
*/
|
||||
class Ok: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
Ok() = default;
|
||||
|
||||
std::vector<unsigned char> getData() const override {
|
||||
return {'O', 'K'};
|
||||
}
|
||||
};
|
||||
}
|
||||
22
src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp
Normal file
22
src/DebugServers/GdbRsp/ResponsePackets/ResponsePacket.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "src/DebugServers/GdbRsp/Packet.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* Upon receiving a CommandPacket from the connected GDB RSP client, the server is expected to respond with a
|
||||
* response packet.
|
||||
*/
|
||||
class ResponsePacket: public Packet
|
||||
{
|
||||
public:
|
||||
ResponsePacket() = default;
|
||||
explicit ResponsePacket(const std::vector<unsigned char>& data) {
|
||||
this->data = data;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "SupportedFeaturesResponse.hpp"
|
||||
|
||||
using namespace Bloom::DebugServers::Gdb::ResponsePackets;
|
||||
|
||||
std::vector<unsigned char> SupportedFeaturesResponse::getData() const {
|
||||
std::string output = "qSupported:";
|
||||
auto gdbFeatureMapping = getGdbFeatureToNameMapping();
|
||||
|
||||
for (const auto& supportedFeature : this->supportedFeatures) {
|
||||
auto featureString = gdbFeatureMapping.valueAt(supportedFeature.first);
|
||||
|
||||
if (featureString.has_value()) {
|
||||
if (supportedFeature.second.has_value()) {
|
||||
output.append(featureString.value() + "=" + supportedFeature.second.value() + ";");
|
||||
} else {
|
||||
output.append(featureString.value() + "+;");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Feature.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
/**
|
||||
* The SupportedFeaturesResponse class implements the response packet structure for the "qSupported" command.
|
||||
*/
|
||||
class SupportedFeaturesResponse: public ResponsePacket
|
||||
{
|
||||
protected:
|
||||
std::set<std::pair<Feature, std::optional<std::string>>> supportedFeatures;
|
||||
|
||||
public:
|
||||
SupportedFeaturesResponse() = default;
|
||||
SupportedFeaturesResponse(const std::set<std::pair<Feature, std::optional<std::string>>>& supportedFeatures)
|
||||
: supportedFeatures(supportedFeatures) {};
|
||||
|
||||
std::vector<unsigned char> getData() const override;
|
||||
};
|
||||
}
|
||||
47
src/DebugServers/GdbRsp/ResponsePackets/TargetStopped.hpp
Normal file
47
src/DebugServers/GdbRsp/ResponsePackets/TargetStopped.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "ResponsePacket.hpp"
|
||||
#include "../Signal.hpp"
|
||||
#include "../StopReason.hpp"
|
||||
#include "src/Targets/TargetRegister.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb::ResponsePackets
|
||||
{
|
||||
using Bloom::Targets::TargetRegisterMap;
|
||||
|
||||
/**
|
||||
* The TargetStopped class implements the response packet structure for any commands that expect a "StopReply"
|
||||
* packet in response.
|
||||
*/
|
||||
class TargetStopped: public ResponsePacket
|
||||
{
|
||||
public:
|
||||
Signal signal;
|
||||
std::optional<TargetRegisterMap> registerMap;
|
||||
std::optional<StopReason> stopReason;
|
||||
|
||||
TargetStopped(Signal signal) : signal(signal) {}
|
||||
|
||||
std::vector<unsigned char> getData() const override {
|
||||
std::string output = "T" + this->dataToHex({static_cast<unsigned char>(this->signal)});
|
||||
|
||||
if (this->stopReason.has_value()) {
|
||||
auto stopReasonMapping = getStopReasonToNameMapping();
|
||||
auto stopReasonName = stopReasonMapping.valueAt(this->stopReason.value());
|
||||
|
||||
if (stopReasonName.has_value()) {
|
||||
output += stopReasonName.value() + ":;";
|
||||
}
|
||||
}
|
||||
|
||||
if (this->registerMap.has_value()) {
|
||||
for (const auto& [registerId, registerValue] : this->registerMap.value()) {
|
||||
output += this->dataToHex({static_cast<unsigned char>(registerId)});
|
||||
output += ":" + this->dataToHex(registerValue.value) + ";";
|
||||
}
|
||||
}
|
||||
|
||||
return std::vector<unsigned char>(output.begin(), output.end());
|
||||
}
|
||||
};
|
||||
}
|
||||
10
src/DebugServers/GdbRsp/Signal.hpp
Normal file
10
src/DebugServers/GdbRsp/Signal.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class Signal: unsigned char
|
||||
{
|
||||
TRAP = 5,
|
||||
INTERRUPTED = 2,
|
||||
};
|
||||
}
|
||||
19
src/DebugServers/GdbRsp/StopReason.hpp
Normal file
19
src/DebugServers/GdbRsp/StopReason.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/Helpers/BiMap.hpp"
|
||||
|
||||
namespace Bloom::DebugServers::Gdb
|
||||
{
|
||||
enum class StopReason: int
|
||||
{
|
||||
SOFTWARE_BREAKPOINT = 0,
|
||||
HARDWARE_BREAKPOINT = 1,
|
||||
};
|
||||
|
||||
static inline BiMap<StopReason, std::string> getStopReasonToNameMapping() {
|
||||
return BiMap<StopReason, std::string>({
|
||||
{StopReason::HARDWARE_BREAKPOINT, "hwbreak"},
|
||||
{StopReason::SOFTWARE_BREAKPOINT, "swbreak"},
|
||||
});
|
||||
}
|
||||
}
|
||||
54
src/DebugToolDrivers/DebugTool.hpp
Normal file
54
src/DebugToolDrivers/DebugTool.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "TargetInterfaces/Microchip/AVR/AVR8/Avr8Interface.hpp"
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
using DebugToolDrivers::TargetInterfaces::Microchip::Avr::Avr8::Avr8Interface;
|
||||
|
||||
/**
|
||||
* A debug tool can be any device that provides access to the connected target. Debug tools are usually connected
|
||||
* to the host machine via USB.
|
||||
*
|
||||
* Each debug tool must implement this interface. Note that target specific driver code should not be placed here.
|
||||
* Each target family will expect the debug tool to provide an interface for that particular group of targets.
|
||||
* For an example, see the Avr8Interface class and the DebugTool::getAvr8Interface().
|
||||
*/
|
||||
class DebugTool
|
||||
{
|
||||
private:
|
||||
bool initialised = false;
|
||||
|
||||
protected:
|
||||
void setInitialised(bool initialised) {
|
||||
this->initialised = initialised;
|
||||
}
|
||||
|
||||
public:
|
||||
bool isInitialised() const {
|
||||
return this->initialised;
|
||||
}
|
||||
|
||||
virtual void init() = 0;
|
||||
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual std::string getName() = 0;
|
||||
|
||||
virtual std::string getSerialNumber() = 0;
|
||||
|
||||
/**
|
||||
* All debug tools that support AVR8 targets must provide an implementation of the Avr8Interface
|
||||
* class, via this method.
|
||||
*
|
||||
* For debug tools that do not support AVR8 targets, this method should return a nullptr.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual Avr8Interface* getAvr8Interface() {
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
virtual ~DebugTool() = default;
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user