diff --git a/Cargo.lock b/Cargo.lock index f7aa569109..0b60270a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,24 +341,25 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd3225fff1be118941c5fb66f1fb1f7f3e86468fac0e7364713c4fb99b72632b" +checksum = "40af6e6f7419110906d0f7b4b8084d3216be64d7da77aa12887885ebe0fc2776" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e3e6679892029f76a99b9059d2b74e77ac03637d573bb014bc21579ec1b7da" +checksum = "88583eb22e5cd0fe1ef66f0b80d0063d21d30e84e887d08b3d369def3ea7b4be" dependencies = [ "byteorder", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", + "gimli", "log", "serde", "smallvec", @@ -368,9 +369,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cabe691548e28ca82ebd218f2fe76eec4c5629b64ef3db8b58443b7d9047275" +checksum = "353872c984943b9134d7c835eb9d12932bd90f4992dbe666593771bee920d673" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -378,24 +379,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d173252ffade4aa6e929090977b9a4cd5ac28e15a42626f878be3844b3000ad9" +checksum = "cd1e96479a56981cce5c8f14d26773195d662ccdbbeca39fb8eba22b5ca8ea6a" [[package]] name = "cranelift-entity" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3498ad9ba021715716a1c52e2b31d7829a149913fb0d88493e4b07d3b772b656" +checksum = "37bc8dc3d4274ededc687d84821f491a8a03447dbb7481983936220892cf55b4" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521a30773b8de74345c807a38853f055aca8fecaa39a0fc7698bfebc5a3da515" +checksum = "4ea33e55bda8c425f3f533355b03e3a43cf29b4e228b35b868b6a1c43b6a139e" dependencies = [ "cranelift-codegen", "log", @@ -405,9 +406,9 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624e755cbe984e437308968239736e7f9aa3193e99928fb76eec7a1946627b34" +checksum = "2c22cfaaa5e69eddaddd6cfa7a76233de964b9b2245e81a5f47dae739931ad0d" dependencies = [ "cranelift-codegen", "raw-cpuid", @@ -416,9 +417,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0320733e518ab9e0e2d1a22034d40e2851fb403ed14db5220cf9b86576b9cfd4" +checksum = "038f8fd636abc83ccd6f110e0891766521c3599b52173c0f37e61c684f19a15d" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -426,7 +427,7 @@ dependencies = [ "log", "serde", "thiserror", - "wasmparser 0.45.2", + "wasmparser 0.47.0", ] [[package]] @@ -1964,12 +1965,6 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1527c84a5bd585215f29c06b0e2a5274e478ad4dfc970d26ffad66fdc6cb311d" -[[package]] -name = "wasmparser" -version = "0.45.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b4eab1d9971d0803729cba3617b56eb04fcb4bd25361cb63880ed41a42f20d5" - [[package]] name = "wasmparser" version = "0.47.0" @@ -2188,6 +2183,7 @@ dependencies = [ name = "wasmtime-runtime" version = "0.9.0" dependencies = [ + "backtrace", "cc", "cfg-if", "indexmap", diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 139959f359..545eafde80 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -147,7 +147,7 @@ impl WrappedCallable for WasmtimeFn { .map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?; // Call the trampoline. - if let Err(message) = unsafe { + if let Err(error) = unsafe { self.instance.with_signals_on(|| { wasmtime_runtime::wasmtime_call_trampoline( vmctx, @@ -156,8 +156,12 @@ impl WrappedCallable for WasmtimeFn { ) }) } { - let trap = - take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message))); + let message = error.0; + let backtrace = error.1; + + let trap = take_api_trap().unwrap_or_else(|| { + Trap::new_with_trace(format!("call error: {}", message), backtrace) + }); return Err(trap); } diff --git a/crates/api/src/trampoline/func.rs b/crates/api/src/trampoline/func.rs index 328a325490..f5d7dde5c3 100644 --- a/crates/api/src/trampoline/func.rs +++ b/crates/api/src/trampoline/func.rs @@ -11,7 +11,9 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::ir::types; use wasmtime_environ::isa::TargetIsa; use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex}; -use wasmtime_environ::{ir, settings, CompiledFunction, Export, Module, TrapInformation}; +use wasmtime_environ::{ + ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module, TrapInformation, +}; use wasmtime_jit::trampoline::ir::{ ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind, }; @@ -136,6 +138,7 @@ fn make_trampoline( let mut context = Context::new(); context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone()); + context.func.collect_frame_layout_info(); let ss = context.func.create_stack_slot(StackSlotData::new( StackSlotKind::ExplicitSlot, @@ -213,8 +216,7 @@ fn make_trampoline( .map_err(|error| pretty_error(&context.func, Some(isa), error)) .expect("compile_and_emit"); - let mut unwind_info = Vec::new(); - context.emit_unwind_info(isa, &mut unwind_info); + let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context); let traps = trap_sink.traps; diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index 5127d1c5c1..ae74c00af3 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -1,6 +1,7 @@ use crate::instance::Instance; use std::fmt; use std::sync::Arc; +use wasmtime_runtime::{get_backtrace, Backtrace, BacktraceFrame}; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -26,10 +27,21 @@ impl Trap { /// assert_eq!("unexpected error", trap.message()); /// ``` pub fn new>(message: I) -> Self { + Self::new_with_trace(message, get_backtrace()) + } + + pub(crate) fn new_with_trace>(message: I, backtrace: Backtrace) -> Self { + let mut trace = Vec::with_capacity(backtrace.len()); + for i in 0..backtrace.len() { + // Don't include frames without backtrace info. + if let Some(info) = FrameInfo::try_from(backtrace[i]) { + trace.push(info); + } + } Trap { inner: Arc::new(TrapInner { message: message.into(), - trace: Vec::new(), + trace, }), } } @@ -62,22 +74,42 @@ impl fmt::Display for Trap { impl std::error::Error for Trap {} #[derive(Debug)] -pub struct FrameInfo; +pub struct FrameInfo { + module_name: Option, + func_index: u32, +} impl FrameInfo { pub fn instance(&self) -> *const Instance { unimplemented!("FrameInfo::instance"); } - pub fn func_index() -> usize { - unimplemented!("FrameInfo::func_index"); + pub fn func_index(&self) -> u32 { + self.func_index } - pub fn func_offset() -> usize { + pub fn func_offset(&self) -> usize { unimplemented!("FrameInfo::func_offset"); } - pub fn module_offset() -> usize { + pub fn module_offset(&self) -> usize { unimplemented!("FrameInfo::module_offset"); } + + pub fn module_name(&self) -> Option<&str> { + self.module_name.as_deref() + } + + pub(crate) fn try_from(backtrace: BacktraceFrame) -> Option { + if let Some(tag) = backtrace.tag() { + let func_index = tag.func_index as u32; + let module_name = tag.module_id.clone(); + Some(FrameInfo { + func_index, + module_name, + }) + } else { + None + } + } } diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index aea6404a9b..6f9cdc582b 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -41,3 +41,116 @@ fn test_trap_return() -> Result<(), String> { Ok(()) } + +#[test] +fn test_trap_trace() -> Result<(), String> { + let store = Store::default(); + let binary = parse_str( + r#" + (module $hello_mod + (func (export "run") (call $hello)) + (func $hello (unreachable)) + ) + "#, + ) + .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + + let module = + Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let instance = Instance::new(&module, &[]) + .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let run_func = instance.exports()[0] + .func() + .expect("expected function export"); + + let e = run_func.call(&[]).err().expect("error calling function"); + + let trace = e.trace(); + assert_eq!(trace.len(), 2); + assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[0].func_index(), 1); + assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[1].func_index(), 0); + assert!(e.message().contains("unreachable")); + + Ok(()) +} + +#[test] +fn test_trap_trace_cb() -> Result<(), String> { + struct ThrowCallback; + + impl Callable for ThrowCallback { + fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> { + Err(Trap::new("cb throw")) + } + } + + let store = Store::default(); + let binary = parse_str( + r#" + (module $hello_mod + (import "" "throw" (func $throw)) + (func (export "run") (call $hello)) + (func $hello (call $throw)) + ) + "#, + ) + .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + + let fn_type = FuncType::new(Box::new([]), Box::new([])); + let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback)); + + let module = + Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let instance = Instance::new(&module, &[fn_func.into()]) + .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let run_func = instance.exports()[0] + .func() + .expect("expected function export"); + + let e = run_func.call(&[]).err().expect("error calling function"); + + let trace = e.trace(); + assert_eq!(trace.len(), 2); + assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[0].func_index(), 1); + assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[1].func_index(), 0); + assert_eq!(e.message(), "cb throw"); + + Ok(()) +} + +#[test] +fn test_trap_stack_overflow() -> Result<(), String> { + let store = Store::default(); + let binary = parse_str( + r#" + (module $rec_mod + (func $run (export "run") (call $run)) + ) + "#, + ) + .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + + let module = + Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let instance = Instance::new(&module, &[]) + .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let run_func = instance.exports()[0] + .func() + .expect("expected function export"); + + let e = run_func.call(&[]).err().expect("error calling function"); + + let trace = e.trace(); + assert!(trace.len() >= 32); + for i in 0..trace.len() { + assert_eq!(trace[i].module_name().unwrap(), "rec_mod"); + assert_eq!(trace[i].func_index(), 0); + } + assert!(e.message().contains("call stack exhausted")); + + Ok(()) +} diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml index 0f3d3e52ac..b8a1a0c7ed 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -13,9 +13,9 @@ edition = "2018" [dependencies] anyhow = "1.0" -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } wasmparser = "0.47.0" lightbeam = { path = "../lightbeam", optional = true, version = "0.9.0" } indexmap = "1.0.2" @@ -46,7 +46,7 @@ tempfile = "3" target-lexicon = { version = "0.10.0", default-features = false } pretty_env_logger = "0.3.0" rand = { version = "0.7.0", default-features = false, features = ["small_rng"] } -cranelift-codegen = { version = "0.54", features = ["enable-serde", "all-arch"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde", "all-arch"] } filetime = "0.2.7" [badges] diff --git a/crates/environ/src/cache/tests.rs b/crates/environ/src/cache/tests.rs index 55685d6de7..7a9bdc31c6 100644 --- a/crates/environ/src/cache/tests.rs +++ b/crates/environ/src/cache/tests.rs @@ -1,7 +1,9 @@ use super::config::tests::test_prolog; use super::*; use crate::address_map::{FunctionAddressMap, InstructionAddressMap}; -use crate::compilation::{CompiledFunction, Relocation, RelocationTarget, TrapInformation}; +use crate::compilation::{ + CompiledFunction, CompiledFunctionUnwindInfo, Relocation, RelocationTarget, TrapInformation, +}; use crate::module::{MemoryPlan, MemoryStyle, Module}; use cranelift_codegen::{binemit, ir, isa, settings, ValueLocRange}; use cranelift_entity::EntityRef; @@ -259,7 +261,7 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData { CompiledFunction { body: (0..(i * 3 / 2)).collect(), jt_offsets: sm, - unwind_info: (0..(i * 3 / 2)).collect(), + unwind_info: CompiledFunctionUnwindInfo::Windows((0..(i * 3 / 2)).collect()), } }) .collect(); diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 3aea3ba284..ae57584008 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -4,13 +4,136 @@ use crate::cache::ModuleCacheDataTupleType; use crate::module; use crate::module_environ::FunctionBodyData; -use cranelift_codegen::{binemit, ir, isa}; +use cranelift_codegen::{binemit, ir, isa, Context}; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, FuncIndex, ModuleTranslationState, WasmError}; use serde::{Deserialize, Serialize}; use std::ops::Range; use thiserror::Error; +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct FDERelocEntry(pub i64, pub usize, pub u8); + +/// Relocation entry for unwind info. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CompiledFunctionUnwindInfoReloc { + /// Entry offest in the code block. + pub offset: u32, + /// Entry addend relative to the code block. + pub addend: u32, +} + +/// Compiled function unwind information. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum CompiledFunctionUnwindInfo { + /// No info. + None, + /// Windows UNWIND_INFO. + Windows(Vec), + /// Frame layout info. + FrameLayout(Vec, usize, Vec), +} + +impl CompiledFunctionUnwindInfo { + /// Constructs unwind info object. + pub fn new(isa: &dyn isa::TargetIsa, context: &Context) -> Self { + use cranelift_codegen::binemit::{ + FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc, + }; + use cranelift_codegen::isa::CallConv; + + struct Sink(Vec, usize, Vec); + impl FrameUnwindSink for Sink { + fn len(&self) -> FrameUnwindOffset { + self.0.len() + } + fn bytes(&mut self, b: &[u8]) { + self.0.extend_from_slice(b); + } + fn reserve(&mut self, len: usize) { + self.0.reserve(len) + } + fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) { + self.2.push(FDERelocEntry( + 0, + off, + match r { + Reloc::Abs4 => 4, + Reloc::Abs8 => 8, + _ => { + panic!("unexpected reloc type"); + } + }, + )) + } + fn set_entry_offset(&mut self, off: FrameUnwindOffset) { + self.1 = off; + } + } + + let kind = match context.func.signature.call_conv { + CallConv::SystemV | CallConv::Fast | CallConv::Cold => FrameUnwindKind::Libunwind, + CallConv::WindowsFastcall => FrameUnwindKind::Fastcall, + _ => { + return CompiledFunctionUnwindInfo::None; + } + }; + + let mut sink = Sink(Vec::new(), 0, Vec::new()); + context.emit_unwind_info(isa, kind, &mut sink); + + let Sink(data, offset, relocs) = sink; + if data.is_empty() { + return CompiledFunctionUnwindInfo::None; + } + + match kind { + FrameUnwindKind::Fastcall => CompiledFunctionUnwindInfo::Windows(data), + FrameUnwindKind::Libunwind => { + CompiledFunctionUnwindInfo::FrameLayout(data, offset, relocs) + } + } + } + + /// Retuns true is no unwind info data. + pub fn is_empty(&self) -> bool { + match self { + CompiledFunctionUnwindInfo::None => true, + CompiledFunctionUnwindInfo::Windows(d) => d.is_empty(), + CompiledFunctionUnwindInfo::FrameLayout(c, _, _) => c.is_empty(), + } + } + + /// Returns size of serilized unwind info. + pub fn len(&self) -> usize { + match self { + CompiledFunctionUnwindInfo::None => 0, + CompiledFunctionUnwindInfo::Windows(d) => d.len(), + CompiledFunctionUnwindInfo::FrameLayout(c, _, _) => c.len(), + } + } + + /// Serializes data into byte array. + pub fn serialize(&self, dest: &mut [u8], relocs: &mut Vec) { + match self { + CompiledFunctionUnwindInfo::None => (), + CompiledFunctionUnwindInfo::Windows(d) => { + dest.copy_from_slice(d); + } + CompiledFunctionUnwindInfo::FrameLayout(code, _fde_offset, r) => { + dest.copy_from_slice(code); + r.iter().for_each(move |r| { + assert_eq!(r.2, 8); + relocs.push(CompiledFunctionUnwindInfoReloc { + offset: r.1 as u32, + addend: r.0 as u32, + }) + }); + } + } + } +} + /// Compiled function: machine code body, jump table offsets, and unwind information. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct CompiledFunction { @@ -21,7 +144,7 @@ pub struct CompiledFunction { pub jt_offsets: ir::JumpTableOffsets, /// The unwind information. - pub unwind_info: Vec, + pub unwind_info: CompiledFunctionUnwindInfo, } type Functions = PrimaryMap; @@ -50,7 +173,7 @@ impl Compilation { .map(|(body_range, jt_offsets, unwind_range)| CompiledFunction { body: buffer[body_range].to_vec(), jt_offsets, - unwind_info: buffer[unwind_range].to_vec(), + unwind_info: CompiledFunctionUnwindInfo::Windows(buffer[unwind_range].to_vec()), }) .collect(), ) diff --git a/crates/environ/src/cranelift.rs b/crates/environ/src/cranelift.rs index 790e6cf642..246838373f 100644 --- a/crates/environ/src/cranelift.rs +++ b/crates/environ/src/cranelift.rs @@ -3,7 +3,8 @@ use crate::address_map::{FunctionAddressMap, InstructionAddressMap}; use crate::cache::{ModuleCacheData, ModuleCacheDataTupleType, ModuleCacheEntry}; use crate::compilation::{ - Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, TrapInformation, + Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Relocation, + RelocationTarget, TrapInformation, }; use crate::func_environ::{ get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name, @@ -204,6 +205,7 @@ impl crate::compilation::Compiler for Cranelift { context.func.name = get_func_name(func_index); context.func.signature = module.signatures[module.functions[func_index]].clone(); + context.func.collect_frame_layout_info(); if generate_debug_info { context.func.collect_debug_info(); } @@ -217,7 +219,6 @@ impl crate::compilation::Compiler for Cranelift { )?; let mut code_buf: Vec = Vec::new(); - let mut unwind_info = Vec::new(); let mut reloc_sink = RelocSink::new(func_index); let mut trap_sink = TrapSink::new(); let mut stackmap_sink = binemit::NullStackmapSink {}; @@ -233,7 +234,7 @@ impl crate::compilation::Compiler for Cranelift { CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) })?; - context.emit_unwind_info(isa, &mut unwind_info); + let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context); let address_transform = if generate_debug_info { let body_len = code_buf.len(); diff --git a/crates/environ/src/func_environ.rs b/crates/environ/src/func_environ.rs index b218641877..c33a2d5c15 100644 --- a/crates/environ/src/func_environ.rs +++ b/crates/environ/src/func_environ.rs @@ -362,8 +362,8 @@ impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environm } impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> { - fn is_wasm_parameter(&self, func: &ir::Function, index: usize) -> bool { - func.signature.params[index].purpose == ir::ArgumentPurpose::Normal + fn is_wasm_parameter(&self, signature: &ir::Signature, index: usize) -> bool { + signature.params[index].purpose == ir::ArgumentPurpose::Normal } fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult { diff --git a/crates/environ/src/lib.rs b/crates/environ/src/lib.rs index fdbea6c870..ec5525fec2 100644 --- a/crates/environ/src/lib.rs +++ b/crates/environ/src/lib.rs @@ -44,8 +44,9 @@ pub use crate::address_map::{ }; pub use crate::cache::{create_new_config as cache_create_new_config, init as cache_init}; pub use crate::compilation::{ - Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget, - Relocations, TrapInformation, Traps, + Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, + CompiledFunctionUnwindInfoReloc, Compiler, Relocation, RelocationTarget, Relocations, + TrapInformation, Traps, }; pub use crate::cranelift::Cranelift; pub use crate::data_structures::*; diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 368b9eb4ad..27490f81e1 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -11,11 +11,11 @@ readme = "README.md" edition = "2018" [dependencies] -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } -cranelift-native = "0.54" -cranelift-frontend = "0.54" +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } +cranelift-native = "0.55" +cranelift-frontend = "0.55" wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-debug = { path = "../debug", version = "0.9.0" } diff --git a/crates/jit/src/action.rs b/crates/jit/src/action.rs index d3668b0887..385ff32aa3 100644 --- a/crates/jit/src/action.rs +++ b/crates/jit/src/action.rs @@ -6,7 +6,10 @@ use std::cmp::max; use std::{fmt, mem, ptr, slice}; use thiserror::Error; use wasmtime_environ::ir; -use wasmtime_runtime::{wasmtime_call_trampoline, Export, InstanceHandle, VMInvokeArgument}; +use wasmtime_runtime::{ + wasmtime_call_trampoline, Backtrace, Export, InstanceHandle, TrapMessageAndStack, + VMInvokeArgument, +}; /// A runtime value. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -103,6 +106,8 @@ pub enum ActionOutcome { Trapped { /// The trap message. message: String, + /// Backtrace. + trace: Backtrace, }, } @@ -191,7 +196,7 @@ pub fn invoke( compiler.publish_compiled_code(); // Call the trampoline. - if let Err(message) = unsafe { + if let Err(TrapMessageAndStack(message, trace)) = unsafe { instance.with_signals_on(|| { wasmtime_call_trampoline( callee_vmctx, @@ -200,7 +205,7 @@ pub fn invoke( ) }) } { - return Ok(ActionOutcome::Trapped { message }); + return Ok(ActionOutcome::Trapped { message, trace }); } // Load the return values out of `values_vec`. diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 49e6d5ee2d..2eb4806364 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -2,14 +2,45 @@ use crate::function_table::FunctionTable; use region; +use std::mem::ManuallyDrop; use std::{cmp, mem}; use wasmtime_environ::{Compilation, CompiledFunction}; use wasmtime_runtime::{Mmap, VMFunctionBody}; +struct CodeMemoryEntry { + mmap: ManuallyDrop, + table: ManuallyDrop, +} + +impl CodeMemoryEntry { + fn new() -> Self { + Self { + mmap: ManuallyDrop::new(Mmap::new()), + table: ManuallyDrop::new(FunctionTable::new()), + } + } + fn with_capacity(cap: usize) -> Result { + Ok(Self { + mmap: ManuallyDrop::new(Mmap::with_at_least(cap)?), + table: ManuallyDrop::new(FunctionTable::new()), + }) + } +} + +impl Drop for CodeMemoryEntry { + fn drop(&mut self) { + unsafe { + // Table needs to be freed before mmap. + ManuallyDrop::drop(&mut self.table); + ManuallyDrop::drop(&mut self.mmap); + } + } +} + /// Memory manager for executable code. pub struct CodeMemory { - current: (Mmap, FunctionTable), - mmaps: Vec<(Mmap, FunctionTable)>, + current: CodeMemoryEntry, + entries: Vec, position: usize, published: usize, } @@ -23,8 +54,8 @@ impl CodeMemory { /// Create a new `CodeMemory` instance. pub fn new() -> Self { Self { - current: (Mmap::new(), FunctionTable::new()), - mmaps: Vec::new(), + current: CodeMemoryEntry::new(), + entries: Vec::new(), position: 0, published: 0, } @@ -81,19 +112,20 @@ impl CodeMemory { self.push_current(0) .expect("failed to push current memory map"); - for (m, t) in &mut self.mmaps[self.published..] { + for CodeMemoryEntry { mmap: m, table: t } in &mut self.entries[self.published..] { + // Remove write access to the pages due to the relocation fixups. + t.publish(m.as_ptr() as u64) + .expect("failed to publish function table"); + if !m.is_empty() { unsafe { region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute) } .expect("unable to make memory readonly and executable"); } - - t.publish(m.as_ptr() as u64) - .expect("failed to publish function table"); } - self.published = self.mmaps.len(); + self.published = self.entries.len(); } /// Allocate `size` bytes of memory which can be made executable later by @@ -103,7 +135,7 @@ impl CodeMemory { /// /// TODO: Add an alignment flag. fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> { - if self.current.0.len() - self.position < size { + if self.current.mmap.len() - self.position < size { self.push_current(cmp::max(0x10000, size))?; } @@ -111,8 +143,8 @@ impl CodeMemory { self.position += size; Ok(( - &mut self.current.0.as_mut_slice()[old_position..self.position], - &mut self.current.1, + &mut self.current.mmap.as_mut_slice()[old_position..self.position], + &mut self.current.table, )) } @@ -153,12 +185,19 @@ impl CodeMemory { // Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary) let padding = ((func.body.len() + 3) & !3) - func.body.len(); let (unwind, remainder) = remainder.split_at_mut(padding + func.unwind_info.len()); - unwind[padding..].copy_from_slice(&func.unwind_info); + let mut relocs = Vec::new(); + func.unwind_info + .serialize(&mut unwind[padding..], &mut relocs); let unwind_start = func_end + (padding as u32); let unwind_end = unwind_start + (func.unwind_info.len() as u32); - table.add_function(func_start, func_end, unwind_start); + relocs.iter_mut().for_each(move |r| { + r.offset += unwind_start; + r.addend += func_start; + }); + + table.add_function(func_start, func_end, unwind_start, &relocs); (unwind_end, remainder, table, vmfunc) } @@ -174,20 +213,17 @@ impl CodeMemory { fn push_current(&mut self, new_size: usize) -> Result<(), String> { let previous = mem::replace( &mut self.current, - ( - if new_size == 0 { - Mmap::new() - } else { - Mmap::with_at_least(cmp::max(0x10000, new_size))? - }, - FunctionTable::new(), - ), + if new_size == 0 { + CodeMemoryEntry::new() + } else { + CodeMemoryEntry::with_capacity(cmp::max(0x10000, new_size))? + }, ); - if !previous.0.is_empty() { - self.mmaps.push(previous); + if !previous.mmap.is_empty() { + self.entries.push(previous); } else { - assert_eq!(previous.1.len(), 0); + assert_eq!(previous.table.len(), 0); } self.position = 0; diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index cc4f5b343b..4197a28cb6 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -16,12 +16,12 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa}; use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex}; use wasmtime_environ::{ - Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module, - ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets, + Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C, + FunctionBodyData, Module, ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets, }; use wasmtime_runtime::{ - get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard, - VMFunctionBody, + get_mut_trap_registry, jit_function_registry, InstantiationError, SignatureRegistry, + TrapRegistrationGuard, VMFunctionBody, }; /// Select which kind of compilation to use. @@ -51,6 +51,7 @@ pub struct Compiler { code_memory: CodeMemory, trap_registration_guards: Vec, + jit_function_ranges: Vec<(usize, usize)>, trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>, signatures: SignatureRegistry, strategy: CompilationStrategy, @@ -66,6 +67,7 @@ impl Compiler { isa, code_memory: CodeMemory::new(), trap_registration_guards: Vec::new(), + jit_function_ranges: Vec::new(), trampoline_park: HashMap::new(), signatures: SignatureRegistry::new(), fn_builder_ctx: FunctionBuilderContext::new(), @@ -85,6 +87,10 @@ impl Drop for Compiler { // Having a custom drop implementation we are independent from the field order // in the struct what reduces potential human error. self.trap_registration_guards.clear(); + + for (start, end) in self.jit_function_ranges.iter() { + jit_function_registry::unregister(*start, *end); + } } } @@ -155,6 +161,18 @@ impl Compiler { &mut self.trap_registration_guards, ); + for (i, allocated) in allocated_functions.iter() { + let ptr = (*allocated) as *const VMFunctionBody; + let body_len = compilation.get(i).body.len(); + self.jit_function_ranges + .push((ptr as usize, ptr as usize + body_len)); + let tag = jit_function_registry::JITFunctionTag { + module_id: module.name.clone(), + func_index: i.index(), + }; + jit_function_registry::register(ptr as usize, ptr as usize + body_len, tag); + } + let dbg = if let Some(debug_data) = debug_data { let target_config = self.isa.frontend_config(); let ofs = VMOffsets::new(target_config.pointer_bytes(), &module); @@ -215,6 +233,7 @@ impl Compiler { signature, value_size, )?; + entry.insert(body); body } @@ -266,6 +285,7 @@ fn make_trampoline( let mut context = Context::new(); context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); + context.func.collect_frame_layout_info(); { let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); @@ -326,7 +346,6 @@ fn make_trampoline( } let mut code_buf = Vec::new(); - let mut unwind_info = Vec::new(); let mut reloc_sink = RelocSink {}; let mut trap_sink = binemit::NullTrapSink {}; let mut stackmap_sink = binemit::NullStackmapSink {}; @@ -346,7 +365,7 @@ fn make_trampoline( ))) })?; - context.emit_unwind_info(isa, &mut unwind_info); + let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context); Ok(code_memory .allocate_for_function(&CompiledFunction { diff --git a/crates/jit/src/function_table.rs b/crates/jit/src/function_table.rs index 3e004e0e28..ffa37ce953 100644 --- a/crates/jit/src/function_table.rs +++ b/crates/jit/src/function_table.rs @@ -2,42 +2,7 @@ //! //! This module is primarily used to track JIT functions on Windows for stack walking and unwind. -/// Represents a runtime function table. -/// -/// The runtime function table is not implemented for non-Windows target platforms. -#[cfg(not(target_os = "windows"))] -pub(crate) struct FunctionTable; - -#[cfg(not(target_os = "windows"))] -impl FunctionTable { - /// Creates a new function table. - pub fn new() -> Self { - Self - } - - /// Returns the number of functions in the table, also referred to as its 'length'. - /// - /// For non-Windows platforms, the table will always be empty. - pub fn len(&self) -> usize { - 0 - } - - /// Adds a function to the table based off of the start offset, end offset, and unwind offset. - /// - /// The offsets are from the "module base", which is provided when the table is published. - /// - /// For non-Windows platforms, this is a no-op. - pub fn add_function(&mut self, _start: u32, _end: u32, _unwind: u32) {} - - /// Publishes the function table using the given base address. - /// - /// A published function table will automatically be deleted when it is dropped. - /// - /// For non-Windows platforms, this is a no-op. - pub fn publish(&mut self, _base_address: u64) -> Result<(), String> { - Ok(()) - } -} +type FunctionTableReloc = wasmtime_environ::CompiledFunctionUnwindInfoReloc; /// Represents a runtime function table. /// @@ -66,7 +31,14 @@ impl FunctionTable { /// Adds a function to the table based off of the start offset, end offset, and unwind offset. /// /// The offsets are from the "module base", which is provided when the table is published. - pub fn add_function(&mut self, start: u32, end: u32, unwind: u32) { + pub fn add_function( + &mut self, + start: u32, + end: u32, + unwind: u32, + _relocs: &[FunctionTableReloc], + ) { + assert_eq!(_relocs.len(), 0); use winapi::um::winnt; assert!(!self.published, "table has already been published"); @@ -133,3 +105,106 @@ impl Drop for FunctionTable { } } } + +/// Represents a runtime function table. +/// +/// This is used to register JIT code with the operating system to enable stack walking and unwinding. +#[cfg(any(target_os = "macos", target_os = "linux"))] +pub(crate) struct FunctionTable { + functions: Vec, + relocs: Vec, + published: Option>, +} + +#[cfg(any(target_os = "macos", target_os = "linux"))] +impl FunctionTable { + /// Creates a new function table. + pub fn new() -> Self { + Self { + functions: Vec::new(), + relocs: Vec::new(), + published: None, + } + } + + /// Returns the number of functions in the table, also referred to as its 'length'. + pub fn len(&self) -> usize { + self.functions.len() + } + + /// Adds a function to the table based off of the start offset, end offset, and unwind offset. + /// + /// The offsets are from the "module base", which is provided when the table is published. + pub fn add_function( + &mut self, + _start: u32, + _end: u32, + unwind: u32, + relocs: &[FunctionTableReloc], + ) { + assert!(self.published.is_none(), "table has already been published"); + self.functions.push(unwind); + self.relocs.extend_from_slice(relocs); + } + + /// Publishes the function table using the given base address. + /// + /// A published function table will automatically be deleted when it is dropped. + pub fn publish(&mut self, base_address: u64) -> Result<(), String> { + if self.published.is_some() { + return Err("function table was already published".into()); + } + + if self.functions.is_empty() { + assert_eq!(self.relocs.len(), 0); + self.published = Some(vec![]); + return Ok(()); + } + + extern "C" { + // libunwind import + fn __register_frame(fde: *const u8); + } + + for reloc in self.relocs.iter() { + let addr = base_address + (reloc.offset as u64); + let target = base_address + (reloc.addend as u64); + unsafe { + std::ptr::write(addr as *mut u64, target); + } + } + + let mut fdes = Vec::with_capacity(self.functions.len()); + for unwind_offset in self.functions.iter() { + let addr = base_address + (*unwind_offset as u64); + let off = unsafe { std::ptr::read::(addr as *const u32) } as usize + 4; + + let fde = (addr + off as u64) as usize; + unsafe { + __register_frame(fde as *const _); + } + fdes.push(fde); + } + + self.published = Some(fdes); + Ok(()) + } +} + +#[cfg(any(target_os = "macos", target_os = "linux"))] +impl Drop for FunctionTable { + fn drop(&mut self) { + extern "C" { + // libunwind import + fn __deregister_frame(fde: *const u8); + } + + if self.published.is_some() { + unsafe { + for fde in self.published.as_ref().unwrap() { + __deregister_frame(*fde as *const _); + } + } + } + } +} diff --git a/crates/lightbeam/Cargo.toml b/crates/lightbeam/Cargo.toml index 225a73c311..f3e140f070 100644 --- a/crates/lightbeam/Cargo.toml +++ b/crates/lightbeam/Cargo.toml @@ -19,7 +19,7 @@ memoffset = "0.5.3" itertools = "0.8.2" capstone = "0.6.0" thiserror = "1.0.9" -cranelift-codegen = "0.54" +cranelift-codegen = "0.55" multi_mut = "0.1" either = "1.5" typemap = "0.3" diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 21c62fcf42..edee855abd 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -20,6 +20,7 @@ indexmap = "1.0.2" thiserror = "1.0.4" more-asserts = "0.2.1" cfg-if = "0.1.9" +backtrace = "0.3.40" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] } diff --git a/crates/runtime/src/backtrace.rs b/crates/runtime/src/backtrace.rs new file mode 100644 index 0000000000..c8cba1eb89 --- /dev/null +++ b/crates/runtime/src/backtrace.rs @@ -0,0 +1,148 @@ +//! Backtrace object and utilities. + +use crate::jit_function_registry; +use std::sync::Arc; + +/// Information about backtrace frame. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BacktraceFrame { + pc: usize, +} + +impl Default for BacktraceFrame { + fn default() -> Self { + Self { pc: 0 } + } +} + +impl BacktraceFrame { + /// Current PC or IP pointer for the frame. + pub fn pc(&self) -> usize { + self.pc + } + /// Additinal frame information. + pub fn tag(&self) -> Option> { + jit_function_registry::find(self.pc) + } +} + +const BACKTRACE_LIMIT: usize = 32; + +/// Backtrace during WebAssembly trap. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Backtrace { + len: usize, + frames: [BacktraceFrame; BACKTRACE_LIMIT], +} + +impl Backtrace { + fn new() -> Self { + Self { + len: 0, + frames: [Default::default(); BACKTRACE_LIMIT], + } + } + fn full(&self) -> bool { + self.len >= BACKTRACE_LIMIT + } + fn push(&mut self, frame: BacktraceFrame) { + assert!(self.len < BACKTRACE_LIMIT); + self.frames[self.len] = frame; + self.len += 1; + } + /// Amount of the backtrace frames. + pub fn len(&self) -> usize { + self.len + } +} + +impl std::ops::Index for Backtrace { + type Output = BacktraceFrame; + fn index(&self, index: usize) -> &Self::Output { + assert!(index < self.len); + &self.frames[index] + } +} + +impl std::fmt::Debug for Backtrace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Backtrace![")?; + for i in 0..self.len() { + let frame = &self.frames[i]; + writeln!(f, " {:x}: {:?}", frame.pc(), frame.tag())?; + } + write!(f, "]")?; + Ok(()) + } +} + +#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))] +fn capture_stack(mut f: F) +where + F: FnMut(usize) -> bool, +{ + use backtrace::trace; + trace(|frame| { + let pc = frame.ip() as usize; + f(pc) + }); +} + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +fn capture_stack(mut f: F) +where + F: FnMut(usize) -> bool, +{ + use std::mem::MaybeUninit; + use std::ptr; + use winapi::um::winnt::{ + RtlCaptureContext, RtlLookupFunctionEntry, RtlVirtualUnwind, CONTEXT, UNW_FLAG_NHANDLER, + }; + + #[repr(C, align(16))] + struct WrappedContext(CONTEXT); + + unsafe { + let mut ctx = WrappedContext(MaybeUninit::uninit().assume_init()); + RtlCaptureContext(&mut ctx.0); + let mut unwind_history_table = MaybeUninit::zeroed().assume_init(); + while ctx.0.Rip != 0 { + let cont = f(ctx.0.Rip as usize); + if !cont { + break; + } + + let mut image_base: u64 = 0; + let mut handler_data: *mut core::ffi::c_void = ptr::null_mut(); + let mut establisher_frame: u64 = 0; + let rf = RtlLookupFunctionEntry(ctx.0.Rip, &mut image_base, &mut unwind_history_table); + if rf.is_null() { + ctx.0.Rip = ptr::read(ctx.0.Rsp as *const u64); + ctx.0.Rsp += 8; + } else { + RtlVirtualUnwind( + UNW_FLAG_NHANDLER, + image_base, + ctx.0.Rip, + rf, + &mut ctx.0, + &mut handler_data, + &mut establisher_frame, + ptr::null_mut(), + ); + } + } + } +} + +/// Returns current backtrace. Only registered wasmtime functions will be listed. +pub fn get_backtrace() -> Backtrace { + let mut frames = Backtrace::new(); + capture_stack(|pc| { + if let Some(_) = jit_function_registry::find(pc) { + frames.push(BacktraceFrame { pc }); + } + !frames.full() + }); + frames +} diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 4fe112281d..2c7710e433 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -9,7 +9,7 @@ use crate::memory::LinearMemory; use crate::mmap::Mmap; use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; use crate::table::Table; -use crate::traphandlers::wasmtime_call; +use crate::traphandlers::{wasmtime_call, TrapMessageAndStack}; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, @@ -568,7 +568,7 @@ impl Instance { // Make the call. unsafe { wasmtime_call(callee_vmctx, callee_address) } - .map_err(InstantiationError::StartTrap) + .map_err(|TrapMessageAndStack(msg, _)| InstantiationError::StartTrap(msg)) } /// Invoke the WebAssembly start function of the instance, if one is present. diff --git a/crates/runtime/src/jit_function_registry.rs b/crates/runtime/src/jit_function_registry.rs new file mode 100644 index 0000000000..60d6d73db0 --- /dev/null +++ b/crates/runtime/src/jit_function_registry.rs @@ -0,0 +1,83 @@ +#![allow(missing_docs)] + +use lazy_static::lazy_static; +use std::collections::BTreeMap; +use std::sync::{Arc, RwLock}; + +lazy_static! { + static ref REGISTRY: RwLock = RwLock::new(JITFunctionRegistry::default()); +} + +#[derive(Clone)] +pub struct JITFunctionTag { + pub module_id: Option, + pub func_index: usize, +} + +impl std::fmt::Debug for JITFunctionTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(ref module_id) = self.module_id { + write!(f, "{}", module_id)?; + } else { + write!(f, "(module)")?; + } + write!(f, ":{}", self.func_index) + } +} + +struct JITFunctionRegistry { + ranges: BTreeMap)>, +} + +impl Default for JITFunctionRegistry { + fn default() -> Self { + Self { + ranges: Default::default(), + } + } +} + +impl JITFunctionRegistry { + fn register(&mut self, fn_start: usize, fn_end: usize, tag: JITFunctionTag) { + self.ranges.insert(fn_end, (fn_start, Arc::new(tag))); + } + + fn unregister(&mut self, fn_end: usize) { + self.ranges.remove(&fn_end); + } + + fn find(&self, pc: usize) -> Option<&Arc> { + self.ranges + .range(pc..) + .next() + .and_then(|(end, (start, s))| { + if *start <= pc && pc < *end { + Some(s) + } else { + None + } + }) + } +} + +pub fn register(fn_start: usize, fn_end: usize, tag: JITFunctionTag) { + REGISTRY + .write() + .expect("jit function registry lock got poisoned") + .register(fn_start, fn_end, tag); +} + +pub fn unregister(_fn_start: usize, fn_end: usize) { + REGISTRY + .write() + .expect("jit function registry lock got poisoned") + .unregister(fn_end); +} + +pub fn find(pc: usize) -> Option> { + REGISTRY + .read() + .expect("jit function registry lock got poisoned") + .find(pc) + .cloned() +} diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 5a48273e12..29996f8550 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -21,6 +21,7 @@ ) )] +mod backtrace; mod export; mod imports; mod instance; @@ -34,8 +35,10 @@ mod trap_registry; mod traphandlers; mod vmcontext; +pub mod jit_function_registry; pub mod libcalls; +pub use crate::backtrace::{get_backtrace, Backtrace, BacktraceFrame}; pub use crate::export::Export; pub use crate::imports::Imports; pub use crate::instance::{InstanceHandle, InstantiationError, LinkError}; @@ -44,7 +47,7 @@ pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; pub use crate::trap_registry::{get_mut_trap_registry, get_trap_registry, TrapRegistrationGuard}; -pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline}; +pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline, TrapMessageAndStack}; pub use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index a2166f20b7..ae89138172 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -1,6 +1,7 @@ //! WebAssembly trap handling, which is built on top of the lower-level //! signalhandling mechanisms. +use crate::backtrace::{get_backtrace, Backtrace}; use crate::trap_registry::get_trap_registry; use crate::trap_registry::TrapDescription; use crate::vmcontext::{VMContext, VMFunctionBody}; @@ -18,7 +19,7 @@ extern "C" { } thread_local! { - static RECORDED_TRAP: Cell> = Cell::new(None); + static RECORDED_TRAP: Cell> = Cell::new(None); static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null()); static RESET_GUARD_PAGE: Cell = Cell::new(false); } @@ -50,6 +51,8 @@ pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) { trap_code: ir::TrapCode::StackOverflow, }); + let trap_backtrace = get_backtrace(); + if reset_guard_page { RESET_GUARD_PAGE.with(|v| v.set(true)); } @@ -60,7 +63,7 @@ pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) { None, "Only one trap per thread can be recorded at a moment!" ); - data.set(Some(trap_desc)) + data.set(Some((trap_desc, trap_backtrace))) }); } @@ -108,15 +111,22 @@ fn reset_guard_page() { #[cfg(not(target_os = "windows"))] fn reset_guard_page() {} -fn trap_message() -> String { +/// Stores trace message with backtrace. +#[derive(Debug)] +pub struct TrapMessageAndStack(pub String, pub Backtrace); + +fn trap_message_and_stack() -> TrapMessageAndStack { let trap_desc = RECORDED_TRAP .with(|data| data.replace(None)) .expect("trap_message must be called after trap occurred"); - format!( - "wasm trap: {}, source location: {}", - trap_code_to_expected_string(trap_desc.trap_code), - trap_desc.source_loc, + TrapMessageAndStack( + format!( + "wasm trap: {}, source location: {}", + trap_code_to_expected_string(trap_desc.0.trap_code), + trap_desc.0.source_loc, + ), + trap_desc.1, ) } @@ -146,9 +156,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( vmctx: *mut VMContext, callee: *const VMFunctionBody, values_vec: *mut u8, -) -> Result<(), String> { +) -> Result<(), TrapMessageAndStack> { if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 { - Err(trap_message()) + Err(trap_message_and_stack()) } else { Ok(()) } @@ -160,9 +170,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( pub unsafe extern "C" fn wasmtime_call( vmctx: *mut VMContext, callee: *const VMFunctionBody, -) -> Result<(), String> { +) -> Result<(), TrapMessageAndStack> { if WasmtimeCall(vmctx as *mut u8, callee) == 0 { - Err(trap_message()) + Err(trap_message_and_stack()) } else { Ok(()) } diff --git a/crates/wasi-c/Cargo.toml b/crates/wasi-c/Cargo.toml index d169491a89..c52e5244db 100644 --- a/crates/wasi-c/Cargo.toml +++ b/crates/wasi-c/Cargo.toml @@ -14,9 +14,9 @@ edition = "2018" wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-jit = { path = "../jit", version = "0.9.0" } -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } target-lexicon = "0.10.0" log = { version = "0.4.8", default-features = false } libc = "0.2.60" diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 4aaca18c0b..1ea8761523 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -16,9 +16,9 @@ wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-jit = { path = "../jit", version = "0.9.0" } wasi-common = { path = "../wasi-common", version = "0.9.0" } -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } target-lexicon = "0.10.0" log = { version = "0.4.8", default-features = false } wig = { path = "../wasi-common/wig", version = "0.9.2" } diff --git a/scripts/cranelift-version.sh b/scripts/cranelift-version.sh index 7af91d26cd..8bcba364fa 100755 --- a/scripts/cranelift-version.sh +++ b/scripts/cranelift-version.sh @@ -9,7 +9,7 @@ topdir=$(dirname "$0")/.. cd "$topdir" # All the cranelift-* crates have the same version number -version="0.53" +version="0.55" # Update all of the Cargo.toml files. echo "Updating crate versions to $version"