All guides
TECHNICAL GUIDE STM32 DEBUG ST-LINK 2026

stlink Tools
st-flash · st-info · st-util · st-trace

The open-source stlink toolset (stlink-org, formerly texane) for probing, flashing, dumping, GDB-debugging and SWO-tracing an STM32L4R5 over SWD — with exact commands, address maps and a copy-pasteable firmware example.

01 The open-source stlink toolset

The stlink project (maintained at github.com/stlink-org/stlink, historically known by the original author's handle texane) is a set of small command-line tools that talk to an ST-LINK/V2, V2-1 or V3 probe over USB and drive an STM32 target over SWD (or JTAG). It is the free alternative to ST's own STM32CubeProgrammer for the common flash/dump/debug workflow.

Four binaries cover everything you normally need on a Cortex-M target such as the STM32L4R5 (Cortex-M4F, RM0432, DS12023):

ToolPurposeTypical one-liner
st-infoDiscover probes, read chip id / flash / SRAM sizest-info --probe
st-flashProgram, read back and erase flash (raw .bin / Intel HEX)st-flash write fw.bin 0x08000000
st-utilGDB remote server (default TCP port 4242)st-util then arm-none-eabi-gdb
st-traceDecode SWO / ITM trace (printf-style debug output)st-trace --clock=120m --trace=2m

Install

bash — install
# Debian / Ubuntu — the package is called stlink-tools
sudo apt install stlink-tools

# Arch
sudo pacman -S stlink

# macOS (Homebrew)
brew install stlink

# From source (newest features: --area, --freq, --connect-under-reset)
git clone https://github.com/stlink-org/stlink.git
cd stlink
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
sudo cmake --install build

# Verify — every tool accepts --version
st-info --version
st-flash --version

udev rules — flashing without sudo (Linux)

Out of the box only root can open the ST-LINK USB device. Install the shipped udev rules once, then re-plug the probe:

bash — udev rules
# Rules ship with the source tree (config/udev/rules.d/)
sudo cp stlink/config/udev/rules.d/*.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger

# If your distro package installed them, they are usually already in
# /lib/udev/rules.d/  (e.g. 49-stlinkv2-1.rules, 49-stlinkv3.rules)
NOTE

On a Nucleo or Discovery board the ST-LINK is on-board: plug the USB into the ST-LINK connector (not the target USB) and it enumerates as one composite device — debug interface + Virtual COM Port + mass-storage. The stlink tools only use the debug interface; the VCP/MSD are independent.

02 st-info — probing the ST-LINK & target

Always start here. st-info --probe confirms the probe is seen, the target is powered and reachable over SWD, and prints the auto-detected chip id, flash size and SRAM size. If this fails, nothing else will work.

st-info — full usage
st-info --version
st-info --probe    [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
st-info --serial   [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
st-info --flash    [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
st-info --pagesize [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
st-info --sram     [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
st-info --chipid   [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
st-info --descr    [--connect-under-reset] [--hot-plug] [--freq=<kHz>]
bash — probe an STM32L4R5
st-info --probe
output (STM32L4R5, 2 MB flash / 640 KB SRAM)
Found 1 stlink programmers
  version:    V2J39S7          # V2 probe, JTAG fw J39, SWIM fw S7
  serial:     0673FF...        # unique probe serial (used with --serial)
  flash:      2097152 (pagesize: 8192)   # 2 MB, 8 KB pages (single-bank)
  sram:       655360           # 640 KB
  chipid:     0x470            # DEV_ID for STM32L4Rxx / L4Sxx
  dev-type:   L4Rx
FieldMeaningScriptable single-value command
versionProbe hw version + on-board firmware (JTAG / SWIM)
serialProbe serial; disambiguates multiple probesst-info --serial
flashTotal flash bytes, read from the F_SIZE registerst-info --flash0x200000
pagesizeErase granularity (bytes)st-info --pagesize0x2000
sramTotal SRAM bytesst-info --sram0xa0000
chipidDBGMCU DEV_ID; 0x470 = L4R/L4S familyst-info --chipid0x0470
dev-typeHuman-readable part family stringst-info --descr
CONNECT MODES

If the target has firmware that reconfigures SWD pins, sleeps aggressively, or is held in reset, plain probing fails. Use --connect-under-reset (holds NRST low while attaching, then releases) or --hot-plug (attach without resetting a running target). Raise or lower the SWD clock with --freq=<kHz> (e.g. --freq=480 for long/flaky wires).

03 STM32L4R5 memory map & addresses

Every st-flash command takes an absolute address. Get these wrong and you either brick option bytes or flash into RAM/void. The STM32L4R5 has 2 MB flash at 0x0800 0000 and 640 KB SRAM starting at 0x2000 0000 (RM0432 / DS12023).

RegionBase addressSizeNotes
Main flash0x0800 00002 MBCode + vector table. Ends at 0x081F FFFF. This is the alias mapped to 0x0000 0000 after boot.
Flash Bank 10x0800 00001 MBDual-bank mode (DBANK=1): 4 KB pages. Boots here when BFB2=0.
Flash Bank 20x0810 00001 MBDual-bank mode. Boots here when BFB2=1. In single-bank mode (DBANK=0) flash is one contiguous 2 MB region of 8 KB pages.
System memory0x1FFF 000028 KBFactory ST bootloader (USART/USB DFU). Selected by BOOT0 / nBOOT0 option.
OTP area0x1FFF 7000512 BOne-time-programmable; write once, never erasable.
Option bytes (bank 1)0x1FFF 7800RDP, WRP, BOR, BFB2, DBANK… Do not blind-write.
Option bytes (bank 2)0x1FFF F800Second-bank WRP registers.
SRAM10x2000 0000192 KBMain RAM. Contiguous with SRAM2/SRAM3.
SRAM20x2003 000064 KBAlso aliased at 0x1000 0000 (0-wait access); can be parity/retention protected.
SRAM30x2004 0000384 KBExtends the contiguous RAM block up to 0x2009 FFFF.

Key info registers (read with st-flash read)

RegisterAddressMeaning
F_SIZE0x1FFF 75E0Flash size in KB (16-bit). st-info reads this.
Unique device ID0x1FFF 759096-bit factory UID (3 × 32-bit words).
DBGMCU_IDCODE0xE004 2000Bits [11:0] = DEV_ID = 0x470; bits [31:16] = REV_ID.
VECTOR TABLE = FLASH BASE

At reset the Cortex-M4 fetches the initial MSP from [0x0800 0000] and the Reset_Handler address from [0x0800 0004]. Therefore your application's vector table must live at the very start of flash. When you st-flash write app.bin 0x08000000, the first 4 bytes of app.bin must be a valid stack-top pointer inside SRAM (0x2000 00000x2009 FFFF), or the core faults immediately.

04 st-flash write — flashing firmware

The everyday command. st-flash works on a raw binary — it has no concept of ELF sections, so you either give it a .bin plus an explicit load address, or an Intel .hex (which carries its own addresses).

st-flash — full usage
usage: st-flash [options] read  [file] [addr] [size]
       st-flash [options] write <file> [addr] [size]
       st-flash [options] write <value>
       st-flash [options] erase [addr] [size]
       st-flash [options] reset

options:
  --freq <kHz>            JTAG/SWD frequency, default 1800 kHz
  --serial <serial>       select probe by serial (see st-info --serial)
  --connect-under-reset  pull NRST low while connecting
  --hot-plug             connect without resetting the target
  --reset                reset the target after writing
  --mass-erase           mass-erase the whole chip before writing
  --format {binary|ihex} file format (ihex carries its own address)
  --flash <size>          override detected flash size, e.g. 2M, 128k
  --area <area>           main(default)|system|otp|option|option_boot_add|optcr|optcr1
  --opt                  skip trailing empty (0xFF) bytes
  --debug                extra debug output
  --version / --help

.elf → .bin → flash

A compiler (arm-none-eabi-gcc, CubeIDE, whatever) produces an .elf. Convert it to raw binary with objcopy, then write it at the flash base:

bash — the canonical flash workflow
# 1. ELF -> raw binary (strips ELF headers, keeps loadable sections)
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

# 2. Flash at the main-flash base 0x08000000
st-flash write firmware.bin 0x08000000

# 3. Reset into the new firmware (or add --reset to step 2)
st-flash reset
File typeCarries load address?How to flash
.bin (raw)No — address is implicitst-flash write fw.bin 0x08000000 (address required)
.hex (Intel HEX)Yes — records encode addressesst-flash --format ihex write fw.hex (no address)
.elfYes, but st-flash can't parse itConvert first (objcopy), or flash via GDB load (see §06)

Complete compilable example — register-level blinky

A minimal, self-contained STM32L4R5 program (no CMSIS, no HAL) that toggles a GPIO. It enables the GPIOB clock via RCC_AHB2ENR, sets the pin to output in GPIOx_MODER, and drives it with the atomic set/reset register GPIOx_BSRR. Split into three files:

main.c — GPIO toggle (STM32L4R5)
/* STM32L4R5 register-level blinky — RM0432 addresses */
#include <stdint.h>

#define RCC_BASE      0x40021000UL
#define RCC_AHB2ENR   (*(volatile uint32_t *)(RCC_BASE + 0x4C))
#define GPIOB_BASE    0x48000400UL
#define GPIOB_MODER   (*(volatile uint32_t *)(GPIOB_BASE + 0x00))
#define GPIOB_BSRR    (*(volatile uint32_t *)(GPIOB_BASE + 0x18))
#define LED_PIN       7u          /* PB7 = LD2 (blue) on Nucleo-L4R5ZI */

static void delay(volatile uint32_t n) { while (n--) __asm__ volatile("nop"); }

int main(void) {
    RCC_AHB2ENR |= (1u << 1);                   /* GPIOBEN */
    GPIOB_MODER &= ~(3u << (LED_PIN * 2));       /* clear mode bits   */
    GPIOB_MODER |=  (1u << (LED_PIN * 2));       /* 01 = general output */

    for (;;) {
        GPIOB_BSRR = (1u << LED_PIN);            /* BSy: set   pin high */
        delay(400000);
        GPIOB_BSRR = (1u << (LED_PIN + 16));     /* BRy: reset pin low  */
        delay(400000);
    }
}
startup.c — vector table + reset
#include <stdint.h>

extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss, _estack;
int  main(void);

void Reset_Handler(void) {
    uint32_t *src = &_sidata, *dst = &_sdata;
    while (dst < &_edata) *dst++ = *src++;      /* copy .data  flash->RAM */
    for (dst = &_sbss; dst < &_ebss; ) *dst++ = 0; /* zero .bss           */
    main();
    for (;;);
}
void Default_Handler(void) { for (;;); }

/* First two words: initial MSP, then Reset_Handler — MUST be at flash base */
__attribute__((section(".isr_vector"), used))
void (* const g_vectors[])(void) = {
    (void (*)(void))&_estack,   /* 0x08000000: initial stack pointer */
    Reset_Handler,              /* 0x08000004: reset vector          */
    Default_Handler,            /* NMI       */
    Default_Handler,            /* HardFault */
};
minimal.ld — linker script (2 MB flash / 640 KB RAM)
ENTRY(Reset_Handler)

MEMORY {
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 2048K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 640K
}
_estack = ORIGIN(RAM) + LENGTH(RAM);   /* 0x200A0000 (top of SRAM) */

SECTIONS {
  .isr_vector : { KEEP(*(.isr_vector)) }          > FLASH
  .text       : { *(.text*) *(.rodata*) }         > FLASH
  _sidata = LOADADDR(.data);
  .data : { _sdata = .; *(.data*) _edata = .; }    > RAM AT > FLASH
  .bss  : { _sbss = .; *(.bss*) *(COMMON) _ebss = .; } > RAM
}
bash — build, convert, flash
# Cortex-M4F: hard-float ABI, single-precision FPU
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb \
    -mfpu=fpv4-sp-d16 -mfloat-abi=hard \
    -Os -Wall -ffreestanding -nostdlib \
    -T minimal.ld startup.c main.c -o firmware.elf

arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
st-flash --reset write firmware.bin 0x08000000

Verifying the write

st-flash already read-verifies each written block and prints Flash written and verified! jolly good! on success. To independently confirm, read the flash back and diff against the source binary:

bash — independent verify
# Read back exactly as many bytes as the .bin, then compare
SIZE=$(stat -c %s firmware.bin)
st-flash read readback.bin 0x08000000 $SIZE
cmp firmware.bin readback.bin && echo "MATCH"

05 st-flash read, erase & mass-erase

Reading dumps flash (or any memory-mapped region) to a file; erasing clears it. st-flash erase with no address does a full-chip mass erase; give it an address + size for a targeted section erase.

bash — read (dump) memory
# Dump the first 4 KB of flash (bootloader/vector inspection)
st-flash read dump.bin 0x08000000 0x1000

# Dump the ENTIRE 2 MB flash
st-flash read full_flash.bin 0x08000000 0x200000

# Read the 96-bit unique device ID (12 bytes @ 0x1FFF7590)
st-flash read uid.bin 0x1FFF7590 0xC
xxd uid.bin
bash — erase
# Full-chip mass erase (no address = wipe everything)
st-flash erase

# Targeted section erase: one 8 KB page at 0x08004000
st-flash erase 0x08004000 0x2000

# Mass-erase implicitly, then program, then reset — one shot
st-flash --mass-erase --reset write firmware.bin 0x08000000
MASS ERASE vs SECTION ERASE

Mass erase resets the entire main flash to 0xFF in one hardware operation (fast, no address). Section erase walks page-by-page over the range you specify and is aligned to the --pagesize reported by st-info (8 KB single-bank / 4 KB dual-bank on L4R5). Mass erase does not touch option bytes or OTP.

Option bytes & RDP recovery with --area

The --area flag redirects read/write to non-main regions. This is how you inspect or repair option bytes — most importantly to lift Read-Out Protection (RDP), which mass-erases user flash as a side effect and un-bricks a locked chip.

--areaTargets
main (default)User flash at 0x08000000
systemSystem memory (ST bootloader region)
otpOne-time-programmable area
optionOption-byte register (RDP, BOR, nBOOT0, DBANK, BFB2…)
option_boot_addBoot address option words
optcr / optcr1Raw option control registers (bank 1 / bank 2)
bash — read & restore option bytes
# Read the current option-byte word
st-flash --area=option read optbytes.bin 0x4

# RDP levels (byte at bits [7:0] of the option register):
#   0xAA = Level 0 (no protection)
#   0xBB = Level 1 (debug blocked; user flash inaccessible)
#   0xCC = Level 2 (PERMANENT lock — never write this!)

# Un-brick: force RDP back to Level 0 -> triggers a full mass erase.
# Value depends on the other option bits; read first, then write the
# modified word with the RDP field set to 0xAA.
st-flash --area=option write 0xFFEFF8AA
DANGER — RDP LEVEL 2 IS IRREVERSIBLE

Never write 0xCC to the RDP field: Level 2 permanently and irrecoverably disables the debug port and option-byte changes — the chip can no longer be flashed or debugged by any tool, ever. Always read the current option word first and change only the RDP byte to 0xAA. Lowering RDP from Level 1 to Level 0 automatically mass-erases user flash (an anti-tamper feature), so back up firmware beforehand if you can still read it.

06 st-util — GDB server debugging

st-util is a GDB remote stub: it opens the target over SWD and exposes it on a TCP socket (default port 4242) speaking the GDB Remote Serial Protocol. You then attach arm-none-eabi-gdb to set breakpoints, single-step, inspect registers, and even flash the ELF directly via load.

st-util — full usage
st-util [options]

  -h, --help                 Print this help
  -V, --version              Print the version
  -v, --verbose[=N]          Verbose logging (0..99)
  -p, --listen_port=PORT     GDB server listen port (default: 4242)
  -m, --multi                Extended mode: keep listening after GDB disconnects
  -n, --no-reset, --hot-plug Do not reset the board on connection
  -u, --connect-under-reset  Attach while holding NRST low
  -F, --freq=1M              SWD/JTAG frequency (e.g. 1800k, 1M, 4M)
      --semihosting          Enable ARM semihosting (printf via debugger)
      --serial <serial>      Select probe by serial number

Two-terminal workflow

terminal 1 — start the GDB server
st-util
# st-util 1.8.0
# Listening at *:4242...

# Keep the server alive across GDB restarts:
st-util -m --semihosting
terminal 2 — connect GDB, flash & run
arm-none-eabi-gdb firmware.elf

# inside gdb:
(gdb) target extended-remote localhost:4242
(gdb) load                 # flash the ELF's sections into the STM32
(gdb) monitor reset        # st-util: also 'halt', 'resume', 'jtag_reset'
(gdb) tbreak main          # temporary breakpoint at main()
(gdb) continue
(gdb) info registers
(gdb) stepi                # single-step one instruction
(gdb) x/4xw 0x08000000     # dump reset vector: MSP then Reset_Handler
(gdb) print/x *(uint32_t*)0xE0042000   # DBGMCU_IDCODE -> 0x....0470
GDB commandEffect via st-util
target extended-remote :4242Attach; extended-remote allows run/restart
loadProgram flash from the loaded ELF (no objcopy needed)
monitor resetReset the target core
monitor haltHalt the core
monitor semihosting enableToggle semihosting at runtime
monitor jtag_resetAssert the hardware reset line
TIP — .gdbinit

Automate the attach/flash/run sequence by dropping these lines in a .gdbinit next to your ELF: target extended-remote :4242 / load / monitor reset / tbreak main / continue. Then just run arm-none-eabi-gdb firmware.elf. Point another debugger at the same port too — VS Code (cortex-debug, servertype: stutil) drives st-util the same way.

07 st-trace — SWO / ITM tracing

st-trace captures the Cortex-M4's SWO (Serial Wire Output) pin through the ST-LINK and decodes ITM (Instrumentation Trace Macrocell) messages — a near-zero-overhead way to get printf-style output without a UART. st-trace programs the DWT/ITM/TPIU on the debug side; your firmware just writes bytes to an ITM stimulus port.

st-trace — full usage
st-trace [options]

  -h, --help            Print this help
  -V, --version         Print the version
  -v, --verbose[=N]     Verbosity level (0..99)
  -c, --clock=XX        Core (HCLK) frequency; suffix k/m/g  (e.g. --clock=120m)
  -t, --trace=XX        SWO trace frequency; suffix k/m/g    (e.g. --trace=2m)
  -n, --no-reset        Do not reset the board on connection
  -s, --serial=XX       Select probe by serial number
  -f, --force           Ignore most initialization errors

The --clock value must match your actual core clock — st-trace uses it to compute the TPIU prescaler that divides HCLK down to the requested --trace SWO baud. A mismatch produces garbled or no output. The SWO pin on STM32L4R5 is PB3 (JTDO/TRACESWO, alternate function). On Nucleo boards it is routed to the on-board ST-LINK, sometimes gated by a solder bridge — check your board schematic.

bash — capture SWO trace
# Core running at 120 MHz, SWO at 2 MHz. Stop the running firmware first
# is not needed; st-trace resets and configures trace, then streams.
st-trace --clock=120m --trace=2m

# If init is fussy (already-running target), relax with --force / --no-reset
st-trace -c 120m -t 2m --no-reset --force

Firmware side — emit bytes on ITM stimulus port 0

st-trace configures the trace hardware over SWD; the application only needs to push characters into the ITM stimulus port. Port 0 lives at 0xE000 0000; the Trace Enable Register (ITM_TER) at 0xE000 0E00 gates it.

itm.c — printf over ITM (port 0)
#include <stdint.h>

#define ITM_PORT0  (*(volatile uint32_t *)0xE0000000)  /* stimulus port 0     */
#define ITM_TER    (*(volatile uint32_t *)0xE0000E00)  /* trace enable regs   */

static void itm_putc(char c) {
    if (ITM_TER & 1u) {                       /* port 0 enabled by st-trace */
        while ((ITM_PORT0 & 1u) == 0) { }     /* wait until FIFO ready      */
        *(volatile uint8_t *)0xE0000000 = (uint8_t)c;  /* 8-bit write */
    }
}

void itm_puts(const char *s) { while (*s) itm_putc(*s++); }

/* in main(): itm_puts("hello from L4R5 over SWO\r\n"); */
NOTE — SWD vs JTAG pin sharing

SWO shares the pin normally used as JTAG TDO. It works fine in SWD mode (which the stlink tools use), but not if the target is configured for full JTAG. If you see no trace: confirm the core clock passed to --clock is exact, that PB3 is left in its default AF (or set to AF0/TRACESWO), and that the board's SWO solder bridge to the ST-LINK is closed.

08 Gotchas & common mistakes

The failure modes below account for the overwhelming majority of "it won't flash / won't connect" reports.

SymptomCause & fix
Firmware flashes OK but never runs You flashed an .elf as if it were raw, or used the wrong base. st-flash needs a .bin at 0x08000000 — run objcopy -O binary first. The first 4 bytes must be a valid stack-top in SRAM.
Missing address argument st-flash write fw.bin with no address is ambiguous for raw binaries. Always pass 0x08000000 for .bin; only --format ihex may omit it.
Permission denied / device not found (Linux) udev rules not installed. Install them (§01) and re-plug, or run with sudo as a test.
Can't connect to a running / low-power target Firmware remaps SWD pins or enters Stop mode. Use --connect-under-reset (or st-util -u), and after recovery add a startup delay before reconfiguring PA13/PA14.
st-util: Address already in use Another st-util/OpenOCD already owns port 4242. Kill it, or pick a new port with st-util -p 4243 and connect GDB to :4243.
GDB load succeeds but code doesn't start st-util leaves the core halted. Issue monitor reset then continue (or use -m extended mode + run).
Flash write fails / verify error mid-way SWD clock too high for the wiring. Lower it: st-flash --freq=480 write .... Also ensure adequate target power (some Nucleos need the target-power jumper set correctly).
Chip appears locked — reads return zeros RDP Level 1 is active. Lower RDP to 0xAA via --area=option (mass-erases flash). If it's Level 2 (0xCC), the part is permanently bricked — replace it.
st-trace shows garbage / nothing --clock ≠ real HCLK, so the SWO prescaler is wrong. Pass the exact core frequency; verify PB3/SWO routing and the board solder bridge.
Wrong flash/SRAM size or unknown chip id Very old stlink build predates the L4R5 (chip id 0x470). Update to ≥ 1.7.0, or override with st-flash --flash=2M.
Two probes plugged in — commands hit the wrong board Select explicitly: get serials with st-info --serial, then pass --serial <serial> to every tool.

Quick reference

  • Probe: st-info --probe — expect chipid 0x470, flash 2097152, sram 655360 for L4R5.
  • Flash: arm-none-eabi-objcopy -O binary fw.elf fw.bin then st-flash --reset write fw.bin 0x08000000.
  • Dump: st-flash read out.bin 0x08000000 0x200000 (whole 2 MB).
  • Wipe: st-flash erase (mass) or st-flash erase <addr> <size> (section).
  • Debug: st-util (port 4242) + arm-none-eabi-gdbtarget extended-remote :4242 / load.
  • Trace: st-trace --clock=<HCLK> --trace=<baud>; firmware writes to ITM port 0 @ 0xE0000000.
  • Never write RDP 0xCC (Level 2) — permanent brick.