wasmtime/runtime/vm/sys/unix/
unwind.rs

1//! Module for System V ABI unwind registry.
2
3use crate::prelude::*;
4use crate::runtime::vm::SendSyncPtr;
5use core::ptr::NonNull;
6
7/// Represents a registration of function unwind information for System V ABI.
8pub struct UnwindRegistration {
9    registrations: Vec<SendSyncPtr<u8>>,
10}
11
12cfg_if::cfg_if! {
13    // FIXME: at least on the `gcc-arm-linux-gnueabihf` toolchain on Ubuntu
14    // these symbols are not provided by default like they are on other targets.
15    // I'm not ARM expert so I don't know why. For now though consider this an
16    // optional integration feature with the platform and stub out the functions
17    // to do nothing which won't break any tests it just means that
18    // runtime-generated backtraces won't have the same level of fidelity they
19    // do on other targets.
20    if #[cfg(target_arch = "arm")] {
21        unsafe extern "C" fn __register_frame(_: *const u8) {}
22        unsafe extern "C" fn __deregister_frame(_: *const u8) {}
23        unsafe extern "C" fn wasmtime_using_libunwind() -> bool {
24            false
25        }
26    } else {
27        unsafe extern "C" {
28            // libunwind import
29            fn __register_frame(fde: *const u8);
30            fn __deregister_frame(fde: *const u8);
31            #[wasmtime_versioned_export_macros::versioned_link]
32            fn wasmtime_using_libunwind() -> bool;
33        }
34    }
35}
36
37/// There are two primary unwinders on Unix platforms: libunwind and libgcc.
38///
39/// Unfortunately their interface to `__register_frame` is different. The
40/// libunwind library takes a pointer to an individual FDE while libgcc takes a
41/// null-terminated list of FDEs. This means we need to know what unwinder
42/// is being used at runtime.
43///
44/// This detection is done currently by looking for a libunwind-specific symbol.
45/// This specific symbol was somewhat recommended by LLVM's
46/// "RTDyldMemoryManager.cpp" file which says:
47///
48/// > We use the presence of __unw_add_dynamic_fde to detect libunwind.
49///
50/// I'll note that there's also a different libunwind project at
51/// https://www.nongnu.org/libunwind/ but that doesn't appear to have
52/// `__register_frame` so I don't think that interacts with this.
53fn using_libunwind() -> bool {
54    // On macOS the libgcc interface is never used so libunwind is always used.
55    // Otherwise delegate to `helpers.c` since weak symbols can't be used from
56    // Rust at this time.
57    cfg!(target_os = "macos") || unsafe { wasmtime_using_libunwind() }
58}
59
60impl UnwindRegistration {
61    #[allow(missing_docs)]
62    pub const SECTION_NAME: &'static str = ".eh_frame";
63
64    /// Registers precompiled unwinding information with the system.
65    ///
66    /// The `_base_address` field is ignored here (only used on other
67    /// platforms), but the `unwind_info` and `unwind_len` parameters should
68    /// describe an in-memory representation of a `.eh_frame` section. This is
69    /// typically arranged for by the `wasmtime-obj` crate.
70    pub unsafe fn new(
71        _base_address: *const u8,
72        unwind_info: *const u8,
73        unwind_len: usize,
74    ) -> Result<UnwindRegistration> {
75        #[cfg(has_virtual_memory)]
76        debug_assert_eq!(
77            unwind_info as usize % crate::runtime::vm::host_page_size(),
78            0,
79            "The unwind info must always be aligned to a page"
80        );
81
82        let mut registrations = Vec::new();
83        if using_libunwind() {
84            // For libunwind, `__register_frame` takes a pointer to a single
85            // FDE. Note that we subtract 4 from the length of unwind info since
86            // wasmtime-encode .eh_frame sections always have a trailing 32-bit
87            // zero for the platforms above.
88            let start = unwind_info;
89            let end = start.add(unwind_len - 4);
90            let mut current = start;
91
92            // Walk all of the entries in the frame table and register them
93            while current < end {
94                let len = current.cast::<u32>().read_unaligned() as usize;
95
96                // Skip over the CIE
97                if current != start {
98                    __register_frame(current);
99                    let cur = NonNull::new(current.cast_mut()).unwrap();
100                    registrations.push(SendSyncPtr::new(cur));
101                }
102
103                // Move to the next table entry (+4 because the length itself is
104                // not inclusive)
105                current = current.add(len + 4);
106            }
107        } else {
108            // On gnu (libgcc), `__register_frame` will walk the FDEs until an
109            // entry of length 0
110            __register_frame(unwind_info);
111            let info = NonNull::new(unwind_info.cast_mut()).unwrap();
112            registrations.push(SendSyncPtr::new(info));
113        }
114
115        Ok(UnwindRegistration { registrations })
116    }
117}
118
119impl Drop for UnwindRegistration {
120    fn drop(&mut self) {
121        unsafe {
122            // libgcc stores the frame entries as a linked list in decreasing
123            // sort order based on the PC value of the registered entry.
124            //
125            // As we store the registrations in increasing order, it would be
126            // O(N^2) to deregister in that order.
127            //
128            // To ensure that we just pop off the first element in the list upon
129            // every deregistration, walk our list of registrations backwards.
130            for fde in self.registrations.iter().rev() {
131                __deregister_frame(fde.as_ptr());
132            }
133        }
134    }
135}