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