#knurling #cargo-runner

no-std app probe-run

Runs embedded programs just like native ones

7 releases

new 0.1.6 Nov 23, 2020
0.1.5 Nov 20, 2020
0.1.3 Aug 19, 2020

#110 in Embedded development

Download history 61/week @ 2020-08-11 147/week @ 2020-08-18 46/week @ 2020-08-25 33/week @ 2020-09-01 38/week @ 2020-09-08 27/week @ 2020-09-15 33/week @ 2020-09-22 32/week @ 2020-09-29 25/week @ 2020-10-06 22/week @ 2020-10-13 28/week @ 2020-10-20 28/week @ 2020-10-27 28/week @ 2020-11-03 99/week @ 2020-11-10 90/week @ 2020-11-17

172 downloads per month


939 lines


Runs embedded programs just like native ones

probe-run is a custom Cargo runner that transparently runs Rust firmware on a remote device.

probe-run is powered by probe-rs and thus supports as many devices and probes as probe-rs does.


  • Acts as a Cargo runner, integrating into cargo run.
  • Displays program output streamed from the device via RTT.
  • Exits the firmware and prints a stack backtrace on breakpoints.


To install probe-run, use cargo install probe-run.

On Linux, you might have to install libudev and libusb from your package manager before installing probe-run.


  1. Set the Cargo runner

The recommend way to use probe-run is to set as the Cargo runner of your application. Add this line to your Cargo configuration (.cargo/config) file:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-run --chip ${PROBE_RUN_CHIP}"

Instead of ${PROBE_RUN_CHIP} you can write the name of your microcontroller. For example, one would use nRF52840_xxAA for the nRF52840 microcontroller. To list all supported chips run probe-run --list-chips.

To support multiple devices, or permit overriding default behavior, you may prefer to set the ${PROBE_RUN_CHIP} environment variable, and set runner (or CARGO_TARGET_${TARGET_ARCH}_RUNNER) to probe-run.

If you have several probes connected, you can specify which one to use by adding the --probe option to the runner or setting the ${PROBE_RUN_PROBE} environment variable with a value containing either ${VID}:${PID} or ${VID}:${PID}:${SERIAL}:

probe-run --probe '0483:3748' --chip ${PROBE_RUN_CHIP}
PROBE_RUN_PROBE='1366:0101:123456' cargo run

To list all connected probes, run probe-run --list-probes.

  1. Enable debug info

Next check that debug info is enabled for all profiles. If you are using the cortex-m-quickstart template then this is already the case. If not check or add these lines to Cargo.toml.

# Cargo.toml
debug = 1 # default is `true`; not needed if not already overridden

debug = 1 # default is `false`; using `true` is also OK
  1. Look out for old dependencies

The cortex-m dependency must be version 0.6.3 or newer. Older versions are not supported. Check your Cargo.lock for old versions. Run cargo update to update the cortex-m dependency if an older one appears in Cargo.lock.

  1. Run

You are all set. You can now run your firmware using cargo run. For example,

use cortex_m::asm;
use cortex_m_rt::entry;
use rtt_target::rprintln;

fn main() -> ! {
    // omitted: rtt initialization
    rprintln!("Hello, world!");
    loop { asm::bkpt() }
$ cargo run --bin hello
Running `probe-run target/thumbv7em-none-eabi/debug/hello`
flashing program ..
resetting device
Hello, world!
stack backtrace:
0: 0x0000031e - __bkpt
1: 0x000001d2 - hello::__cortex_m_rt_main
2: 0x00000108 - main
3: 0x000002fa - Reset

Stack backtraces

When the firmware reaches a BKPT instruction the device halts. The probe-run tool treats this halted state as the "end" of the application and exits with exit-code = 0. Before exiting, probe-run prints the stack backtrace of the halted program.

This backtrace follows the format of the std backtraces you get from std::panic! but includes <exception entry> lines to indicate where an exception/interrupt occurred.

use cortex_m::asm;
use rtt_target::rprintln;
fn main() -> ! {
    // omitted: rtt initialization
    rprintln!("after PendSV");
    loop { asm::bkpt() }
fn PendSV() {
$ cargo run --bin exception --release
stack backtrace:
0: 0x00000902 - __bkpt
<exception entry>
1: 0x000004de - nrf52::__cortex_m_rt_main
2: 0x00000408 - main
3: 0x000005ee - Reset

Non-zero exit code

When the device raises a hard fault exception probe-run will print a backtrace and exit with non-zero exit code.

You can trigger a hard fault exception with the UDF instruction.

use cortex_m::asm;
fn main() -> ! {
$ cargo run --bin hard-fault
stack backtrace:
   0: 0x000003e0 - HardFaultTrampoline
      <exception entry>
   1: 0x00000140 - __udf
   2: 0x00000118 - cortex_m::asm::udf
   3: 0x0000012c - hard_fault::__cortex_m_rt_main
   4: 0x00000122 - main
   5: 0x000000fa - Reset

$ echo $?

NOTE when you run your application with probe-run the HardFault handler, default or user-defined one, will NOT be executed.


probe-run is part of the Knurling project, Ferrous Systems' effort at improving tooling used to develop for embedded systems.

If you think that our work is useful, consider sponsoring it via GitHub Sponsors.


Licensed under either of

at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.


~206K SLoC