vexide_panic/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
//! Panic handler implementation for [`vexide`](https://crates.io/crates/vexide)
//!
//! Supports capturing and printing backtraces to aid in debugging.
//!
//! If the `display_panics` feature is enabled, it will also display the panic message on the V5 Brain display.

#![no_std]

extern crate alloc;

#[allow(unused_imports)]
use alloc::{
    boxed::Box,
    string::{String, ToString},
};
#[allow(unused_imports)]
use core::{cell::UnsafeCell, fmt::Write};

use vexide_core::{backtrace::Backtrace, println, sync::Mutex};
#[cfg(feature = "display_panics")]
use vexide_devices::{
    display::{Display, Font, FontFamily, FontSize, Rect, Text},
    math::Point2,
};

/// Draw an error box to the display.
///
/// This function is internally used by the vexide panic handler for displaying
/// panic messages graphically before exiting.
#[cfg(feature = "display_panics")]
fn draw_error(display: &mut Display, msg: &str, backtrace: &Backtrace) {
    const ERROR_BOX_MARGIN: i16 = 16;
    const ERROR_BOX_PADDING: i16 = 16;
    const LINE_HEIGHT: i16 = 20;
    const LINE_MAX_WIDTH: usize = 52;

    fn draw_text(screen: &mut Display, buffer: &str, line: i16) {
        screen.draw_text(
            &Text::new(
                buffer,
                Font::new(FontSize::SMALL, FontFamily::Monospace),
                Point2 {
                    x: ERROR_BOX_MARGIN + ERROR_BOX_PADDING,
                    y: ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * LINE_HEIGHT),
                },
            ),
            (255, 255, 255),
            None,
        );
    }

    display.set_render_mode(vexide_devices::display::RenderMode::Immediate);

    let error_box_rect = Rect::new(
        Point2 {
            x: ERROR_BOX_MARGIN,
            y: ERROR_BOX_MARGIN,
        },
        Point2 {
            x: Display::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN,
            y: Display::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN,
        },
    );

    display.fill(&error_box_rect, (255, 0, 0));
    display.stroke(&error_box_rect, (255, 255, 255));

    let mut buffer = String::new();
    let mut line: i16 = 0;

    for (i, character) in msg.char_indices() {
        if !character.is_ascii_control() {
            buffer.push(character);
        }

        if character == '\n' || ((buffer.len() % LINE_MAX_WIDTH == 0) && (i > 0)) {
            draw_text(display, &buffer, line);
            line += 1;
            buffer.clear();
        }
    }

    if !buffer.is_empty() {
        draw_text(display, &buffer, line);

        line += 1;
    }

    line += 1;
    draw_text(display, "stack backtrace:", line);
    line += 1;

    if !backtrace.frames.is_empty() {
        const ROW_LENGTH: usize = 3;
        for (col, frames) in backtrace.frames.chunks(ROW_LENGTH).enumerate() {
            let mut msg = String::new();
            for (row, frame) in frames.iter().enumerate() {
                write!(msg, "{:>3}: {:?}    ", col * ROW_LENGTH + row, frame).unwrap();
            }
            draw_text(display, msg.trim_end(), line);
            line += 1;
        }
    }
}

/// The default panic handler.
///
/// This function is called when a panic occurs and no custom panic hook is set,
/// but you can also use it as a fallback in your custom panic hook to print the
/// panic message to the screen and stdout.
///
/// It will print the panic message to the serial connection, and if the
/// `display_panics` feature is enabled, it will also display the panic message
/// on the V5 Brain display.
///
/// Note that if `display_panics` is not enabled, this function will not return.
/// It will immediately exit the program after printing the panic message. If
/// you do not want this behavior, you should use your own
///
/// # Examples
///
/// ```
/// # use vexide_panic::{default_panic_hook, set_hook};
/// #
/// set_hook(|info| {
///     // Do something interesting with the panic info, like printing it to the
///     // controller screen so the driver knows something has gone wrong.
///     // ...
///
///     // Then, call the default panic hook to show the message on the screen
///     default_panic_hook(info);
/// });
/// ```
pub fn default_panic_hook(info: &core::panic::PanicInfo<'_>) {
    println!("{info}");

    let backtrace = Backtrace::capture();

    #[cfg(feature = "display_panics")]
    draw_error(
        unsafe { &mut Display::new() },
        &info.to_string(),
        &backtrace,
    );

    if !backtrace.frames.is_empty() {
        println!("{backtrace}");
    }

    #[cfg(not(feature = "display_panics"))]
    vexide_core::program::exit();
}

/// The panic hook type
///
/// This mirrors the one available in the standard library.
enum Hook {
    Default,
    Custom(Box<dyn Fn(&core::panic::PanicInfo<'_>) + Send>),
}

/// A word-for-word copy of the Rust `std` impl
impl Hook {
    #[inline]
    fn into_box(self) -> Box<dyn Fn(&core::panic::PanicInfo<'_>) + Send> {
        match self {
            Hook::Default => Box::new(default_panic_hook),
            Hook::Custom(hook) => hook,
        }
    }
}

static HOOK: Mutex<Hook> = Mutex::new(Hook::Default);

/// Registers a custom panic hook, replacing the current one if any.
///
/// This can be used to, for example, output a different message to the screen
/// than the default one shown when the `display_panics` feature is enabled, or
/// to log the panic message to a log file or other output (you will need to use
/// `unsafe` to get peripheral access).
///
/// **Note**: Just like in the standard library, a custom panic hook _does
/// override_ the default panic hook. In `vexide`'s case, this means that the
/// error _will not automatically print to the screen or console_ when you set
/// a custom panic hook. You will need to either do that yourself in the custom
/// panic hook, or call [`default_panic_hook`] from your hook.
///
/// # Examples
///
/// ```
/// use vexide_panic::set_panic_hook;
///
/// set_hook(|info| {
///     // Do something with the panic info
///     // This is pretty useless since vexide already does this
///     println!("{:?}", info);
/// });
/// ```
pub fn set_hook<F>(hook: F)
where
    F: Fn(&core::panic::PanicInfo<'_>) + Send + 'static,
{
    // Try to lock the mutex. This should always succeed since the mutex is only
    // locked when the program panics and by the set_hook and take_hook
    // functions, which don't panic while holding a lock.
    let mut guard = HOOK
        .try_lock()
        .expect("failed to set custom panic hook (mutex poisoned or locked)");
    // If we used a simple assignment, like
    // *guard = Hook::Custom(Box::new(hook));
    // the old value will be dropped. Since the old value is `dyn`, it
    // could have arbitrary side effects in its destructor, *including
    // panicking*. We need to avoid panicking here, since the panic
    // handler would not be able to lock the mutex, which is kind of
    // important.
    // Don't do anything that could panic until guard is dropped
    let old_handler = core::mem::replace(&mut *guard, Hook::Custom(Box::new(hook))).into_box();
    // Drop the guard first to avoid a deadlock
    core::mem::drop(guard);
    // Now we can drop the old handler
    core::mem::drop(old_handler); // This could panic
}

/// Unregisters the current panic hook, if any, and returns it, replacing it
/// with the default panic hook.
///
/// The default panic hook will remain registered if no custom hook was set.
pub fn take_hook() -> Box<dyn Fn(&core::panic::PanicInfo<'_>) + Send> {
    // Try to lock the mutex. This should always succeed since the mutex is only
    // locked when the program panics and by the set_hook and take_hook
    // functions, which don't panic while holding a lock.
    let mut guard = HOOK
        .try_lock()
        .expect("failed to set custom panic hook (mutex poisoned or locked)");
    // Don't do anything that could panic until guard is dropped
    let old_hook = core::mem::replace(&mut *guard, Hook::Default).into_box();
    core::mem::drop(guard);
    old_hook
}

#[panic_handler]
/// The panic handler for vexide.
pub fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
    // Try to lock the HOOK mutex. If we can't, we'll just use the default panic
    // handler, since it's probably not good to panic in the panic handler and
    // leave the user clueless about what happened.
    //
    // We should be able to lock the mutex, since we know that the mutex is only
    // otherwise locked in `take_hook` and `set_hook`, which don't panic.
    if let Some(mut guard) = HOOK.try_lock() {
        let hook = core::mem::replace(&mut *guard, Hook::Default);
        // Drop the guard first to avoid preventing set_hook or take_hook from
        // getting a hook
        core::mem::drop(guard);
        (hook.into_box())(info);
    } else {
        // Since this is in theory unreachable, if it is reached, let's ask the
        // user to file a bug report.
        println!("Panic handler hook mutex is poisoned. Using default `vexide-panic` panic handler. This should never happen.");
        println!("If you see this, please consider filing a bug: https://github.com/vexide/vexide/issues/new");
        default_panic_hook(info);
    }

    // enter into an endless loop if the panic hook didn't exit the program
    loop {
        unsafe {
            // Flush the serial buffer so that the panic message is printed
            vex_sdk::vexTasksRun();
        }
    }
}