Browse Source
Add the ST-Link GDB server (part of the STM32CubeCLT) as west runner. The STM32CubeCLT is an all-in-one multi-OS command-line toolset, which is part of the STM32Cube ecosystem, and notably includes a GDB server for debugging using ST-Link probes. This runner supports the "attach", "debug" and "debugserver" commands. Documentation: https://www.st.com/en/development-tools/stm32cubeclt.html Signed-off-by: Mathieu Choplain <mathieu.choplain@st.com>pull/91613/merge
6 changed files with 265 additions and 26 deletions
@ -0,0 +1,4 @@ |
|||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
|
||||||
|
board_set_debugger_ifnset(stlink_gdbserver) |
||||||
|
board_finalize_runner_args(stlink_gdbserver) |
@ -0,0 +1,188 @@ |
|||||||
|
# Copyright (c) 2025 STMicroelectronics |
||||||
|
# |
||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
|
||||||
|
""" |
||||||
|
Runner for debugging applications using the ST-LINK GDB server |
||||||
|
from STMicroelectronics, provided as part of the STM32CubeCLT. |
||||||
|
""" |
||||||
|
|
||||||
|
import argparse |
||||||
|
import platform |
||||||
|
import re |
||||||
|
import shutil |
||||||
|
from pathlib import Path |
||||||
|
|
||||||
|
from runners.core import MissingProgram, RunnerCaps, RunnerConfig, ZephyrBinaryRunner |
||||||
|
|
||||||
|
STLINK_GDB_SERVER_DEFAULT_PORT = 61234 |
||||||
|
|
||||||
|
|
||||||
|
class STLinkGDBServerRunner(ZephyrBinaryRunner): |
||||||
|
@classmethod |
||||||
|
def _get_stm32cubeclt_paths(cls) -> tuple[Path, Path]: |
||||||
|
""" |
||||||
|
Returns a tuple of two elements of class pathlib.Path: |
||||||
|
[0]: path to the ST-LINK_gdbserver executable |
||||||
|
[1]: path to the "STM32CubeProgrammer/bin" folder |
||||||
|
""" |
||||||
|
|
||||||
|
def find_highest_clt_version(tools_folder: Path) -> Path | None: |
||||||
|
if not tools_folder.is_dir(): |
||||||
|
return None |
||||||
|
|
||||||
|
# List all CubeCLT installations present in tools folder |
||||||
|
CUBECLT_FLDR_RE = re.compile(r"stm32cubeclt_([1-9]).(\d+).(\d+)", re.IGNORECASE) |
||||||
|
installations: list[tuple[int, Path]] = [] |
||||||
|
for f in tools_folder.iterdir(): |
||||||
|
m = CUBECLT_FLDR_RE.match(f.name) |
||||||
|
if m is not None: |
||||||
|
# Compute a number that can be easily compared |
||||||
|
# from the STM32CubeCLT version number |
||||||
|
major, minor, revis = int(m[1]), int(m[2]), int(m[3]) |
||||||
|
ver_num = major * 1000000 + minor * 1000 + revis |
||||||
|
installations.append((ver_num, f)) |
||||||
|
|
||||||
|
if len(installations) == 0: |
||||||
|
return None |
||||||
|
|
||||||
|
# Sort candidates and return the path to the most recent version |
||||||
|
most_recent_install = sorted(installations, key=lambda e: e[0], reverse=True)[0] |
||||||
|
return most_recent_install[1] |
||||||
|
|
||||||
|
cur_platform = platform.system() |
||||||
|
|
||||||
|
# Attempt to find via shutil.which() |
||||||
|
if cur_platform in ["Linux", "Windows"]: |
||||||
|
gdbserv = shutil.which("ST-LINK_gdbserver") |
||||||
|
cubeprg = shutil.which("STM32_Programmer_CLI") |
||||||
|
if gdbserv and cubeprg: |
||||||
|
# Return the parent of cubeprg as [1] should be the path |
||||||
|
# to the folder containing STM32_Programmer_CLI, not the |
||||||
|
# path to the executable itself |
||||||
|
return (Path(gdbserv), Path(cubeprg).parent) |
||||||
|
|
||||||
|
# Search in OS-specific paths |
||||||
|
search_path: str |
||||||
|
tool_suffix = "" |
||||||
|
if cur_platform == "Linux": |
||||||
|
search_path = "/opt/st/" |
||||||
|
elif cur_platform == "Windows": |
||||||
|
search_path = "C:\\ST\\" |
||||||
|
tool_suffix = ".exe" |
||||||
|
elif cur_platform == "Darwin": |
||||||
|
search_path = "/opt/ST/" |
||||||
|
else: |
||||||
|
raise RuntimeError("Unsupported OS") |
||||||
|
|
||||||
|
clt = find_highest_clt_version(Path(search_path)) |
||||||
|
if clt is None: |
||||||
|
raise MissingProgram("ST-LINK_gdbserver (from STM32CubeCLT)") |
||||||
|
|
||||||
|
gdbserver_path = clt / "STLink-gdb-server" / "bin" / f"ST-LINK_gdbserver{tool_suffix}" |
||||||
|
cubeprg_bin_path = clt / "STM32CubeProgrammer" / "bin" |
||||||
|
|
||||||
|
return (gdbserver_path, cubeprg_bin_path) |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def name(cls) -> str: |
||||||
|
return "stlink_gdbserver" |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def capabilities(cls) -> RunnerCaps: |
||||||
|
return RunnerCaps(commands={"attach", "debug", "debugserver"}, dev_id=True, extload=True) |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def extload_help(cls) -> str: |
||||||
|
return "External Loader for ST-Link GDB server" |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def do_add_parser(cls, parser: argparse.ArgumentParser): |
||||||
|
# Expose a subset of the ST-LINK GDB server arguments |
||||||
|
parser.add_argument( |
||||||
|
"--swd", action='store_true', default=True, help="Enable SWD debug mode" |
||||||
|
) |
||||||
|
parser.add_argument("--apid", type=int, default=0, help="Target DAP ID") |
||||||
|
parser.add_argument( |
||||||
|
"--port-number", |
||||||
|
type=int, |
||||||
|
default=STLINK_GDB_SERVER_DEFAULT_PORT, |
||||||
|
help="Port number for GDB client", |
||||||
|
) |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def do_create(cls, cfg: RunnerConfig, args: argparse.Namespace) -> "STLinkGDBServerRunner": |
||||||
|
return STLinkGDBServerRunner( |
||||||
|
cfg, args.swd, args.apid, args.dev_id, args.port_number, args.extload |
||||||
|
) |
||||||
|
|
||||||
|
def __init__( |
||||||
|
self, |
||||||
|
cfg: RunnerConfig, |
||||||
|
swd: bool, |
||||||
|
ap_id: int | None, |
||||||
|
stlink_serial: str | None, |
||||||
|
gdb_port: int, |
||||||
|
external_loader: str | None, |
||||||
|
): |
||||||
|
super().__init__(cfg) |
||||||
|
self.ensure_output('elf') |
||||||
|
|
||||||
|
self._swd = swd |
||||||
|
self._gdb_port = gdb_port |
||||||
|
self._stlink_serial = stlink_serial |
||||||
|
self._ap_id = ap_id |
||||||
|
self._external_loader = external_loader |
||||||
|
|
||||||
|
def do_run(self, command: str, **kwargs): |
||||||
|
if command in ["attach", "debug", "debugserver"]: |
||||||
|
self.do_attach_debug_debugserver(command) |
||||||
|
else: |
||||||
|
raise ValueError(f"{command} not supported") |
||||||
|
|
||||||
|
def do_attach_debug_debugserver(self, command: str): |
||||||
|
# self.ensure_output('elf') is called in constructor |
||||||
|
# and validated that self.cfg.elf_file is non-null. |
||||||
|
# This assertion is required for the test framework, |
||||||
|
# which doesn't have this insight - it should never |
||||||
|
# trigger in real-world scenarios. |
||||||
|
assert self.cfg.elf_file is not None |
||||||
|
elf_path = Path(self.cfg.elf_file).as_posix() |
||||||
|
|
||||||
|
gdb_args = ["-ex", f"target remote :{self._gdb_port}", elf_path] |
||||||
|
|
||||||
|
(gdbserver_path, cubeprg_path) = STLinkGDBServerRunner._get_stm32cubeclt_paths() |
||||||
|
gdbserver_cmd = [gdbserver_path.as_posix()] |
||||||
|
gdbserver_cmd += ["--stm32cubeprogrammer-path", str(cubeprg_path.absolute())] |
||||||
|
gdbserver_cmd += ["--port-number", str(self._gdb_port)] |
||||||
|
gdbserver_cmd += ["--apid", str(self._ap_id)] |
||||||
|
gdbserver_cmd += ["--halt"] |
||||||
|
|
||||||
|
if self._swd: |
||||||
|
gdbserver_cmd.append("--swd") |
||||||
|
|
||||||
|
if command == "attach": |
||||||
|
gdbserver_cmd += ["--attach"] |
||||||
|
else: # debug/debugserver |
||||||
|
gdbserver_cmd += ["--initialize-reset"] |
||||||
|
gdb_args += ["-ex", f"load {elf_path}"] |
||||||
|
|
||||||
|
if self._stlink_serial: |
||||||
|
gdbserver_cmd += ["--serial-number", self._stlink_serial] |
||||||
|
|
||||||
|
if self._external_loader: |
||||||
|
extldr_path = cubeprg_path / "ExternalLoader" / self._external_loader |
||||||
|
if not extldr_path.exists(): |
||||||
|
raise RuntimeError(f"External loader {self._external_loader} does not exist") |
||||||
|
gdbserver_cmd += ["--extload", str(extldr_path)] |
||||||
|
|
||||||
|
self.require(gdbserver_cmd[0]) |
||||||
|
|
||||||
|
if command == "debugserver": |
||||||
|
self.check_call(gdbserver_cmd) |
||||||
|
elif self.cfg.gdb is None: # attach/debug |
||||||
|
raise RuntimeError("GDB is required for attach/debug") |
||||||
|
else: # attach/debug |
||||||
|
gdb_cmd = [self.cfg.gdb] + gdb_args |
||||||
|
self.require(gdb_cmd[0]) |
||||||
|
self.run_server_and_client(gdbserver_cmd, gdb_cmd) |
Loading…
Reference in new issue