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}