$ echo $?
1
NOTE: To enable this feature on panic-semihosting, edit your Cargo.toml dependencies section where panic-semihosting is specified with:
panic-semihosting = { version = "VERSION", features = ["exit"] }
where VERSION is the version desired. For more information on dependencies features check the specifying dependencies section of the Cargo book.
Panicking is a core part of the Rust language. Built-in operations like indexing are runtime checked for memory safety. When out of bounds indexing is attempted this results in a panic.
In the standard library panicking has a defined behavior: it unwinds the stack of the panicking thread, unless the user opted for aborting the program on panics.
In programs without standard library, however, the panicking behavior is left undefined. A behavior can be chosen by declaring a #[panic_handler] function. This function must appear exactly once in the dependency graph of a program, and must have the following signature: fn(&PanicInfo) -> !, where PanicInfo is a struct containing information about the location of the panic.
Given that embedded systems range from user facing to safety critical (cannot crash) there's no one size fits all panicking behavior but there are plenty of commonly used behaviors. These common behaviors have been packaged into crates that define the #[panic_handler] function. Some examples include:
• panic-abort. A panic causes the abort instruction to be executed.
• panic-halt. A panic causes the program, or the current thread, to halt by entering an infinite loop.
• panic-itm. The panicking message is logged using the ITM, an ARM Cortex-M specific peripheral.
• panic-semihosting. The panicking message is logged to the host using the semihosting technique.
You may be able to find even more crates searching for the panic-handler keyword on crates.io.
A program can pick one of these behaviors simply by linking to the corresponding crate. The fact that the panicking behavior is expressed in the source of an application as a single line of code is not only useful as documentation but can also be used to change the panicking behavior according to the compilation profile. For example:
#![no_main]
#![no_std]
// dev profile: easier to debug panics; can put a breakpoint on `rust_begin_unwind`
#[cfg(debug_assertions)]
use panic_halt as _;
// release profile: minimize the binary size of the application
#[cfg(not(debug_assertions))]
use panic_abort as _;
// ..
In this example the crate links to the panic-halt crate when built with the dev profile (cargo build), but links to the panic-abort crate when built with the release profile (cargo build --release).
The use panic_abort as _; form of the use statement is used to ensure the panic_abort panic handler is included in our final executable while making it clear to the compiler that we won't explicitly use anything from the crate. Without the as _ rename, the compiler would warn that we have an unused import. Sometimes you might see extern crate panic_abort instead, which is an older style used before the 2018 edition of Rust, and should now only be used for "sysroot" crates (those distributed with Rust itself) such as proc_macro, alloc, std, and test.
Here's an example that tries to index an array beyond its length. The operation results in a panic.
#![no_main]
#![no_std]
use panic_semihosting as _;
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
let xs = [0, 1, 2];
let i = xs.len() + 1;
let _y = xs[i]; // out of bounds access
loop {}
}
This example chose the panic-semihosting behavior which prints the panic message to the host console using semihosting.
$ cargo run
Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
panicked at 'index out of bounds: the len is 3 but the index is 4', src/main.rs:12:13
You can try changing the behavior to panic-halt and confirm that no message is printed in that case.
Exceptions, and interrupts, are a hardware mechanism by which the processor handles asynchronous events and fatal errors (e.g. executing an invalid instruction). Exceptions imply preemption and involve exception handlers, subroutines executed in response to the signal that triggered the event.
The cortex-m-rt crate provides an exception attribute to declare exception handlers.
// Exception handler for the SysTick (System Timer) exception
#[exception]
fn SysTick() {
// ..
}
Other than the exception attribute exception handlers look like plain functions but there's one more difference: exception handlers can not be called by software. Following the previous example, the statement SysTick(); would result in a compilation error.
This behavior is pretty much intended and it's required to provide a feature: static mut variables declared inside exception handlers are safe to use.
#[exception]
fn SysTick() {
static mut COUNT: u32 = 0;
// `COUNT` has transformed to type `&mut u32` and it's safe to use
*COUNT += 1;
}
As you may know, using static mut variables in a function makes it non-reentrant. It's undefined behavior to call a non-reentrant function, directly or indirectly, from more than one exception / interrupt handler or from main and one or more exception / interrupt handlers.
Safe Rust must never result in undefined behavior so non-reentrant functions must be marked as unsafe. Yet I just told that exception handlers can safely use static mut variables. How is this possible? This is possible because exception handlers can not be called by software thus reentrancy is not possible.
Note that the exception attribute transforms definitions of static variables inside the function by wrapping them into unsafe blocks and providing us with new appropriate variables of type &mut of the same name. Thus we can derefence the reference via * to access the values of the variables without needing to wrap them in an unsafe block.