diff --git a/crates/c-api/src/trap.rs b/crates/c-api/src/trap.rs index 941ec465aa..6a1da6b3f7 100644 --- a/crates/c-api/src/trap.rs +++ b/crates/c-api/src/trap.rs @@ -157,7 +157,9 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa #[no_mangle] pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize { - frame.trap.trace()[frame.idx].func_offset() + frame.trap.trace()[frame.idx] + .func_offset() + .unwrap_or(usize::MAX) } #[no_mangle] @@ -167,7 +169,9 @@ pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t) -> *mut wasm_i #[no_mangle] pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize { - frame.trap.trace()[frame.idx].module_offset() + frame.trap.trace()[frame.idx] + .module_offset() + .unwrap_or(usize::MAX) } #[no_mangle] diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 18402cc647..f02fee01d2 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -63,6 +63,7 @@ impl Compiler { context: &Context, data: &FunctionBodyData<'_>, body_len: u32, + tunables: &Tunables, ) -> FunctionAddressMap { // Generate artificial srcloc for function start/end to identify boundary // within module. @@ -75,17 +76,21 @@ impl Compiler { // New-style backend: we have a `MachCompileResult` that will give us `MachSrcLoc` mapping // tuples. - let instructions = collect_address_maps( - body_len, - context - .mach_compile_result - .as_ref() - .unwrap() - .buffer - .get_srclocs_sorted() - .into_iter() - .map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))), - ); + let instructions = if tunables.generate_address_map { + collect_address_maps( + body_len, + context + .mach_compile_result + .as_ref() + .unwrap() + .buffer + .get_srclocs_sorted() + .into_iter() + .map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))), + ) + } else { + Vec::new() + }; FunctionAddressMap { instructions: instructions.into(), @@ -179,7 +184,7 @@ impl wasmtime_environ::Compiler for Compiler { .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; let address_transform = - self.get_function_address_map(&context, &input, code_buf.len() as u32); + self.get_function_address_map(&context, &input, code_buf.len() as u32, tunables); let ranges = if tunables.generate_native_debuginfo { Some( @@ -221,7 +226,7 @@ impl wasmtime_environ::Compiler for Compiler { translation: &ModuleTranslation, types: &TypeTables, funcs: PrimaryMap>, - emit_dwarf: bool, + tunables: &Tunables, obj: &mut Object<'static>, ) -> Result<(PrimaryMap, Vec)> { let funcs: crate::CompiledFunctions = funcs @@ -244,7 +249,9 @@ impl wasmtime_environ::Compiler for Compiler { let mut func_starts = Vec::with_capacity(funcs.len()); for (i, func) in funcs.iter() { let range = builder.func(i, func); - addrs.push(range.clone(), &func.address_map.instructions); + if tunables.generate_address_map { + addrs.push(range.clone(), &func.address_map.instructions); + } traps.push(range.clone(), &func.traps); func_starts.push(range.start); if self.linkopts.padding_between_functions > 0 { @@ -266,7 +273,7 @@ impl wasmtime_environ::Compiler for Compiler { builder.unwind_info(); - if emit_dwarf && funcs.len() > 0 { + if tunables.generate_native_debuginfo && funcs.len() > 0 { let ofs = VMOffsets::new( self.isa .triple() @@ -297,7 +304,10 @@ impl wasmtime_environ::Compiler for Compiler { } builder.finish()?; - addrs.append_to(obj); + + if tunables.generate_address_map { + addrs.append_to(obj); + } traps.append_to(obj); Ok(( diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 2cb7e94af1..6a38dc23cb 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -171,7 +171,7 @@ pub trait Compiler: Send + Sync { module: &ModuleTranslation, types: &TypeTables, funcs: PrimaryMap>, - emit_dwarf: bool, + tunables: &Tunables, obj: &mut Object<'static>, ) -> Result<(PrimaryMap, Vec)>; diff --git a/crates/environ/src/tunables.rs b/crates/environ/src/tunables.rs index 06bf968813..f3d75d0d3f 100644 --- a/crates/environ/src/tunables.rs +++ b/crates/environ/src/tunables.rs @@ -42,6 +42,10 @@ pub struct Tunables { /// Whether or not linear memory allocations will have a guard region at the /// beginning of the allocation in addition to the end. pub guard_before_linear_memory: bool, + + /// Indicates whether an address map from compiled native code back to wasm + /// offsets in the original file is generated. + pub generate_address_map: bool, } impl Default for Tunables { @@ -86,6 +90,7 @@ impl Default for Tunables { consume_fuel: false, static_memory_bound_is_maximum: false, guard_before_linear_memory: true, + generate_address_map: true, } } } diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index 47969420fe..cc6a3844d1 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -297,16 +297,21 @@ impl CompiledModule { }; let mut ret = Self { - meta: info.meta, module: Arc::new(info.module), funcs: info.funcs, trampolines: info.trampolines, wasm_data: subslice_range(section(ELF_WASM_DATA)?, code.mmap), - address_map_data: subslice_range(section(ELF_WASMTIME_ADDRMAP)?, code.mmap), + address_map_data: code + .obj + .section_by_name(ELF_WASMTIME_ADDRMAP) + .and_then(|s| s.data().ok()) + .map(|slice| subslice_range(slice, code.mmap)) + .unwrap_or(0..0), trap_data: subslice_range(section(ELF_WASMTIME_TRAPS)?, code.mmap), code: subslice_range(code.text, code.mmap), dbg_jit_registration: None, code_memory, + meta: info.meta, }; ret.register_debug_and_profiling(profiler)?; @@ -500,6 +505,15 @@ impl CompiledModule { pub fn has_unparsed_debuginfo(&self) -> bool { self.meta.has_unparsed_debuginfo } + + /// Indicates whether this module came with n address map such that lookups + /// via `wasmtime_environ::lookup_file_pos` will succeed. + /// + /// If this function returns `false` then `lookup_file_pos` will always + /// return `None`. + pub fn has_address_map(&self) -> bool { + !self.address_map_data().is_empty() + } } type Addr2LineContext<'a> = addr2line::Context>; diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 00a04dd19d..a2ee71dbf6 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1052,6 +1052,20 @@ impl Config { self } + /// Configures whether compiled artifacts will contain information to map + /// native program addresses back to the original wasm module. + /// + /// This configuration option is `true` by default and, if enables, + /// generates the appropriate tables in compiled modules to map from native + /// address back to wasm source addresses. This is used for displaying wasm + /// program counters in backtraces as well as generating filenames/line + /// numbers if so configured as well (and the original wasm module has DWARF + /// debugging information present). + pub fn generate_address_map(&mut self, generate: bool) -> &mut Self { + self.tunables.generate_address_map = generate; + self + } + pub(crate) fn build_allocator(&self) -> Result> { #[cfg(feature = "async")] let stack_size = self.async_stack_size; diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index 8761c94923..f7f5cbe099 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -50,7 +50,7 @@ impl Engine { // Ensure that wasmtime_runtime's signal handlers are configured. This // is the per-program initialization required for handling traps, such // as configuring signals, vectored exception handlers, etc. - wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_pc); + wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_trap_pc); debug_builtins::ensure_exported(); let registry = SignatureRegistry::new(); diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 4d45669de8..9fc2a471d9 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -398,13 +398,10 @@ impl Module { .collect(); let mut obj = engine.compiler().object()?; - let (funcs, trampolines) = engine.compiler().emit_obj( - &translation, - &types, - funcs, - tunables.generate_native_debuginfo, - &mut obj, - )?; + let (funcs, trampolines) = + engine + .compiler() + .emit_obj(&translation, &types, funcs, tunables, &mut obj)?; // If configured, attempt to use paged memory initialization // instead of the default mode of memory initialization diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs index 2c5e05df89..60bbbfffd7 100644 --- a/crates/wasmtime/src/module/registry.rs +++ b/crates/wasmtime/src/module/registry.rs @@ -172,13 +172,12 @@ pub struct GlobalModuleRegistry(BTreeMap); impl GlobalModuleRegistry { /// Returns whether the `pc`, according to globally registered information, /// is a wasm trap or not. - pub(crate) fn is_wasm_pc(pc: usize) -> bool { + pub(crate) fn is_wasm_trap_pc(pc: usize) -> bool { let modules = GLOBAL_MODULES.read().unwrap(); match modules.module(pc) { Some((entry, text_offset)) => { - wasmtime_environ::lookup_file_pos(entry.module.address_map_data(), text_offset) - .is_some() + wasmtime_environ::lookup_trap_code(entry.module.trap_data(), text_offset).is_some() } None => false, } @@ -275,14 +274,15 @@ impl GlobalRegisteredModule { // the function, because otherwise something is buggy along the way and // not accounting for all the instructions. This isn't super critical // though so we can omit this check in release mode. + // + // Note that if the module doesn't even have an address map due to + // compilation settings then it's expected that `instr` is `None`. debug_assert!( - instr.is_some(), + instr.is_some() || !self.module.has_address_map(), "failed to find instruction for {:#x}", text_offset ); - let instr = instr.unwrap_or(info.start_srcloc); - // Use our wasm-relative pc to symbolize this frame. If there's a // symbolication context (dwarf debug info) available then we can try to // look this up there. @@ -294,7 +294,7 @@ impl GlobalRegisteredModule { let mut symbols = Vec::new(); if let Some(s) = &self.module.symbolize_context().ok().and_then(|c| c) { - if let Some(offset) = instr.file_offset() { + if let Some(offset) = instr.and_then(|i| i.file_offset()) { let to_lookup = u64::from(offset) - s.code_section_offset(); if let Ok(mut frames) = s.addr2line().find_frames(to_lookup) { while let Ok(Some(frame)) = frames.next() { @@ -344,7 +344,7 @@ pub struct FrameInfo { func_index: u32, func_name: Option, func_start: FilePos, - instr: FilePos, + instr: Option, symbols: Vec, } @@ -393,8 +393,14 @@ impl FrameInfo { /// /// The offset here is the offset from the beginning of the original wasm /// module to the instruction that this frame points to. - pub fn module_offset(&self) -> usize { - self.instr.file_offset().unwrap_or(u32::MAX) as usize + /// + /// Note that `None` may be returned if the original module was not + /// compiled with mapping information to yield this information. This is + /// controlled by the + /// [`Config::generate_address_map`](crate::Config::generate_address_map) + /// configuration option. + pub fn module_offset(&self) -> Option { + Some(self.instr?.file_offset()? as usize) } /// Returns the offset from the original wasm module's function to this @@ -403,11 +409,15 @@ impl FrameInfo { /// The offset here is the offset from the beginning of the defining /// function of this frame (within the wasm module) to the instruction this /// frame points to. - pub fn func_offset(&self) -> usize { - match self.instr.file_offset() { - Some(i) => (i - self.func_start.file_offset().unwrap()) as usize, - None => u32::MAX as usize, - } + /// + /// Note that `None` may be returned if the original module was not + /// compiled with mapping information to yield this information. This is + /// controlled by the + /// [`Config::generate_address_map`](crate::Config::generate_address_map) + /// configuration option. + pub fn func_offset(&self) -> Option { + let instr_offset = self.instr?.file_offset()?; + Some((instr_offset - self.func_start.file_offset()?) as usize) } /// Returns the debug symbols found, if any, for this function frame. diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 8693ce1e96..d3377b442f 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -601,6 +601,12 @@ impl<'a> SerializedModule<'a> { // This doesn't affect compilation, it's just a runtime setting. dynamic_memory_growth_reserve: _, + + // This does technically affect compilation but modules with/without + // trap information can be loaded into engines with the opposite + // setting just fine (it's just a section in the compiled file and + // whether it's present or not) + generate_address_map: _, } = self.metadata.tunables; Self::check_int( diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 93dfe12a16..87d1832009 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -321,7 +321,11 @@ impl fmt::Display for Trap { writeln!(f, "\nwasm backtrace:")?; for (i, frame) in self.trace().iter().enumerate() { let name = frame.module_name().unwrap_or(""); - write!(f, " {:>3}: {:#6x} - ", i, frame.module_offset())?; + write!(f, " {:>3}: ", i)?; + + if let Some(offset) = frame.module_offset() { + write!(f, "{:#6x} - ", offset)?; + } let demangle = |f: &mut fmt::Formatter<'_>, name: &str| match rustc_demangle::try_demangle(name) { diff --git a/tests/all/traps.rs b/tests/all/traps.rs index 998d11f908..efe192ede7 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -54,13 +54,13 @@ fn test_trap_trace() -> Result<()> { assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); assert_eq!(trace[0].func_index(), 1); assert_eq!(trace[0].func_name(), Some("hello")); - assert_eq!(trace[0].func_offset(), 1); - assert_eq!(trace[0].module_offset(), 0x26); + assert_eq!(trace[0].func_offset(), Some(1)); + assert_eq!(trace[0].module_offset(), Some(0x26)); assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); assert_eq!(trace[1].func_index(), 0); assert_eq!(trace[1].func_name(), None); - assert_eq!(trace[1].func_offset(), 1); - assert_eq!(trace[1].module_offset(), 0x21); + assert_eq!(trace[1].func_offset(), Some(1)); + assert_eq!(trace[1].module_offset(), Some(0x21)); assert!( e.to_string().contains("unreachable"), "wrong message: {}", @@ -637,3 +637,37 @@ fn multithreaded_traps() -> Result<()> { Ok(()) } + +#[test] +#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64 +fn traps_without_address_map() -> Result<()> { + let mut config = Config::new(); + config.generate_address_map(false); + let engine = Engine::new(&config)?; + let mut store = Store::new(&engine, ()); + let wat = r#" + (module $hello_mod + (func (export "run") (call $hello)) + (func $hello (unreachable)) + ) + "#; + + let module = Module::new(store.engine(), wat)?; + let instance = Instance::new(&mut store, &module, &[])?; + let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?; + + let e = run_func + .call(&mut store, ()) + .err() + .expect("error calling function"); + + let trace = e.trace(); + assert_eq!(trace.len(), 2); + assert_eq!(trace[0].func_name(), Some("hello")); + assert_eq!(trace[0].func_index(), 1); + assert_eq!(trace[0].module_offset(), None); + assert_eq!(trace[1].func_name(), None); + assert_eq!(trace[1].func_index(), 0); + assert_eq!(trace[1].module_offset(), None); + Ok(()) +}