Add a compilation section to disable address maps (#3598)

* Add a compilation section to disable address maps

This commit adds a new `Config::generate_address_map` compilation
setting which is used to disable emission of the `.wasmtime.addrmap`
section of compiled artifacts. This section is currently around the size
of the entire `.text` section itself unfortunately and for size reasons
may wish to be omitted. Functionality-wise all that is lost is knowing
the precise wasm module offset address of a faulting instruction or in a
backtrace of instructions. This also means that if the module has DWARF
debugging information available with it Wasmtime isn't able to produce a
filename and line number in the backtrace.

This option remains enabled by default. This option may not be needed in
the future with #3547 perhaps, but in the meantime it seems reasonable
enough to support a configuration mode where the section is entirely
omitted if the smallest module possible is desired.

* Fix some CI issues

* Update tests/all/traps.rs

Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>

* Do less work in compilation for address maps

But only when disabled

Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
This commit is contained in:
Alex Crichton
2021-12-13 13:48:05 -06:00
committed by GitHub
parent c1c4c59670
commit f1225dfd93
12 changed files with 147 additions and 49 deletions

View File

@@ -157,7 +157,9 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa
#[no_mangle] #[no_mangle]
pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize { 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] #[no_mangle]
@@ -167,7 +169,9 @@ pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t) -> *mut wasm_i
#[no_mangle] #[no_mangle]
pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize { 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] #[no_mangle]

View File

@@ -63,6 +63,7 @@ impl Compiler {
context: &Context, context: &Context,
data: &FunctionBodyData<'_>, data: &FunctionBodyData<'_>,
body_len: u32, body_len: u32,
tunables: &Tunables,
) -> FunctionAddressMap { ) -> FunctionAddressMap {
// Generate artificial srcloc for function start/end to identify boundary // Generate artificial srcloc for function start/end to identify boundary
// within module. // within module.
@@ -75,7 +76,8 @@ impl Compiler {
// New-style backend: we have a `MachCompileResult` that will give us `MachSrcLoc` mapping // New-style backend: we have a `MachCompileResult` that will give us `MachSrcLoc` mapping
// tuples. // tuples.
let instructions = collect_address_maps( let instructions = if tunables.generate_address_map {
collect_address_maps(
body_len, body_len,
context context
.mach_compile_result .mach_compile_result
@@ -85,7 +87,10 @@ impl Compiler {
.get_srclocs_sorted() .get_srclocs_sorted()
.into_iter() .into_iter()
.map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))), .map(|&MachSrcLoc { start, end, loc }| (loc, start, (end - start))),
); )
} else {
Vec::new()
};
FunctionAddressMap { FunctionAddressMap {
instructions: instructions.into(), instructions: instructions.into(),
@@ -179,7 +184,7 @@ impl wasmtime_environ::Compiler for Compiler {
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
let address_transform = 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 { let ranges = if tunables.generate_native_debuginfo {
Some( Some(
@@ -221,7 +226,7 @@ impl wasmtime_environ::Compiler for Compiler {
translation: &ModuleTranslation, translation: &ModuleTranslation,
types: &TypeTables, types: &TypeTables,
funcs: PrimaryMap<DefinedFuncIndex, Box<dyn Any + Send>>, funcs: PrimaryMap<DefinedFuncIndex, Box<dyn Any + Send>>,
emit_dwarf: bool, tunables: &Tunables,
obj: &mut Object<'static>, obj: &mut Object<'static>,
) -> Result<(PrimaryMap<DefinedFuncIndex, FunctionInfo>, Vec<Trampoline>)> { ) -> Result<(PrimaryMap<DefinedFuncIndex, FunctionInfo>, Vec<Trampoline>)> {
let funcs: crate::CompiledFunctions = funcs let funcs: crate::CompiledFunctions = funcs
@@ -244,7 +249,9 @@ impl wasmtime_environ::Compiler for Compiler {
let mut func_starts = Vec::with_capacity(funcs.len()); let mut func_starts = Vec::with_capacity(funcs.len());
for (i, func) in funcs.iter() { for (i, func) in funcs.iter() {
let range = builder.func(i, func); let range = builder.func(i, func);
if tunables.generate_address_map {
addrs.push(range.clone(), &func.address_map.instructions); addrs.push(range.clone(), &func.address_map.instructions);
}
traps.push(range.clone(), &func.traps); traps.push(range.clone(), &func.traps);
func_starts.push(range.start); func_starts.push(range.start);
if self.linkopts.padding_between_functions > 0 { if self.linkopts.padding_between_functions > 0 {
@@ -266,7 +273,7 @@ impl wasmtime_environ::Compiler for Compiler {
builder.unwind_info(); builder.unwind_info();
if emit_dwarf && funcs.len() > 0 { if tunables.generate_native_debuginfo && funcs.len() > 0 {
let ofs = VMOffsets::new( let ofs = VMOffsets::new(
self.isa self.isa
.triple() .triple()
@@ -297,7 +304,10 @@ impl wasmtime_environ::Compiler for Compiler {
} }
builder.finish()?; builder.finish()?;
if tunables.generate_address_map {
addrs.append_to(obj); addrs.append_to(obj);
}
traps.append_to(obj); traps.append_to(obj);
Ok(( Ok((

View File

@@ -171,7 +171,7 @@ pub trait Compiler: Send + Sync {
module: &ModuleTranslation, module: &ModuleTranslation,
types: &TypeTables, types: &TypeTables,
funcs: PrimaryMap<DefinedFuncIndex, Box<dyn Any + Send>>, funcs: PrimaryMap<DefinedFuncIndex, Box<dyn Any + Send>>,
emit_dwarf: bool, tunables: &Tunables,
obj: &mut Object<'static>, obj: &mut Object<'static>,
) -> Result<(PrimaryMap<DefinedFuncIndex, FunctionInfo>, Vec<Trampoline>)>; ) -> Result<(PrimaryMap<DefinedFuncIndex, FunctionInfo>, Vec<Trampoline>)>;

View File

@@ -42,6 +42,10 @@ pub struct Tunables {
/// Whether or not linear memory allocations will have a guard region at the /// Whether or not linear memory allocations will have a guard region at the
/// beginning of the allocation in addition to the end. /// beginning of the allocation in addition to the end.
pub guard_before_linear_memory: bool, 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 { impl Default for Tunables {
@@ -86,6 +90,7 @@ impl Default for Tunables {
consume_fuel: false, consume_fuel: false,
static_memory_bound_is_maximum: false, static_memory_bound_is_maximum: false,
guard_before_linear_memory: true, guard_before_linear_memory: true,
generate_address_map: true,
} }
} }
} }

View File

@@ -297,16 +297,21 @@ impl CompiledModule {
}; };
let mut ret = Self { let mut ret = Self {
meta: info.meta,
module: Arc::new(info.module), module: Arc::new(info.module),
funcs: info.funcs, funcs: info.funcs,
trampolines: info.trampolines, trampolines: info.trampolines,
wasm_data: subslice_range(section(ELF_WASM_DATA)?, code.mmap), 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), trap_data: subslice_range(section(ELF_WASMTIME_TRAPS)?, code.mmap),
code: subslice_range(code.text, code.mmap), code: subslice_range(code.text, code.mmap),
dbg_jit_registration: None, dbg_jit_registration: None,
code_memory, code_memory,
meta: info.meta,
}; };
ret.register_debug_and_profiling(profiler)?; ret.register_debug_and_profiling(profiler)?;
@@ -500,6 +505,15 @@ impl CompiledModule {
pub fn has_unparsed_debuginfo(&self) -> bool { pub fn has_unparsed_debuginfo(&self) -> bool {
self.meta.has_unparsed_debuginfo 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<gimli::EndianSlice<'a, gimli::LittleEndian>>; type Addr2LineContext<'a> = addr2line::Context<gimli::EndianSlice<'a, gimli::LittleEndian>>;

View File

@@ -1052,6 +1052,20 @@ impl Config {
self 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<Box<dyn InstanceAllocator>> { pub(crate) fn build_allocator(&self) -> Result<Box<dyn InstanceAllocator>> {
#[cfg(feature = "async")] #[cfg(feature = "async")]
let stack_size = self.async_stack_size; let stack_size = self.async_stack_size;

View File

@@ -50,7 +50,7 @@ impl Engine {
// Ensure that wasmtime_runtime's signal handlers are configured. This // Ensure that wasmtime_runtime's signal handlers are configured. This
// is the per-program initialization required for handling traps, such // is the per-program initialization required for handling traps, such
// as configuring signals, vectored exception handlers, etc. // 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(); debug_builtins::ensure_exported();
let registry = SignatureRegistry::new(); let registry = SignatureRegistry::new();

View File

@@ -398,13 +398,10 @@ impl Module {
.collect(); .collect();
let mut obj = engine.compiler().object()?; let mut obj = engine.compiler().object()?;
let (funcs, trampolines) = engine.compiler().emit_obj( let (funcs, trampolines) =
&translation, engine
&types, .compiler()
funcs, .emit_obj(&translation, &types, funcs, tunables, &mut obj)?;
tunables.generate_native_debuginfo,
&mut obj,
)?;
// If configured, attempt to use paged memory initialization // If configured, attempt to use paged memory initialization
// instead of the default mode of memory initialization // instead of the default mode of memory initialization

View File

@@ -172,13 +172,12 @@ pub struct GlobalModuleRegistry(BTreeMap<usize, GlobalRegisteredModule>);
impl GlobalModuleRegistry { impl GlobalModuleRegistry {
/// Returns whether the `pc`, according to globally registered information, /// Returns whether the `pc`, according to globally registered information,
/// is a wasm trap or not. /// 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(); let modules = GLOBAL_MODULES.read().unwrap();
match modules.module(pc) { match modules.module(pc) {
Some((entry, text_offset)) => { Some((entry, text_offset)) => {
wasmtime_environ::lookup_file_pos(entry.module.address_map_data(), text_offset) wasmtime_environ::lookup_trap_code(entry.module.trap_data(), text_offset).is_some()
.is_some()
} }
None => false, None => false,
} }
@@ -275,14 +274,15 @@ impl GlobalRegisteredModule {
// the function, because otherwise something is buggy along the way and // the function, because otherwise something is buggy along the way and
// not accounting for all the instructions. This isn't super critical // not accounting for all the instructions. This isn't super critical
// though so we can omit this check in release mode. // 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!( debug_assert!(
instr.is_some(), instr.is_some() || !self.module.has_address_map(),
"failed to find instruction for {:#x}", "failed to find instruction for {:#x}",
text_offset text_offset
); );
let instr = instr.unwrap_or(info.start_srcloc);
// Use our wasm-relative pc to symbolize this frame. If there's a // Use our wasm-relative pc to symbolize this frame. If there's a
// symbolication context (dwarf debug info) available then we can try to // symbolication context (dwarf debug info) available then we can try to
// look this up there. // look this up there.
@@ -294,7 +294,7 @@ impl GlobalRegisteredModule {
let mut symbols = Vec::new(); let mut symbols = Vec::new();
if let Some(s) = &self.module.symbolize_context().ok().and_then(|c| c) { 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(); let to_lookup = u64::from(offset) - s.code_section_offset();
if let Ok(mut frames) = s.addr2line().find_frames(to_lookup) { if let Ok(mut frames) = s.addr2line().find_frames(to_lookup) {
while let Ok(Some(frame)) = frames.next() { while let Ok(Some(frame)) = frames.next() {
@@ -344,7 +344,7 @@ pub struct FrameInfo {
func_index: u32, func_index: u32,
func_name: Option<String>, func_name: Option<String>,
func_start: FilePos, func_start: FilePos,
instr: FilePos, instr: Option<FilePos>,
symbols: Vec<FrameSymbol>, symbols: Vec<FrameSymbol>,
} }
@@ -393,8 +393,14 @@ impl FrameInfo {
/// ///
/// The offset here is the offset from the beginning of the original wasm /// The offset here is the offset from the beginning of the original wasm
/// module to the instruction that this frame points to. /// 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<usize> {
Some(self.instr?.file_offset()? as usize)
} }
/// Returns the offset from the original wasm module's function to this /// 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 /// The offset here is the offset from the beginning of the defining
/// function of this frame (within the wasm module) to the instruction this /// function of this frame (within the wasm module) to the instruction this
/// frame points to. /// frame points to.
pub fn func_offset(&self) -> usize { ///
match self.instr.file_offset() { /// Note that `None` may be returned if the original module was not
Some(i) => (i - self.func_start.file_offset().unwrap()) as usize, /// compiled with mapping information to yield this information. This is
None => u32::MAX as usize, /// controlled by the
} /// [`Config::generate_address_map`](crate::Config::generate_address_map)
/// configuration option.
pub fn func_offset(&self) -> Option<usize> {
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. /// Returns the debug symbols found, if any, for this function frame.

View File

@@ -601,6 +601,12 @@ impl<'a> SerializedModule<'a> {
// This doesn't affect compilation, it's just a runtime setting. // This doesn't affect compilation, it's just a runtime setting.
dynamic_memory_growth_reserve: _, 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.metadata.tunables;
Self::check_int( Self::check_int(

View File

@@ -321,7 +321,11 @@ impl fmt::Display for Trap {
writeln!(f, "\nwasm backtrace:")?; writeln!(f, "\nwasm backtrace:")?;
for (i, frame) in self.trace().iter().enumerate() { for (i, frame) in self.trace().iter().enumerate() {
let name = frame.module_name().unwrap_or("<unknown>"); let name = frame.module_name().unwrap_or("<unknown>");
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 = let demangle =
|f: &mut fmt::Formatter<'_>, name: &str| match rustc_demangle::try_demangle(name) { |f: &mut fmt::Formatter<'_>, name: &str| match rustc_demangle::try_demangle(name) {

View File

@@ -54,13 +54,13 @@ fn test_trap_trace() -> Result<()> {
assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
assert_eq!(trace[0].func_index(), 1); assert_eq!(trace[0].func_index(), 1);
assert_eq!(trace[0].func_name(), Some("hello")); assert_eq!(trace[0].func_name(), Some("hello"));
assert_eq!(trace[0].func_offset(), 1); assert_eq!(trace[0].func_offset(), Some(1));
assert_eq!(trace[0].module_offset(), 0x26); assert_eq!(trace[0].module_offset(), Some(0x26));
assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
assert_eq!(trace[1].func_index(), 0); assert_eq!(trace[1].func_index(), 0);
assert_eq!(trace[1].func_name(), None); assert_eq!(trace[1].func_name(), None);
assert_eq!(trace[1].func_offset(), 1); assert_eq!(trace[1].func_offset(), Some(1));
assert_eq!(trace[1].module_offset(), 0x21); assert_eq!(trace[1].module_offset(), Some(0x21));
assert!( assert!(
e.to_string().contains("unreachable"), e.to_string().contains("unreachable"),
"wrong message: {}", "wrong message: {}",
@@ -637,3 +637,37 @@ fn multithreaded_traps() -> Result<()> {
Ok(()) 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(())
}