Previous to this commit Wasmtime would use the `GlobalModuleRegistry` when learning information about a trap such as its trap code, the symbols for each frame, etc. This has a downside though of holding a global read-write lock for the duration of this operation which hinders registration of new modules in parallel. In addition there was a fair amount of internal duplication between this "global module registry" and the store-local module registry. Finally relying on global state for information like this gets a bit more brittle over time as it seems best to scope global queries to precisely what's necessary rather than holding extra information. With the refactoring in wasm backtraces done in #4183 it's now possible to always have a `StoreOpaque` reference when a backtrace is collected for symbolication and otherwise Trap-identification purposes. This commit adds a `StoreOpaque` parameter to the `Trap::from_runtime` constructor and then plumbs that everywhere. Note that while doing this I changed the internal `traphandlers::lazy_per_thread_init` function to no longer return a `Result` and instead just `panic!` on Unix if memory couldn't be allocated for a stack. This removed quite a lot of error-handling code for a case that's expected to quite rarely happen. If necessary in the future we can add a fallible initialization point but this feels like a better default balance for the code here. With a `StoreOpaque` in use when a trap is being symbolicated that means we have a `ModuleRegistry` which can be used for queries and such. This meant that the `GlobalModuleRegistry` state could largely be dismantled and moved to per-`Store` state (within the `ModuleRegistry`, mostly just moving methods around). The final state is that the global rwlock is not exclusively scoped around insertions/deletions/`is_wasm_trap_pc` which is just a lookup and atomic add. Otherwise symbolication for a backtrace exclusively uses store-local state now (as intended). The original motivation for this commit was that frame information lookup and pieces were looking to get somewhat complicated with the addition of components which are a new vector of traps coming out of Cranelift-generated code. My hope is that by having a `Store` around for more operations it's easier to plumb all this through.
80 lines
3.0 KiB
Rust
80 lines
3.0 KiB
Rust
use crate::traphandlers::{tls, wasmtime_longjmp};
|
|
use std::io;
|
|
use winapi::um::errhandlingapi::*;
|
|
use winapi::um::minwinbase::*;
|
|
use winapi::um::winnt::*;
|
|
use winapi::vc::excpt::*;
|
|
|
|
/// Function which may handle custom signals while processing traps.
|
|
pub type SignalHandler<'a> =
|
|
dyn Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool + Send + Sync + 'a;
|
|
|
|
pub unsafe fn platform_init() {
|
|
// our trap handler needs to go first, so that we can recover from
|
|
// wasm faults and continue execution, so pass `1` as a true value
|
|
// here.
|
|
if AddVectoredExceptionHandler(1, Some(exception_handler)).is_null() {
|
|
panic!(
|
|
"failed to add exception handler: {}",
|
|
io::Error::last_os_error()
|
|
);
|
|
}
|
|
}
|
|
|
|
unsafe extern "system" fn exception_handler(exception_info: PEXCEPTION_POINTERS) -> LONG {
|
|
// Check the kind of exception, since we only handle a subset within
|
|
// wasm code. If anything else happens we want to defer to whatever
|
|
// the rest of the system wants to do for this exception.
|
|
let record = &*(*exception_info).ExceptionRecord;
|
|
if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION
|
|
&& record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION
|
|
&& record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO
|
|
&& record.ExceptionCode != EXCEPTION_INT_OVERFLOW
|
|
{
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
// FIXME: this is what the previous C++ did to make sure that TLS
|
|
// works by the time we execute this trap handling code. This isn't
|
|
// exactly super easy to call from Rust though and it's not clear we
|
|
// necessarily need to do so. Leaving this here in case we need this
|
|
// in the future, but for now we can probably wait until we see a
|
|
// strange fault before figuring out how to reimplement this in
|
|
// Rust.
|
|
//
|
|
// if (!NtCurrentTeb()->Reserved1[sThreadLocalArrayPointerIndex]) {
|
|
// return EXCEPTION_CONTINUE_SEARCH;
|
|
// }
|
|
|
|
// This is basically the same as the unix version above, only with a
|
|
// few parameters tweaked here and there.
|
|
tls::with(|info| {
|
|
let info = match info {
|
|
Some(info) => info,
|
|
None => return EXCEPTION_CONTINUE_SEARCH,
|
|
};
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(target_arch = "x86_64")] {
|
|
let ip = (*(*exception_info).ContextRecord).Rip as *const u8;
|
|
} else if #[cfg(target_arch = "x86")] {
|
|
let ip = (*(*exception_info).ContextRecord).Eip as *const u8;
|
|
} else {
|
|
compile_error!("unsupported platform");
|
|
}
|
|
}
|
|
let jmp_buf = info.jmp_buf_if_trap(ip, |handler| handler(exception_info));
|
|
if jmp_buf.is_null() {
|
|
EXCEPTION_CONTINUE_SEARCH
|
|
} else if jmp_buf as usize == 1 {
|
|
EXCEPTION_CONTINUE_EXECUTION
|
|
} else {
|
|
info.capture_backtrace(ip);
|
|
wasmtime_longjmp(jmp_buf)
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn lazy_per_thread_init() {
|
|
// Unused on Windows
|
|
}
|