-
Notifications
You must be signed in to change notification settings - Fork 32
Add test for forced unwind #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| [package] | ||
| name = "forced_unwind" | ||
| version = "0.1.0" | ||
| edition = "2024" | ||
|
|
||
| [dependencies] | ||
| unwinding = { path = "../..", features = ["system-alloc", "personality", "panic-handler"] } | ||
| libc = "0.2" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| #!/usr/bin/env bash | ||
| set -o pipefail | ||
| trap "rm -f run.log" EXIT | ||
| ${CARGO:-cargo} run --release $BUILD_STD 2>&1 | tee run.log | ||
| if [ $? -ne 0 ]; then | ||
| echo process did not exit successfully | ||
| exit 1 | ||
| fi | ||
| grep -Pz 'forced unwind reached end of stack with cleanups run' run.log |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,111 @@ | ||||||
| #![no_std] | ||||||
| #![no_main] | ||||||
|
|
||||||
| extern crate alloc; | ||||||
| extern crate unwinding; | ||||||
|
|
||||||
| use alloc::boxed::Box; | ||||||
| use core::ffi::{c_int, c_void}; | ||||||
| use core::mem::MaybeUninit; | ||||||
| use core::ptr; | ||||||
| use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; | ||||||
|
|
||||||
| use unwinding::abi::*; | ||||||
| use unwinding::print::*; | ||||||
|
|
||||||
| #[link(name = "c")] | ||||||
| unsafe extern "C" {} | ||||||
|
|
||||||
| static STOP_ARG: u8 = 0; | ||||||
|
|
||||||
| static EXCEPTION: AtomicPtr<UnwindException> = AtomicPtr::new(ptr::null_mut()); | ||||||
|
|
||||||
| static DROPPED: AtomicBool = AtomicBool::new(false); | ||||||
|
|
||||||
| static EXCEPTION_FREED: AtomicBool = AtomicBool::new(false); | ||||||
|
|
||||||
| struct RecordOnDrop; | ||||||
|
|
||||||
| impl Drop for RecordOnDrop { | ||||||
| fn drop(&mut self) { | ||||||
| DROPPED.store(true, Ordering::SeqCst); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| unsafe extern "C" fn exception_cleanup(_code: UnwindReasonCode, _exception: *mut UnwindException) { | ||||||
| EXCEPTION_FREED.store(true, Ordering::SeqCst); | ||||||
| } | ||||||
|
|
||||||
| unsafe extern "C" fn stop_fn( | ||||||
| version: c_int, | ||||||
| actions: UnwindAction, | ||||||
| _exception_class: u64, | ||||||
| exception: *mut UnwindException, | ||||||
| _unwind_ctx: &mut UnwindContext<'_>, | ||||||
| arg: *mut c_void, | ||||||
| ) -> UnwindReasonCode { | ||||||
| assert_eq!(version, 1); | ||||||
| assert_eq!( | ||||||
| arg, &STOP_ARG as *const u8 as *mut c_void, | ||||||
| "stop argument not passed through" | ||||||
| ); | ||||||
| assert_eq!( | ||||||
| exception, | ||||||
| EXCEPTION.load(Ordering::SeqCst), | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why SeqCst?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no real reason, just started out with SeqCst without thinking about it much. Switched to Relaxed. |
||||||
| "exception object not passed through" | ||||||
| ); | ||||||
| assert!( | ||||||
| actions.contains(UnwindAction::FORCE_UNWIND), | ||||||
| "FORCE_UNWIND not set" | ||||||
| ); | ||||||
| assert!( | ||||||
| actions.contains(UnwindAction::CLEANUP_PHASE), | ||||||
| "CLEANUP_PHASE not set" | ||||||
| ); | ||||||
|
|
||||||
| if actions.contains(UnwindAction::END_OF_STACK) { | ||||||
| // Unwinder is at end of stack, so `_d` should have been cleaned up. | ||||||
| assert!( | ||||||
| DROPPED.load(Ordering::SeqCst), | ||||||
| "reached end of stack but the cleanup never ran" | ||||||
| ); | ||||||
| // Unwinder must not have freed our exception. | ||||||
| assert!( | ||||||
| !EXCEPTION_FREED.load(Ordering::SeqCst), | ||||||
| "unwinder called the exception cleanup routine during forced unwind" | ||||||
| ); | ||||||
| eprintln!("forced unwind reached end of stack with cleanups run"); | ||||||
| unsafe { libc::exit(0) }; | ||||||
| } | ||||||
| // Not at end of stack yet, keep unwinding. | ||||||
| UnwindReasonCode::NO_REASON | ||||||
| } | ||||||
|
|
||||||
| fn foo() { | ||||||
| // Leak the box so it outlives `foo()`. | ||||||
| let exception: *mut UnwindException = | ||||||
| Box::leak(Box::new(MaybeUninit::<UnwindException>::zeroed())).as_mut_ptr(); | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||
| unsafe { | ||||||
| (*exception).exception_class = u64::from_ne_bytes(*b"TSTFRCEU"); | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's this string?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was experimenting with a
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should still be a meaningful string though, e.g. "TESTRUST"
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||
| (*exception).exception_cleanup = Some(exception_cleanup); | ||||||
| } | ||||||
| EXCEPTION.store(exception, Ordering::SeqCst); | ||||||
|
|
||||||
| let arg = &STOP_ARG as *const u8 as *mut c_void; | ||||||
| let code = unsafe { _Unwind_ForcedUnwind(exception, stop_fn, arg) }; | ||||||
|
|
||||||
| // The stop function exits at end of stack, so reaching here means the test failed. | ||||||
| panic!("forced unwind returned unexpectedly: {}", code.0); | ||||||
| } | ||||||
|
|
||||||
| fn main() { | ||||||
| let _d = RecordOnDrop; | ||||||
| foo(); | ||||||
| } | ||||||
|
|
||||||
| // `C-unwind` lets the unwind propagate past `main`, to the end of the stack. | ||||||
| #[unsafe(export_name = "main")] | ||||||
| extern "C-unwind" fn start(_argc: isize, _argv: *const *const u8) -> isize { | ||||||
| main(); | ||||||
| 0 | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done