wasmtime_runtime/sys/unix/
unwind.rs

1//! Module for System V ABI unwind registry.
2
3use crate::SendSyncPtr;
4use anyhow::Result;
5use std::ptr::{self, NonNull};
6use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
7
8/// Represents a registration of function unwind information for System V ABI.
9pub struct UnwindRegistration {
10    registrations: Vec<SendSyncPtr<u8>>,
11}
12
13extern "C" {
14    // libunwind import
15    fn __register_frame(fde: *const u8);
16    fn __deregister_frame(fde: *const u8);
17}
18
19/// There are two primary unwinders on Unix platforms: libunwind and libgcc.
20///
21/// Unfortunately their interface to `__register_frame` is different. The
22/// libunwind library takes a pointer to an individual FDE while libgcc takes a
23/// null-terminated list of FDEs. This means we need to know what unwinder
24/// is being used at runtime.
25///
26/// This detection is done currently by looking for a libunwind-specific symbol.
27/// This specific symbol was somewhat recommended by LLVM's
28/// "RTDyldMemoryManager.cpp" file which says:
29///
30/// > We use the presence of __unw_add_dynamic_fde to detect libunwind.
31///
32/// I'll note that there's also a different libunwind project at
33/// https://www.nongnu.org/libunwind/ but that doesn't appear to have
34/// `__register_frame` so I don't think that interacts with this.
35fn using_libunwind() -> bool {
36    static USING_LIBUNWIND: AtomicUsize = AtomicUsize::new(LIBUNWIND_UNKNOWN);
37
38    const LIBUNWIND_UNKNOWN: usize = 0;
39    const LIBUNWIND_YES: usize = 1;
40    const LIBUNWIND_NO: usize = 2;
41
42    // On macOS the libgcc interface is never used so libunwind is always used.
43    if cfg!(target_os = "macos") {
44        return true;
45    }
46
47    // On other platforms the unwinder can vary. Sometimes the unwinder is
48    // selected at build time and sometimes it differs at build time and runtime
49    // (or at least I think that's possible). Fall back to a `libc::dlsym` to
50    // figure out what we're using and branch based on that.
51    //
52    // Note that the result of `libc::dlsym` is cached to only look this up
53    // once.
54    match USING_LIBUNWIND.load(Relaxed) {
55        LIBUNWIND_YES => true,
56        LIBUNWIND_NO => false,
57        LIBUNWIND_UNKNOWN => {
58            let looks_like_libunwind = unsafe {
59                !libc::dlsym(ptr::null_mut(), "__unw_add_dynamic_fde\0".as_ptr().cast()).is_null()
60            };
61            USING_LIBUNWIND.store(
62                if looks_like_libunwind {
63                    LIBUNWIND_YES
64                } else {
65                    LIBUNWIND_NO
66                },
67                Relaxed,
68            );
69            looks_like_libunwind
70        }
71        _ => unreachable!(),
72    }
73}
74
75impl UnwindRegistration {
76    #[allow(missing_docs)]
77    pub const SECTION_NAME: &'static str = ".eh_frame";
78
79    /// Registers precompiled unwinding information with the system.
80    ///
81    /// The `_base_address` field is ignored here (only used on other
82    /// platforms), but the `unwind_info` and `unwind_len` parameters should
83    /// describe an in-memory representation of a `.eh_frame` section. This is
84    /// typically arranged for by the `wasmtime-obj` crate.
85    pub unsafe fn new(
86        _base_address: *const u8,
87        unwind_info: *const u8,
88        unwind_len: usize,
89    ) -> Result<UnwindRegistration> {
90        debug_assert_eq!(
91            unwind_info as usize % crate::page_size(),
92            0,
93            "The unwind info must always be aligned to a page"
94        );
95
96        let mut registrations = Vec::new();
97        if using_libunwind() {
98            // For libunwind, `__register_frame` takes a pointer to a single
99            // FDE. Note that we subtract 4 from the length of unwind info since
100            // wasmtime-encode .eh_frame sections always have a trailing 32-bit
101            // zero for the platforms above.
102            let start = unwind_info;
103            let end = start.add(unwind_len - 4);
104            let mut current = start;
105
106            // Walk all of the entries in the frame table and register them
107            while current < end {
108                let len = current.cast::<u32>().read_unaligned() as usize;
109
110                // Skip over the CIE
111                if current != start {
112                    __register_frame(current);
113                    let cur = NonNull::new(current.cast_mut()).unwrap();
114                    registrations.push(SendSyncPtr::new(cur));
115                }
116
117                // Move to the next table entry (+4 because the length itself is
118                // not inclusive)
119                current = current.add(len + 4);
120            }
121        } else {
122            // On gnu (libgcc), `__register_frame` will walk the FDEs until an
123            // entry of length 0
124            __register_frame(unwind_info);
125            let info = NonNull::new(unwind_info.cast_mut()).unwrap();
126            registrations.push(SendSyncPtr::new(info));
127        }
128
129        Ok(UnwindRegistration { registrations })
130    }
131}
132
133impl Drop for UnwindRegistration {
134    fn drop(&mut self) {
135        unsafe {
136            // libgcc stores the frame entries as a linked list in decreasing
137            // sort order based on the PC value of the registered entry.
138            //
139            // As we store the registrations in increasing order, it would be
140            // O(N^2) to deregister in that order.
141            //
142            // To ensure that we just pop off the first element in the list upon
143            // every deregistration, walk our list of registrations backwards.
144            for fde in self.registrations.iter().rev() {
145                __deregister_frame(fde.as_ptr());
146            }
147        }
148    }
149}