Fuzz-code-coverage motivated improvements (#3905)

* fuzz: Fuzz padding between compiled functions

This commit hooks up the custom
`wasmtime_linkopt_padding_between_functions` configuration option to the
cranelift compiler into the fuzz configuration, enabling us to ensure
that randomly inserting a moderate amount of padding between functions
shouldn't tamper with any results.

* fuzz: Fuzz the `Config::generate_address_map` option

This commit adds fuzz configuration where `generate_address_map` is
either enabled or disabled, unlike how it's always enabled for fuzzing
today.

* Remove unnecessary handling of relocations

This commit removes a number of bits and pieces all related to handling
relocations in JIT code generated by Wasmtime. None of this is necessary
nowadays that the "old backend" has been removed (quite some time ago)
and relocations are no longer expected to be in the JIT code at all.
Additionally with the minimum x86_64 features required to run wasm code
it should be expected that no libcalls are required either for
Wasmtime-based JIT code.
This commit is contained in:
Alex Crichton
2022-03-09 12:58:27 -06:00
committed by GitHub
parent 29298b1f88
commit f21aa98ccb
7 changed files with 48 additions and 193 deletions

View File

@@ -8,7 +8,7 @@ repository = "https://github.com/bytecodealliance/wasmtime"
documentation = "https://docs.rs/wasmtime-cranelift/" documentation = "https://docs.rs/wasmtime-cranelift/"
categories = ["wasm"] categories = ["wasm"]
keywords = ["webassembly", "wasm"] keywords = ["webassembly", "wasm"]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"

View File

@@ -547,36 +547,22 @@ impl Compiler {
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
) -> Result<CompiledFunction, CompileError> { ) -> Result<CompiledFunction, CompileError> {
let mut code_buf = Vec::new(); let mut code_buf = Vec::new();
let mut relocs = Vec::new();
context context
.compile_and_emit(isa, &mut code_buf) .compile_and_emit(isa, &mut code_buf)
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
for &MachReloc { // Processing relocations isn't the hardest thing in the world here but
offset, // no trampoline should currently generate a relocation, so assert that
srcloc: _, // they're all empty and if this ever trips in the future then handling
kind, // will need to be added here to ensure they make their way into the
ref name, // `CompiledFunction` below.
addend, assert!(context
} in context
.mach_compile_result .mach_compile_result
.as_ref() .as_ref()
.unwrap() .unwrap()
.buffer .buffer
.relocs() .relocs()
{ .is_empty());
let reloc_target = if let ir::ExternalName::LibCall(libcall) = *name {
RelocationTarget::LibCall(libcall)
} else {
panic!("unrecognized external name")
};
relocs.push(Relocation {
reloc: kind,
reloc_target,
offset,
addend,
});
}
let unwind_info = context let unwind_info = context
.create_unwind_info(isa) .create_unwind_info(isa)
@@ -585,7 +571,7 @@ impl Compiler {
Ok(CompiledFunction { Ok(CompiledFunction {
body: code_buf, body: code_buf,
unwind_info, unwind_info,
relocations: relocs, relocations: Vec::new(),
stack_slots: Default::default(), stack_slots: Default::default(),
value_labels_ranges: Default::default(), value_labels_ranges: Default::default(),
info: Default::default(), info: Default::default(),

View File

@@ -17,7 +17,6 @@ use crate::debug::{DwarfSection, DwarfSectionRelocTarget};
use crate::{CompiledFunction, Relocation, RelocationTarget}; use crate::{CompiledFunction, Relocation, RelocationTarget};
use anyhow::Result; use anyhow::Result;
use cranelift_codegen::binemit::Reloc; use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::LibCall;
use cranelift_codegen::isa::{ use cranelift_codegen::isa::{
unwind::{systemv, UnwindInfo}, unwind::{systemv, UnwindInfo},
TargetIsa, TargetIsa,
@@ -69,30 +68,6 @@ macro_rules! for_each_libcall {
}; };
} }
fn write_libcall_symbols(obj: &mut Object) -> HashMap<LibCall, SymbolId> {
let mut libcalls = HashMap::new();
macro_rules! add_libcall_symbol {
[$(($libcall:ident, $export:ident)),*] => {{
$(
let symbol_id = obj.add_symbol(Symbol {
name: stringify!($export).as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: true,
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
});
libcalls.insert(LibCall::$libcall, symbol_id);
)+
}};
}
for_each_libcall!(add_libcall_symbol);
libcalls
}
/// A helper structure used to assemble the final text section of an exectuable, /// A helper structure used to assemble the final text section of an exectuable,
/// plus unwinding information and other related details. /// plus unwinding information and other related details.
/// ///
@@ -110,10 +85,6 @@ pub struct ObjectBuilder<'a> {
/// The WebAssembly module we're generating code for. /// The WebAssembly module we're generating code for.
module: &'a Module, module: &'a Module,
/// Map of injected symbols for all possible libcalls, used whenever there's
/// a relocation against a libcall.
libcalls: HashMap<LibCall, SymbolId>,
windows_unwind_info_id: Option<SectionId>, windows_unwind_info_id: Option<SectionId>,
/// Packed form of windows unwind tables which, if present, will get emitted /// Packed form of windows unwind tables which, if present, will get emitted
@@ -190,15 +161,12 @@ impl<'a> ObjectBuilder<'a> {
func_symbols.push(symbol_id); func_symbols.push(symbol_id);
} }
let libcalls = write_libcall_symbols(obj);
Self { Self {
isa, isa,
obj, obj,
module, module,
text_section, text_section,
func_symbols, func_symbols,
libcalls,
windows_unwind_info_id: None, windows_unwind_info_id: None,
windows_unwind_info: Vec::new(), windows_unwind_info: Vec::new(),
systemv_unwind_info_id: None, systemv_unwind_info_id: None,
@@ -266,7 +234,7 @@ impl<'a> ObjectBuilder<'a> {
} }
for r in func.relocations.iter() { for r in func.relocations.iter() {
let (symbol, symbol_offset) = match r.reloc_target { match r.reloc_target {
// Relocations against user-defined functions means that this is // Relocations against user-defined functions means that this is
// a relocation against a module-local function, typically a // a relocation against a module-local function, typically a
// call between functions. The `text` field is given priority to // call between functions. The `text` field is given priority to
@@ -284,43 +252,27 @@ impl<'a> ObjectBuilder<'a> {
continue; continue;
} }
// FIXME(#3009) once the old backend is removed all // At this time it's expected that all relocations are
// inter-function relocations should be handled by // handled by `text.resolve_reloc`, and anything that isn't
// `self.text`. This can become `unreachable!()` in that // handled is a bug in `text.resolve_reloc` or something
// case. // transitively there. If truly necessary, though, then this
self.relocations.push((r, off)); // loop could also be updated to forward the relocation to
continue; // the final object file as well.
panic!(
"unresolved relocation could not be procesed against \
{index:?}: {r:?}"
);
} }
// These relocations, unlike against user funcs above, typically // At this time it's not expected that any libcall relocations
// involve absolute addresses and need to get resolved at load // are generated. Ideally we don't want relocations against
// time. These are persisted immediately into the object file. // libcalls anyway as libcalls should go through indirect
// // `VMContext` tables to avoid needing to apply relocations at
// FIXME: these, like user-defined-functions, should probably // module-load time as well.
// use relative jumps and avoid absolute relocations. They don't RelocationTarget::LibCall(call) => {
// seem too common though so aren't necessarily that important unimplemented!("cannot generate relocation against libcall {call:?}");
// to optimize. }
RelocationTarget::LibCall(call) => (self.libcalls[&call], 0),
}; };
let (kind, encoding, size) = match r.reloc {
Reloc::Abs4 => (RelocationKind::Absolute, RelocationEncoding::Generic, 32),
Reloc::Abs8 => (RelocationKind::Absolute, RelocationEncoding::Generic, 64),
other => unimplemented!("Unimplemented relocation {:?}", other),
};
self.obj
.add_relocation(
self.text_section,
ObjectRelocation {
offset: off + r.offset as u64,
size,
kind,
encoding,
symbol,
addend: r.addend.wrapping_add(symbol_offset as i64),
},
)
.unwrap();
} }
(symbol_id, off..off + body_len) (symbol_id, off..off + body_len)
} }

View File

@@ -211,6 +211,8 @@ pub struct WasmtimeConfig {
/// Configuration for the instance allocation strategy to use. /// Configuration for the instance allocation strategy to use.
pub strategy: InstanceAllocationStrategy, pub strategy: InstanceAllocationStrategy,
codegen: CodegenSettings, codegen: CodegenSettings,
padding_between_functions: Option<u16>,
generate_address_map: bool,
} }
/// Configuration for linear memories in Wasmtime. /// Configuration for linear memories in Wasmtime.
@@ -393,7 +395,8 @@ impl Config {
16 << 20, 16 << 20,
self.wasmtime.memory_guaranteed_dense_image_size, self.wasmtime.memory_guaranteed_dense_image_size,
)) ))
.allocation_strategy(self.wasmtime.strategy.to_wasmtime()); .allocation_strategy(self.wasmtime.strategy.to_wasmtime())
.generate_address_map(self.wasmtime.generate_address_map);
self.wasmtime.codegen.configure(&mut cfg); self.wasmtime.codegen.configure(&mut cfg);
@@ -418,6 +421,16 @@ impl Config {
} }
} }
if let Some(pad) = self.wasmtime.padding_between_functions {
unsafe {
cfg.cranelift_flag_set(
"wasmtime_linkopt_padding_between_functions",
&pad.to_string(),
)
.unwrap();
}
}
match &self.wasmtime.memory_config { match &self.wasmtime.memory_config {
MemoryConfig::Normal(memory_config) => { MemoryConfig::Normal(memory_config) => {
cfg.static_memory_maximum_size( cfg.static_memory_maximum_size(

View File

@@ -148,18 +148,12 @@ impl CodeMemory {
std::slice::from_raw_parts_mut(ret.text.as_ptr() as *mut u8, ret.text.len()); std::slice::from_raw_parts_mut(ret.text.as_ptr() as *mut u8, ret.text.len());
let text_offset = ret.text.as_ptr() as usize - ret.mmap.as_ptr() as usize; let text_offset = ret.text.as_ptr() as usize - ret.mmap.as_ptr() as usize;
let text_range = text_offset..text_offset + text_mut.len(); let text_range = text_offset..text_offset + text_mut.len();
let mut text_section_readwrite = false;
for (offset, r) in text.relocations() { // Double-check there are no relocations in the text section. At
// If the text section was mapped at readonly we need to make it // this time relocations are not expected at all from loaded code
// briefly read/write here as we apply relocations. // since everything should be resolved at compile time. Handling
if !text_section_readwrite && self.mmap.is_readonly() { // must be added here, though, if relocations pop up.
self.mmap assert!(text.relocations().count() == 0);
.make_writable(text_range.clone())
.expect("unable to make memory writable");
text_section_readwrite = true;
}
crate::link::apply_reloc(&ret.obj, text_mut, offset, r);
}
// Switch the executable portion from read/write to // Switch the executable portion from read/write to
// read/execute, notably not using read/write/execute to prevent // read/execute, notably not using read/write/execute to prevent

View File

@@ -24,7 +24,6 @@ mod code_memory;
mod debug; mod debug;
mod demangling; mod demangling;
mod instantiate; mod instantiate;
mod link;
mod profiling; mod profiling;
mod unwind; mod unwind;

View File

@@ -1,89 +0,0 @@
//! Linking for JIT-compiled code.
use object::read::{Object, Relocation, RelocationTarget};
use object::{File, NativeEndian as NE, ObjectSymbol, RelocationEncoding, RelocationKind};
use std::convert::TryFrom;
use wasmtime_runtime::libcalls;
type I32 = object::I32Bytes<NE>;
type U64 = object::U64Bytes<NE>;
/// Applies the relocation `r` at `offset` within `code`, according to the
/// symbols found in `obj`.
///
/// This method is used at runtime to resolve relocations in ELF images,
/// typically with respect to where the memory was placed in the final address
/// in memory.
pub fn apply_reloc(obj: &File, code: &mut [u8], offset: u64, r: Relocation) {
let target_func_address: usize = match r.target() {
RelocationTarget::Symbol(i) => {
// Processing relocation target is a named symbols that is compiled
// wasm function or runtime libcall.
let sym = obj.symbol_by_index(i).unwrap();
if sym.is_local() {
&code[sym.address() as usize] as *const u8 as usize
} else {
match sym.name() {
Ok(name) => {
if let Some(addr) = to_libcall_address(name) {
addr
} else {
panic!("unknown function to link: {}", name);
}
}
Err(_) => panic!("unexpected relocation target: not a symbol"),
}
}
}
_ => panic!("unexpected relocation target: not a symbol"),
};
match (r.kind(), r.encoding(), r.size()) {
(RelocationKind::Absolute, RelocationEncoding::Generic, 64) => {
let reloc_address = reloc_address::<U64>(code, offset);
let reloc_abs = (target_func_address as u64)
.checked_add(r.addend() as u64)
.unwrap();
reloc_address.set(NE, reloc_abs);
}
// FIXME(#3009) after the old backend is removed this won't ever show up
// again so it can be removed.
(RelocationKind::Relative, RelocationEncoding::Generic, 32) => {
let reloc_address = reloc_address::<I32>(code, offset);
let val = (target_func_address as i64)
.wrapping_add(r.addend())
.wrapping_sub(reloc_address as *const _ as i64);
reloc_address.set(NE, i32::try_from(val).expect("relocation out-of-bounds"));
}
other => panic!("unsupported reloc kind: {:?}", other),
}
}
fn reloc_address<T: object::Pod>(code: &mut [u8], offset: u64) -> &mut T {
let (reloc, _rest) = usize::try_from(offset)
.ok()
.and_then(move |offset| code.get_mut(offset..))
.and_then(|range| object::from_bytes_mut(range).ok())
.expect("invalid reloc offset");
reloc
}
fn to_libcall_address(name: &str) -> Option<usize> {
use self::libcalls::*;
use wasmtime_environ::for_each_libcall;
macro_rules! add_libcall_symbol {
[$(($libcall:ident, $export:ident)),*] => {
Some(match name {
$(
stringify!($export) => $export as usize,
)+
_ => {
return None;
}
})
};
}
for_each_libcall!(add_libcall_symbol)
}