diff --git a/cpu/nrf52/Makefile.include b/cpu/nrf52/Makefile.include index 15d5174ef16d..7b39179b9160 100644 --- a/cpu/nrf52/Makefile.include +++ b/cpu/nrf52/Makefile.include @@ -42,5 +42,8 @@ JLINK_DEVICE = $(subst xx,_xx,$(CPU_MODEL)) VECTORS_O ?= $(BINDIR)/nrf52_vectors/vectors_$(CPU_MODEL).o +SVD_VENDOR := Nordic +SVD_MODEL := $(patsubst %xxaa,%,$(CPU_MODEL)) + include $(RIOTCPU)/nrf5x_common/Makefile.include include $(RIOTMAKE)/arch/cortexm.inc.mk diff --git a/cpu/stm32/stm32_info.mk b/cpu/stm32/stm32_info.mk index 3bbf17b74ac3..d3bc5e0b572e 100644 --- a/cpu/stm32/stm32_info.mk +++ b/cpu/stm32/stm32_info.mk @@ -29,6 +29,7 @@ else endif CPU_FAM = $(call lowercase,$(STM32_TYPE)$(STM32_FAMILY)) +SVD_VENDOR := STMicro ifeq (f0,$(CPU_FAM)) CPU_CORE = cortex-m0 @@ -40,6 +41,7 @@ else ifneq (,$(filter $(CPU_FAM),g4 wb wl)) CPU_CORE = cortex-m4 else ifeq (f7,$(CPU_FAM)) CPU_CORE = cortex-m7 + SVD_MODEL := STM32F7x$(STM32_MODEL3) else ifneq (,$(filter $(CPU_FAM),g0 l0 c0)) CPU_CORE = cortex-m0plus else ifneq (,$(filter $(CPU_FAM),l5 u5)) diff --git a/dist/tools/PyCortexMDebug/.gitignore b/dist/tools/PyCortexMDebug/.gitignore new file mode 100644 index 000000000000..5e660dc18ee0 --- /dev/null +++ b/dist/tools/PyCortexMDebug/.gitignore @@ -0,0 +1 @@ +/checkout diff --git a/dist/tools/PyCortexMDebug/Makefile b/dist/tools/PyCortexMDebug/Makefile new file mode 100644 index 000000000000..b78c6a092df9 --- /dev/null +++ b/dist/tools/PyCortexMDebug/Makefile @@ -0,0 +1,16 @@ +PKG_NAME = PyCortexMDebug +PKG_URL = https://github.com/bnahill/PyCortexMDebug +PKG_VERSION = ce371500500a9168729f04b9dcad5f84440deadd +PKG_LICENSE = GPL-3.0-only + +# manually set some RIOT env vars, so this Makefile can be called stand-alone +RIOTBASE ?= $(CURDIR)/../../.. +RIOTTOOLS ?= $(CURDIR)/.. + +PKG_SOURCE_DIR = $(CURDIR)/checkout +PKG_BUILD_OUT_OF_SOURCE := 0 + +include $(RIOTBASE)/pkg/pkg.mk + +.PHONY: all +all: prepare diff --git a/dist/tools/PyCortexMDebug/README.md b/dist/tools/PyCortexMDebug/README.md new file mode 100644 index 000000000000..9a063b88f8dd --- /dev/null +++ b/dist/tools/PyCortexMDebug/README.md @@ -0,0 +1,25 @@ +PyCortexMDebug +============== + +This is the RIOT integration of [PyCortexMDebug][], a GDB extension that allows +inspecting and interpreting memory of MCUs based on the info provided in SVD +files. + +This integration adds PRs 58 and 59 on top of the upstream code. To get the +CMSIS database, run: + +```sh +wget -O ~/.cache/cmdebug/cmsis-svd-data.zip https://github.com/cmsis-svd/cmsis-svd-data/archive/refs/heads/main.zip +``` + +Automating Loading of Correct SVD File +--------------------------------------- + +If `SVD_MODEL` and `SVD_VENDOR` are declared, the build system will inject +an `svd_load $(SVD_MODEL) $(SVD_VENDOR)` command into GDB, so that users don't +need to call that themselves. + +Ideally, those variables are provided in `cpu/$(CPU)/Makefile.include` +by inferring it from the `$(CPU_MODEL)` variable each board already provides. + +[PyCortexMDebug]: https://github.com/bnahill/PyCortexMDebug diff --git a/dist/tools/PyCortexMDebug/patches/0001-cmdebug-LoadSVD-Fix-exception-in-complete.patch b/dist/tools/PyCortexMDebug/patches/0001-cmdebug-LoadSVD-Fix-exception-in-complete.patch new file mode 100644 index 000000000000..5cfe38905fb5 --- /dev/null +++ b/dist/tools/PyCortexMDebug/patches/0001-cmdebug-LoadSVD-Fix-exception-in-complete.patch @@ -0,0 +1,41 @@ +From 597434eab992cd3f9495434ed6b990c8c491d66b Mon Sep 17 00:00:00 2001 +From: Marian Buschsieweke +Date: Mon, 3 Nov 2025 23:11:09 +0100 +Subject: [PATCH] cmdebug: LoadSVD(): Fix exception in complete() + +The parameter `word` may actually be `None`, as shown by this +exception: + + (gdb) svd_load STraceback (most recent call last): + File "/usr/lib/python3.12/site-packages/cmdebug/svd_gdb.py", line 65, in complete + prefix = word.lower() + ^^^^^^^^^^ + AttributeError: 'NoneType' object has no attribute 'lower' + +This lets prefix fall back to `""` in case word is `None` to fix the +issue. +--- + cmdebug/svd_gdb.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/cmdebug/svd_gdb.py b/cmdebug/svd_gdb.py +index 4fb993b..c6e1e9c 100644 +--- a/cmdebug/svd_gdb.py ++++ b/cmdebug/svd_gdb.py +@@ -62,11 +62,11 @@ class LoadSVD(gdb.Command): + + # "svd_load " or "svd_load ST" + if num_args == 1: +- prefix = word.lower() ++ prefix = "" if word is None else word.lower() + return [vendor for vendor in self.vendors if vendor.lower().startswith(prefix)] + # "svd_load STMicro" or "svd_load STMicro STM32F1" + elif num_args == 2 and args[0] in self.vendors: +- prefix = word.lower() ++ prefix = "" if word is None else word.lower() + filenames = self.vendors[args[0]] + return [fname for fname in filenames if fname.lower().startswith(prefix)] + return gdb.COMPLETE_NONE +-- +2.51.2 + diff --git a/dist/tools/PyCortexMDebug/patches/0002-LoadSVD-Load-SVD-from-ZIP-file-instead-of-pkg_resour.patch b/dist/tools/PyCortexMDebug/patches/0002-LoadSVD-Load-SVD-from-ZIP-file-instead-of-pkg_resour.patch new file mode 100644 index 000000000000..51afe21140cc --- /dev/null +++ b/dist/tools/PyCortexMDebug/patches/0002-LoadSVD-Load-SVD-from-ZIP-file-instead-of-pkg_resour.patch @@ -0,0 +1,115 @@ +From 568ef712d68eb70574fbce5808fea63963aa908f Mon Sep 17 00:00:00 2001 +From: Marian Buschsieweke +Date: Sun, 9 Nov 2025 12:25:11 +0100 +Subject: [PATCH] LoadSVD: Load SVD from ZIP file instead of pkg_resources + +This changes the logic with `svd_load ` to not search in +the pkg_resources of Python module `cmsis_svd.data` but instead search +in a ZIP file at `$CMSIS_SVD_ZIPFILE` if that environment variable is +defined, otherwise it looks `$XDG_CACHE_HOME/cmdebug/cmsis-svd-data.zip` +with `$XDG_CACHE_HOME` falling back to `~/.cache` if not found in the +environment. + +As of 2025-11-09, the `HEAD` of https://github.com/cmsis-svd/cmsis-svd-data +is 5.8 GiB in size uncompressed, or 254 MiB when zipped. Personally, I +do not perceive any additional latency by having to unzip the chosen +SVD file first. +--- + cmdebug/svd_gdb.py | 52 +++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 42 insertions(+), 10 deletions(-) + +diff --git a/cmdebug/svd_gdb.py b/cmdebug/svd_gdb.py +index 4fb993b..33c7bec 100644 +--- a/cmdebug/svd_gdb.py ++++ b/cmdebug/svd_gdb.py +@@ -16,11 +16,15 @@ along with PyCortexMDebug. If not, see . + """ + + import gdb +-import re + import math +-import sys ++import pathlib ++import re + import struct +-import pkg_resources ++import sys ++import tempfile ++import zipfile ++ ++from os import environ + + sys.path.append('.') + from cmdebug.svd import SVDFile +@@ -31,6 +35,16 @@ BITS_TO_UNPACK_FORMAT = { + 32: "I", + } + ++if "XDG_CACHE_HOME" in environ and environ["XDG_CACHE_HOME"] != "": ++ CACHE_DIR = pathlib.Path.joinpath(environ["XDG_CACHE_HOME"], "cmdebug") ++else: ++ CACHE_DIR = pathlib.Path.joinpath(pathlib.Path.home(), ".cache", "cmdebug") ++ ++if "CMSIS_SVD_ZIPFILE" in environ and environ["CMSIS_SVD_ZIPFILE"] != "": ++ CMSIS_SVD_ZIPFILE = pathlib.Path(environ("CMSIS_SVD_ZIPFILE")) ++else: ++ CMSIS_SVD_ZIPFILE = pathlib.Path.joinpath(CACHE_DIR, "cmsis-svd-data.zip") ++ + + class LoadSVD(gdb.Command): + """ A command to load an SVD file and to create the command for inspecting +@@ -40,11 +54,16 @@ class LoadSVD(gdb.Command): + def __init__(self): + self.vendors = {} + try: +- vendor_names = pkg_resources.resource_listdir("cmsis_svd", "data") +- for vendor in vendor_names: +- fnames = pkg_resources.resource_listdir("cmsis_svd", "data/{}".format(vendor)) +- self.vendors[vendor] = [fname for fname in fnames if fname.lower().endswith(".svd")] +- except: ++ with zipfile.ZipFile(CMSIS_SVD_ZIPFILE, mode='r') as svdzip: ++ for entry in svdzip.namelist(): ++ if entry.lower().endswith(".svd"): ++ p = pathlib.Path(entry) ++ vendor = p.parent.name ++ model = p.name[:-4] ++ if vendor not in self.vendors: ++ self.vendors[vendor] = list() ++ self.vendors[vendor].append(model) ++ except FileNotFoundError: + pass + + if len(self.vendors) > 0: +@@ -75,14 +94,27 @@ class LoadSVD(gdb.Command): + def invoke(args, from_tty): + args = gdb.string_to_argv(args) + argc = len(args) ++ f = None + if argc == 1: + gdb.write("Loading SVD file {}...\n".format(args[0])) + f = args[0] + elif argc == 2: + gdb.write("Loading SVD file {}/{}...\n".format(args[0], args[1])) +- f = pkg_resources.resource_filename("cmsis_svd", "data/{}/{}".format(args[0], args[1])) ++ name = args[0].lower() + "/" + args[1].lower() + ".svd" ++ try: ++ with zipfile.ZipFile(CMSIS_SVD_ZIPFILE, mode='r') as svdzip: ++ for entry in svdzip.namelist(): ++ lower = entry.lower() ++ if lower == name or lower.endswith("/" + name): ++ f = svdzip.extract(entry, path=tempfile.TemporaryDirectory().name) ++ break ++ except FileNotFoundError: ++ raise gdb.GdbError(f"Could not open \"{CMSIS_SVD_ZIPFILE}\"") ++ ++ if f is None: ++ raise gdb.GdbError(f"Could not find SVD for \"{args[0]}/{args[1]}\" in \"{CMSIS_SVD_ZIPFILE}\": No case-insensitive match for \"**/{name}\"") + else: +- raise gdb.GdbError("Usage: svd_load or svd_load \n") ++ raise gdb.GdbError("Usage: svd_load or svd_load \n") + try: + SVD(SVDFile(f)) + except Exception as e: +-- +2.51.2 + diff --git a/dist/tools/jlink/jlink.sh b/dist/tools/jlink/jlink.sh index 2a41d1349791..a6dfbf9f30e7 100755 --- a/dist/tools/jlink/jlink.sh +++ b/dist/tools/jlink/jlink.sh @@ -68,10 +68,18 @@ # The setsid command is needed so that Ctrl+C in GDB doesn't kill OpenOCD : ${SETSID:=setsid} +# GDB command, usually a separate command for each platform (e.g. arm-none-eabi-gdb) +: ${GDB:=gdb} +# Debugger client command, can be used to wrap GDB in a front-end +: ${DBG:=${GDB}} +# Start with tui by default +: ${TUI:=-tui} +# Extra debugger flags, added by the user +: ${DBG_EXTRA_FLAGS:=} # default GDB port -_GDB_PORT=3333 +: ${GDB_PORT:=3333} # default telnet port -_TELNET_PORT=4444 +: ${TELNET_PORT:=4444} # default J-Link command names, interface and speed _JLINK=JLinkExe _JLINK_SERVER=JLinkGDBServer @@ -115,15 +123,6 @@ test_binfile() { fi } -test_ports() { - if [ -z "${GDB_PORT}" ]; then - GDB_PORT=${_GDB_PORT} - fi - if [ -z "${TELNET_PORT}" ]; then - TELNET_PORT=${_TELNET_PORT} - fi -} - test_elffile() { if [ ! -f "${ELFFILE}" ]; then echo "Error: Unable to locate ELFFILE" @@ -132,12 +131,6 @@ test_elffile() { fi } -test_tui() { - if [ -n "${TUI}" ]; then - TUI=-tui - fi -} - test_serial() { if [ -n "${JLINK_SERIAL}" ]; then JLINK_SERIAL_SERVER="-select usb='${JLINK_SERIAL}'" @@ -145,12 +138,6 @@ test_serial() { fi } -test_dbg() { - if [ -z "${DBG}" ]; then - DBG="${GDB}" - fi -} - test_term() { if [ -z "${JLINK_TERMPROG}" ]; then JLINK_TERMPROG="${_JLINK_TERMPROG}" @@ -246,9 +233,6 @@ do_debug() { test_serial test_version test_elffile - test_ports - test_tui - test_dbg # start the J-Link GDB server ${SETSID} sh -c "${JLINK_SERVER} ${JLINK_SERIAL_SERVER} \ -nogui \ @@ -261,7 +245,7 @@ do_debug() { # save PID for terminating the server afterwards DBG_PID=$! # connect to the GDB server - ${DBG} -q ${TUI} -ex "tar ext :${GDB_PORT}" ${ELFFILE} + sh -c "${DBG} -q ${TUI} -ex \"tar ext :${GDB_PORT}\" ${DBG_EXTRA_FLAGS} \"${ELFFILE}\"" # clean up kill ${DBG_PID} } @@ -270,7 +254,6 @@ do_debugserver() { test_config test_serial test_version - test_ports # start the J-Link GDB server sh -c "${JLINK_SERVER} ${JLINK_SERIAL_SERVER} \ -nogui \ diff --git a/makefiles/tools/dbg_common.inc.mk b/makefiles/tools/dbg_common.inc.mk new file mode 100644 index 000000000000..06ba82ff08ab --- /dev/null +++ b/makefiles/tools/dbg_common.inc.mk @@ -0,0 +1,11 @@ +# Control whether to use PyCortexMDebug +USE_PYCORTEXMDEBUG ?= 0 + +ifeq (1,$(USE_PYCORTEXMDEBUG)) + DEBUGDEPS += $(RIOTTOOLS)/PyCortexMDebug/checkout + DBG_EXTRA_FLAGS += --init-command='$(RIOTTOOLS)/PyCortexMDebug/checkout/scripts/gdb.py' + + ifneq (,$(SVD_MODEL)) + DBG_EXTRA_FLAGS += --eval-command='svd_load $(SVD_VENDOR) $(SVD_MODEL)' + endif +endif diff --git a/makefiles/tools/jlink.inc.mk b/makefiles/tools/jlink.inc.mk index 645e27257881..ce5d12f5764b 100644 --- a/makefiles/tools/jlink.inc.mk +++ b/makefiles/tools/jlink.inc.mk @@ -1,3 +1,5 @@ +include $(RIOTMAKE)/tools/dbg_common.inc.mk + FLASHER ?= $(RIOTTOOLS)/jlink/jlink.sh DEBUGGER ?= $(RIOTTOOLS)/jlink/jlink.sh DEBUGSERVER ?= $(RIOTTOOLS)/jlink/jlink.sh @@ -18,7 +20,8 @@ JLINK_PRE_FLASH ?= JLINK_POST_FLASH ?= JLINK_FLASH_TARGETS = flash flash% -JLINK_TARGETS = debug% $(JLINK_FLASH_TARGETS) reset term-rtt +JLINK_DEBUG_TARGETS = debug debug-server +JLINK_TARGETS = $(JLINK_DEBUG_TARGETS) $(JLINK_FLASH_TARGETS) reset term-rtt # Export JLINK_SERIAL to required targets $(call target-export-variables,$(JLINK_TARGETS),JLINK_SERIAL) @@ -50,3 +53,8 @@ endif ifneq (,$(JLINK_POST_FLASH)) $(call target-export-variables,JLINK_FLASH_TARGETS,JLINK_POST_FLASH) endif + +ifneq (,$(DBG_EXTRA_FLAGS)) + # Export OPENOCD_DBG_EXTRA_CMD only to the flash/flash-only target + $(call target-export-variables,$(JLINK_DEBUG_TARGETS),DBG_EXTRA_FLAGS) +endif diff --git a/makefiles/tools/openocd.inc.mk b/makefiles/tools/openocd.inc.mk index e8ace950a9c9..dda963b8010b 100644 --- a/makefiles/tools/openocd.inc.mk +++ b/makefiles/tools/openocd.inc.mk @@ -1,3 +1,5 @@ +include $(RIOTMAKE)/tools/dbg_common.inc.mk + FLASHER ?= $(RIOTTOOLS)/openocd/openocd.sh DEBUGGER ?= $(RIOTTOOLS)/openocd/openocd.sh DEBUGSERVER ?= $(RIOTTOOLS)/openocd/openocd.sh @@ -78,6 +80,11 @@ ifneq (,$(OPENOCD_DBG_EXTRA_CMD)) $(call target-export-variables,$(OPENOCD_DEBUG_TARGETS),OPENOCD_DBG_EXTRA_CMD) endif +ifneq (,$(DBG_EXTRA_FLAGS)) + # Export OPENOCD_DBG_EXTRA_CMD only to the flash/flash-only target + $(call target-export-variables,$(OPENOCD_DEBUG_TARGETS),DBG_EXTRA_FLAGS) +endif + OPENOCD_FLASH_TARGETS = flash flash-only flashr ifneq (,$(IMAGE_OFFSET)) diff --git a/makefiles/tools/targets.inc.mk b/makefiles/tools/targets.inc.mk index 36d642f6031c..345b4496da53 100644 --- a/makefiles/tools/targets.inc.mk +++ b/makefiles/tools/targets.inc.mk @@ -75,3 +75,8 @@ $(RIOTTOOLS)/pioasm/pioasm: $(RIOTTOOLS)/pioasm/Makefile @echo "[INFO] pioasm not found - fetching it from GitHub now" CC= CFLAGS= $(MAKE) -C $(RIOTTOOLS)/pioasm @echo "[INFO] pioasm successfully fetched!" + +$(RIOTTOOLS)/PyCortexMDebug/checkout: $(RIOTTOOLS)/PyCortexMDebug/Makefile + @echo "[INFO] PyCortexMDebug not found - fetching it from GitHub now" + CC= CFLAGS= $(MAKE) -C $(RIOTTOOLS)/PyCortexMDebug + @echo "[INFO] PyCortexMDebug successfully fetched!"