> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lagerdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Debug

> Embedded debug operations for J-Link probes

Control embedded debug operations including device connection, firmware flashing, reset, and memory access using J-Link debug probes.

## Import

```python theme={null}
from lager import Net, NetType
```

## Methods

The Net-based API provides methods for embedded debugging operations.

| Method                                                             | Description                                                            |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| `connect(speed, transport, *, script, force, ignore_if_connected)` | Connect to target device (optional per-connect J-Link script override) |
| `disconnect()`                                                     | Disconnect from target                                                 |
| `reset(halt)`                                                      | Reset the device                                                       |
| `flash(firmware_path)`                                             | Flash firmware to device                                               |
| `erase()`                                                          | Perform full chip erase                                                |
| `read_memory(address, length)`                                     | Read memory from device                                                |
| `status()`                                                         | Get connection status                                                  |
| `rtt(channel, search_addr, search_size, chunk_size)`               | Create RTT session for bidirectional communication (raw bytes)         |
| `rtt_defmt(elf, channel)`                                          | RTT session decoded through `defmt-print` (yields log lines)           |
| `session(...)`                                                     | Scoped session: connect on entry, guaranteed teardown on exit          |

## Method Reference

### `Net.get(name, type=NetType.Debug)`

Get a debug net by name.

```python theme={null}
from lager import Net, NetType

dbg = Net.get('DUT', type=NetType.Debug)
```

**Parameters:**

| Parameter | Type      | Description             |
| --------- | --------- | ----------------------- |
| `name`    | `str`     | Name of the debug net   |
| `type`    | `NetType` | Must be `NetType.Debug` |

**Returns:** Debug Net instance

**Note:** The debug net must be configured with the target device name stored in the `channel` field (e.g., 'NRF52840\_XXAA', 'R7FA0E107').

### `connect(speed=None, transport=None, *, script=None, force=False, ignore_if_connected=False)`

Connect to the target device (start the gdbserver for this probe). The backend
(J-Link or OpenOCD) is chosen automatically from the probe.

```python theme={null}
# Connect with default settings (4000 kHz, SWD)
dbg.connect()

# Connect with custom speed
dbg.connect(speed='adaptive')

# Connect with JTAG
dbg.connect(transport='JTAG')

# Connect with a per-connect J-Link script override (box path or base64 blob)
dbg.connect(script='/home/lagerdata/probes/my_target.JLinkScript')
```

**Parameters:**

| Parameter             | Type   | Default  | Description                                                                                                                                                                                                                                                                       |
| --------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `speed`               | `str`  | `'4000'` | Interface speed in kHz (e.g., '4000') or 'adaptive'                                                                                                                                                                                                                               |
| `transport`           | `str`  | `'SWD'`  | Transport protocol ('SWD' or 'JTAG')                                                                                                                                                                                                                                              |
| `script`              | `str`  | `None`   | **J-Link only** — a per-connect `.JLinkScript` override: a path on the box **or** a base64-encoded script blob. It is copied to the shared script temp path so subsequent `flash()` / `reset()` / `read_memory()` calls pick it up immediately. The OpenOCD backend ignores this. |
| `force`               | `bool` | `False`  | Stop any gdbserver already running for this probe and start fresh.                                                                                                                                                                                                                |
| `ignore_if_connected` | `bool` | `False`  | If a gdbserver is already running for this probe, return its status without touching it.                                                                                                                                                                                          |

**Returns:** `dict` - Status dictionary with connection information

<Note>
  A `script` override is only adopted by a *relaunch* of the gdbserver. If a server
  is already running, pass `force=True` to restart it with the new script;
  `ignore_if_connected=True` returns early without relaunching (though the script
  file is still repointed for subsequent Commander operations). Invalid input — a
  missing path that isn't valid base64, or an empty string — is silently ignored and
  the net's previously materialised script stays in effect. **Concurrency caveat:**
  two debug nets connecting with different scripts share one temp path and can clobber
  each other.
</Note>

### `disconnect()`

Disconnect from the target device.

```python theme={null}
dbg.disconnect()
```

**Returns:** `dict` - Status dictionary

### `reset(halt=False)`

Reset the device.

```python theme={null}
# Reset and continue execution
output = dbg.reset(halt=False)
print(output)

# Reset and halt for debugging
output = dbg.reset(halt=True)
print(output)
```

**Parameters:**

| Parameter | Type   | Default | Description          |
| --------- | ------ | ------- | -------------------- |
| `halt`    | `bool` | `False` | Halt CPU after reset |

**Returns:** `str` - Combined output from reset operation

**Self-heal (both backends):** Right after a `flash()` there is a short window where the debug server isn't reachable yet — for J-Link the restarted GDB server's PID isn't observable, for OpenOCD a transient daemon/RPC fault — and a bare call would raise. `reset()` now retries with bounded backoff on both the J-Link and OpenOCD backends, and only (re)starts a server when one is genuinely down. It never tears down a server that is already running, so an attached RTT session is left intact, and callers no longer need their own retry wrappers.

**DA1469x exception:** On DA1469x, `flash()` deliberately leaves the server down (it ends in a software reset rather than a server restart) and the documented flow is an explicit, halt-aware reconnect. So on DA1469x the self-heal retries but never auto-starts a server — a genuinely-down server still surfaces the original error, exactly as before, instead of silently coming up unhalted (which can yield garbage QSPI-XIP reads).

### `flash(firmware_path)`

Flash firmware to the device.

```python theme={null}
# Flash a hex file
output = dbg.flash('/path/to/firmware.hex')
print(output)

# Flash a binary file (address 0x00000000 assumed)
output = dbg.flash('/path/to/firmware.bin')
print(output)

# Flash an ELF file
output = dbg.flash('/path/to/firmware.elf')
print(output)
```

**Parameters:**

| Parameter       | Type  | Description                                 |
| --------------- | ----- | ------------------------------------------- |
| `firmware_path` | `str` | Path to firmware file (.hex, .bin, or .elf) |

**Returns:** `str` - Combined output from flash operation

**Note:** For .bin files, flash address defaults to 0x00000000.

### `erase()`

Perform full chip erase. This erases ALL flash memory including protection settings.

```python theme={null}
# Full chip erase
output = dbg.erase()
print(output)
```

**Returns:** `str` - Combined output from erase operation

### `read_memory(address, length)`

Read memory from the target device.

```python theme={null}
# Read 256 bytes starting at address 0x20000000
data = dbg.read_memory(0x20000000, 256)
print(f"Read {len(data)} bytes")
print(data.hex())
```

**Parameters:**

| Parameter | Type  | Description             |
| --------- | ----- | ----------------------- |
| `address` | `int` | Starting memory address |
| `length`  | `int` | Number of bytes to read |

**Returns:** `bytes` - Memory data

**Self-heal:** like `reset()`, `read_memory()` retries with bounded backoff across the brief post-`flash()` settling window on both backends and only reconnects when no server is running, never disturbing a live session. `erase()` behaves the same way. The same DA1469x exception applies — no server is auto-started, so a post-flash DA1469x read raises clearly rather than returning unhalted-XIP garbage.

### `status()`

Get the current connection status.

```python theme={null}
status = dbg.status()
print(f"Connected: {status.get('connected', False)}")
```

**Returns:** `dict` - Status dictionary with connection information

### `session(speed=None, transport=None, connect=True, ignore_if_connected=True, disconnect_on_exit=True)`

Scoped debug session. Connects on entry and guarantees teardown on exit, so the safe flash → attach-RTT → reset ordering is encoded once instead of being rediscovered in every script. The `with` target is the net itself, so the full surface (`flash`, `rtt_defmt`, `reset`, `read_memory`, …) is available inside the block.

```python theme={null}
with dbg.session() as s:
    s.flash('build/app.hex')                 # built-in stop->flash->restart handoff
    with s.rtt_defmt(elf='build/app.elf') as logs:
        s.reset(halt=False)                  # reader re-attaches across the reset blip
        for line in logs:
            if 'boot ok' in line:
                break
# GDB server is torn down here (disconnect_on_exit=True)
```

**Parameters:**

| Parameter             | Type            | Default | Description                                                                               |
| --------------------- | --------------- | ------- | ----------------------------------------------------------------------------------------- |
| `speed`               | `str` or `None` | `None`  | Forwarded to `connect()`                                                                  |
| `transport`           | `str` or `None` | `None`  | Forwarded to `connect()`                                                                  |
| `connect`             | `bool`          | `True`  | Connect on entry. Set `False` to attach to a server you manage yourself                   |
| `ignore_if_connected` | `bool`          | `True`  | Reuse a running server instead of raising (regression-safe: never restarts a live server) |
| `disconnect_on_exit`  | `bool`          | `True`  | Stop the GDB server on exit. Set `False` to leave it running for later commands           |

**Returns:** a context manager yielding the debug net.

**Why it pairs with RTT:** the in-process RTT reader is reconnect-aware (see below), so a `flash()` or `reset()` inside the session that bounces the GDB server doesn't kill a log stream opened in the same block.

### `rtt(channel=0, search_addr=None, search_size=None, chunk_size=None)`

Create an RTT (Real-Time Transfer) session for bidirectional communication with the target device.

```python theme={null}
# Open RTT session on default channel (0)
with dbg.rtt() as rtt:
    # Read debug output
    data = rtt.read_some(timeout=1.0)
    if data:
        print(data.decode('utf-8'))

    # Send commands to device
    rtt.write(b'test_command\n')

# Use different RTT channel
with dbg.rtt(channel=1) as rtt:
    data = rtt.read_some(timeout=2.0)

# Specify RAM search region for RTT control block
with dbg.rtt(search_addr=0x20000000, search_size=0x10000) as rtt:
    data = rtt.read_some(timeout=1.0)
```

**Parameters:**

| Parameter     | Type            | Default | Description                                    |
| ------------- | --------------- | ------- | ---------------------------------------------- |
| `channel`     | `int`           | `0`     | RTT channel number (typically 0-15)            |
| `search_addr` | `int` or `None` | `None`  | RAM start address for RTT control block search |
| `search_size` | `int` or `None` | `None`  | Size of RAM region to search in bytes          |
| `chunk_size`  | `int` or `None` | `None`  | Size of each read chunk in bytes               |

**Returns:** RTT context manager with methods:

* `read_some(timeout)` - Read available data with timeout (returns bytes or None)
* `write(data)` - Write data to target (accepts bytes or str)

**Note:** Debug connection must be active before using RTT. Call `connect()` first.

**Reconnect-aware (both backends):** a J-Link `flash()` (and `reset()` via its Commander grab) briefly frees the probe's USB and restarts the GDB server on the *same* ports, dropping the RTT socket. The reader transparently re-attaches to the same RTT telnet port instead of going silent, so a long-lived `read_some()` / `rtt_defmt()` loop keeps producing across a flash. The OpenOCD reader is reconnect-aware too — OpenOCD keeps its daemon up across an ordinary flash so the socket rarely drops, but if it does (daemon force-restart or rtt-server bounce) the reader re-runs the `rtt setup` / `rtt server start` and re-attaches. In both cases reconnection is bounded (default 30 s) and only re-attaches once the server/daemon is actually back up — it never *starts* one — so a flash that deliberately leaves the server down (e.g. DA1469x) won't spin forever, and the reader can't disturb a DA1469x left intentionally down. Pass `reconnect=False` for the legacy one-shot behavior.

## Examples

### Flash Firmware and Reset

```python theme={null}
from lager import Net, NetType

# Get debug net
dbg = Net.get('DUT', type=NetType.Debug)

# Connect to target
status = dbg.connect()
print(f"Connected: {status}")

# Flash firmware
output = dbg.flash('/etc/lager/firmware/app.hex')
print(output)

# Reset and run
output = dbg.reset(halt=False)
print(output)

# Disconnect
dbg.disconnect()
```

### Chip Erase Before Programming

```python theme={null}
from lager import Net, NetType

# Get debug net
dbg = Net.get('DUT', type=NetType.Debug)

# Connect to target
status = dbg.connect()
print(f"Connected: {status}")

# Erase entire chip first (ensures clean state)
print("Erasing chip...")
output = dbg.erase()
print(output)

# Flash new firmware
output = dbg.flash('/etc/lager/firmware/app.hex')
print(output)

# Disconnect
dbg.disconnect()
```

### Read Memory

```python theme={null}
from lager import Net, NetType

dbg = Net.get('DUT', type=NetType.Debug)

# Connect to target
dbg.connect()

# Read 256 bytes from RAM
data = dbg.read_memory(0x20000000, 256)
print(f"Read {len(data)} bytes")
print(data.hex())

# Disconnect
dbg.disconnect()
```

## CLI Commands (Recommended)

For most use cases, the CLI provides a simpler interface:

```bash theme={null}
# Start GDB server (connect to target)
lager debug <net> gdbserver --box <box-name>

# Flash firmware
lager debug <net> flash --hex firmware.hex --box <box-name>

# Reset device
lager debug <net> reset --box <box-name>

# Erase flash
lager debug <net> erase --box <box-name>

# Read memory
lager debug <net> memrd 0x20000000 256 --box <box-name>

# Disconnect
lager debug <net> disconnect --box <box-name>

# Check status
lager debug <net> status --box <box-name>
```

See the [CLI Debug Reference](/source/reference/cli/debug) for full CLI documentation.

## RTT Streaming

SEGGER Real-Time Transfer (RTT) enables high-speed bidirectional communication with embedded devices during debugging (faster than UART, no timing impact).

```python theme={null}
from lager import Net, NetType

# Connect debug probe first
debug = Net.get('debug1', type=NetType.Debug)
debug.connect()

# Open RTT session for reading debug output
with debug.rtt() as rtt:
    # Read debug output from MCU
    data = rtt.read_some(timeout=1.0)
    if data:
        print(data.decode('utf-8'))

    # Can also write commands to MCU
    rtt.write(b'start_test\n')
```

**RTT Methods:**

| Method               | Description                                              |
| -------------------- | -------------------------------------------------------- |
| `read_some(timeout)` | Read available data with timeout (returns bytes or None) |
| `write(data)`        | Write data to RTT (accepts bytes or str)                 |

<Warning>
  `rtt().read_some()` returns **raw, still-encoded** bytes. Firmware that logs with [defmt](https://defmt.ferrous-systems.com/) (the de-facto standard for embedded Rust) emits a compressed binary format — calling `.decode('utf-8')` on it yields garbage. For defmt firmware, use `rtt_defmt()` below or the CLI pipe, both of which decode through `defmt-print`.
</Warning>

### Decoding defmt logs with `rtt_defmt()`

`rtt_defmt(elf, channel=0)` opens an RTT session and pipes it through `defmt-print` (preinstalled on the Lager Box), yielding **decoded log lines** instead of raw bytes. The `elf` must be the exact firmware flashed on the target — defmt needs its symbol metadata to decode.

```python theme={null}
from lager import Net, NetType
import time

dbg = Net.get('debug1', type=NetType.Debug)
dbg.connect(ignore_if_connected=True)  # reuse a running gdbserver if one is up
dbg.flash('build/app.elf')   # skip if already flashed; same ELF you decode against
dbg.reset()                  # restart to capture boot logs

# Capture a bounded ~10s window of decoded logs
with dbg.rtt_defmt(elf='build/app.elf', channel=0) as logs:
    deadline = time.time() + 10
    while time.time() < deadline:
        line = logs.read_line(timeout=1.0)   # decoded str, or None
        if line:
            print(line)
            assert 'panic' not in line.lower(), f"firmware panicked: {line}"
```

`rtt_defmt()` returns a context manager exposing:

| Method                          | Description                                                       |
| ------------------------------- | ----------------------------------------------------------------- |
| `read_line(timeout=None)`       | Next decoded log line as `str`, or `None` on timeout / stream end |
| iteration (`for line in logs:`) | Yield decoded lines until the stream ends                         |

Like the CLI pipe, the RTT stream never ends on its own — bound your read loop with a time budget or line count, then exit the `with` block.

**Parameters:**

| Parameter         | Type            | Default  | Description                                                                                                      |
| ----------------- | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `elf`             | `str`           | required | Path to the firmware ELF flashed on the DUT (relative paths resolve against the script's working dir on the box) |
| `channel`         | `int`           | `0`      | RTT channel number                                                                                               |
| `defmt_print_bin` | `str` or `None` | `None`   | Override the `defmt-print` binary (path or name on PATH)                                                         |
| `read_timeout`    | `float`         | `0.5`    | Poll interval (seconds) for the internal RTT read loop                                                           |

**CLI Alternative:** For interactive tailing, pipe the CLI directly: `lager debug <net> gdbserver --box <box> --rtt 2>/dev/null | defmt-print -e build/app.elf`. See the [CLI Debug Reference](/source/reference/cli/debug#decoding-defmt-logs). Use `rtt_defmt()` when you need to assert on log content inside a test script; use the pipe when you just want to watch logs.

## Supported Devices

J-Link supports a wide range of ARM Cortex-M and other microcontrollers. Common device names:

| Manufacturer | Device Name        | Description              |
| ------------ | ------------------ | ------------------------ |
| Nordic       | `NRF52840_XXAA`    | nRF52840                 |
| Nordic       | `NRF52833_XXAA`    | nRF52833                 |
| Nordic       | `NRF5340_XXAA_APP` | nRF5340 Application Core |
| Renesas      | `R7FA0E107`        | RA0E1 Series             |
| Renesas      | `R7FA2L1`          | RA2L1 Series             |
| STMicro      | `STM32F103C8`      | STM32F1 Series           |
| STMicro      | `STM32F407VG`      | STM32F4 Series           |
| STMicro      | `STM32L476RG`      | STM32L4 Series           |

For a complete list, see [SEGGER's supported devices](https://www.segger.com/supported-devices/jlink/).

## Supported Hardware

| Debug Probe | Features                              |
| ----------- | ------------------------------------- |
| J-Link      | JTAG/SWD debugging, flash programming |
| CMSIS-DAP   | SWD debugging (via pyOCD backend)     |
| ST-Link     | SWD debugging (via pyOCD backend)     |

## Notes

* Debug nets must be configured with the target device name in the `channel` field
* The CLI (`lager debug`) is recommended for most use cases
* Python Net API is intended for advanced automation scripts running on the Lager Box
* Always call `disconnect()` when finished to release the debug probe
* Use `erase()` to perform a full chip erase and clear protection settings
* RTT requires an active debug connection (see RTT Streaming section above)
