Skip to content

Commit ca960df

Browse files
authored
Add resume_unwind, à la Rust's version from std::panic (#497)
1 parent a966924 commit ca960df

5 files changed

Lines changed: 68 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

applications/unwind_test/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ path = "../../kernel/terminal_print"
1717

1818
[dependencies.task]
1919
path = "../../kernel/task"
20+
21+
[dependencies.catch_unwind]
22+
path = "../../kernel/catch_unwind"

applications/unwind_test/src/lib.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate alloc;
55
#[macro_use] extern crate log;
66
// #[macro_use] extern crate terminal_print;
77
extern crate task;
8+
extern crate catch_unwind;
89

910

1011
use alloc::vec::Vec;
@@ -15,7 +16,7 @@ use alloc::string::String;
1516
struct MyStruct(pub usize);
1617
impl Drop for MyStruct {
1718
fn drop(&mut self) {
18-
warn!("DROPPING MYSTRUCT({})", self.0);
19+
warn!("\nDROPPING MYSTRUCT({})\n", self.0);
1920
}
2021
}
2122

@@ -36,7 +37,7 @@ fn foo(cause_page_fault: bool) {
3637
}
3738

3839

39-
pub fn main(_args: Vec<String>) -> isize {
40+
pub fn main(args: Vec<String>) -> isize {
4041

4142
// // dump some info about the this loaded app crate
4243
// {
@@ -52,11 +53,37 @@ pub fn main(_args: Vec<String>) -> isize {
5253

5354
let _my_struct = MyStruct(5);
5455

55-
let cause_page_fault = match _args.get(0).map(|s| &**s) {
56-
Some("-e") => true,
57-
_ => false,
56+
match args.get(0).map(|s| &**s) {
57+
// cause a page fault to test unwinding through a machine exception
58+
Some("-e") => foo(true),
59+
// test catch_unwind and then resume_unwind
60+
Some("-c") => catch_resume_unwind(),
61+
_ => foo(false),
5862
};
5963

60-
foo(cause_page_fault);
64+
error!("Test failure: unwind_test::main should not return!");
65+
6166
0
6267
}
68+
69+
#[inline(never)]
70+
fn catch_resume_unwind() {
71+
let _my_struct6 = MyStruct(6);
72+
73+
let res = catch_unwind::catch_unwind_with_arg(fn_to_catch, MyStruct(22));
74+
warn!("CAUGHT UNWINDING ACTION, as expected.");
75+
let _my_struct7 = MyStruct(7);
76+
if let Err(e) = res {
77+
let _my_struct8 = MyStruct(8);
78+
catch_unwind::resume_unwind(e);
79+
}
80+
81+
error!("Test failure: catch_resume_unwind should not return!");
82+
}
83+
84+
#[inline(never)]
85+
fn fn_to_catch(_s: MyStruct) {
86+
let _my_struct9 = MyStruct(9);
87+
88+
panic!("intentional panic in unwind_test::fn_to_catch()")
89+
}

kernel/catch_unwind/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,28 @@ fn try_intrinsic_trampoline<F, A, R>(try_intrinsic_arg: *mut u8) where F: FnOnce
9090
);
9191
}
9292
}
93+
94+
95+
/// Resumes the unwinding procedure after it was caught with [`catch_unwind_with_arg()`].
96+
///
97+
/// This is analogous to the Rust's [`std::panic::resume_unwind()`] in that it is
98+
/// intended to be used to continue unwinding after a panic was caught.
99+
///
100+
/// The argument is a [`KillReason`] instead of a typical Rust panic "payload"
101+
/// (which is usually `Box<Any + Send>`) for two reasons:
102+
/// 1. `KillReason` is the type returned by [`catch_unwind_with_arg()`] upon failure,
103+
/// so it makes sense to continue unwinding with that same error type.
104+
/// 2. It's more flexible than the standard Rust panic info type because it must also
105+
/// represent the possibility of a non-panic failure, e.g., a machine exception.
106+
///
107+
/// [`std::panic::resume_unwind()`]: https://doc.rust-lang.org/std/panic/fn.resume_unwind.html
108+
pub fn resume_unwind(caught_panic_reason: KillReason) -> ! {
109+
// We can skip up to 2 frames here: `unwind::start_unwinding` and `resume_unwind` (this function)
110+
let result = unwind::start_unwinding(caught_panic_reason, 2);
111+
112+
// `start_unwinding` should not return
113+
panic!("BUG: start_unwinding() returned {:?}. This is an unexpected failure, as no unwinding occurred. Task: {:?}.",
114+
result,
115+
task::get_my_current_task()
116+
);
117+
}

kernel/unwind/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,9 +689,14 @@ fn get_eh_frame_info(crate_ref: &StrongCrateRef) -> Option<(StrongSectionRef, Ba
689689
///
690690
/// # Arguments
691691
/// * `reason`: the reason why the current task is being killed, e.g., due to a panic, exception, etc.
692-
/// * `stack_frames_to_skip`: the number of stack frames that should be skipped in order to avoid unwinding them.
692+
/// * `stack_frames_to_skip`: the number of stack frames that can be skipped in order to avoid unwinding them.
693+
/// Those frames should have nothing that needs to be unwound, e.g., no landing pads that invoke drop handlers.
693694
/// For example, for a panic, the first `5` frames in the call stack can be ignored.
694695
///
696+
/// ## Note: Skipping frames
697+
/// If you are unsure how many frames you could possibly skip, then it's always safe to pass `0`
698+
/// such that all function frames on the stack are unwound.
699+
///
695700
#[doc(hidden)]
696701
pub fn start_unwinding(reason: KillReason, stack_frames_to_skip: usize) -> Result<(), &'static str> {
697702
// Here we have to be careful to have no resources waiting to be dropped/freed/released on the stack.

0 commit comments

Comments
 (0)