]> git.proxmox.com Git - rustc.git/blame - src/doc/embedded-book/src/start/exceptions.md
New upstream version 1.49.0~beta.4+dfsg1
[rustc.git] / src / doc / embedded-book / src / start / exceptions.md
CommitLineData
9fa01778
XL
1# Exceptions
2
3Exceptions, and interrupts, are a hardware mechanism by which the processor
4handles asynchronous events and fatal errors (e.g. executing an invalid
5instruction). Exceptions imply preemption and involve exception handlers,
6subroutines executed in response to the signal that triggered the event.
7
8The `cortex-m-rt` crate provides an [`exception`] attribute to declare exception
9handlers.
10
11[`exception`]: https://docs.rs/cortex-m-rt-macros/latest/cortex_m_rt_macros/attr.exception.html
12
13``` rust,ignore
14// Exception handler for the SysTick (System Timer) exception
15#[exception]
16fn SysTick() {
17 // ..
18}
19```
20
21Other than the `exception` attribute exception handlers look like plain
22functions but there's one more difference: `exception` handlers can *not* be
23called by software. Following the previous example, the statement `SysTick();`
24would result in a compilation error.
25
26This behavior is pretty much intended and it's required to provide a feature:
27`static mut` variables declared *inside* `exception` handlers are *safe* to use.
28
29``` rust,ignore
30#[exception]
31fn SysTick() {
32 static mut COUNT: u32 = 0;
33
f035d41b 34 // `COUNT` has transformed to type `&mut u32` and it's safe to use
9fa01778
XL
35 *COUNT += 1;
36}
37```
38
39As you may know, using `static mut` variables in a function makes it
532ac7d7 40[*non-reentrant*](https://en.wikipedia.org/wiki/Reentrancy_(computing)). It's undefined behavior to call a non-reentrant function,
9fa01778
XL
41directly or indirectly, from more than one exception / interrupt handler or from
42`main` and one or more exception / interrupt handlers.
43
44Safe Rust must never result in undefined behavior so non-reentrant functions
45must be marked as `unsafe`. Yet I just told that `exception` handlers can safely
46use `static mut` variables. How is this possible? This is possible because
47`exception` handlers can *not* be called by software thus reentrancy is not
48possible.
49
f035d41b
XL
50> Note that the `exception` attribute transforms definitions of static variables
51> inside the function by wrapping them into `unsafe` blocks and providing us
52> with new appropriate variables of type `&mut` of the same name.
53> Thus we can derefence the reference via `*` to access the values of the variables without
54> needing to wrap them in an `unsafe` block.
55
9fa01778
XL
56## A complete example
57
58Here's an example that uses the system timer to raise a `SysTick` exception
59roughly every second. The `SysTick` exception handler keeps track of how many
60times it has been called in the `COUNT` variable and then prints the value of
61`COUNT` to the host console using semihosting.
62
63> **NOTE**: You can run this example on any Cortex-M device; you can also run it
64> on QEMU
65
532ac7d7 66```rust,ignore
9fa01778
XL
67#![deny(unsafe_code)]
68#![no_main]
69#![no_std]
70
f035d41b 71use panic_halt as _;
9fa01778
XL
72
73use core::fmt::Write;
74
75use cortex_m::peripheral::syst::SystClkSource;
76use cortex_m_rt::{entry, exception};
77use cortex_m_semihosting::{
78 debug,
79 hio::{self, HStdout},
80};
81
82#[entry]
83fn main() -> ! {
84 let p = cortex_m::Peripherals::take().unwrap();
85 let mut syst = p.SYST;
86
87 // configures the system timer to trigger a SysTick exception every second
88 syst.set_clock_source(SystClkSource::Core);
89 // this is configured for the LM3S6965 which has a default CPU clock of 12 MHz
90 syst.set_reload(12_000_000);
416331ca 91 syst.clear_current();
9fa01778
XL
92 syst.enable_counter();
93 syst.enable_interrupt();
94
95 loop {}
96}
97
98#[exception]
99fn SysTick() {
100 static mut COUNT: u32 = 0;
101 static mut STDOUT: Option<HStdout> = None;
102
103 *COUNT += 1;
104
105 // Lazy initialization
106 if STDOUT.is_none() {
107 *STDOUT = hio::hstdout().ok();
108 }
109
110 if let Some(hstdout) = STDOUT.as_mut() {
111 write!(hstdout, "{}", *COUNT).ok();
112 }
113
114 // IMPORTANT omit this `if` block if running on real hardware or your
115 // debugger will end in an inconsistent state
116 if *COUNT == 9 {
117 // This will terminate the QEMU process
118 debug::exit(debug::EXIT_SUCCESS);
119 }
120}
121```
122
123``` console
124$ tail -n5 Cargo.toml
125```
126
127``` toml
128[dependencies]
129cortex-m = "0.5.7"
130cortex-m-rt = "0.6.3"
131panic-halt = "0.2.0"
132cortex-m-semihosting = "0.3.1"
133```
134
135``` console
136$ cargo run --release
137 Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
138123456789
139```
140
141If you run this on the Discovery board you'll see the output on the OpenOCD
142console. Also, the program will *not* stop when the count reaches 9.
143
144## The default exception handler
145
146What the `exception` attribute actually does is *override* the default exception
147handler for a specific exception. If you don't override the handler for a
148particular exception it will be handled by the `DefaultHandler` function, which
149defaults to:
150
151``` rust,ignore
152fn DefaultHandler() {
153 loop {}
154}
155```
156
157This function is provided by the `cortex-m-rt` crate and marked as
158`#[no_mangle]` so you can put a breakpoint on "DefaultHandler" and catch
159*unhandled* exceptions.
160
161It's possible to override this `DefaultHandler` using the `exception` attribute:
162
163``` rust,ignore
164#[exception]
165fn DefaultHandler(irqn: i16) {
166 // custom default handler
167}
168```
169
170The `irqn` argument indicates which exception is being serviced. A negative
171value indicates that a Cortex-M exception is being serviced; and zero or a
172positive value indicate that a device specific exception, AKA interrupt, is
173being serviced.
174
175## The hard fault handler
176
177The `HardFault` exception is a bit special. This exception is fired when the
178program enters an invalid state so its handler can *not* return as that could
179result in undefined behavior. Also, the runtime crate does a bit of work before
180the user defined `HardFault` handler is invoked to improve debuggability.
181
182The result is that the `HardFault` handler must have the following signature:
183`fn(&ExceptionFrame) -> !`. The argument of the handler is a pointer to
184registers that were pushed into the stack by the exception. These registers are
185a snapshot of the processor state at the moment the exception was triggered and
186are useful to diagnose a hard fault.
187
188Here's an example that performs an illegal operation: a read to a nonexistent
189memory location.
190
191> **NOTE**: This program won't work, i.e. it won't crash, on QEMU because
192> `qemu-system-arm -machine lm3s6965evb` doesn't check memory loads and will
193> happily return `0 `on reads to invalid memory.
194
532ac7d7 195```rust,ignore
9fa01778
XL
196#![no_main]
197#![no_std]
198
f035d41b 199use panic_halt as _;
9fa01778
XL
200
201use core::fmt::Write;
202use core::ptr;
203
204use cortex_m_rt::{entry, exception, ExceptionFrame};
205use cortex_m_semihosting::hio;
206
207#[entry]
208fn main() -> ! {
209 // read a nonexistent memory location
210 unsafe {
211 ptr::read_volatile(0x3FFF_FFFE as *const u32);
212 }
213
214 loop {}
215}
216
217#[exception]
218fn HardFault(ef: &ExceptionFrame) -> ! {
219 if let Ok(mut hstdout) = hio::hstdout() {
220 writeln!(hstdout, "{:#?}", ef).ok();
221 }
222
223 loop {}
224}
225```
226
227The `HardFault` handler prints the `ExceptionFrame` value. If you run this
228you'll see something like this on the OpenOCD console.
229
230``` console
231$ openocd
232(..)
233ExceptionFrame {
234 r0: 0x3ffffffe,
235 r1: 0x00f00000,
236 r2: 0x20000000,
237 r3: 0x00000000,
238 r12: 0x00000000,
239 lr: 0x080008f7,
240 pc: 0x0800094a,
241 xpsr: 0x61000000
242}
243```
244
245The `pc` value is the value of the Program Counter at the time of the exception
246and it points to the instruction that triggered the exception.
247
248If you look at the disassembly of the program:
249
250
251``` console
29967ef6 252$ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex
9fa01778
XL
253(..)
254ResetTrampoline:
255 8000942: movw r0, #0xfffe
256 8000946: movt r0, #0x3fff
257 800094a: ldr r0, [r0]
258 800094c: b #-0x4 <ResetTrampoline+0xa>
259```
260
532ac7d7
XL
261You can lookup the value of the program counter `0x0800094a` in the dissassembly.
262You'll see that a load operation (`ldr r0, [r0]` ) caused the exception.
263The `r0` field of `ExceptionFrame` will tell you the value of register `r0`
264was `0x3fff_fffe` at that time.