Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cpu/nrf52/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions cpu/stm32/stm32_info.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down
1 change: 1 addition & 0 deletions dist/tools/PyCortexMDebug/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/checkout
16 changes: 16 additions & 0 deletions dist/tools/PyCortexMDebug/Makefile
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions dist/tools/PyCortexMDebug/README.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
From 597434eab992cd3f9495434ed6b990c8c491d66b Mon Sep 17 00:00:00 2001
From: Marian Buschsieweke <[email protected]>
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 <tab>" or "svd_load ST<tab>"
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<tab>" or "svd_load STMicro STM32F1<tab>"
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

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
From 568ef712d68eb70574fbce5808fea63963aa908f Mon Sep 17 00:00:00 2001
From: Marian Buschsieweke <[email protected]>
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 <VENDOR> <MODEL>` 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 <http://www.gnu.org/licenses/>.
"""

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 <vendor> <device.svd> or svd_load <path/to/filename.svd>\n")
+ raise gdb.GdbError("Usage: svd_load <vendor> <device> or svd_load <path/to/filename.svd>\n")
try:
SVD(SVDFile(f))
except Exception as e:
--
2.51.2

39 changes: 11 additions & 28 deletions dist/tools/jlink/jlink.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,24 @@
# @author Hauke Peteresen <[email protected]>

# Set IMAGE_OFFSET to zero by default.
: ${IMAGE_OFFSET:=0}

Check warning on line 65 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

This default assignment may cause DoS due to globbing. Quote it. [SC2223]
# Allow overwriting the reset commands.
: ${JLINK_RESET_FILE:=${RIOTTOOLS}/jlink/reset.seg}
# 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
Expand Down Expand Up @@ -115,15 +123,6 @@
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"
Expand All @@ -132,25 +131,13 @@
fi
}

test_tui() {
if [ -n "${TUI}" ]; then
TUI=-tui
fi
}

test_serial() {
if [ -n "${JLINK_SERIAL}" ]; then
JLINK_SERIAL_SERVER="-select usb='${JLINK_SERIAL}'"
JLINK_SERIAL="-selectemubysn '${JLINK_SERIAL}'"
fi
}

test_dbg() {
if [ -z "${DBG}" ]; then
DBG="${GDB}"
fi
}

test_term() {
if [ -z "${JLINK_TERMPROG}" ]; then
JLINK_TERMPROG="${_JLINK_TERMPROG}"
Expand All @@ -167,21 +154,21 @@
# on older versions, JLINK_VERSION will still be parsed correctly.
JLINK_VERSION=$(echo q | "${JLINK}" "${JLINK_SERIAL}" -nogui 1 2> /dev/null | grep "^DLL version*" | grep -oE "[0-9]+\.[0-9]+")

if [ $? -ne 0 ]; then

Check warning on line 157 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Check exit code directly with e.g. 'if ! mycmd;', not indirectly with $?. [SC2181]
echo "Error: J-Link appears not to be installed on your PATH"
exit 1
fi

"$RIOTTOOLS"/has_minimal_version/has_minimal_version.sh "$JLINK_VERSION" "$JLINK_MINIMUM_VERSION" 2> /dev/null

if [ $? -ne 0 ]; then

Check warning on line 164 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Check exit code directly with e.g. 'if ! mycmd;', not indirectly with $?. [SC2181]
echo "Error: J-Link V$JLINK_MINIMUM_VERSION is required, but V${JLINK_VERSION} is installed"
exit 1
fi
fi
}

flash_common() {

Check warning on line 171 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

flash_common references arguments, but none are ever passed. [SC2120]
test_config
test_serial
test_version
Expand Down Expand Up @@ -221,7 +208,7 @@
# address to flash is hex formatted, as required by JLink
ADDR_TO_FLASH="$(printf "0x%08x\n" "$((FLASH_ADDR + IMAGE_OFFSET))")"
echo "loadbin ${BINFILE} ${ADDR_TO_FLASH}" >> "${BINDIR}/burn.seg"
flash_common

Check warning on line 211 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Use flash_common "$@" if function's $1 should mean script's $1. [SC2119]
}

do_flash_elf() {
Expand All @@ -237,7 +224,7 @@
done >> "${BINDIR}/burn.seg"
fi
echo "loadfile ${ELFFILE}" >> "${BINDIR}/burn.seg"
flash_common

Check warning on line 227 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Use flash_common "$@" if function's $1 should mean script's $1. [SC2119]
}

do_debug() {
Expand All @@ -246,9 +233,6 @@
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 \
Expand All @@ -261,7 +245,7 @@
# 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}
}
Expand All @@ -270,7 +254,6 @@
test_config
test_serial
test_version
test_ports
# start the J-Link GDB server
sh -c "${JLINK_SERVER} ${JLINK_SERIAL_SERVER} \
-nogui \
Expand Down Expand Up @@ -306,15 +289,15 @@
JLINK_PIDFILE=$(mktemp -t "jilnk_pid.XXXXXXXXXX")
# will be called by trap
cleanup() {
if [ -f $JLINK_PIDFILE ]; then

Check warning on line 292 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Double quote to prevent globbing and word splitting. [SC2086]
JLINK_PID="$(cat ${JLINK_PIDFILE})"

Check warning on line 293 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Double quote to prevent globbing and word splitting. [SC2086]
kill ${JLINK_PID}

Check warning on line 294 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Double quote to prevent globbing and word splitting. [SC2086]
rm -r "${JLINK_PIDFILE}"
fi
exit 0
}
# cleanup after script terminates
trap "cleanup ${JLINK_PIDFILE}" EXIT INT

Check warning on line 300 in dist/tools/jlink/jlink.sh

View workflow job for this annotation

GitHub Actions / static-tests

Use single quotes, otherwise this expands now rather than when signalled. [SC2064]

# start J-link as RTT server
sh -c "${JLINK} ${JLINK_SERIAL} \
Expand Down
11 changes: 11 additions & 0 deletions makefiles/tools/dbg_common.inc.mk
Original file line number Diff line number Diff line change
@@ -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
10 changes: 9 additions & 1 deletion makefiles/tools/jlink.inc.mk
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
7 changes: 7 additions & 0 deletions makefiles/tools/openocd.inc.mk
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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))
Expand Down
5 changes: 5 additions & 0 deletions makefiles/tools/targets.inc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
Loading