diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..99e7ec1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - i686-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - riscv64gc-unknown-linux-gnu + - riscv32gc-unknown-linux-gnu + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install Rust + run: | + rustup update nightly + rustup default nightly + - name: Install cross-compilation tools + uses: taiki-e/setup-cross-toolchain-action@v1 + with: + target: ${{ matrix.target }} + + - name: Build library + run: cargo build --release $BUILD_STD + + - name: Run tests + run: cargo test --release $BUILD_STD diff --git a/.gitignore b/.gitignore index 98ae53c..b3e9e33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .vscode/ target -Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4ad1349 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,87 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "catch_std_exception" +version = "0.1.0" +dependencies = [ + "libc", + "unwinding", +] + +[[package]] +name = "compiler_builtins" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a6d58e9c3408138099a396a98fd0d0e6cfb25d723594d2ae48b5004513fd5b" + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "rustc-std-workspace-alloc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff66d57013a5686e1917ed6a025d54dd591fcda71a41fe07edf4d16726aefa86" + +[[package]] +name = "rustc-std-workspace-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "std_catch_exception" +version = "0.1.0" +dependencies = [ + "libc", + "unwinding", +] + +[[package]] +name = "throw_and_catch" +version = "0.1.0" +dependencies = [ + "libc", + "unwinding", +] + +[[package]] +name = "unwinding" +version = "0.2.1" +dependencies = [ + "compiler_builtins", + "gimli", + "libc", + "rustc-std-workspace-core", + "spin", +] + +[[package]] +name = "unwinding_dyn" +version = "0.1.0" +dependencies = [ + "libc", + "unwinding", +] diff --git a/Cargo.toml b/Cargo.toml index b27aef4..49a4129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,18 @@ [package] name = "unwinding" -version = "0.1.5" +version = "0.2.1" authors = ["Gary Guo "] -edition = "2018" +edition = "2021" license = "MIT OR Apache-2.0" description = "Unwinding library in Rust and for Rust" repository = "https://github.com/nbdd0121/unwinding/" [dependencies] -gimli = { version = "0.26.1", default-features = false, features = ["read-core"] } +gimli = { version = "0.28", default-features = false, features = ["read-core"] } libc = { version = "0.2", optional = true } -spin = { version = "0.9", optional = true, default-features = false, features = ["mutex", "spin_mutex"] } +spin = { version = "0.9.8", optional = true, default-features = false, features = ["mutex", "spin_mutex"] } +core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' } +compiler_builtins = { version = "0.1.2", optional = true } [features] alloc = [] @@ -21,6 +23,7 @@ fde-phdr-aux = ["fde-phdr"] fde-registry = ["alloc"] fde-static = [] fde-gnu-eh-frame-hdr = [] +fde-custom = [] dwarf-expr = [] hide-trace = [] personality = [] @@ -32,6 +35,7 @@ panic-handler = ["print", "panic"] panic-handler-dummy = [] system-alloc = [] default = ["unwinder", "dwarf-expr", "hide-trace", "fde-phdr-dl", "fde-registry"] +rustc-dep-of-std = ["core", "gimli/rustc-dep-of-std", "compiler_builtins"] [package.metadata.docs.rs] features = ["panic-handler"] diff --git a/README.md b/README.md index 21f5509..3b5f86b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This library serves two purposes: 1. Provide a pure Rust alternative to libgcc_eh or libunwind. 2. Provide easier unwinding support for `#![no_std]` targets. -Currently supports x86_64, x86, RV64 and AArch64. +Currently supports x86_64, x86, RV64, RV32 and AArch64. ## Unwinder @@ -23,6 +23,7 @@ The unwinder can be enabled with `unwinder` feature. Here are the feature gates | fde-registry | Yes | Provide `__register__frame` and others for dynamic registration. Requires either `libc` or `spin` for a mutex implementation. | | fde-gnu-eh-frame-hdr | No | Use `__executable_start`, `__etext` and `__GNU_EH_FRAME_HDR` to retrieve frame unwind table. The former two symbols are usually provided by the linker, while the last one is provided if GNU LD is used and --eh-frame-hdr option is enabled. | | fde-static | No | Use `__executable_start`, `__etext` and `__eh_frame` to retrieve frame unwind table. The former two symbols are usually provided by the linker, while the last one would need to be provided by the user via linker script. | +| fde-custom | No | Allow the program to provide a custom means of retrieving frame unwind table at runtime via the `set_custom_eh_frame_finder` function. | | dwarf-expr | Yes | Enable the dwarf expression evaluator. Usually not necessary for Rust | | hide-trace | Yes | Hide unwinder frames in back trace | diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/abi.rs b/src/abi.rs index 737cb0f..12766c1 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -60,11 +60,11 @@ impl UnwindAction { pub type UnwindExceptionCleanupFn = unsafe extern "C" fn(UnwindReasonCode, *mut UnwindException); -pub type UnwindStopFn = extern "C" fn( +pub type UnwindStopFn = unsafe extern "C" fn( c_int, UnwindAction, u64, - &mut UnwindException, + *mut UnwindException, &mut UnwindContext<'_>, *mut c_void, ) -> UnwindReasonCode; @@ -78,7 +78,7 @@ pub struct UnwindException { } pub type UnwindTraceFn = - extern "C" fn(ctx: &mut UnwindContext<'_>, arg: *mut c_void) -> UnwindReasonCode; + extern "C" fn(ctx: &UnwindContext<'_>, arg: *mut c_void) -> UnwindReasonCode; #[cfg(not(feature = "unwinder"))] #[repr(C)] @@ -87,31 +87,49 @@ pub struct UnwindContext<'a> { phantom: core::marker::PhantomData<&'a ()>, } -pub type PersonalityRoutine = extern "C" fn( +pub type PersonalityRoutine = unsafe extern "C" fn( c_int, UnwindAction, u64, - &mut UnwindException, + *mut UnwindException, &mut UnwindContext<'_>, ) -> UnwindReasonCode; #[cfg(not(feature = "unwinder"))] macro_rules! binding { - ($(extern $abi: literal $([$t:tt])? fn $name: ident ($($arg: ident : $arg_ty: ty),*$(,)?) $(-> $ret: ty)?;)*) => { - $( - #[allow(non_snake_case)] - #[inline] - pub $($t)? fn $name($($arg: $arg_ty),*) $(-> $ret)? { - extern $abi { - fn $name($($arg: $arg_ty),*) $(-> $ret)?; - } - unsafe { $name($($arg),*) } + () => {}; + (unsafe extern $abi: literal fn $name: ident ($($arg: ident : $arg_ty: ty),*$(,)?) $(-> $ret: ty)?; $($rest: tt)*) => { + extern $abi { + pub fn $name($($arg: $arg_ty),*) $(-> $ret)?; + } + binding!($($rest)*); + }; + + (extern $abi: literal fn $name: ident ($($arg: ident : $arg_ty: ty),*$(,)?) $(-> $ret: ty)?; $($rest: tt)*) => { + #[allow(non_snake_case)] + #[inline] + pub fn $name($($arg: $arg_ty),*) $(-> $ret)? { + extern $abi { + fn $name($($arg: $arg_ty),*) $(-> $ret)?; } - )* + unsafe { $name($($arg),*) } + } + binding!($($rest)*); + }; +} + +#[cfg(feature = "unwinder")] +macro_rules! binding { + () => {}; + (unsafe extern $abi: literal fn $name: ident ($($arg: ident : $arg_ty: ty),*$(,)?) $(-> $ret: ty)?; $($rest: tt)*) => { + const _: unsafe extern $abi fn($($arg_ty),*) $(-> $ret)? = $name; + }; + + (extern $abi: literal fn $name: ident ($($arg: ident : $arg_ty: ty),*$(,)?) $(-> $ret: ty)?; $($rest: tt)*) => { + const _: extern $abi fn($($arg_ty),*) $(-> $ret)? = $name; }; } -#[cfg(not(feature = "unwinder"))] binding! { extern "C" fn _Unwind_GetGR(unwind_ctx: &UnwindContext<'_>, index: c_int) -> usize; extern "C" fn _Unwind_GetCFA(unwind_ctx: &UnwindContext<'_>) -> usize; @@ -134,19 +152,19 @@ binding! { extern "C" fn _Unwind_GetTextRelBase(unwind_ctx: &UnwindContext<'_>) -> usize; extern "C" fn _Unwind_GetDataRelBase(unwind_ctx: &UnwindContext<'_>) -> usize; extern "C" fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void; - extern "C-unwind" fn _Unwind_RaiseException( - exception: &mut UnwindException, + unsafe extern "C-unwind" fn _Unwind_RaiseException( + exception: *mut UnwindException, ) -> UnwindReasonCode; - extern "C-unwind" fn _Unwind_ForcedUnwind( - exception: &mut UnwindException, + unsafe extern "C-unwind" fn _Unwind_ForcedUnwind( + exception: *mut UnwindException, stop: UnwindStopFn, stop_arg: *mut c_void, ) -> UnwindReasonCode; - extern "C-unwind" fn _Unwind_Resume(exception: &mut UnwindException) -> !; - extern "C-unwind" fn _Unwind_Resume_or_Rethrow( - exception: &mut UnwindException, + unsafe extern "C-unwind" fn _Unwind_Resume(exception: *mut UnwindException) -> !; + unsafe extern "C-unwind" fn _Unwind_Resume_or_Rethrow( + exception: *mut UnwindException, ) -> UnwindReasonCode; - extern "C" [unsafe] fn _Unwind_DeleteException(exception: *mut UnwindException); + unsafe extern "C" fn _Unwind_DeleteException(exception: *mut UnwindException); extern "C-unwind" fn _Unwind_Backtrace( trace: UnwindTraceFn, trace_argument: *mut c_void, diff --git a/src/lib.rs b/src/lib.rs index bdeb56d..23e776e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,18 @@ #![doc = include_str!("../README.md")] #![feature(c_unwind)] #![feature(naked_functions)] +#![feature(non_exhaustive_omitted_patterns_lint)] +// lang_items is an internal feature. `internal_features` lint is added recently +// so also allow unknown lints to prevent warning in older nightly versions. +#![cfg_attr( + any( + feature = "personality", + feature = "personality-dummy", + feature = "panicking", + feature = "panic-handler-dummy" + ), + allow(internal_features) +)] #![cfg_attr( any(feature = "personality", feature = "personality-dummy"), feature(lang_items) @@ -20,6 +32,9 @@ extern crate alloc; #[cfg(feature = "unwinder")] mod unwinder; +#[cfg(all(feature = "unwinder", feature = "fde-custom"))] +pub use unwinder::custom_eh_frame_finder; + pub mod abi; mod arch; @@ -30,7 +45,7 @@ pub mod print; #[cfg(feature = "personality")] mod personality; -#[cfg(feature = "personality-dummy")] +#[cfg(all(not(feature = "personality"), feature = "personality-dummy"))] mod personality_dummy; #[cfg(feature = "panic")] @@ -40,7 +55,7 @@ pub mod panicking; #[cfg(feature = "panic-handler")] mod panic_handler; -#[cfg(feature = "panic-handler-dummy")] +#[cfg(all(not(feature = "panic-handler"), feature = "panic-handler-dummy"))] mod panic_handler_dummy; #[cfg(feature = "system-alloc")] diff --git a/src/panic.rs b/src/panic.rs index 8d5bd9d..b69e958 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -7,6 +7,8 @@ use crate::abi::*; pub use crate::panic_handler::*; use crate::panicking::Exception; +static CANARY: u8 = 0; + #[repr(transparent)] struct RustPanic(Box, DropGuard); @@ -18,13 +20,15 @@ impl Drop for DropGuard { { drop_panic(); } - core::intrinsics::abort(); + crate::util::abort(); } } #[repr(C)] struct ExceptionWithPayload { exception: MaybeUninit, + // See rust/library/panic_unwind/src/gcc.rs for the canary values + canary: *const u8, payload: RustPanic, } @@ -34,12 +38,23 @@ unsafe impl Exception for RustPanic { fn wrap(this: Self) -> *mut UnwindException { Box::into_raw(Box::new(ExceptionWithPayload { exception: MaybeUninit::uninit(), + canary: &CANARY, payload: this, })) as *mut UnwindException } unsafe fn unwrap(ex: *mut UnwindException) -> Self { - let ex = unsafe { Box::from_raw(ex as *mut ExceptionWithPayload) }; + let ex = ex as *mut ExceptionWithPayload; + let canary = unsafe { core::ptr::addr_of!((*ex).canary).read() }; + if !core::ptr::eq(canary, &CANARY) { + // This is a Rust exception but not generated by us. + #[cfg(feature = "panic-handler")] + { + foreign_exception(); + } + crate::util::abort(); + } + let ex = unsafe { Box::from_raw(ex) }; ex.payload } } @@ -57,7 +72,7 @@ pub fn catch_unwind R>(f: F) -> Result> { foreign_exception(); } - core::intrinsics::abort(); + crate::util::abort(); } Some(e) => { #[cfg(feature = "panic-handler")] diff --git a/src/panic_handler.rs b/src/panic_handler.rs index 508ad8c..1d07768 100644 --- a/src/panic_handler.rs +++ b/src/panic_handler.rs @@ -59,10 +59,7 @@ fn stack_trace() { struct CallbackData { counter: usize, } - extern "C" fn callback( - unwind_ctx: &mut UnwindContext<'_>, - arg: *mut c_void, - ) -> UnwindReasonCode { + extern "C" fn callback(unwind_ctx: &UnwindContext<'_>, arg: *mut c_void) -> UnwindReasonCode { let data = unsafe { &mut *(arg as *mut CallbackData) }; data.counter += 1; eprintln!( @@ -80,7 +77,7 @@ fn do_panic(msg: Box) -> ! { if PANIC_COUNT.get() >= 1 { stack_trace(); eprintln!("thread panicked while processing panic. aborting."); - core::intrinsics::abort(); + crate::util::abort(); } PANIC_COUNT.set(1); if check_env() { @@ -88,7 +85,7 @@ fn do_panic(msg: Box) -> ! { } let code = crate::panic::begin_panic(Box::new(msg)); eprintln!("failed to initiate panic, error {}", code.0); - core::intrinsics::abort(); + crate::util::abort(); } #[panic_handler] diff --git a/src/panic_handler_dummy.rs b/src/panic_handler_dummy.rs index 74c8a5d..6770705 100644 --- a/src/panic_handler_dummy.rs +++ b/src/panic_handler_dummy.rs @@ -2,5 +2,5 @@ use core::panic::PanicInfo; #[panic_handler] fn panic(_info: &PanicInfo<'_>) -> ! { - core::intrinsics::abort(); + crate::util::abort(); } diff --git a/src/panicking.rs b/src/panicking.rs index 24da451..805a210 100644 --- a/src/panicking.rs +++ b/src/panicking.rs @@ -21,7 +21,7 @@ pub fn begin_panic(exception: E) -> UnwindReasonCode { unsafe { (*ex).exception_class = u64::from_be_bytes(E::CLASS); (*ex).exception_cleanup = Some(exception_cleanup::); - _Unwind_RaiseException(&mut *ex) + _Unwind_RaiseException(ex) } } @@ -39,7 +39,7 @@ pub fn catch_unwind R>(f: F) -> Result, data_ptr, do_catch::) == 0 { + return if core::intrinsics::catch_unwind(do_call::, data_ptr, do_catch::) == 0 { Ok(ManuallyDrop::into_inner(data.r)) } else { Err(ManuallyDrop::into_inner(data.p)) diff --git a/src/personality.rs b/src/personality.rs index 2dda30a..3bbdb16 100644 --- a/src/personality.rs +++ b/src/personality.rs @@ -136,11 +136,11 @@ fn find_eh_action( } #[lang = "eh_personality"] -fn rust_eh_personality( +unsafe fn rust_eh_personality( version: c_int, actions: UnwindAction, _exception_class: u64, - exception: &mut UnwindException, + exception: *mut UnwindException, unwind_ctx: &mut UnwindContext<'_>, ) -> UnwindReasonCode { if version != 1 { @@ -170,7 +170,7 @@ fn rust_eh_personality( _Unwind_SetGR( unwind_ctx, Arch::UNWIND_DATA_REG.0 .0 as _, - exception as *mut _ as usize, + exception as usize, ); _Unwind_SetGR(unwind_ctx, Arch::UNWIND_DATA_REG.1 .0 as _, 0); _Unwind_SetIP(unwind_ctx, lpad); diff --git a/src/personality_dummy.rs b/src/personality_dummy.rs index 8d9465a..ff026c7 100644 --- a/src/personality_dummy.rs +++ b/src/personality_dummy.rs @@ -2,11 +2,11 @@ use crate::abi::*; use crate::util::*; #[lang = "eh_personality"] -extern "C" fn personality( +unsafe extern "C" fn personality( version: c_int, _actions: UnwindAction, _exception_class: u64, - _exception: &mut UnwindException, + _exception: *mut UnwindException, _ctx: &mut UnwindContext<'_>, ) -> UnwindReasonCode { if version != 1 { diff --git a/src/print.rs b/src/print.rs index 26c16a9..fc6d9ac 100644 --- a/src/print.rs +++ b/src/print.rs @@ -49,3 +49,22 @@ macro_rules! eprint { let _ = core::write!($crate::print::StderrPrinter, $($arg)*); }) } + +#[macro_export] +macro_rules! dbg { + () => { + $crate::eprintln!("[{}:{}]", ::core::file!(), ::core::line!()) + }; + ($val:expr $(,)?) => { + match $val { + tmp => { + $crate::eprintln!("[{}:{}] {} = {:#?}", + ::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} diff --git a/src/unwinder/arch/aarch64.rs b/src/unwinder/arch/aarch64.rs index abe588c..28b740c 100644 --- a/src/unwinder/arch/aarch64.rs +++ b/src/unwinder/arch/aarch64.rs @@ -59,25 +59,34 @@ impl ops::IndexMut for Context { } #[naked] -pub extern "C-unwind" fn save_context() -> Context { +pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) { // No need to save caller-saved registers here. unsafe { asm!( " - stp d8, d9, [x8, 0x140] - stp d10, d11, [x8, 0x150] - stp d12, d13, [x8, 0x160] - stp d14, d15, [x8, 0x170] - - str x19, [x8, 0x98] - stp x20, x21, [x8, 0xA0] - stp x22, x23, [x8, 0xB0] - stp x24, x25, [x8, 0xC0] - stp x26, x27, [x8, 0xD0] - stp x28, x29, [x8, 0xE0] + stp x29, x30, [sp, -16]! + sub sp, sp, 512 + mov x8, x0 mov x0, sp - stp x30, x0, [x8, 0xF0] + stp d8, d9, [sp, 0x140] + stp d10, d11, [sp, 0x150] + stp d12, d13, [sp, 0x160] + stp d14, d15, [sp, 0x170] + + str x19, [sp, 0x98] + stp x20, x21, [sp, 0xA0] + stp x22, x23, [sp, 0xB0] + stp x24, x25, [sp, 0xC0] + stp x26, x27, [sp, 0xD0] + stp x28, x29, [sp, 0xE0] + add x2, sp, 528 + stp x30, x2, [sp, 0xF0] + + blr x8 + + add sp, sp, 512 + ldp x29, x30, [sp], 16 ret ", options(noreturn) @@ -85,8 +94,7 @@ pub extern "C-unwind" fn save_context() -> Context { } } -#[naked] -pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { +pub unsafe fn restore_context(ctx: &Context) -> ! { unsafe { asm!( " @@ -127,6 +135,7 @@ pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { ldp x0, x1, [x0, 0x00] ret ", + in("x0") ctx, options(noreturn) ); } diff --git a/src/unwinder/arch/mod.rs b/src/unwinder/arch/mod.rs index 67d08c1..8158353 100644 --- a/src/unwinder/arch/mod.rs +++ b/src/unwinder/arch/mod.rs @@ -13,6 +13,11 @@ mod riscv64; #[cfg(target_arch = "riscv64")] pub use riscv64::*; +#[cfg(target_arch = "riscv32")] +mod riscv32; +#[cfg(target_arch = "riscv32")] +pub use riscv32::*; + #[cfg(target_arch = "aarch64")] mod aarch64; #[cfg(target_arch = "aarch64")] @@ -22,6 +27,7 @@ pub use aarch64::*; target_arch = "x86_64", target_arch = "x86", target_arch = "riscv64", + target_arch = "riscv32", target_arch = "aarch64" )))] compile_error!("Current architecture is not supported"); diff --git a/src/unwinder/arch/riscv32.rs b/src/unwinder/arch/riscv32.rs new file mode 100644 index 0000000..bebd390 --- /dev/null +++ b/src/unwinder/arch/riscv32.rs @@ -0,0 +1,243 @@ +use core::arch::asm; +use core::fmt; +use core::ops; +use gimli::{Register, RiscV}; + +// Match DWARF_FRAME_REGISTERS in libgcc +pub const MAX_REG_RULES: usize = 65; + +#[cfg(all(target_feature = "f", not(target_feature = "d")))] +compile_error!("RISC-V with only F extension is not supported"); + +#[repr(C)] +#[derive(Clone, Default)] +pub struct Context { + pub gp: [usize; 32], + #[cfg(target_feature = "d")] + pub fp: [u64; 32], +} + +impl fmt::Debug for Context { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut fmt = fmt.debug_struct("Context"); + for i in 0..=31 { + fmt.field(RiscV::register_name(Register(i as _)).unwrap(), &self.gp[i]); + } + #[cfg(target_feature = "d")] + for i in 0..=31 { + fmt.field( + RiscV::register_name(Register((i + 32) as _)).unwrap(), + &self.fp[i], + ); + } + fmt.finish() + } +} + +impl ops::Index for Context { + type Output = usize; + + fn index(&self, reg: Register) -> &usize { + match reg { + Register(0..=31) => &self.gp[reg.0 as usize], + // We cannot support indexing fp here. It is 64-bit if D extension is implemented, + // and 32-bit if only F extension is implemented. + _ => unimplemented!(), + } + } +} + +impl ops::IndexMut for Context { + fn index_mut(&mut self, reg: Register) -> &mut usize { + match reg { + Register(0..=31) => &mut self.gp[reg.0 as usize], + // We cannot support indexing fp here. It is 64-bit if D extension is implemented, + // and 32-bit if only F extension is implemented. + _ => unimplemented!(), + } + } +} + +macro_rules! code { + (save_gp) => { + " + sw x0, 0x00(sp) + sw ra, 0x04(sp) + sw t0, 0x08(sp) + sw gp, 0x0C(sp) + sw tp, 0x10(sp) + sw s0, 0x20(sp) + sw s1, 0x24(sp) + sw s2, 0x48(sp) + sw s3, 0x4C(sp) + sw s4, 0x50(sp) + sw s5, 0x54(sp) + sw s6, 0x58(sp) + sw s7, 0x5C(sp) + sw s8, 0x60(sp) + sw s9, 0x64(sp) + sw s10, 0x68(sp) + sw s11, 0x6C(sp) + " + }; + (save_fp) => { + " + fsd fs0, 0xC0(sp) + fsd fs1, 0xC8(sp) + fsd fs2, 0x110(sp) + fsd fs3, 0x118(sp) + fsd fs4, 0x120(sp) + fsd fs5, 0x128(sp) + fsd fs6, 0x130(sp) + fsd fs7, 0x138(sp) + fsd fs8, 0x140(sp) + fsd fs9, 0x148(sp) + fsd fs10, 0x150(sp) + fsd fs11, 0x158(sp) + " + }; + (restore_gp) => { + " + lw ra, 0x04(a0) + lw sp, 0x08(a0) + lw gp, 0x0C(a0) + lw tp, 0x10(a0) + lw t0, 0x14(a0) + lw t1, 0x18(a0) + lw t2, 0x1C(a0) + lw s0, 0x20(a0) + lw s1, 0x24(a0) + lw a1, 0x2C(a0) + lw a2, 0x30(a0) + lw a3, 0x34(a0) + lw a4, 0x38(a0) + lw a5, 0x3C(a0) + lw a6, 0x40(a0) + lw a7, 0x44(a0) + lw s2, 0x48(a0) + lw s3, 0x4C(a0) + lw s4, 0x50(a0) + lw s5, 0x54(a0) + lw s6, 0x58(a0) + lw s7, 0x5C(a0) + lw s8, 0x60(a0) + lw s9, 0x64(a0) + lw s10, 0x68(a0) + lw s11, 0x6C(a0) + lw t3, 0x70(a0) + lw t4, 0x74(a0) + lw t5, 0x78(a0) + lw t6, 0x7C(a0) + " + }; + (restore_fp) => { + " + fld ft0, 0x80(a0) + fld ft1, 0x88(a0) + fld ft2, 0x90(a0) + fld ft3, 0x98(a0) + fld ft4, 0xA0(a0) + fld ft5, 0xA8(a0) + fld ft6, 0xB0(a0) + fld ft7, 0xB8(a0) + fld fs0, 0xC0(a0) + fld fs1, 0xC8(a0) + fld fa0, 0xD0(a0) + fld fa1, 0xD8(a0) + fld fa2, 0xE0(a0) + fld fa3, 0xE8(a0) + fld fa4, 0xF0(a0) + fld fa5, 0xF8(a0) + fld fa6, 0x100(a0) + fld fa7, 0x108(a0) + fld fs2, 0x110(a0) + fld fs3, 0x118(a0) + fld fs4, 0x120(a0) + fld fs5, 0x128(a0) + fld fs6, 0x130(a0) + fld fs7, 0x138(a0) + fld fs8, 0x140(a0) + fld fs9, 0x148(a0) + fld fs10, 0x150(a0) + fld fs11, 0x158(a0) + fld ft8, 0x160(a0) + fld ft9, 0x168(a0) + fld ft10, 0x170(a0) + fld ft11, 0x178(a0) + " + }; +} + +#[naked] +pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) { + // No need to save caller-saved registers here. + #[cfg(target_feature = "d")] + unsafe { + asm!( + " + mv t0, sp + add sp, sp, -0x190 + sw ra, 0x180(sp) + ", + code!(save_gp), + code!(save_fp), + " + mv t0, a0 + mv a0, sp + jalr t0 + lw ra, 0x180(sp) + add sp, sp, 0x190 + ret + ", + options(noreturn) + ); + } + #[cfg(not(target_feature = "d"))] + unsafe { + asm!( + " + mv t0, sp + add sp, sp, -0x90 + sw ra, 0x80(sp) + ", + code!(save_gp), + " + mv t0, a0 + mv a0, sp + jalr t0 + lw ra, 0x80(sp) + add sp, sp, 0x90 + ret + ", + options(noreturn) + ); + } +} + +pub unsafe fn restore_context(ctx: &Context) -> ! { + #[cfg(target_feature = "d")] + unsafe { + asm!( + code!(restore_fp), + code!(restore_gp), + " + lw a0, 0x28(a0) + ret + ", + in("a0") ctx, + options(noreturn) + ); + } + #[cfg(not(target_feature = "d"))] + unsafe { + asm!( + code!(restore_gp), + " + lw a0, 0x28(a0) + ret + ", + in("a0") ctx, + options(noreturn) + ); + } +} diff --git a/src/unwinder/arch/riscv64.rs b/src/unwinder/arch/riscv64.rs index 4852b93..badc433 100644 --- a/src/unwinder/arch/riscv64.rs +++ b/src/unwinder/arch/riscv64.rs @@ -6,6 +6,9 @@ use gimli::{Register, RiscV}; // Match DWARF_FRAME_REGISTERS in libgcc pub const MAX_REG_RULES: usize = 65; +#[cfg(all(target_feature = "f", not(target_feature = "d")))] +compile_error!("RISC-V with only F extension is not supported"); + #[repr(C)] #[derive(Clone, Default)] pub struct Context { @@ -58,39 +61,39 @@ impl ops::IndexMut for Context { macro_rules! code { (save_gp) => { " - sd x0, 0x00(a0) - sd ra, 0x08(a0) - sd sp, 0x10(a0) - sd gp, 0x18(a0) - sd tp, 0x20(a0) - sd s0, 0x40(a0) - sd s1, 0x48(a0) - sd s2, 0x90(a0) - sd s3, 0x98(a0) - sd s4, 0xA0(a0) - sd s5, 0xA8(a0) - sd s6, 0xB0(a0) - sd s7, 0xB8(a0) - sd s8, 0xC0(a0) - sd s9, 0xC8(a0) - sd s10, 0xD0(a0) - sd s11, 0xD8(a0) + sd x0, 0x00(sp) + sd ra, 0x08(sp) + sd t0, 0x10(sp) + sd gp, 0x18(sp) + sd tp, 0x20(sp) + sd s0, 0x40(sp) + sd s1, 0x48(sp) + sd s2, 0x90(sp) + sd s3, 0x98(sp) + sd s4, 0xA0(sp) + sd s5, 0xA8(sp) + sd s6, 0xB0(sp) + sd s7, 0xB8(sp) + sd s8, 0xC0(sp) + sd s9, 0xC8(sp) + sd s10, 0xD0(sp) + sd s11, 0xD8(sp) " }; (save_fp) => { " - fsd fs0, 0x140(a0) - fsd fs1, 0x148(a0) - fsd fs2, 0x190(a0) - fsd fs3, 0x198(a0) - fsd fs4, 0x1A0(a0) - fsd fs5, 0x1A8(a0) - fsd fs6, 0x1B0(a0) - fsd fs7, 0x1B8(a0) - fsd fs8, 0x1C0(a0) - fsd fs9, 0x1C8(a0) - fsd fs10, 0x1D0(a0) - fsd fs11, 0x1D8(a0) + fsd fs0, 0x140(sp) + fsd fs1, 0x148(sp) + fsd fs2, 0x190(sp) + fsd fs3, 0x198(sp) + fsd fs4, 0x1A0(sp) + fsd fs5, 0x1A8(sp) + fsd fs6, 0x1B0(sp) + fsd fs7, 0x1B8(sp) + fsd fs8, 0x1C0(sp) + fsd fs9, 0x1C8(sp) + fsd fs10, 0x1D0(sp) + fsd fs11, 0x1D8(sp) " }; (restore_gp) => { @@ -166,34 +169,74 @@ macro_rules! code { } #[naked] -pub extern "C-unwind" fn save_context() -> Context { +pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) { // No need to save caller-saved registers here. #[cfg(target_feature = "d")] unsafe { asm!( - concat!(code!(save_gp), code!(save_fp), "ret"), + " + mv t0, sp + add sp, sp, -0x210 + sd ra, 0x200(sp) + ", + code!(save_gp), + code!(save_fp), + " + mv t0, a0 + mv a0, sp + jalr t0 + ld ra, 0x200(sp) + add sp, sp, 0x210 + ret + ", options(noreturn) ); } #[cfg(not(target_feature = "d"))] unsafe { - asm!(concat!(code!(save_gp), "ret"), options(noreturn)); + asm!( + " + mv t0, sp + add sp, sp, -0x110 + sd ra, 0x100(sp) + ", + code!(save_gp), + " + mv t0, a0 + mv a0, sp + jalr t0 + ld ra, 0x100(sp) + add sp, sp, 0x110 + ret + ", + options(noreturn) + ); } } -#[naked] -pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { +pub unsafe fn restore_context(ctx: &Context) -> ! { #[cfg(target_feature = "d")] unsafe { asm!( - concat!(code!(restore_fp), code!(restore_gp), "ld a0, 0x50(a0)\nret"), + code!(restore_fp), + code!(restore_gp), + " + ld a0, 0x50(a0) + ret + ", + in("a0") ctx, options(noreturn) ); } #[cfg(not(target_feature = "d"))] unsafe { asm!( - concat!(code!(restore_gp), "ld a0, 0x50(a0)\nret"), + code!(restore_gp), + " + ld a0, 0x50(a0) + ret + ", + in("a0") ctx, options(noreturn) ); } diff --git a/src/unwinder/arch/x86.rs b/src/unwinder/arch/x86.rs index 181378c..e627e7c 100644 --- a/src/unwinder/arch/x86.rs +++ b/src/unwinder/arch/x86.rs @@ -56,41 +56,50 @@ impl ops::IndexMut for Context { } #[naked] -pub extern "C-unwind" fn save_context() -> Context { +pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) { // No need to save caller-saved registers here. unsafe { asm!( " - mov eax, [esp + 4] - mov [eax + 4], ecx - mov [eax + 8], edx - mov [eax + 12], ebx - mov [eax + 20], ebp - mov [eax + 24], esi - mov [eax + 28], edi + sub esp, 52 + + mov [esp + 4], ecx + mov [esp + 8], edx + mov [esp + 12], ebx /* Adjust the stack to account for the return address */ - lea edx, [esp + 4] - mov [eax + 16], edx + lea eax, [esp + 56] + mov [esp + 16], eax - mov edx, [esp] - mov [eax + 32], edx - stmxcsr [eax + 36] - fnstcw [eax + 40] - ret 4 + mov [esp + 20], ebp + mov [esp + 24], esi + mov [esp + 28], edi + + /* Return address */ + mov eax, [esp + 52] + mov [esp + 32], eax + + stmxcsr [esp + 36] + fnstcw [esp + 40] + + mov eax, [esp + 60] + mov ecx, esp + push eax + push ecx + call [esp + 64] + + add esp, 60 + ret ", options(noreturn) ); } } -#[naked] -pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { +pub unsafe fn restore_context(ctx: &Context) -> ! { unsafe { asm!( " - mov edx, [esp + 4] - /* Restore stack */ mov esp, [edx + 16] @@ -118,6 +127,7 @@ pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { ret ", + in("edx") ctx, options(noreturn) ); } diff --git a/src/unwinder/arch/x86_64.rs b/src/unwinder/arch/x86_64.rs index d33e26b..6923e3c 100644 --- a/src/unwinder/arch/x86_64.rs +++ b/src/unwinder/arch/x86_64.rs @@ -58,27 +58,35 @@ impl ops::IndexMut for Context { } #[naked] -pub extern "C-unwind" fn save_context() -> Context { +pub extern "C-unwind" fn save_context(f: extern "C" fn(&mut Context, *mut ()), ptr: *mut ()) { // No need to save caller-saved registers here. unsafe { asm!( " - mov rax, rdi - mov [rax + 0x18], rbx - mov [rax + 0x30], rbp + sub rsp, 0x98 + mov [rsp + 0x18], rbx + mov [rsp + 0x30], rbp /* Adjust the stack to account for the return address */ - lea rdi, [rsp + 8] - mov [rax + 0x38], rdi + lea rax, [rsp + 0xA0] + mov [rsp + 0x38], rax - mov [rax + 0x60], r12 - mov [rax + 0x68], r13 - mov [rax + 0x70], r14 - mov [rax + 0x78], r15 - mov rdx, [rsp] - mov [rax + 0x80], rdx - /* stmxcsr [rax + 0x88] */ - fnstcw [rax + 0x90] + mov [rsp + 0x60], r12 + mov [rsp + 0x68], r13 + mov [rsp + 0x70], r14 + mov [rsp + 0x78], r15 + + /* Return address */ + mov rax, [rsp + 0x98] + mov [rsp + 0x80], rax + + /* stmxcsr [rsp + 0x88] */ + fnstcw [rsp + 0x90] + + mov rax, rdi + mov rdi, rsp + call rax + add rsp, 0x98 ret ", options(noreturn) @@ -86,8 +94,7 @@ pub extern "C-unwind" fn save_context() -> Context { } } -#[naked] -pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { +pub unsafe fn restore_context(ctx: &Context) -> ! { unsafe { asm!( " @@ -121,11 +128,12 @@ pub unsafe extern "C" fn restore_context(ctx: &Context) -> ! { mov r14, [rdi + 0x70] mov r15, [rdi + 0x78] - /* RDI resotred last */ + /* RDI restored last */ mov rdi, [rdi + 0x28] ret ", + in("rdi") ctx, options(noreturn) ); } diff --git a/src/unwinder/find_fde/custom.rs b/src/unwinder/find_fde/custom.rs new file mode 100644 index 0000000..25da562 --- /dev/null +++ b/src/unwinder/find_fde/custom.rs @@ -0,0 +1,171 @@ +use super::{FDEFinder, FDESearchResult}; +use crate::util::{deref_pointer, get_unlimited_slice}; + +use core::sync::atomic::{AtomicU32, Ordering}; +use gimli::{BaseAddresses, EhFrame, EhFrameHdr, NativeEndian, UnwindSection}; + +pub(crate) struct CustomFinder(()); + +pub(crate) fn get_finder() -> &'static CustomFinder { + &CustomFinder(()) +} + +impl FDEFinder for CustomFinder { + fn find_fde(&self, pc: usize) -> Option { + get_custom_eh_frame_finder().and_then(|eh_frame_finder| find_fde(eh_frame_finder, pc)) + } +} + +/// A trait for types whose values can be used as the global EH frame finder set by [`set_custom_eh_frame_finder`]. +pub unsafe trait EhFrameFinder { + fn find(&self, pc: usize) -> Option; +} + +pub struct FrameInfo { + pub text_base: Option, + pub kind: FrameInfoKind, +} + +pub enum FrameInfoKind { + EhFrameHdr(usize), + EhFrame(usize), +} + +static mut CUSTOM_EH_FRAME_FINDER: Option<&(dyn EhFrameFinder + Sync)> = None; + +static CUSTOM_EH_FRAME_FINDER_STATE: AtomicU32 = AtomicU32::new(UNINITIALIZED); + +const UNINITIALIZED: u32 = 0; +const INITIALIZING: u32 = 1; +const INITIALIZED: u32 = 2; + +/// The type returned by [`set_custom_eh_frame_finder`] if [`set_custom_eh_frame_finder`] has +/// already been called. +#[derive(Debug)] +pub struct SetCustomEhFrameFinderError(()); + +/// Sets the global EH frame finder. +/// +/// This function should only be called once during the lifetime of the program. +/// +/// # Errors +/// +/// An error is returned if this function has already been called during the lifetime of the +/// program. +pub fn set_custom_eh_frame_finder( + fde_finder: &'static (dyn EhFrameFinder + Sync), +) -> Result<(), SetCustomEhFrameFinderError> { + match CUSTOM_EH_FRAME_FINDER_STATE.compare_exchange( + UNINITIALIZED, + INITIALIZING, + Ordering::SeqCst, + Ordering::SeqCst, + ) { + Ok(UNINITIALIZED) => { + unsafe { + CUSTOM_EH_FRAME_FINDER = Some(fde_finder); + } + CUSTOM_EH_FRAME_FINDER_STATE.store(INITIALIZED, Ordering::SeqCst); + Ok(()) + } + Err(INITIALIZING) => { + while CUSTOM_EH_FRAME_FINDER_STATE.load(Ordering::SeqCst) == INITIALIZING { + core::hint::spin_loop(); + } + Err(SetCustomEhFrameFinderError(())) + } + Err(INITIALIZED) => Err(SetCustomEhFrameFinderError(())), + _ => { + unreachable!() + } + } +} + +fn get_custom_eh_frame_finder() -> Option<&'static dyn EhFrameFinder> { + if CUSTOM_EH_FRAME_FINDER_STATE.load(Ordering::SeqCst) == INITIALIZED { + Some(unsafe { CUSTOM_EH_FRAME_FINDER.unwrap() }) + } else { + None + } +} + +fn find_fde(eh_frame_finder: &T, pc: usize) -> Option { + let info = eh_frame_finder.find(pc)?; + let text_base = info.text_base; + match info.kind { + FrameInfoKind::EhFrameHdr(eh_frame_hdr) => { + find_fde_with_eh_frame_hdr(pc, text_base, eh_frame_hdr) + } + FrameInfoKind::EhFrame(eh_frame) => find_fde_with_eh_frame(pc, text_base, eh_frame), + } +} + +fn find_fde_with_eh_frame_hdr( + pc: usize, + text_base: Option, + eh_frame_hdr: usize, +) -> Option { + unsafe { + let mut bases = BaseAddresses::default().set_eh_frame_hdr(eh_frame_hdr as _); + if let Some(text_base) = text_base { + bases = bases.set_text(text_base as _); + } + let eh_frame_hdr = EhFrameHdr::new( + get_unlimited_slice(eh_frame_hdr as usize as _), + NativeEndian, + ) + .parse(&bases, core::mem::size_of::() as _) + .ok()?; + let eh_frame = deref_pointer(eh_frame_hdr.eh_frame_ptr()); + let bases = bases.set_eh_frame(eh_frame as _); + let eh_frame = EhFrame::new(get_unlimited_slice(eh_frame as _), NativeEndian); + + // Use binary search table for address if available. + if let Some(table) = eh_frame_hdr.table() { + if let Ok(fde) = + table.fde_for_address(&eh_frame, &bases, pc as _, EhFrame::cie_from_offset) + { + return Some(FDESearchResult { + fde, + bases, + eh_frame, + }); + } + } + + // Otherwise do the linear search. + if let Ok(fde) = eh_frame.fde_for_address(&bases, pc as _, EhFrame::cie_from_offset) { + return Some(FDESearchResult { + fde, + bases, + eh_frame, + }); + } + + None + } +} + +fn find_fde_with_eh_frame( + pc: usize, + text_base: Option, + eh_frame: usize, +) -> Option { + unsafe { + let mut bases = BaseAddresses::default().set_eh_frame(eh_frame as _); + if let Some(text_base) = text_base { + bases = bases.set_text(text_base as _); + } + let eh_frame = EhFrame::new(get_unlimited_slice(eh_frame as _), NativeEndian); + + if let Ok(fde) = eh_frame.fde_for_address(&bases, pc as _, EhFrame::cie_from_offset) { + return Some(FDESearchResult { + fde, + bases, + eh_frame, + }); + } + + None + } +} diff --git a/src/unwinder/find_fde/mod.rs b/src/unwinder/find_fde/mod.rs index d203df4..4db64a0 100644 --- a/src/unwinder/find_fde/mod.rs +++ b/src/unwinder/find_fde/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "fde-custom")] +mod custom; #[cfg(feature = "fde-static")] mod fixed; #[cfg(feature = "fde-gnu-eh-frame-hdr")] @@ -10,6 +12,14 @@ mod registry; use crate::util::*; use gimli::{BaseAddresses, EhFrame, FrameDescriptionEntry}; +#[cfg(feature = "fde-custom")] +pub mod custom_eh_frame_finder { + pub use super::custom::{ + set_custom_eh_frame_finder, EhFrameFinder, FrameInfo, FrameInfoKind, + SetCustomEhFrameFinderError, + }; +} + #[derive(Debug)] pub struct FDESearchResult { pub fde: FrameDescriptionEntry, @@ -25,6 +35,10 @@ pub struct GlobalFinder(()); impl FDEFinder for GlobalFinder { fn find_fde(&self, pc: usize) -> Option { + #[cfg(feature = "fde-custom")] + if let Some(v) = custom::get_finder().find_fde(pc) { + return Some(v); + } #[cfg(feature = "fde-registry")] if let Some(v) = registry::get_finder().find_fde(pc) { return Some(v); diff --git a/src/unwinder/find_fde/registry.rs b/src/unwinder/find_fde/registry.rs index 5341a35..5b29b10 100644 --- a/src/unwinder/find_fde/registry.rs +++ b/src/unwinder/find_fde/registry.rs @@ -32,7 +32,7 @@ unsafe fn lock_global_state() -> impl ops::DerefMut { #[cfg(feature = "libc")] { static mut MUTEX: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER; - unsafe { libc::pthread_mutex_lock(&mut MUTEX) }; + unsafe { libc::pthread_mutex_lock(core::ptr::addr_of_mut!(MUTEX)) }; static mut STATE: GlobalState = GlobalState { object: ptr::null_mut(), @@ -41,20 +41,22 @@ unsafe fn lock_global_state() -> impl ops::DerefMut { struct LockGuard; impl Drop for LockGuard { fn drop(&mut self) { - unsafe { libc::pthread_mutex_unlock(&mut MUTEX) }; + unsafe { libc::pthread_mutex_unlock(core::ptr::addr_of_mut!(MUTEX)) }; } } impl ops::Deref for LockGuard { type Target = GlobalState; + + #[allow(static_mut_ref)] fn deref(&self) -> &GlobalState { - unsafe { &STATE } + unsafe { &*core::ptr::addr_of!(STATE) } } } impl ops::DerefMut for LockGuard { fn deref_mut(&mut self) -> &mut GlobalState { - unsafe { &mut STATE } + unsafe { &mut *core::ptr::addr_of_mut!(STATE) } } } diff --git a/src/unwinder/frame.rs b/src/unwinder/frame.rs index 0053899..9ede0a1 100644 --- a/src/unwinder/frame.rs +++ b/src/unwinder/frame.rs @@ -125,6 +125,11 @@ impl Frame { Err(gimli::Error::UnsupportedEvaluation) } + pub fn adjust_stack_for_args(&self, ctx: &mut Context) { + let size = self.row.saved_args_size(); + ctx[Arch::SP] = ctx[Arch::SP].wrapping_add(size as usize); + } + pub fn unwind(&self, ctx: &Context) -> Result { let row = &self.row; let mut new_ctx = ctx.clone(); @@ -139,6 +144,7 @@ impl Frame { new_ctx[Arch::SP] = cfa as _; new_ctx[Arch::RA] = 0; + #[warn(non_exhaustive_omitted_patterns)] for (reg, rule) in row.registers() { let value = match *rule { RegisterRule::Undefined | RegisterRule::SameValue => ctx[*reg], @@ -153,6 +159,8 @@ impl Frame { } RegisterRule::ValExpression(expr) => self.evaluate_expression(ctx, expr)?, RegisterRule::Architectural => unreachable!(), + RegisterRule::Constant(value) => value as usize, + _ => unreachable!(), }; new_ctx[*reg] = value; } diff --git a/src/unwinder/mod.rs b/src/unwinder/mod.rs index a62a911..5bcc383 100644 --- a/src/unwinder/mod.rs +++ b/src/unwinder/mod.rs @@ -13,6 +13,36 @@ use arch::*; use find_fde::FDEFinder; use frame::Frame; +#[cfg(feature = "fde-custom")] +pub use find_fde::custom_eh_frame_finder; + +// Helper function to turn `save_context` which takes function pointer to a closure-taking function. +fn with_context T>(f: F) -> T { + use core::mem::ManuallyDrop; + + union Data { + f: ManuallyDrop, + t: ManuallyDrop, + } + + extern "C" fn delegate T>(ctx: &mut Context, ptr: *mut ()) { + // SAFETY: This function is called exactly once; it extracts the function, call it and + // store the return value. This function is `extern "C"` so we don't need to worry about + // unwinding past it. + unsafe { + let data = &mut *ptr.cast::>(); + let t = ManuallyDrop::take(&mut data.f)(ctx); + data.t = ManuallyDrop::new(t); + } + } + + let mut data = Data { + f: ManuallyDrop::new(f), + }; + save_context(delegate::, ptr::addr_of_mut!(data).cast()); + unsafe { ManuallyDrop::into_inner(data.t) } +} + #[repr(C)] pub struct UnwindException { pub exception_class: u64, @@ -119,60 +149,63 @@ macro_rules! try2 { #[inline(never)] #[no_mangle] -pub extern "C-unwind" fn _Unwind_RaiseException( - exception: &mut UnwindException, +pub unsafe extern "C-unwind" fn _Unwind_RaiseException( + exception: *mut UnwindException, ) -> UnwindReasonCode { - let saved_ctx = save_context(); + with_context(|saved_ctx| { + // Phase 1: Search for handler + let mut ctx = saved_ctx.clone(); + let mut signal = false; + loop { + if let Some(frame) = try1!(Frame::from_context(&ctx, signal)) { + if let Some(personality) = frame.personality() { + let result = unsafe { + personality( + 1, + UnwindAction::SEARCH_PHASE, + (*exception).exception_class, + exception, + &mut UnwindContext { + frame: Some(&frame), + ctx: &mut ctx, + signal, + }, + ) + }; - // Phase 1: Search for handler - let mut ctx = saved_ctx.clone(); - let mut signal = false; - loop { - if let Some(frame) = try1!(Frame::from_context(&ctx, signal)) { - 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, - signal, - }, - ); - - match result { - UnwindReasonCode::CONTINUE_UNWIND => (), - UnwindReasonCode::HANDLER_FOUND => { - break; + match result { + UnwindReasonCode::CONTINUE_UNWIND => (), + UnwindReasonCode::HANDLER_FOUND => { + break; + } + _ => return UnwindReasonCode::FATAL_PHASE1_ERROR, } - _ => return UnwindReasonCode::FATAL_PHASE1_ERROR, } + + ctx = try1!(frame.unwind(&ctx)); + signal = frame.is_signal_trampoline(); + } else { + return UnwindReasonCode::END_OF_STACK; } - - ctx = try1!(frame.unwind(&ctx)); - signal = frame.is_signal_trampoline(); - } else { - return UnwindReasonCode::END_OF_STACK; } - } - // Disambiguate normal frame and signal frame. - let handler_cfa = ctx[Arch::SP] - signal as usize; - exception.private_1 = None; - exception.private_2 = handler_cfa; + // Disambiguate normal frame and signal frame. + let handler_cfa = ctx[Arch::SP] - signal as usize; + unsafe { + (*exception).private_1 = None; + (*exception).private_2 = handler_cfa; + } - 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, - } + let code = raise_exception_phase2(exception, saved_ctx, handler_cfa); + match code { + UnwindReasonCode::INSTALL_CONTEXT => unsafe { restore_context(saved_ctx) }, + _ => code, + } + }) } fn raise_exception_phase2( - exception: &mut UnwindException, + exception: *mut UnwindException, ctx: &mut Context, handler_cfa: usize, ) -> UnwindReasonCode { @@ -181,26 +214,31 @@ fn raise_exception_phase2( if let Some(frame) = try2!(Frame::from_context(ctx, signal)) { let frame_cfa = ctx[Arch::SP] - signal as usize; if let Some(personality) = frame.personality() { - let code = personality( - 1, - UnwindAction::CLEANUP_PHASE - | if frame_cfa == handler_cfa { - UnwindAction::HANDLER_FRAME - } else { - UnwindAction::empty() + let code = unsafe { + personality( + 1, + UnwindAction::CLEANUP_PHASE + | if frame_cfa == handler_cfa { + UnwindAction::HANDLER_FRAME + } else { + UnwindAction::empty() + }, + (*exception).exception_class, + exception, + &mut UnwindContext { + frame: Some(&frame), + ctx, + signal, }, - exception.exception_class, - exception, - &mut UnwindContext { - frame: Some(&frame), - ctx, - signal, - }, - ); + ) + }; match code { UnwindReasonCode::CONTINUE_UNWIND => (), - UnwindReasonCode::INSTALL_CONTEXT => break, + UnwindReasonCode::INSTALL_CONTEXT => { + frame.adjust_stack_for_args(ctx); + return UnwindReasonCode::INSTALL_CONTEXT; + } _ => return UnwindReasonCode::FATAL_PHASE2_ERROR, } } @@ -211,31 +249,31 @@ fn raise_exception_phase2( return UnwindReasonCode::FATAL_PHASE2_ERROR; } } - - UnwindReasonCode::INSTALL_CONTEXT } #[inline(never)] #[no_mangle] -pub extern "C-unwind" fn _Unwind_ForcedUnwind( - exception: &mut UnwindException, +pub unsafe extern "C-unwind" fn _Unwind_ForcedUnwind( + exception: *mut UnwindException, stop: UnwindStopFn, stop_arg: *mut c_void, ) -> UnwindReasonCode { - let mut ctx = save_context(); + with_context(|ctx| { + unsafe { + (*exception).private_1 = Some(stop); + (*exception).private_2 = stop_arg as _; + } - exception.private_1 = Some(stop); - exception.private_2 = stop_arg as _; - - let code = force_unwind_phase2(exception, &mut ctx, stop, stop_arg); - match code { - UnwindReasonCode::INSTALL_CONTEXT => unsafe { restore_context(&ctx) }, - _ => code, - } + let code = force_unwind_phase2(exception, ctx, stop, stop_arg); + match code { + UnwindReasonCode::INSTALL_CONTEXT => unsafe { restore_context(ctx) }, + _ => code, + } + }) } fn force_unwind_phase2( - exception: &mut UnwindException, + exception: *mut UnwindException, ctx: &mut Context, stop: UnwindStopFn, stop_arg: *mut c_void, @@ -244,24 +282,26 @@ fn force_unwind_phase2( loop { let frame = try2!(Frame::from_context(ctx, signal)); - let code = stop( - 1, - UnwindAction::FORCE_UNWIND - | UnwindAction::END_OF_STACK - | if frame.is_none() { - UnwindAction::END_OF_STACK - } else { - UnwindAction::empty() + let code = unsafe { + stop( + 1, + UnwindAction::FORCE_UNWIND + | UnwindAction::END_OF_STACK + | if frame.is_none() { + UnwindAction::END_OF_STACK + } else { + UnwindAction::empty() + }, + (*exception).exception_class, + exception, + &mut UnwindContext { + frame: frame.as_ref(), + ctx, + signal, }, - exception.exception_class, - exception, - &mut UnwindContext { - frame: frame.as_ref(), - ctx, - signal, - }, - stop_arg, - ); + stop_arg, + ) + }; match code { UnwindReasonCode::NO_REASON => (), _ => return UnwindReasonCode::FATAL_PHASE2_ERROR, @@ -269,21 +309,26 @@ fn force_unwind_phase2( 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, - signal, - }, - ); + let code = unsafe { + personality( + 1, + UnwindAction::FORCE_UNWIND | UnwindAction::CLEANUP_PHASE, + (*exception).exception_class, + exception, + &mut UnwindContext { + frame: Some(&frame), + ctx, + signal, + }, + ) + }; match code { UnwindReasonCode::CONTINUE_UNWIND => (), - UnwindReasonCode::INSTALL_CONTEXT => break, + UnwindReasonCode::INSTALL_CONTEXT => { + frame.adjust_stack_for_args(ctx); + return UnwindReasonCode::INSTALL_CONTEXT; + } _ => return UnwindReasonCode::FATAL_PHASE2_ERROR, } } @@ -294,47 +339,45 @@ fn force_unwind_phase2( return UnwindReasonCode::END_OF_STACK; } } - - UnwindReasonCode::INSTALL_CONTEXT } #[inline(never)] #[no_mangle] -pub extern "C-unwind" fn _Unwind_Resume(exception: &mut UnwindException) -> ! { - let mut ctx = save_context(); +pub unsafe extern "C-unwind" fn _Unwind_Resume(exception: *mut UnwindException) -> ! { + with_context(|ctx| { + let code = match unsafe { (*exception).private_1 } { + None => { + let handler_cfa = unsafe { (*exception).private_2 }; + raise_exception_phase2(exception, ctx, handler_cfa) + } + Some(stop) => { + let stop_arg = unsafe { (*exception).private_2 as _ }; + force_unwind_phase2(exception, ctx, stop, stop_arg) + } + }; + assert!(code == UnwindReasonCode::INSTALL_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 _; - force_unwind_phase2(exception, &mut ctx, stop, stop_arg) - } - }; - assert!(code == UnwindReasonCode::INSTALL_CONTEXT); - - unsafe { restore_context(&ctx) } + unsafe { restore_context(ctx) } + }) } #[inline(never)] #[no_mangle] -pub extern "C-unwind" fn _Unwind_Resume_or_Rethrow( - exception: &mut UnwindException, +pub unsafe extern "C-unwind" fn _Unwind_Resume_or_Rethrow( + exception: *mut UnwindException, ) -> UnwindReasonCode { - let stop = match exception.private_1 { - None => return _Unwind_RaiseException(exception), + let stop = match unsafe { (*exception).private_1 } { + None => return unsafe { _Unwind_RaiseException(exception) }, Some(v) => v, }; - let mut ctx = save_context(); + with_context(|ctx| { + let stop_arg = unsafe { (*exception).private_2 as _ }; + let code = force_unwind_phase2(exception, ctx, stop, stop_arg); + assert!(code == UnwindReasonCode::INSTALL_CONTEXT); - let stop_arg = exception.private_2 as _; - let code = force_unwind_phase2(exception, &mut ctx, stop, stop_arg); - assert!(code == UnwindReasonCode::INSTALL_CONTEXT); - - unsafe { restore_context(&ctx) } + unsafe { restore_context(ctx) } + }) } #[no_mangle] @@ -350,36 +393,38 @@ pub extern "C-unwind" fn _Unwind_Backtrace( trace: UnwindTraceFn, trace_argument: *mut c_void, ) -> UnwindReasonCode { - let mut ctx = save_context(); - let mut signal = false; - let mut skipping = cfg!(feature = "hide-trace"); + with_context(|ctx| { + let mut ctx = ctx.clone(); + let mut signal = false; + let mut skipping = cfg!(feature = "hide-trace"); - loop { - let frame = try1!(Frame::from_context(&ctx, signal)); - if !skipping { - let code = trace( - &mut UnwindContext { - frame: frame.as_ref(), - ctx: &mut ctx, - signal, - }, - 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; + loop { + let frame = try1!(Frame::from_context(&ctx, signal)); + if !skipping { + let code = trace( + &UnwindContext { + frame: frame.as_ref(), + ctx: &mut ctx, + signal, + }, + trace_argument, + ); + match code { + UnwindReasonCode::NO_REASON => (), + _ => return UnwindReasonCode::FATAL_PHASE1_ERROR, } } - ctx = try1!(frame.unwind(&ctx)); - signal = frame.is_signal_trampoline(); - } else { - return UnwindReasonCode::END_OF_STACK; + if let Some(frame) = frame { + if skipping { + if frame.initial_address() == _Unwind_Backtrace as usize { + skipping = false; + } + } + ctx = try1!(frame.unwind(&ctx)); + signal = frame.is_signal_trampoline(); + } else { + return UnwindReasonCode::END_OF_STACK; + } } - } + }) } diff --git a/src/util.rs b/src/util.rs index 1d738ce..7c66e81 100644 --- a/src/util.rs +++ b/src/util.rs @@ -23,3 +23,19 @@ pub use libc::c_int; #[cfg(not(feature = "libc"))] #[allow(non_camel_case_types)] pub type c_int = i32; + +#[cfg(all( + any(feature = "panic", feature = "panic-handler-dummy"), + feature = "libc" +))] +pub fn abort() -> ! { + unsafe { libc::abort() }; +} + +#[cfg(all( + any(feature = "panic", feature = "panic-handler-dummy"), + not(feature = "libc") +))] +pub fn abort() -> ! { + core::intrinsics::abort(); +} diff --git a/tests/compile_tests.rs b/tests/compile_tests.rs new file mode 100644 index 0000000..26a5697 --- /dev/null +++ b/tests/compile_tests.rs @@ -0,0 +1,20 @@ +use std::process::Command; + +#[test] +fn main() { + let dir = env!("CARGO_MANIFEST_DIR"); + + let tests = [ + "throw_and_catch", + "catch_std_exception", + "std_catch_exception", + ]; + + for test in tests { + let status = Command::new("./check.sh") + .current_dir(format!("{dir}/test_crates/{test}")) + .status() + .unwrap(); + assert!(status.success()); + } +}