From 9a035aabc3988695f29cd1664756c120442f75aa Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Fri, 3 Feb 2023 15:08:07 +0100 Subject: [PATCH 1/4] Made unwind handling cross-platform, with some limitation on Windows unwind info that needs Windows syscall for now (for #3508) --- lib/compiler/src/engine/mod.rs | 3 + lib/compiler/src/engine/unwind.rs | 230 ++++++++++++++++++ lib/compiler/src/engine/unwind/dummy.rs | 32 --- lib/compiler/src/engine/unwind/mod.rs | 13 - lib/compiler/src/engine/unwind/systemv.rs | 124 ---------- lib/compiler/src/engine/unwind/windows_x64.rs | 105 -------- 6 files changed, 233 insertions(+), 274 deletions(-) create mode 100644 lib/compiler/src/engine/unwind.rs delete mode 100644 lib/compiler/src/engine/unwind/dummy.rs delete mode 100644 lib/compiler/src/engine/unwind/mod.rs delete mode 100644 lib/compiler/src/engine/unwind/systemv.rs delete mode 100644 lib/compiler/src/engine/unwind/windows_x64.rs diff --git a/lib/compiler/src/engine/mod.rs b/lib/compiler/src/engine/mod.rs index bf54a791311..1b17aa53a07 100644 --- a/lib/compiler/src/engine/mod.rs +++ b/lib/compiler/src/engine/mod.rs @@ -38,6 +38,9 @@ pub use self::resolver::resolve_imports; pub use self::trap::*; #[cfg(not(target_arch = "wasm32"))] pub use self::tunables::{BaseTunables, Tunables}; +#[cfg(feature = "translator")] +#[cfg(not(target_arch = "wasm32"))] +pub use self::unwind::*; #[cfg(feature = "translator")] #[cfg(not(target_arch = "wasm32"))] diff --git a/lib/compiler/src/engine/unwind.rs b/lib/compiler/src/engine/unwind.rs new file mode 100644 index 00000000000..b793e4597b6 --- /dev/null +++ b/lib/compiler/src/engine/unwind.rs @@ -0,0 +1,230 @@ +// This file contains code from external sources. +// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md + +#[cfg(all(windows, target_arch = "x86_64"))] +use std::collections::HashMap; + +use wasmer_types::CompiledFunctionUnwindInfo; + +#[cfg(all(windows, target_arch = "x86_64"))] +use winapi::um::winnt; + +/// Unwind will work for Dwarf or Windows64 unwind infoo +/// And will fall back for a dummy implementation for other types + +#[derive(PartialEq, Debug)] +enum UnwindType { + Unknown, + Dummy, + SystemV, + #[cfg(all(windows, target_arch = "x86_64"))] + WindowsX64, +} + +/// Represents a registry of function unwind information for System V or Windows X64 ABI. +/// Not that Windows X64 ABI unwind info will only work on native Windows +/// Cross-compiling from Linux to Windows will not handle the Unwind info for now +pub struct UnwindRegistry { + ty: UnwindType, + // WINNT: A hashmap mapping the baseaddress with the registered runtime functions + #[cfg(all(windows, target_arch = "x86_64"))] + functions: HashMap>, + // SYSV: registraction vector + registrations: Vec, + // common: published? + published: bool, +} + +// SystemV helper +extern "C" { + // libunwind import + fn __register_frame(fde: *const u8); + fn __deregister_frame(fde: *const u8); +} + +impl UnwindRegistry { + /// Creates a new unwind registry with the given base address. + pub fn new() -> Self { + Self { + ty: UnwindType::Unknown, + #[cfg(all(windows, target_arch = "x86_64"))] + functions: HashMap::new(), + registrations: Vec::new(), + published: false, + } + } + + /// Registers a function given the start offset, length, and unwind information. + pub fn register( + &mut self, + _base_address: usize, + _func_start: u32, + _func_len: u32, + info: &CompiledFunctionUnwindInfo, + ) -> Result<(), String> { + if self.published { + return Err("unwind registry has already been published".to_string()); + } + + match info { + // Windows Unwind need to use Windows system function for now + // No unwind information will be handled only on the Windows platform itself + // Cross-compiling will fallback to the Dummy implementation + #[cfg(all(windows, target_arch = "x86_64"))] + CompiledFunctionUnwindInfo::WindowsX64(_) => { + if self.ty != UnwindType::Unknown && self.ty != UnwindType::WindowsX64 { + return Err("unwind registry has already un incompatible type".to_string()); + } + self.ty = UnwindType::WindowsX64; + let mut entry = winnt::RUNTIME_FUNCTION::default(); + + entry.BeginAddress = _func_start; + entry.EndAddress = _func_start + _func_len; + + // The unwind information should be immediately following the function + // with padding for 4 byte alignment + unsafe { + *entry.u.UnwindInfoAddress_mut() = (entry.EndAddress + 3) & !3; + } + let entries = self + .functions + .entry(_base_address) + .or_insert_with(|| Vec::new()); + + entries.push(entry); + } + CompiledFunctionUnwindInfo::Dwarf => { + if self.ty != UnwindType::Unknown && self.ty != UnwindType::SystemV { + return Err("unwind registry has already un incompatible type".to_string()); + } + self.ty = UnwindType::SystemV; + } + _ => { + if self.ty != UnwindType::Unknown && self.ty != UnwindType::Dummy { + return Err("unwind registry has already un incompatible type".to_string()); + } + self.ty = UnwindType::Dummy; + } + }; + Ok(()) + } + + /// Publishes all registered functions. + pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> { + if self.published { + return Err("unwind registry has already been published".to_string()); + } + + match self.ty { + UnwindType::SystemV => { + if let Some(eh_frame) = eh_frame { + unsafe { + self.register_frames(eh_frame); + } + } + } + #[cfg(all(windows, target_arch = "x86_64"))] + UnwindType::WindowsX64 => { + if !self.functions.is_empty() { + for (_base_address, functions) in self.functions.iter_mut() { + // Windows heap allocations are 32-bit aligned, but assert just in case + assert_eq!( + (functions.as_mut_ptr() as u64) % 4, + 0, + "function table allocation was not aligned" + ); + unsafe { + if winnt::RtlAddFunctionTable( + functions.as_mut_ptr(), + functions.len() as u32, + *_base_address as u64, + ) == 0 + { + return Err("failed to register function tables".to_string()); + } + } + } + } + } + _ => {} + }; + + self.published = true; + Ok(()) + } + + #[allow(clippy::cast_ptr_alignment)] + unsafe fn register_frames(&mut self, eh_frame: &[u8]) { + if cfg!(any( + all(target_os = "linux", target_env = "gnu"), + target_os = "freebsd" + )) { + // Registering an empty `eh_frame` (i.e. which + // contains empty FDEs) cause problems on Linux when + // deregistering it. We must avoid this + // scenario. Usually, this is handled upstream by the + // compilers. + debug_assert_ne!( + eh_frame, + &[0, 0, 0, 0], + "`eh_frame` seems to contain empty FDEs" + ); + + // On gnu (libgcc), `__register_frame` will walk the FDEs until an entry of length 0 + let ptr = eh_frame.as_ptr(); + __register_frame(ptr); + self.registrations.push(ptr as usize); + } else { + // For libunwind, `__register_frame` takes a pointer to a single FDE + let start = eh_frame.as_ptr(); + let end = start.add(eh_frame.len()); + let mut current = start; + + // Walk all of the entries in the frame table and register them + while current < end { + let len = std::ptr::read::(current as *const u32) as usize; + + // Skip over the CIE and zero-length FDEs. + // LLVM's libunwind emits a warning on zero-length FDEs. + if current != start && len != 0 { + __register_frame(current); + self.registrations.push(current as usize); + } + + // Move to the next table entry (+4 because the length itself is not inclusive) + current = current.add(len + 4); + } + } + } +} + +impl Drop for UnwindRegistry { + fn drop(&mut self) { + if self.published { + match self.ty { + #[cfg(all(windows, target_arch = "x86_64"))] + UnwindType::WindowsX64 => unsafe { + for functions in self.functions.values_mut() { + winnt::RtlDeleteFunctionTable(functions.as_mut_ptr()); + } + }, + UnwindType::SystemV => { + unsafe { + // libgcc stores the frame entries as a linked list in decreasing sort order + // based on the PC value of the registered entry. + // + // As we store the registrations in increasing order, it would be O(N^2) to + // deregister in that order. + // + // To ensure that we just pop off the first element in the list upon every + // deregistration, walk our list of registrations backwards. + for fde in self.registrations.iter().rev() { + __deregister_frame(*fde as *const _); + } + } + } + _ => {} + } + } + } +} diff --git a/lib/compiler/src/engine/unwind/dummy.rs b/lib/compiler/src/engine/unwind/dummy.rs deleted file mode 100644 index 0a34e29fd56..00000000000 --- a/lib/compiler/src/engine/unwind/dummy.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Module for Dummy unwind registry. - -use wasmer_types::CompiledFunctionUnwindInfo; - -/// Represents a registry of function unwind information when the host system -/// support any one in specific. -pub struct DummyUnwindRegistry {} - -impl DummyUnwindRegistry { - /// Creates a new unwind registry with the given base address. - pub fn new() -> Self { - DummyUnwindRegistry {} - } - - /// Registers a function given the start offset, length, and unwind information. - pub fn register( - &mut self, - _base_address: usize, - _func_start: u32, - _func_len: u32, - _info: &CompiledFunctionUnwindInfo, - ) -> Result<(), String> { - // Do nothing - Ok(()) - } - - /// Publishes all registered functions. - pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> { - // Do nothing - Ok(()) - } -} diff --git a/lib/compiler/src/engine/unwind/mod.rs b/lib/compiler/src/engine/unwind/mod.rs deleted file mode 100644 index 7d61694a210..00000000000 --- a/lib/compiler/src/engine/unwind/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -cfg_if::cfg_if! { - if #[cfg(all(windows, target_arch = "x86_64"))] { - mod windows_x64; - pub use self::windows_x64::*; - } else if #[cfg(unix)] { - mod systemv; - pub use self::systemv::*; - } else { - // Otherwise, we provide a dummy fallback without unwinding - mod dummy; - pub use self::dummy::DummyUnwindRegistry as UnwindRegistry; - } -} diff --git a/lib/compiler/src/engine/unwind/systemv.rs b/lib/compiler/src/engine/unwind/systemv.rs deleted file mode 100644 index 11e08e4b34b..00000000000 --- a/lib/compiler/src/engine/unwind/systemv.rs +++ /dev/null @@ -1,124 +0,0 @@ -// This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md - -//! Module for System V ABI unwind registry. - -use wasmer_types::CompiledFunctionUnwindInfo; - -/// Represents a registry of function unwind information for System V ABI. -pub struct UnwindRegistry { - registrations: Vec, - published: bool, -} - -extern "C" { - // libunwind import - fn __register_frame(fde: *const u8); - fn __deregister_frame(fde: *const u8); -} - -impl UnwindRegistry { - /// Creates a new unwind registry with the given base address. - pub fn new() -> Self { - Self { - registrations: Vec::new(), - published: false, - } - } - - /// Registers a function given the start offset, length, and unwind information. - pub fn register( - &mut self, - _base_address: usize, - _func_start: u32, - _func_len: u32, - info: &CompiledFunctionUnwindInfo, - ) -> Result<(), String> { - match info { - CompiledFunctionUnwindInfo::Dwarf => {} - _ => return Err(format!("unsupported unwind information {info:?}")), - }; - Ok(()) - } - - /// Publishes all registered functions. - pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> { - if self.published { - return Err("unwind registry has already been published".to_string()); - } - - if let Some(eh_frame) = eh_frame { - unsafe { - self.register_frames(eh_frame); - } - } - - self.published = true; - - Ok(()) - } - - #[allow(clippy::cast_ptr_alignment)] - unsafe fn register_frames(&mut self, eh_frame: &[u8]) { - if cfg!(any( - all(target_os = "linux", target_env = "gnu"), - target_os = "freebsd" - )) { - // Registering an empty `eh_frame` (i.e. which - // contains empty FDEs) cause problems on Linux when - // deregistering it. We must avoid this - // scenario. Usually, this is handled upstream by the - // compilers. - debug_assert_ne!( - eh_frame, - &[0, 0, 0, 0], - "`eh_frame` seems to contain empty FDEs" - ); - - // On gnu (libgcc), `__register_frame` will walk the FDEs until an entry of length 0 - let ptr = eh_frame.as_ptr(); - __register_frame(ptr); - self.registrations.push(ptr as usize); - } else { - // For libunwind, `__register_frame` takes a pointer to a single FDE - let start = eh_frame.as_ptr(); - let end = start.add(eh_frame.len()); - let mut current = start; - - // Walk all of the entries in the frame table and register them - while current < end { - let len = std::ptr::read::(current as *const u32) as usize; - - // Skip over the CIE and zero-length FDEs. - // LLVM's libunwind emits a warning on zero-length FDEs. - if current != start && len != 0 { - __register_frame(current); - self.registrations.push(current as usize); - } - - // Move to the next table entry (+4 because the length itself is not inclusive) - current = current.add(len + 4); - } - } - } -} - -impl Drop for UnwindRegistry { - fn drop(&mut self) { - if self.published { - unsafe { - // libgcc stores the frame entries as a linked list in decreasing sort order - // based on the PC value of the registered entry. - // - // As we store the registrations in increasing order, it would be O(N^2) to - // deregister in that order. - // - // To ensure that we just pop off the first element in the list upon every - // deregistration, walk our list of registrations backwards. - for fde in self.registrations.iter().rev() { - __deregister_frame(*fde as *const _); - } - } - } - } -} diff --git a/lib/compiler/src/engine/unwind/windows_x64.rs b/lib/compiler/src/engine/unwind/windows_x64.rs deleted file mode 100644 index d5cde49d25d..00000000000 --- a/lib/compiler/src/engine/unwind/windows_x64.rs +++ /dev/null @@ -1,105 +0,0 @@ -// This file contains code from external sources. -// Attributions: https://github.com/wasmerio/wasmer/blob/master/ATTRIBUTIONS.md - -//! Module for Windows x64 ABI unwind registry. -use std::collections::HashMap; -use wasmer_types::CompiledFunctionUnwindInfo; -use winapi::um::winnt; - -/// Represents a registry of function unwind information for Windows x64 ABI. -pub struct UnwindRegistry { - // A hashmap mapping the baseaddress with the registered runtime functions - functions: HashMap>, - published: bool, -} - -impl UnwindRegistry { - /// Creates a new unwind registry with the given base address. - pub fn new() -> Self { - Self { - functions: HashMap::new(), - published: false, - } - } - - /// Registers a function given the start offset, length, and unwind information. - pub fn register( - &mut self, - base_address: usize, - func_start: u32, - func_len: u32, - info: &CompiledFunctionUnwindInfo, - ) -> Result<(), String> { - if self.published { - return Err("unwind registry has already been published".to_string()); - } - - match info { - CompiledFunctionUnwindInfo::WindowsX64(_) => {} - _ => return Err("unsupported unwind information".to_string()), - }; - - let mut entry = winnt::RUNTIME_FUNCTION::default(); - - entry.BeginAddress = func_start; - entry.EndAddress = func_start + func_len; - - // The unwind information should be immediately following the function - // with padding for 4 byte alignment - unsafe { - *entry.u.UnwindInfoAddress_mut() = (entry.EndAddress + 3) & !3; - } - let entries = self - .functions - .entry(base_address) - .or_insert_with(|| Vec::new()); - - entries.push(entry); - - Ok(()) - } - - /// Publishes all registered functions. - pub fn publish(&mut self, _eh_frame: Option<&[u8]>) -> Result<(), String> { - if self.published { - return Err("unwind registry has already been published".to_string()); - } - - self.published = true; - - if !self.functions.is_empty() { - for (base_address, functions) in self.functions.iter_mut() { - // Windows heap allocations are 32-bit aligned, but assert just in case - assert_eq!( - (functions.as_mut_ptr() as u64) % 4, - 0, - "function table allocation was not aligned" - ); - unsafe { - if winnt::RtlAddFunctionTable( - functions.as_mut_ptr(), - functions.len() as u32, - *base_address as u64, - ) == 0 - { - return Err("failed to register function tables".to_string()); - } - } - } - } - - Ok(()) - } -} - -impl Drop for UnwindRegistry { - fn drop(&mut self) { - if self.published { - unsafe { - for functions in self.functions.values_mut() { - winnt::RtlDeleteFunctionTable(functions.as_mut_ptr()); - } - } - } - } -} From b836261c0f4d28f26fe345e915fc723c0d561100 Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Fri, 3 Feb 2023 15:47:26 +0100 Subject: [PATCH 2/4] Made unwind using the dummy implementation on cross-platform, as the needed functions are not there --- lib/compiler/src/engine/unwind.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/compiler/src/engine/unwind.rs b/lib/compiler/src/engine/unwind.rs index b793e4597b6..0c940f9a354 100644 --- a/lib/compiler/src/engine/unwind.rs +++ b/lib/compiler/src/engine/unwind.rs @@ -16,26 +16,29 @@ use winapi::um::winnt; enum UnwindType { Unknown, Dummy, + #[cfg(unix)] SystemV, #[cfg(all(windows, target_arch = "x86_64"))] WindowsX64, } /// Represents a registry of function unwind information for System V or Windows X64 ABI. -/// Not that Windows X64 ABI unwind info will only work on native Windows -/// Cross-compiling from Linux to Windows will not handle the Unwind info for now +/// Cross-compiling will not handle the Unwind info for now, +/// and will fallback to the Dummy implementation pub struct UnwindRegistry { ty: UnwindType, // WINNT: A hashmap mapping the baseaddress with the registered runtime functions #[cfg(all(windows, target_arch = "x86_64"))] functions: HashMap>, // SYSV: registraction vector + #[cfg(unix)] registrations: Vec, // common: published? published: bool, } // SystemV helper +#[cfg(unix)] extern "C" { // libunwind import fn __register_frame(fde: *const u8); @@ -49,6 +52,7 @@ impl UnwindRegistry { ty: UnwindType::Unknown, #[cfg(all(windows, target_arch = "x86_64"))] functions: HashMap::new(), + #[cfg(unix)] registrations: Vec::new(), published: false, } @@ -93,6 +97,7 @@ impl UnwindRegistry { entries.push(entry); } + #[cfg(unix)] CompiledFunctionUnwindInfo::Dwarf => { if self.ty != UnwindType::Unknown && self.ty != UnwindType::SystemV { return Err("unwind registry has already un incompatible type".to_string()); @@ -110,14 +115,15 @@ impl UnwindRegistry { } /// Publishes all registered functions. - pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> { + pub fn publish(&mut self, _eh_frame: Option<&[u8]>) -> Result<(), String> { if self.published { return Err("unwind registry has already been published".to_string()); } match self.ty { + #[cfg(unix)] UnwindType::SystemV => { - if let Some(eh_frame) = eh_frame { + if let Some(_eh_frame) = eh_frame { unsafe { self.register_frames(eh_frame); } @@ -154,6 +160,7 @@ impl UnwindRegistry { } #[allow(clippy::cast_ptr_alignment)] + #[cfg(unix)] unsafe fn register_frames(&mut self, eh_frame: &[u8]) { if cfg!(any( all(target_os = "linux", target_env = "gnu"), @@ -208,6 +215,7 @@ impl Drop for UnwindRegistry { winnt::RtlDeleteFunctionTable(functions.as_mut_ptr()); } }, + #[cfg(unix)] UnwindType::SystemV => { unsafe { // libgcc stores the frame entries as a linked list in decreasing sort order From f9bbfd9c0e4ab328e064350b3c658a6901fc1bf0 Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Fri, 3 Feb 2023 15:49:56 +0100 Subject: [PATCH 3/4] Fixed linux build --- lib/compiler/src/engine/unwind.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compiler/src/engine/unwind.rs b/lib/compiler/src/engine/unwind.rs index 0c940f9a354..c90bba348b7 100644 --- a/lib/compiler/src/engine/unwind.rs +++ b/lib/compiler/src/engine/unwind.rs @@ -123,7 +123,7 @@ impl UnwindRegistry { match self.ty { #[cfg(unix)] UnwindType::SystemV => { - if let Some(_eh_frame) = eh_frame { + if let Some(eh_frame) = _eh_frame { unsafe { self.register_frames(eh_frame); } From edd6110911817059ecf8b5db82fe2cdcd7e4d816 Mon Sep 17 00:00:00 2001 From: ptitSeb Date: Fri, 3 Feb 2023 16:23:30 +0100 Subject: [PATCH 4/4] Fixed an issue with linux unwind when publish is used without any previous call to register --- lib/compiler/src/engine/unwind.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/compiler/src/engine/unwind.rs b/lib/compiler/src/engine/unwind.rs index c90bba348b7..c49efc4afd7 100644 --- a/lib/compiler/src/engine/unwind.rs +++ b/lib/compiler/src/engine/unwind.rs @@ -115,15 +115,35 @@ impl UnwindRegistry { } /// Publishes all registered functions. - pub fn publish(&mut self, _eh_frame: Option<&[u8]>) -> Result<(), String> { + pub fn publish(&mut self, eh_frame: Option<&[u8]>) -> Result<(), String> { if self.published { return Err("unwind registry has already been published".to_string()); } + let have_eh_frame = eh_frame.is_some(); + match self.ty { + #[cfg(unix)] + UnwindType::SystemV => {} + #[cfg(all(windows, target_arch = "x86_64"))] + UnwindType::WindowsX64 => { + if have_eh_frame { + return Err("unwind mysmatch eh_frame on WindowsX64".to_string()); + } + } + UnwindType::Dummy => {} + UnwindType::Unknown => + { + #[cfg(unix)] + if have_eh_frame { + self.ty = UnwindType::SystemV; + } + } + } + match self.ty { #[cfg(unix)] UnwindType::SystemV => { - if let Some(eh_frame) = _eh_frame { + if let Some(eh_frame) = eh_frame { unsafe { self.register_frames(eh_frame); }