diff --git a/src/abi.rs b/src/abi.rs index b7ae323..9adaf8e 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -51,8 +51,8 @@ pub type UnwindStopFn = unsafe extern "C" fn( c_int, UnwindAction, u64, - *mut UnwindException, - *mut UnwindContext<'_>, + &mut UnwindException, + &mut UnwindContext<'_>, *mut c_void, ) -> UnwindReasonCode; @@ -60,6 +60,8 @@ pub type UnwindStopFn = unsafe extern "C" fn( pub struct UnwindException { pub exception_class: u64, pub exception_cleanup: Option, + private_1: Option, + private_2: usize, } pub type UnwindTraceFn = @@ -149,5 +151,272 @@ pub extern "C" fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void .find_fde(pc as usize - 1) .map(|r| r.fde.initial_address() as usize as _) .unwrap_or(ptr::null_mut()) +} + +macro_rules! try1 { + ($e: expr) => {{ + match $e { + Ok(v) => v, + Err(_) => return UnwindReasonCode::FATAL_PHASE1_ERROR, + } + }}; +} + +macro_rules! try2 { + ($e: expr) => {{ + match $e { + Ok(v) => v, + Err(_) => return UnwindReasonCode::FATAL_PHASE2_ERROR, + } + }}; +} + +#[no_mangle] +pub extern "C-unwind" fn _Unwind_RaiseException( + exception: &mut UnwindException, +) -> UnwindReasonCode { + let saved_ctx = save_context(); + + // Phase 1: Search for handler + let mut ctx = saved_ctx.clone(); + let handler_cfa = loop { + if let Some(frame) = try1!(Frame::from_context(&ctx)) { + if let Some(personality) = frame.personality() { + let result = personality( + 1, + UnwindAction::SEARCH_PHASE, + exception.exception_class, + exception, + &mut UnwindContext { + frame: Some(&frame), + ctx: &mut ctx, + }, + ); + + match result { + UnwindReasonCode::CONTINUE_UNWIND => (), + UnwindReasonCode::HANDLER_FOUND => { + exception.private_1 = None; + exception.private_2 = ctx[Arch::SP]; + break ctx[Arch::SP]; + } + _ => return UnwindReasonCode::FATAL_PHASE1_ERROR, + } + } + + ctx = try1!(frame.unwind(&ctx)); + } else { + return UnwindReasonCode::END_OF_STACK; + } + }; + + let mut ctx = saved_ctx; + let code = raise_exception_phase2(exception, &mut ctx, handler_cfa); + match code { + UnwindReasonCode::INSTALL_CONTEXT => unsafe { restore_context(&ctx) }, + _ => code, + } +} + +fn raise_exception_phase2( + exception: &mut UnwindException, + ctx: &mut Context, + handler_cfa: usize, +) -> UnwindReasonCode { + loop { + if let Some(frame) = try2!(Frame::from_context(ctx)) { + let is_handler = ctx[Arch::SP] == handler_cfa; + if let Some(personality) = frame.personality() { + let code = personality( + 1, + UnwindAction::CLEANUP_PHASE + | if is_handler { + UnwindAction::HANDLER_FRAME + } else { + UnwindAction::NONE + }, + exception.exception_class, + exception, + &mut UnwindContext { + frame: Some(&frame), + ctx, + }, + ); + + match code { + UnwindReasonCode::CONTINUE_UNWIND => (), + UnwindReasonCode::INSTALL_CONTEXT => break, + _ => return UnwindReasonCode::FATAL_PHASE2_ERROR, + } + } + + *ctx = try2!(frame.unwind(ctx)); + } else { + return UnwindReasonCode::FATAL_PHASE2_ERROR; + } + } + + UnwindReasonCode::INSTALL_CONTEXT +} + +#[no_mangle] +pub unsafe extern "C-unwind" fn _Unwind_ForceUnwind( + exception: &mut UnwindException, + stop: UnwindStopFn, + stop_arg: *mut c_void, +) -> UnwindReasonCode { + let mut ctx = save_context(); + + exception.private_1 = Some(stop); + exception.private_2 = stop_arg as _; + + let code = unsafe { force_unwind_phase2(exception, &mut ctx, stop, stop_arg) }; + match code { + UnwindReasonCode::INSTALL_CONTEXT => unsafe { restore_context(&ctx) }, + _ => code, + } +} + +unsafe fn force_unwind_phase2( + exception: &mut UnwindException, + ctx: &mut Context, + stop: UnwindStopFn, + stop_arg: *mut c_void, +) -> UnwindReasonCode { + loop { + let frame = try2!(Frame::from_context(ctx)); + + let code = unsafe { + stop( + 1, + UnwindAction::FORCE_UNWIND + | UnwindAction::END_OF_STACK + | if frame.is_none() { + UnwindAction::END_OF_STACK + } else { + UnwindAction::NONE + }, + exception.exception_class, + exception, + &mut UnwindContext { + frame: frame.as_ref(), + ctx, + }, + stop_arg, + ) + }; + match code { + UnwindReasonCode::NO_REASON => (), + _ => return UnwindReasonCode::FATAL_PHASE2_ERROR, + } + + if let Some(frame) = frame { + if let Some(personality) = frame.personality() { + let code = personality( + 1, + UnwindAction::FORCE_UNWIND | UnwindAction::CLEANUP_PHASE, + exception.exception_class, + exception, + &mut UnwindContext { + frame: Some(&frame), + ctx, + }, + ); + + match code { + UnwindReasonCode::CONTINUE_UNWIND => (), + UnwindReasonCode::INSTALL_CONTEXT => break, + _ => return UnwindReasonCode::FATAL_PHASE2_ERROR, + } + } + + *ctx = try2!(frame.unwind(ctx)); + } else { + return UnwindReasonCode::END_OF_STACK; + } + } + + UnwindReasonCode::INSTALL_CONTEXT +} + +#[no_mangle] +pub extern "C-unwind" fn _Unwind_Resume(exception: &mut UnwindException) -> ! { + let mut ctx = save_context(); + + let code = match exception.private_1 { + None => { + let handler_cfa = exception.private_2; + raise_exception_phase2(exception, &mut ctx, handler_cfa) + } + Some(stop) => { + let stop_arg = exception.private_2 as _; + unsafe { force_unwind_phase2(exception, &mut ctx, stop, stop_arg) } + } + }; + assert!(code == UnwindReasonCode::INSTALL_CONTEXT); + + unsafe { restore_context(&ctx) } +} + +#[no_mangle] +pub extern "C-unwind" fn _Unwind_Resume_or_Rethrow( + exception: &mut UnwindException, +) -> UnwindReasonCode { + let stop = match exception.private_1 { + None => return _Unwind_RaiseException(exception), + Some(v) => v, + }; + + let mut ctx = save_context(); + + let stop_arg = exception.private_2 as _; + let code = unsafe { force_unwind_phase2(exception, &mut ctx, stop, stop_arg) }; + assert!(code == UnwindReasonCode::INSTALL_CONTEXT); + + unsafe { restore_context(&ctx) } +} + +#[no_mangle] +pub unsafe extern "C" fn _Unwind_DeleteException(exception: *mut UnwindException) { + if let Some(cleanup) = unsafe { (*exception).exception_cleanup } { + unsafe { cleanup(UnwindReasonCode::FOREIGN_EXCEPTION_CAUGHT, exception) }; + } +} + +#[no_mangle] +pub unsafe extern "C-unwind" fn _Unwind_Backtrace( + trace: UnwindTraceFn, + trace_argument: *mut c_void, +) -> UnwindReasonCode { + let mut ctx = save_context(); + let mut skipping = true; + + loop { + let frame = try1!(Frame::from_context(&ctx)); + if !skipping { + let code = unsafe { + trace( + &mut UnwindContext { + frame: frame.as_ref(), + ctx: &mut ctx, + }, + trace_argument, + ) + }; + match code { + UnwindReasonCode::NO_REASON => (), + _ => return UnwindReasonCode::FATAL_PHASE1_ERROR, + } + } + if let Some(frame) = frame { + if skipping { + if frame.initial_address() == _Unwind_Backtrace as usize { + skipping = false; + } + } + ctx = try1!(frame.unwind(&ctx)); + } else { + return UnwindReasonCode::END_OF_STACK; + } } }