]> git.proxmox.com Git - rustc.git/blob - src/doc/embedded-book/src/start/exceptions.md
bump version to 1.79.0+dfsg1-1~bpo12+pve2
[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 transformed to 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 > 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 dereference the reference via `*` to access the values of the variables without
54 > needing to wrap them in an `unsafe` block.
55
56 ## A complete example
57
58 Here's an example that uses the system timer to raise a `SysTick` exception
59 roughly every second. The `SysTick` exception handler keeps track of how many
60 times 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
66 ```rust,ignore
67 #![deny(unsafe_code)]
68 #![no_main]
69 #![no_std]
70
71 use panic_halt as _;
72
73 use core::fmt::Write;
74
75 use cortex_m::peripheral::syst::SystClkSource;
76 use cortex_m_rt::{entry, exception};
77 use cortex_m_semihosting::{
78 debug,
79 hio::{self, HStdout},
80 };
81
82 #[entry]
83 fn 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);
91 syst.clear_current();
92 syst.enable_counter();
93 syst.enable_interrupt();
94
95 loop {}
96 }
97
98 #[exception]
99 fn 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]
129 cortex-m = "0.5.7"
130 cortex-m-rt = "0.6.3"
131 panic-halt = "0.2.0"
132 cortex-m-semihosting = "0.3.1"
133 ```
134
135 ``` text
136 $ cargo run --release
137 Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..)
138 123456789
139 ```
140
141 If you run this on the Discovery board you'll see the output on the OpenOCD
142 console. Also, the program will *not* stop when the count reaches 9.
143
144 ## The default exception handler
145
146 What the `exception` attribute actually does is *override* the default exception
147 handler for a specific exception. If you don't override the handler for a
148 particular exception it will be handled by the `DefaultHandler` function, which
149 defaults to:
150
151 ``` rust,ignore
152 fn DefaultHandler() {
153 loop {}
154 }
155 ```
156
157 This 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
161 It's possible to override this `DefaultHandler` using the `exception` attribute:
162
163 ``` rust,ignore
164 #[exception]
165 fn DefaultHandler(irqn: i16) {
166 // custom default handler
167 }
168 ```
169
170 The `irqn` argument indicates which exception is being serviced. A negative
171 value indicates that a Cortex-M exception is being serviced; and zero or a
172 positive value indicate that a device specific exception, AKA interrupt, is
173 being serviced.
174
175 ## The hard fault handler
176
177 The `HardFault` exception is a bit special. This exception is fired when the
178 program enters an invalid state so its handler can *not* return as that could
179 result in undefined behavior. Also, the runtime crate does a bit of work before
180 the user defined `HardFault` handler is invoked to improve debuggability.
181
182 The result is that the `HardFault` handler must have the following signature:
183 `fn(&ExceptionFrame) -> !`. The argument of the handler is a pointer to
184 registers that were pushed into the stack by the exception. These registers are
185 a snapshot of the processor state at the moment the exception was triggered and
186 are useful to diagnose a hard fault.
187
188 Here's an example that performs an illegal operation: a read to a nonexistent
189 memory 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
195 ```rust,ignore
196 #![no_main]
197 #![no_std]
198
199 use panic_halt as _;
200
201 use core::fmt::Write;
202 use core::ptr;
203
204 use cortex_m_rt::{entry, exception, ExceptionFrame};
205 use cortex_m_semihosting::hio;
206
207 #[entry]
208 fn 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]
218 fn HardFault(ef: &ExceptionFrame) -> ! {
219 if let Ok(mut hstdout) = hio::hstdout() {
220 writeln!(hstdout, "{:#?}", ef).ok();
221 }
222
223 loop {}
224 }
225 ```
226
227 The `HardFault` handler prints the `ExceptionFrame` value. If you run this
228 you'll see something like this on the OpenOCD console.
229
230 ``` text
231 $ openocd
232 (..)
233 ExceptionFrame {
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
245 The `pc` value is the value of the Program Counter at the time of the exception
246 and it points to the instruction that triggered the exception.
247
248 If you look at the disassembly of the program:
249
250
251 ``` text
252 $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex
253 (..)
254 ResetTrampoline:
255 8000942: movw r0, #0xfffe
256 8000946: movt r0, #0x3fff
257 800094a: ldr r0, [r0]
258 800094c: b #-0x4 <ResetTrampoline+0xa>
259 ```
260
261 You can lookup the value of the program counter `0x0800094a` in the disassembly.
262 You'll see that a load operation (`ldr r0, [r0]` ) caused the exception.
263 The `r0` field of `ExceptionFrame` will tell you the value of register `r0`
264 was `0x3fff_fffe` at that time.