wasmer_compiler/engine/unwind/
systemv.rs

1// This file contains code from external sources.
2// Attributions: https://github.com/wasmerio/wasmer/blob/main/docs/ATTRIBUTIONS.md
3
4//! Module for System V ABI unwind registry.
5
6use core::sync::atomic::{
7    AtomicBool, AtomicUsize,
8    Ordering::{self, Relaxed},
9};
10use std::sync::Once;
11
12use crate::types::unwind::CompiledFunctionUnwindInfoReference;
13
14/// Represents a registry of function unwind information for System V ABI.
15pub struct UnwindRegistry {
16    registrations: Vec<usize>,
17    published: bool,
18    #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
19    compact_unwind_mgr: compact_unwind::CompactUnwindManager,
20}
21
22unsafe extern "C" {
23    // libunwind import
24    fn __register_frame(fde: *const u8);
25    fn __deregister_frame(fde: *const u8);
26}
27
28// Apple-specific unwind functions - the following is taken from LLVM's libunwind itself.
29#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
30mod compact_unwind;
31
32/// There are two primary unwinders on Unix platforms: libunwind and libgcc.
33///
34/// Unfortunately their interface to `__register_frame` is different. The
35/// libunwind library takes a pointer to an individual FDE while libgcc takes a
36/// null-terminated list of FDEs. This means we need to know what unwinder
37/// is being used at runtime.
38///
39/// This detection is done currently by looking for a libunwind-specific symbol.
40/// This specific symbol was somewhat recommended by LLVM's
41/// "RTDyldMemoryManager.cpp" file which says:
42///
43/// > We use the presence of __unw_add_dynamic_fde to detect libunwind.
44///
45/// I'll note that there's also a different libunwind project at
46/// https://www.nongnu.org/libunwind/ but that doesn't appear to have
47/// `__register_frame` so I don't think that interacts with this.
48#[allow(dead_code)]
49fn using_libunwind() -> bool {
50    static USING_LIBUNWIND: AtomicUsize = AtomicUsize::new(LIBUNWIND_UNKNOWN);
51
52    const LIBUNWIND_UNKNOWN: usize = 0;
53    const LIBUNWIND_YES: usize = 1;
54    const LIBUNWIND_NO: usize = 2;
55
56    // On macOS the libgcc interface is never used so libunwind is always used.
57    if cfg!(target_os = "macos") {
58        return true;
59    }
60
61    // TODO: wasmtime started using weak symbol definition that makes the detection
62    // more reliable on linux-musl target: https://github.com/bytecodealliance/wasmtime/pull/9479
63    if cfg!(target_env = "musl") {
64        return true;
65    }
66
67    // On other platforms the unwinder can vary. Sometimes the unwinder is
68    // selected at build time and sometimes it differs at build time and runtime
69    // (or at least I think that's possible). Fall back to a `libc::dlsym` to
70    // figure out what we're using and branch based on that.
71    //
72    // Note that the result of `libc::dlsym` is cached to only look this up
73    // once.
74    match USING_LIBUNWIND.load(Relaxed) {
75        LIBUNWIND_YES => true,
76        LIBUNWIND_NO => false,
77        LIBUNWIND_UNKNOWN => {
78            let looks_like_libunwind = unsafe {
79                !libc::dlsym(
80                    std::ptr::null_mut(),
81                    c"__unw_add_dynamic_fde".as_ptr().cast(),
82                )
83                .is_null()
84            };
85            USING_LIBUNWIND.store(
86                if looks_like_libunwind {
87                    LIBUNWIND_YES
88                } else {
89                    LIBUNWIND_NO
90                },
91                Relaxed,
92            );
93            looks_like_libunwind
94        }
95        _ => unreachable!(),
96    }
97}
98
99impl UnwindRegistry {
100    /// Creates a new unwind registry with the given base address.
101    pub fn new() -> Self {
102        Self {
103            registrations: Vec::new(),
104            published: false,
105            #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
106            compact_unwind_mgr: Default::default(),
107        }
108    }
109
110    /// Registers a function given the start offset, length, and unwind information.
111    pub fn register(
112        &mut self,
113        _base_address: usize,
114        _func_start: u32,
115        _func_len: u32,
116        info: &CompiledFunctionUnwindInfoReference,
117    ) -> Result<(), String> {
118        match info {
119            CompiledFunctionUnwindInfoReference::Dwarf => {}
120            _ => return Err(format!("unsupported unwind information {info:?}")),
121        };
122        Ok(())
123    }
124
125    /// Publishes all registered functions.
126    pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> {
127        if self.published {
128            return Err("unwind registry has already been published".to_string());
129        }
130
131        if let Some(eh_frame) = eh_frame {
132            unsafe {
133                self.register_frames(eh_frame);
134            }
135        }
136
137        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
138        {
139            self.compact_unwind_mgr
140                .finalize()
141                .map_err(|v| v.to_string())?;
142            self.compact_unwind_mgr.register();
143        }
144
145        self.published = true;
146
147        Ok(())
148    }
149
150    #[allow(clippy::cast_ptr_alignment)]
151    unsafe fn register_frames(&mut self, eh_frame: &[u8]) {
152        // Register atexit handler that will tell us if exit has been called.
153        static INIT: Once = Once::new();
154        INIT.call_once(|| unsafe {
155            let result = libc::atexit(atexit_handler);
156            assert_eq!(result, 0, "libc::atexit must succeed");
157        });
158
159        assert!(
160            !EXIT_CALLED.load(Ordering::SeqCst),
161            "Cannot register unwind information during the process exit"
162        );
163
164        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
165        {
166            // Special call for macOS on aarch64 to register the `.eh_frame` section.
167            // TODO: I am not 100% sure if it's correct to never deregister the `.eh_frame` section. It was this way before
168            // I started working on this, so I kept it that way.
169            unsafe {
170                compact_unwind::__unw_add_dynamic_eh_frame_section(eh_frame.as_ptr() as usize);
171            }
172        }
173
174        #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
175        {
176            // Validate that the `.eh_frame` is well-formed before registering it.
177            // See https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html for more details.
178            // We put the frame records into a vector before registering them, because
179            // calling `__register_frame` with invalid data can cause segfaults.
180
181            // Pointers to the registrations that will be registered with `__register_frame`.
182            // For libgcc based systems, these are CIEs.
183            // For libunwind based systems, these are FDEs.
184            let mut records_to_register = Vec::new();
185
186            let mut current = 0;
187            let mut last_len = 0;
188            let using_libunwind = using_libunwind();
189            while current <= (eh_frame.len() - size_of::<u32>()) {
190                // If a CFI or a FDE starts with 0u32 it is a terminator.
191                let len = u32::from_ne_bytes(eh_frame[current..(current + 4)].try_into().unwrap());
192                if len == 0 {
193                    current += size_of::<u32>();
194                    last_len = 0;
195                    continue;
196                }
197                // The first record after a terminator is always a CIE.
198                let is_cie = last_len == 0;
199                last_len = len;
200                let record = eh_frame.as_ptr() as usize + current;
201                current = current + len as usize + 4;
202
203                if using_libunwind {
204                    // For libunwind based systems, `__register_frame` takes a pointer to an FDE.
205                    if !is_cie {
206                        // Every record that's not a CIE is an FDE.
207                        records_to_register.push(record);
208                    }
209                } else {
210                    // For libgcc based systems, `__register_frame` takes a pointer to a CIE.
211                    if is_cie {
212                        records_to_register.push(record);
213                    }
214                }
215            }
216
217            assert_eq!(
218                last_len, 0,
219                "The last record in the `.eh_frame` must be a terminator (but it actually has length {last_len})"
220            );
221            assert_eq!(
222                current,
223                eh_frame.len(),
224                "The `.eh_frame` must be finished after the last record",
225            );
226
227            for record in records_to_register {
228                // Register the CFI with libgcc
229                unsafe {
230                    __register_frame(record as *const u8);
231                }
232                self.registrations.push(record);
233            }
234        }
235    }
236
237    pub(crate) fn register_compact_unwind(
238        &mut self,
239        compact_unwind: Option<&[u8]>,
240        eh_personality_addr_in_got: Option<usize>,
241    ) -> Result<(), String> {
242        #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
243        unsafe {
244            if let Some(slice) = compact_unwind {
245                self.compact_unwind_mgr.read_compact_unwind_section(
246                    slice.as_ptr() as _,
247                    slice.len(),
248                    eh_personality_addr_in_got,
249                )?;
250            }
251        }
252
253        #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
254        {
255            _ = compact_unwind;
256            _ = eh_personality_addr_in_got;
257        }
258        Ok(())
259    }
260}
261
262/// Global flag indicating whether the program exit has been initiated.
263/// Set to true by an atexit handler to prevent crashes during shutdown
264/// when deregistering unwind frames. Accesses use `Ordering::SeqCst`
265/// to ensure correct memory ordering across threads.
266pub static EXIT_CALLED: AtomicBool = AtomicBool::new(false);
267
268extern "C" fn atexit_handler() {
269    EXIT_CALLED.store(true, Ordering::SeqCst);
270}
271
272impl Drop for UnwindRegistry {
273    fn drop(&mut self) {
274        if self.published {
275            unsafe {
276                // libgcc stores the frame entries as a linked list in decreasing sort order
277                // based on the PC value of the registered entry.
278                //
279                // As we store the registrations in increasing order, it would be O(N^2) to
280                // deregister in that order.
281                //
282                // To ensure that we just pop off the first element in the list upon every
283                // deregistration, walk our list of registrations backwards.
284                for registration in self.registrations.iter().rev() {
285                    // We don't want to deregister frames in UnwindRegistry::Drop as that could be called during
286                    // program shutdown and can collide with release_registered_frames and lead to
287                    // crashes.
288                    if EXIT_CALLED.load(Ordering::SeqCst) {
289                        return;
290                    }
291
292                    #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
293                    {
294                        compact_unwind::__unw_remove_dynamic_eh_frame_section(*registration);
295                        self.compact_unwind_mgr.deregister();
296                    }
297
298                    #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
299                    {
300                        __deregister_frame(*registration as *const _);
301                    }
302                }
303            }
304        }
305    }
306}