Commit 19fe604a authored by Mathieu Choplain's avatar Mathieu Choplain Committed by Fabio Baltieri
Browse files

scripts: west_commands: runners: add ST-LINK GDB server

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: default avatarMathieu Choplain <mathieu.choplain@st.com>
parent 0829c2bb
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: Apache-2.0

board_set_debugger_ifnset(stlink_gdbserver)
board_finalize_runner_args(stlink_gdbserver)
+47 −3
Original line number Diff line number Diff line
@@ -525,6 +525,50 @@ to ``rfp-cli`` when flashing:

   west flash --rfp-cli ~/Downloads/RFP_CLI_Linux_V31800_x64/linux-x64/rfp-cli

.. _stm32cubeclt-host-tools:
.. _runner_stlink_gdbserver:

STM32CubeCLT Flash & Debug Host Tools
*************************************

STMicroelectronics provides `STM32CubeCLT`_ as an official all-in-one toolset compatible with
Linux |reg|, macOS |reg| and Windows |reg|, allowing the use of STMicroelectronics proprietary
tools within third-party development environments.

It notably provides a GDB debugging server (the *ST-LINK GDB Server*) that can be used to debug
applications on STM32 boards thanks to on-board or external ST-LINK debug probes.

It is compatible with the following debug probes:

- :ref:`stlink-v21-onboard-debug-probe`
- Standalone `ST-LINK-V2`_, `ST-LINK-V3`_, and `STLINK-V3PWR`_ probes

Install STM32CubeCLT
--------------------

The easiest way to get the ST-LINK GDB Server is to install `STM32CubeCLT`_ from STMicroelectronics' website.
A valid email address is needed to receive the downloading link.

Basic usage
-----------

The ST-Link GDB Server can be used through the ``west attach``, ``west debug`` or ``west debugserver`` commands
to debug Zephyr applications.

.. code-block:: console

   west debug --runner stlink_gdbserver

.. note::

   The `STM32CubeProgrammer`_ version contained in the `STM32CubeCLT`_ installation can also be used to flash
   applications. To do so, the dedicated :ref:`STM32CubeProgrammer runner <runner_stm32cubeprogrammer>` should
   be used instead of ``stlink_gdbserver``, as done in the following example:

   .. code-block:: console

      west flash --runner stm32cubeprogrammer

.. _stm32cubeprog-flash-host-tools:
.. _runner_stm32cubeprogrammer:

@@ -650,12 +694,12 @@ For more about the UF2 format and its tooling, see `USB Flashing Format (UF2)`_.
.. _probe-rs Supported Devices:
   https://probe.rs/targets/

.. _STM32CubeProgrammer:
   https://www.st.com/en/development-tools/stm32cubeprog.html

.. _STM32CubeCLT:
   https://www.st.com/en/development-tools/stm32cubeclt.html

.. _STM32CubeProgrammer:
   https://www.st.com/en/development-tools/stm32cubeprog.html

.. _STM32CubeProgrammer User Manual:
   https://www.st.com/resource/en/user_manual/um2237-stm32cubeprogrammer-software-description-stmicroelectronics.pdf

+24 −23
Original line number Diff line number Diff line
@@ -36,29 +36,29 @@ with pyOCD or OpenOCD debug host tools, or with J-Link firmware to communicate
with J-Link debug host tools.


+------------------------------------------+---------------------------------------------------------------------------------------------------------+
+------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------+
|| *Debug Probes & Host Tools*             |                                                            Host Tools                                                            |
+| *Compatibility Chart*                   +--------------------+--------------------+---------------------+--------------------+--------------------+
|                                          |  **J-Link Debug**  |    **OpenOCD**     |      **pyOCD**      |   **NXP S32DS**    | **NXP LinkServer** |
+----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **J-Link External**     |           ✓        |          ✓         |                     |                    |                    |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **LPC-Link2 CMSIS-DAP** |                    |                    |                     |                    |         ✓          |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **LPC-Link2 J-Link**    |           ✓        |                    |                     |                    |                    |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **MCU-Link CMSIS-DAP**  |                    |                    |                     |                    |         ✓          |
|  Debug Probes  +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **MCU-Link J-Link**     |           ✓        |                    |                     |                    |                    |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **NXP S32 Debug Probe** |                    |                    |                     |          ✓         |                    |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **OpenSDA DAPLink**     |                    |          ✓         |          ✓          |                    |         ✓          |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **OpenSDA J-Link**      |           ✓        |                    |                     |                    |                    |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
|                | **ST-LINK/V2-1**        |           ✓        |          ✓         | *some STM32 boards* |                    |                    |
+----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+
+| *Compatibility Chart*                   +--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                                          |  **J-Link Debug**  |    **OpenOCD**     |      **pyOCD**      |   **NXP S32DS**    | **NXP LinkServer** | **ST-LINK GDB Server** |
+----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **J-Link External**     |           ✓        |          ✓         |                     |                    |                    |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **LPC-Link2 CMSIS-DAP** |                    |                    |                     |                    |         ✓          |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **LPC-Link2 J-Link**    |           ✓        |                    |                     |                    |                    |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **MCU-Link CMSIS-DAP**  |                    |                    |                     |                    |         ✓          |                        |
|  Debug Probes  +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **MCU-Link J-Link**     |           ✓        |                    |                     |                    |                    |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **NXP S32 Debug Probe** |                    |                    |                     |          ✓         |                    |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **OpenSDA DAPLink**     |                    |          ✓         |          ✓          |                    |         ✓          |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **OpenSDA J-Link**      |           ✓        |                    |                     |                    |                    |                        |
|                +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+
|                | **ST-LINK/V2-1**        |           ✓        |          ✓         | *some STM32 boards* |                    |                    |           ✓            |
+----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+


Some supported boards in Zephyr do not include an onboard debug probe and
@@ -374,6 +374,7 @@ It is compatible with the following host debug tools:

- :ref:`openocd-debug-host-tools`
- :ref:`jlink-debug-host-tools`
- :ref:`stm32cubeclt-host-tools`

For some STM32 based boards, it is also compatible with:

+1 −0
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ _names = [
    'silabs_commander',
    'spi_burn',
    'spsdk',
    'stlink_gdbserver',
    'stm32cubeprogrammer',
    'stm32flash',
    'sy1xx',
+188 −0
Original line number Diff line number Diff line
# 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