Skip to main content

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 crate::sync::OnceLock;
6use core::ptr::NonNull;
7
8type UnwindFn = unsafe extern "C" fn(usize);
9
10/// Represents a registration of function unwind information for System V ABI.
11pub enum UnwindRegistration {
12    /// libgcc registers the whole table with one call to `__register_frame`.
13    SingleFrame(SendSyncPtr<u8>),
14    /// Older libunwind versions register each FDE separately.
15    MultipleFrames(TryVec<SendSyncPtr<u8>>),
16    /// Newer libunwind versions can register an entire `.eh_frame` section.
17    LibunwindSection {
18        section: SendSyncPtr<u8>,
19        deregister: UnwindFn,
20    },
21}
22
23// Keep this layout in sync with `struct Libunwind` in helpers.c.
24#[repr(C)]
25#[derive(Default)]
26struct Libunwind {
27    add_dynamic_fde: Option<UnwindFn>,
28    add_dynamic_eh_frame_section: Option<UnwindFn>,
29    remove_dynamic_eh_frame_section: Option<UnwindFn>,
30}
31
32cfg_select! {
33    // FIXME: at least on the `gcc-arm-linux-gnueabihf` toolchain on Ubuntu
34    // these symbols are not provided by default like they are on other targets.
35    // I'm not ARM expert so I don't know why. For now though consider this an
36    // optional integration feature with the platform and stub out the functions
37    // to do nothing which won't break any tests it just means that
38    // runtime-generated backtraces won't have the same level of fidelity they
39    // do on other targets.
40    target_arch = "arm" => {
41        unsafe extern "C" fn __register_frame(_: *const u8) {}
42        unsafe extern "C" fn __deregister_frame(_: *const u8) {}
43        unsafe fn wasmtime_libunwind() -> Libunwind {
44            Libunwind::default()
45        }
46    }
47    _ => {
48        unsafe extern "C" {
49            fn __register_frame(fde: *const u8);
50            fn __deregister_frame(fde: *const u8);
51            #[wasmtime_versioned_export_macros::versioned_link]
52            fn wasmtime_libunwind() -> Libunwind;
53        }
54    }
55}
56
57impl Libunwind {
58    fn get() -> &'static Self {
59        static LIBUNWIND: OnceLock<Libunwind> = OnceLock::new();
60        LIBUNWIND.get_or_init(|| {
61            // SAFETY: the C helper returns nullable function pointers with the
62            // signatures and layout declared above.
63            let api = unsafe { wasmtime_libunwind() };
64            #[cfg(target_os = "macos")]
65            let api = {
66                let mut api = api;
67                if let Some((add, remove)) = load_unwind_section_api() {
68                    api.add_dynamic_eh_frame_section = Some(add);
69                    api.remove_dynamic_eh_frame_section = Some(remove);
70                }
71                api
72            };
73            api
74        })
75    }
76
77    /// There are two primary unwinders on Unix platforms: libunwind and libgcc.
78    ///
79    /// Unfortunately their interface to `__register_frame` is different. The
80    /// libunwind library takes a pointer to an individual FDE while libgcc takes a
81    /// null-terminated list of FDEs. This means we need to know what unwinder
82    /// is being used at runtime.
83    ///
84    /// This detection is done currently by looking for a libunwind-specific symbol.
85    /// This specific symbol was somewhat recommended by LLVM's
86    /// "RTDyldMemoryManager.cpp" file which says:
87    ///
88    /// > We use the presence of __unw_add_dynamic_fde to detect libunwind.
89    ///
90    /// I'll note that there's also a different libunwind project at
91    /// https://www.nongnu.org/libunwind/ but that doesn't appear to have
92    /// `__register_frame` so I don't think that interacts with this.
93    fn is_libunwind(&self) -> bool {
94        // macOS always uses the libunwind convention.
95        cfg!(target_os = "macos") || self.add_dynamic_fde.is_some()
96    }
97}
98
99#[cfg(target_os = "macos")]
100fn load_unwind_section_api() -> Option<(UnwindFn, UnwindFn)> {
101    // Weak imports tolerate symbols missing at runtime, but the linker still
102    // needs their exports in the build SDK. The macOS 11.3 SDK has the older
103    // __unw_add_dynamic_fde export but lacks the section APIs, so resolve those
104    // at runtime to keep building with older SDKs.
105    //
106    // SAFETY: dladdr initializes info on success. The returned path is a C
107    // string owned by the loaded image containing our linked registration API.
108    unsafe {
109        let mut info = core::mem::MaybeUninit::<libc::Dl_info>::uninit();
110        if libc::dladdr((__register_frame as *const ()).cast(), info.as_mut_ptr()) == 0 {
111            return None;
112        }
113        let info = info.assume_init();
114        if info.dli_fname.is_null() {
115            return None;
116        }
117
118        // Use the same provider as the linked individual-FDE API. RTLD_FIRST
119        // prevents borrowing a missing API from a dependency's separate registry.
120        let lib = libc::dlopen(
121            info.dli_fname,
122            libc::RTLD_LAZY | libc::RTLD_LOCAL | libc::RTLD_FIRST,
123        );
124        if lib.is_null() {
125            return None;
126        }
127
128        // Search overrides or interposition can change which library is opened.
129        if libc::dlsym(lib, c"__register_frame".as_ptr())
130            != __register_frame as *const () as *mut libc::c_void
131            || libc::dlsym(lib, c"__deregister_frame".as_ptr())
132                != __deregister_frame as *const () as *mut libc::c_void
133        {
134            libc::dlclose(lib);
135            return None;
136        }
137        let add = libc::dlsym(lib, c"__unw_add_dynamic_eh_frame_section".as_ptr());
138        let remove = libc::dlsym(lib, c"__unw_remove_dynamic_eh_frame_section".as_ptr());
139        if add.is_null() || remove.is_null() {
140            libc::dlclose(lib);
141            return None;
142        }
143
144        // Both symbols have the UnwindFn ABI. Keep the library handle open for
145        // the lifetime of the function pointers cached in Libunwind::get.
146        Some((
147            core::mem::transmute::<*mut libc::c_void, UnwindFn>(add),
148            core::mem::transmute::<*mut libc::c_void, UnwindFn>(remove),
149        ))
150    }
151}
152
153impl UnwindRegistration {
154    pub const SECTION_NAME: &'static str = ".eh_frame";
155
156    /// Registers precompiled unwinding information with the system.
157    ///
158    /// The `_base_address` field is ignored here (only used on other
159    /// platforms), but the `unwind_info` and `unwind_len` parameters should
160    /// describe an in-memory representation of a `.eh_frame` section. This is
161    /// typically arranged for by the `wasmtime-obj` crate.
162    pub unsafe fn new(
163        _base_address: *const u8,
164        unwind_info: *const u8,
165        unwind_len: usize,
166    ) -> Result<UnwindRegistration> {
167        #[cfg(has_virtual_memory)]
168        debug_assert_eq!(
169            unwind_info as usize % crate::runtime::vm::host_page_size(),
170            0,
171            "The unwind info must always be aligned to a page"
172        );
173
174        let api = Libunwind::get();
175        let info = SendSyncPtr::new(NonNull::new(unwind_info.cast_mut()).unwrap());
176        unsafe {
177            if !api.is_libunwind() {
178                // libgcc walks the table until an entry of length zero.
179                __register_frame(unwind_info);
180                return Ok(Self::SingleFrame(info));
181            }
182
183            // Require both APIs so removal uses the same registration grouping.
184            if let (Some(add), Some(remove)) = (
185                api.add_dynamic_eh_frame_section,
186                api.remove_dynamic_eh_frame_section,
187            ) {
188                add(unwind_info as usize);
189                return Ok(Self::LibunwindSection {
190                    section: info,
191                    deregister: remove,
192                });
193            }
194
195            // libunwind's __register_frame takes a single FDE. Our .eh_frame
196            // section ends with a four-byte zero terminator.
197            let mut registration = Self::MultipleFrames(TryVec::new());
198            let Self::MultipleFrames(frames) = &mut registration else {
199                unreachable!();
200            };
201            let end = unwind_info.add(unwind_len - 4);
202            let mut current = unwind_info;
203            while current < end {
204                let len = current.cast::<u32>().read_unaligned() as usize;
205                // Skip the CIE at the beginning of the section.
206                if current != unwind_info {
207                    // Record the pointer before registering it, so an allocation
208                    // failure unregisters all previous entries via Drop.
209                    frames.push(SendSyncPtr::new(NonNull::new(current.cast_mut()).unwrap()))?;
210                    __register_frame(current);
211                }
212                current = current.add(len + 4);
213            }
214            Ok(registration)
215        }
216    }
217}
218
219impl Drop for UnwindRegistration {
220    fn drop(&mut self) {
221        unsafe {
222            match self {
223                Self::SingleFrame(frame) => __deregister_frame(frame.as_ptr()),
224                Self::MultipleFrames(frames) => {
225                    // Preserve reverse registration order when removing FDEs.
226                    for frame in frames.iter().rev() {
227                        __deregister_frame(frame.as_ptr());
228                    }
229                }
230                Self::LibunwindSection {
231                    section,
232                    deregister,
233                } => {
234                    deregister(section.as_ptr() as usize);
235                }
236            }
237        }
238    }
239}