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 pub const SECTION_NAME: &'static str = ".eh_frame";
62
63 /// Registers precompiled unwinding information with the system.
64 ///
65 /// The `_base_address` field is ignored here (only used on other
66 /// platforms), but the `unwind_info` and `unwind_len` parameters should
67 /// describe an in-memory representation of a `.eh_frame` section. This is
68 /// typically arranged for by the `wasmtime-obj` crate.
69 pub unsafe fn new(
70 _base_address: *const u8,
71 unwind_info: *const u8,
72 unwind_len: usize,
73 ) -> Result<UnwindRegistration> {
74 #[cfg(has_virtual_memory)]
75 debug_assert_eq!(
76 unwind_info as usize % crate::runtime::vm::host_page_size(),
77 0,
78 "The unwind info must always be aligned to a page"
79 );
80
81 let mut registrations = Vec::new();
82 if using_libunwind() {
83 // For libunwind, `__register_frame` takes a pointer to a single
84 // FDE. Note that we subtract 4 from the length of unwind info since
85 // wasmtime-encode .eh_frame sections always have a trailing 32-bit
86 // zero for the platforms above.
87 let start = unwind_info;
88 let end = start.add(unwind_len - 4);
89 let mut current = start;
90
91 // Walk all of the entries in the frame table and register them
92 while current < end {
93 let len = current.cast::<u32>().read_unaligned() as usize;
94
95 // Skip over the CIE
96 if current != start {
97 __register_frame(current);
98 let cur = NonNull::new(current.cast_mut()).unwrap();
99 registrations.push(SendSyncPtr::new(cur));
100 }
101
102 // Move to the next table entry (+4 because the length itself is
103 // not inclusive)
104 current = current.add(len + 4);
105 }
106 } else {
107 // On gnu (libgcc), `__register_frame` will walk the FDEs until an
108 // entry of length 0
109 __register_frame(unwind_info);
110 let info = NonNull::new(unwind_info.cast_mut()).unwrap();
111 registrations.push(SendSyncPtr::new(info));
112 }
113
114 Ok(UnwindRegistration { registrations })
115 }
116}
117
118impl Drop for UnwindRegistration {
119 fn drop(&mut self) {
120 unsafe {
121 // libgcc stores the frame entries as a linked list in decreasing
122 // sort order based on the PC value of the registered entry.
123 //
124 // As we store the registrations in increasing order, it would be
125 // O(N^2) to deregister in that order.
126 //
127 // To ensure that we just pop off the first element in the list upon
128 // every deregistration, walk our list of registrations backwards.
129 for fde in self.registrations.iter().rev() {
130 __deregister_frame(fde.as_ptr());
131 }
132 }
133 }
134}