Skip to main content
Control debugger operations for embedded development including flashing, GDB server management, memory access, and RTT logging.

Syntax

Global Options

Commands

Command Reference

gdbserver

Start the GDB server for remote debugging. This is the primary command to establish a debug connection. The box selects the backend from the probe. A J-Link probe uses JLinkGDBServer. Other supported probes use OpenOCD. See Supported Debug Probes.
Options:
  • --box TEXT - Lager Box name or IP
  • --force / --no-force - Force new connection (default: reuse existing)
  • --halt / --no-halt - Halt device when connecting (default: no-halt)
  • --speed KHZ - SWD/JTAG speed in kHz (e.g., 100, 4000) or “adaptive”
  • --quiet - Suppress informational messages
  • --json - Output results in JSON format
  • --rtt - Automatically stream RTT logs after starting GDB server
  • --rtt-reset - Reset device then stream RTT (captures boot sequence)
  • -i, --interactive - Bi-directional RTT: forward stdin to the target’s RTT down-channel while streaming the up-channel to stdout (requires --rtt or --rtt-reset)
  • --rtt-channel N - RTT channel to stream, in both directions (default: 0)
  • --reset - Reset device after starting GDB server
  • --gdb-port PORT - Override the auto-allocated GDB server port. By default the box picks a port from the probe’s slot (2331 for the first probe, 2334 for the second). Pass this only when you need a specific port. Avoid it on multi-probe boxes.
  • --rtt-search-addr HEX - RAM start address for the RTT control block search (hex, e.g., 0x20020000)
  • --rtt-search-size HEX - Size of the RAM region to search for the RTT control block (hex, e.g., 0x4000)
  • --rtt-chunk-size HEX - Read chunk size for the RTT search (hex, e.g., 0x1000)
  • --local-port PORT - Local port for the tunnel on a box behind a gateway (default: the same port as the GDB server on the box)
  • --no-tunnel - On a box behind a gateway, start the GDB server and return without a tunnel to it
Examples:
Connecting with GDB:
The command prints the exact address to use. On a plain Lager Box, it prints the address of the box and returns. Boxes behind a gateway: Some boxes are behind an authenticating gateway (see lager login). The gateway checks your sign-in on each connection. The GDB port of such a box is not open to the network, so GDB cannot connect to the box directly. On such a box, gdbserver starts the GDB server and then opens a tunnel through the gateway. The tunnel listens on localhost only, on the same port number as the GDB server on the box. The command stays in the foreground while the tunnel is open:
  • The command decides for each box whether a tunnel is necessary. You do not set an option for it.
  • GDB can disconnect and connect again while the tunnel is open. More than one client can connect at the same time.
  • Ctrl-C closes the tunnel only. The GDB server continues to run on the box. To stop it, use lager debug <NET> disconnect.
  • If the local port is in use, the command stops with an error. Use --local-port to select a different local port.
  • With --rtt, --rtt-reset or --interactive, the tunnel stays open while the RTT stream runs. RTT does not use the tunnel: it goes through the gateway as normal HTTP.
  • With --json, the output has a tunnel object with local_host and local_port. Then the command stays in the foreground.
  • A script that must continue after the GDB server starts can use --no-tunnel. The command then returns and does not open a tunnel.
  • If an administrator removes your access to the box, the gateway closes the tunnel within about one minute. The command tells you. The next connection gets the access error.
If the gateway of the box does not support debug tunnels yet, the command still starts the GDB server and exits successfully. It warns that no debugger on this machine can reach the server, and prints no address to connect to. The gateway must be updated before you can attach a debugger. Speak to the administrator of the box.

disconnect

Stop the GDB server (JLinkGDBServer or OpenOCD) and free debug resources.
Options:
  • --box TEXT - Lager Box name or IP
  • --keep-server - Keep the GDB server running for external connections
Examples:

flash

Flash firmware to target. Supports Intel HEX, ELF, and binary file formats.
Options:
  • --box TEXT - Lager Box name or IP
  • --hex FILE - Path to Intel HEX file
  • --elf FILE - Path to ELF executable
  • --bin FILE,ADDRESS - Path to a binary file and its load address, as one argument (for example build/app.bin,0x08000000)
  • --verbose - Show detailed J-Link output
  • --force-reconnect - Force clean reconnect before flash
  • --no-erase - Skip the erase step (by default flash erases before programming for a clean state)
  • --erase-start ADDR - First address of the erase before programming, hex (0x16000000) or decimal. Given together with --erase-size.
  • --erase-size BYTES - Bytes to erase from --erase-start: decimal, 0x hex, or a K/M suffix such as 2M.
  • --halt / --no-halt - Halt device after flashing (default: no-halt)
flash erases before programming by default, so no flag is needed for a clean state (this is what RTT initialization wants). Pass --no-erase only when you intentionally want to preserve existing flash contents. The older --erase flag is now a no-op kept for backward compatibility.
--erase-start and --erase-size set the range of that erase, on both backends. Without them, flash erases the whole chip, or 1 MiB from 0x16000000 on a DA1469x. The erase section below describes the range, and the box version it needs. Failure output:
  • If the erase before programming fails, flash prints Flash erase failed: and the error, then exits 1. It does not program the target.
  • If programming fails, flash prints Flash failed: and the failing line, then The target was NOT programmed. If this ran without --no-erase it is now erased. It exits 1.
  • On J-Link, the output of the probe decides. These lines fail the command: a connect failure, a probe that another client holds, and a failed RAMCode download. Examples are Selected interface (SWD) is not supported by the connected probe. and Failed to download RAMCode!. The erase section below lists the connect-failure lines. Success also needs J-Link’s own evidence: a J-Link: Flash download: line (or O.K.) after Downloading file. A flash without it did not program the target, and it fails.
  • A failed J-Link flash adds Diagnosis: lines. They name any other J-Link program that used the probe during the flash. On a DA1469x, they also tell if the target reset during programming.
  • If your target’s J-Link output does not contain these evidence lines, set LAGER_JLINK_REQUIRE_EVIDENCE=0 in the environment of the CLI. The CLI then does not require them for flash and erase. Failure lines still fail the command.
Examples:

reset

Reset the target device.
Options:
  • --box TEXT - Lager Box name or IP
  • --halt / --no-halt - Halt after reset (default: no-halt)
  • --force-reconnect - Force clean reconnect before reset
Examples:

erase

Erase flash memory on the target: all of it, or the range you give with --erase-start and --erase-size. This is a destructive operation. On a DA1469x, erase erases only a range of the external QSPI flash. See DA1469x Targets.
Options:
  • --box TEXT - Lager Box name or IP
  • --speed KHZ - SWD/JTAG speed in kHz (default: 4000)
  • --yes - Skip confirmation prompt
  • --quiet - Suppress warning messages. This also skips the confirmation prompt.
  • --json - Output results in JSON format. This also skips the confirmation prompt.
  • --erase-start ADDR - First address to erase, hex (0x16000000) or decimal. Given together with --erase-size.
  • --erase-size BYTES - Bytes to erase from --erase-start: decimal, 0x hex, or a K/M suffix such as 2M.
  • --halt / --no-halt - Halt after erase (default: no-halt)
Erase range: --erase-start and --erase-size are given together. --erase-start takes hex (0x16000000) or decimal. --erase-size takes decimal, 0x hex, or a K/M suffix such as 2M (1024-based). The range must fit in the 32-bit address space. On a DA1469x it must lie inside 0x16000000–0x17FFFFFF, and it replaces both the 1 MiB default and the LAGER_ERASE_RANGE line of the JLinkScript. On any other target, the range replaces the full chip erase: J-Link runs erase <start> <end>, and OpenOCD runs flash erase_address. A bad range is refused before the CLI contacts the box. A box on 0.50.0 or later accepts the range. On an older box, the CLI refuses the two options before the erase, with a message that names lager update. The box never sees the request, so it never erases its default range in place of yours. On success the CLI prints Erase complete: and the range the box erased, for example Erase complete: 0x16000000-0x161FFFFF (2 MiB), or Erase complete: full chip. A box older than 0.50.0 does not report the range, and the CLI prints Erase complete!. With --json, the erase_range field carries start, end (inclusive), length, source, and text. source is request, script, or default. The field is null for a full chip erase. Failure output: If the erase fails, erase prints Erase failed: and the error, then exits 1. A box on 0.42.0 or later finds a J-Link attach failure itself and returns an error, which the CLI prints after Erase failed:. An older box returns the probe output with no error. The CLI then checks each line of that output. A line fails the erase when it is, starts with, or ends with one of these messages:
  • ERROR: Could not connect to target.
  • Could not connect to target.
  • Could not connect to the target device.
  • Cannot connect to target.
  • Failed to power up DAP
For such a line, the CLI prints Erase failed: and the line, then The target was NOT erased. Check that it is connected and powered. If the output contains none of these lines, erase exits 0. Examples:

memrd

Read memory from the target device.
Arguments:
  • START_ADDR - Starting memory address (e.g., 0x20000000)
  • LENGTH - Number of bytes to read
Options:
  • --box TEXT - Lager Box name or IP
  • --json - Output results in JSON format
  • --halt / --no-halt - Halt device during read (default: no-halt). --no-halt overrides the auto-halt for DA1469x QSPI XIP
  • --no-reset - DA1469x only. Skip the reset+halt the box performs before the read. A running DA1469x has SWD disabled, so without the reset the read fails — use this only on a blank/awake part to avoid rebooting it
Examples:
Output:

status

Show debug net status and configuration information.
Options:
  • --box TEXT - Lager Box name or IP
Examples:
Output:
GDB server running is about the process on the box. Target attached is about the part: the box reads the target to answer it. The two fields differ, so the CLI reports them separately. A gdbserver can outlive the device it drove, and a command that flashes or erases cares about the second field, not the first. To answer Target attached, status reads the Cortex-M CPUID register through the running GDB server. The read does not halt the core. A slow probe can make status take up to 20 seconds. Target attached reads No in two cases:
  • No GDB server runs for the probe.
  • The log of the GDB server records a failed attach.
Target attached reads Unknown when the box cannot establish an answer. These cases give that result:
  • The box is older than this field.
  • The probe cannot run, because a debugger already holds the session.
  • The read of the register timed out.
  • OpenOCD gave an empty reply.
Unknown does not mean that the target is absent.

health

Check debug service health and resource usage. The output includes a Features line: the capabilities the box’s debug service supports beyond its original requests, such as erase_range for --erase-start and --erase-size. A box that predates the list prints none reported.
Options:
  • --box TEXT - Lager Box name or IP
  • --verbose - Show detailed health information
Examples:
Output (verbose):

Listing Debug Nets

When invoked with only --box and no subcommand, lists all debug nets on the Lager Box:
Output:

RTT (Real-Time Transfer) Logging

RTT provides low-latency logging over the debug probe. Use the --rtt or --rtt-reset flags with gdbserver:

Decoding defmt logs

Most Rust (and much C) firmware logs via defmt, a compressed binary format. Raw RTT bytes from defmt firmware are not human-readable. defmt-print must decode them, and it needs the exact ELF that is flashed on the target.
Two things to watch:
  • Redirect stderr. The RTT payload is written to stdout; status messages (JLinkGDBServer started!, etc.) go to stderr. Pipe stdout only — append 2>/dev/null (or 2>debug.log) so status lines never corrupt defmt-print’s input.
  • The stream never ends. --rtt runs until the process is killed. In scripts or non-interactive sessions, wrap it in timeout <seconds> to capture a fixed window. When you kill the lager process, the pipe closes and defmt-print exits on EOF.
Install defmt-print with cargo install defmt-print on the machine where you run the pipe (the same machine that holds the .elf).

Interactive (bi-directional) RTT

Add --interactive to send data to the target as well as read from it. Whatever you type on stdin is forwarded to the target’s RTT down-channel, which is how firmware that exposes a command console over RTT is driven:
stdout remains the raw up-channel byte stream, so this composes with defmt-print exactly as plain --rtt does:
Your keystrokes are echoed by your terminal, not written to stdout, so they never reach the decoder. Use --rtt-channel N to work on a channel other than 0; the same channel is used in both directions.
The firmware must declare an RTT down buffer on the channel you use. The defmt-rtt crate sets up only the up buffer. With no down buffer, the target discards what it receives and gives no indication that it did so. That reads as a host-side failure, but it is not one. Firmware using rtt-target’s down channel, or a SEGGER_RTT down buffer, works as expected.
--interactive expects a terminal. In scripts and other non-interactive contexts, keep using plain --rtt with timeout. Only one interactive session can be attached to a given probe and channel at a time, because the underlying RTT connection accepts a single client. A second attempt is refused rather than silently taking the stream from the first.

Typical Workflows

Development Cycle

RTT Debugging

Memory Inspection

Clean Up

JLinkScript Support

JLinkScript files allow you to customize J-Link debug probe behavior for specific hardware configurations. They can handle custom reset sequences, clock initialization, pin configurations, and other device-specific operations that the standard J-Link connection flow does not cover.

Configuring JLinkScript

There are three ways to attach a J-Link script to a debug net: 1. During net creation:
2. On an existing net:
3. Per-project in .lager config:

Script Priority

When both a net-level script (stored on the box via set-script) and a project-level script (in .lager config) exist, the project-level script takes priority. This allows you to override the box-stored script for specific projects.

Managing Scripts

Once attached, the script is used automatically for all debug operations (connect, flash, erase, reset) without any additional flags.

DA1469x Targets

A device name that contains DA1469 selects the DA1469x handling. On this family, flash and erase act on the external QSPI flash. The CPU maps that flash for execute-in-place (XIP) at 0x16000000.
  • Addresses. Pass an absolute XIP address, for example --bin build/app.bin,0x16000000. Do not pass a flash offset.
  • Erase. By default, erase and the erase before flash erase 1 MiB from 0x16000000 on both backends. This family never gets a full chip erase.
  • Erase range. --erase-start and --erase-size set the range on both backends, and it must lie inside 0x16000000–0x17FFFFFF. On J-Link, when the two options are absent, a line such as LAGER_ERASE_RANGE: 0x16000000 0x160FFFFF in the JLinkScript of the net sets the range. The J-Link backend then erases that inclusive range. The options win over the script line, and the script line wins over the default.
On an OpenOCD probe, mainline OpenOCD has no driver for this QSPI flash. The box runs a RAM-resident flash loader instead:
  • An address outside 0x16000000–0x17FFFFFF is refused before the box touches the probe.
  • Use --bin. The loader writes the bytes of the file unchanged, so it does not decode a .hex or .elf file.
  • Put flash_loader.elf and flash_loader.elf.bin in ~/third_party/customer-binaries/openocd/flash-loaders/da1469x/ on the box host. The container sees that directory as /home/www-data/customer-binaries/openocd/flash-loaders/da1469x/.
  • To use a different parent directory, set LAGER_FLASH_LOADERS_DIR to a container path with lager box-config env set, then run lager box-config apply.
  • OpenOCD has no built-in target config for this family. Attach one to the net with lager nets set-script or lager nets add --openocd-config.
  • A dropped debug read does not stop the loader. The loader retries the read until the deadline of its step. The deadlines are 10 s for boot, 60 s per MiB for erase, and 30 s per program chunk.
A loader failure names the last progress line that the loader printed:
If the message says that the QSPI erase already ran, the board can be blank. Flash it again before you reset it.

Supported Debug Probes

The box selects the backend from the USB vendor ID of the probe.

Supported Device Families

Lager supports 70+ ARM Cortex-M device families with automatic architecture detection. The device type is specified as the channel when creating a debug net (e.g., STM32F407VG, nRF52840).

Cortex-M0/M0+ (ARMv6-M)

Cortex-M3 (ARMv7-M)

Cortex-M4/M7 (ARMv7E-M)

Cortex-M23 (ARMv8-M Base)

Cortex-M33/M55 (ARMv8-M Main)

Devices not in the table above default to Cortex-M4 (ARMv7E-M) architecture. If your device is not detected correctly, specify the full device part number (e.g., STM32F407VG rather than just STM32F4) when creating the debug net.

Notes

  • Debug nets are created with lager nets add <name> debug <device_type> <address>
  • The system auto-connects when needed for commands like flash and reset
  • Before flash, erase and reset run, the CLI checks that the target answers. If the GDB server is up but the target does not answer, the CLI prints The debug session is up, but the target does not answer; reconnecting... and starts a new session. If that reconnect fails, the command exits 1.
  • flash erases before programming by default, giving a clean state for RTT initialization (use --no-erase to opt out)
  • RTT streaming requires the device to have RTT support in firmware
  • Memory reads are more reliable with --halt to pause the CPU
  • Use lager debug health --verbose to diagnose connection issues
  • JLinkScript files are base64-encoded for storage and decoded automatically on the box

See Also