1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-27 23:41:18 +01:00

dist/tools/PyCortexMDebug: Integrate GDB extension into RIOT

When running

     make RIOT_USE_PYCORTEXMDEBUG=1 debug

RIOT will now fetch PyCortexMDebug and instruct GDB to load that
extension on start. If additionally RIOT provides `SVD_VENDOR` and
`SVD_MODEL` to identify the SVD file to load, an
`svd_load $(SVD_VENDOR) $(SVD_CLIENT)` is also passed to GDB.

Co-authored-by: crasbe <crasbe@gmail.com>
Co-authored-by: Ann🐸 <git@annsann.eu>
This commit is contained in:
Marian Buschsieweke 2025-11-09 21:20:08 +01:00 committed by Marian Buschsieweke
parent 6ad9cf1ec7
commit 7b474698a8
No known key found for this signature in database
GPG Key ID: 758BD52517F79C41
9 changed files with 230 additions and 1 deletions

1
dist/tools/PyCortexMDebug/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/checkout

16
dist/tools/PyCortexMDebug/Makefile vendored Normal file
View File

@ -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
dist/tools/PyCortexMDebug/README.md vendored Normal file
View File

@ -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

View File

@ -0,0 +1,41 @@
From 597434eab992cd3f9495434ed6b990c8c491d66b Mon Sep 17 00:00:00 2001
From: Marian Buschsieweke <marian.buschsieweke@posteo.net>
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

View File

@ -0,0 +1,115 @@
From 568ef712d68eb70574fbce5808fea63963aa908f Mon Sep 17 00:00:00 2001
From: Marian Buschsieweke <marian.buschsieweke@posteo.net>
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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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!"