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
.
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
.
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
[profile.dev]
debug = 1 # default is `true`; not needed if not already overridden
[profile.release]
debug = 1 # default is `false`; using `true` is also OK
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
.
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;
#[entry]
fn main() -> ! {
// omitted: rtt initialization
rprintln!("Hello, world!");
loop { asm::bkpt() }
}
would output
$ cargo run --bin hello
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `probe-run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/hello`
(HOST) INFO flashing program (30.22 KiB)
(HOST) INFO success!
────────────────────────────────────────────────────────────────────────────────
INFO:hello -- Hello, world!
────────────────────────────────────────────────────────────────────────────────
(HOST) INFO exiting because the device halted.
To see the backtrace at the exit point repeat this run with
`probe-run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/hello --force-backtrace`
When the device raises a hard fault exception, indicating e.g. a panic or a stack overflow, probe-run
will print a backtrace and exit with a non-zero exit code.
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.
#![no_main]
#![no_std]
use cortex_m::asm;
#[entry]
fn main() -> ! {
// trigger a hard fault exception with the UDF instruction.
asm::udf()
}
Finished dev [optimized + debuginfo] target(s) in 0.04s
Running `probe-run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/hard-fault`
(HOST) INFO flashing program (30.08 KiB)
(HOST) INFO success!
────────────────────────────────────────────────────────────────────────────────
stack backtrace:
0: HardFaultTrampoline
<exception entry>
1: __udf
2: cortex_m::asm::udf
at /<...>/cortex-m-0.6.4/src/asm.rs:104
3: panic::__cortex_m_rt_main
at src/bin/hard-fault.rs:12
4: main
at src/bin/hard-fault.rs:8
5: ResetTrampoline
at /<...>3/cortex-m-rt-0.6.13/src/lib.rs:547
6: Reset
at /<...>/cortex-m-rt-0.6.13/src/lib.rs:550
If we look at the return code emitted by this cargo run
, we'll see that it is non-0:
$ echo $?
134
probe-run
, the HardFault
handler (default or user-defined) will NOT be executed.
If you'd like to see a backtrace at the end of successful program runs as well, you can enable this by setting the --force-backtrace
flag:
$ cargo run --bin hello --force-backtrace
Apart from a faulty connection between your computer and the target device, this could be caused by several things:
In order for probe-run
to find the device you'd like to run your code on, your system needs permission to access the device as a non-root user.
In order to grant these permissions, you'll need to add a new set of udev rules.
To learn how to do this for the nRF52840 Development Kit, check out the installation instructions in our embedded training materials.
To use probe-run
you need a "probe" (also known as "debugger") that sits between your PC and the microcontroller.
Most development boards, especially the bigger ones, have a probe "on-board": If the product description of your board mentions something like a J-Link or ST-Link on-board debugger you're good to go. With these boards, all you need to do is connect your PC to the dev board using a USB cable you are all set to use probe-run
!
If this is not the case for your board, check in the datasheet if it exposes exposes SWD or JTAG pins. If they are exposed, you can connect a "stand alone" probe device to the microcontroller and then connect the probe to your PC via USB. Some examples of stand alone probes are: the ST-Link and the J-Link.
Note that this may involve some soldering if your board does not come with a pre-attached header to plug your debugger into.
This may instead present as Error: RTT control block not found in target memory.
Your code, or a library you're using (e.g. RTIC) might be putting your CPU to sleep when idle. You can verify that this is the problem by busy looping instead of sleeping. When using RTIC, this can be achieved by adding an idle handler to your app:
#[idle]
fn idle(_ctx: idle::Context) -> ! {
loop {}
}
Assuming you'd like to still sleep in order to save power, you need to configure your microcontroller so that RTT can still be handled even when the CPU is sleeping. How to do this varies between microcontrollers.
On an STM32G0 running RTIC it can be done by amending your init function to set
the dmaen
bit on RCC.ahbenr
. e.g.:
#[init]
fn init(ctx: init::Context) -> init::LateResources {
ctx.device.RCC.ahbenr.write(|w| w.dmaen().set_bit());
...
}
Follow the instructions in the error message to resolve the mismatch.
If you are building probe-run
from source, you can disable the version check by setting the PROBE_RUN_IGNORE_VERSION
environment variable to true
or 1
at compile time.
For easier copy-paste-ability, here's an example how to try out your local probe_run
modifications.
$ cd probe-run/
$ PROBE_RUN_IGNORE_VERSION=1 cargo run -- --chip nRF52840_xxAA --max-backtrace-len=10 hello
ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ ˆˆˆˆˆ
environment variables extra flags binary to be
(optional) (optional) flashed & run
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
-
Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
-
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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.