Add differential fuzzing against V8 (#3264)
* Add differential fuzzing against V8 This commit adds a differential fuzzing target to Wasmtime along the lines of the wasmi and spec interpreters we already have, but with V8 instead. The intention here is that wasmi is unlikely to receive updates over time (e.g. for SIMD), and the spec interpreter is not suitable for fuzzing against in general due to its performance characteristics. The hope is that V8 is indeed appropriate to fuzz against because it's naturally receiving updates and it also is expected to have good performance. Here the `rusty_v8` crate is used which provides bindings to V8 as well as precompiled binaries by default. This matches exactly the use case we need and at least for now I think the `rusty_v8` crate will be maintained by the Deno folks as they continue to develop it. If it becomes an issue though maintaining we can evaluate other options to have differential fuzzing against. For now this commit enables the SIMD and bulk-memory feature of fuzz-target-generation which should enable them to get differentially-fuzzed with V8 in addition to the compilation fuzzing we're already getting. * Use weak linkage for GDB jit helpers This should help us deduplicate our symbol with other JIT runtimes, if any. For now this leans on some C helpers to define the weak linkage since Rust doesn't support that on stable yet. * Don't use rusty_v8 on MinGW They don't have precompiled libraries there. * Fix msvc build * Comment about execution
This commit is contained in:
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -1274,6 +1274,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fslock"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbc585f4fe7227b37ef0216444c87ca8ab6051622e4e2bc75d4bed4ea5106148"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fst"
|
name = "fst"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -2649,6 +2659,19 @@ dependencies = [
|
|||||||
"wait-timeout",
|
"wait-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusty_v8"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81fc062fb861b82fa7ac4e1a009da873279a10180d2133574e4219d870038c1c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fslock",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"which",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -3739,6 +3762,7 @@ dependencies = [
|
|||||||
"env_logger 0.8.3",
|
"env_logger 0.8.3",
|
||||||
"log",
|
"log",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"rusty_v8",
|
||||||
"wasm-encoder",
|
"wasm-encoder",
|
||||||
"wasm-smith",
|
"wasm-smith",
|
||||||
"wasm-spec-interpreter",
|
"wasm-spec-interpreter",
|
||||||
@@ -3905,6 +3929,17 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "4.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wiggle"
|
name = "wiggle"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ wasm-smith = "0.7.0"
|
|||||||
wasm-spec-interpreter = { path = "./wasm-spec-interpreter" }
|
wasm-spec-interpreter = { path = "./wasm-spec-interpreter" }
|
||||||
wasmi = "0.7.0"
|
wasmi = "0.7.0"
|
||||||
|
|
||||||
|
# We rely on precompiled v8 binaries, but rusty-v8 doesn't have a precompiled
|
||||||
|
# binary for MinGW which is built on our CI. It does have one for Windows-msvc,
|
||||||
|
# though, so we could use that if we wanted. For now though just simplify a bit
|
||||||
|
# and don't depend on this on Windows.
|
||||||
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
|
rusty_v8 = "0.27"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wat = "1.0.37"
|
wat = "1.0.37"
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ use std::time::{Duration, Instant};
|
|||||||
use wasmtime::*;
|
use wasmtime::*;
|
||||||
use wasmtime_wast::WastContext;
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
pub use v8::*;
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
mod v8;
|
||||||
|
|
||||||
static CNT: AtomicUsize = AtomicUsize::new(0);
|
static CNT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
fn log_wasm(wasm: &[u8]) {
|
fn log_wasm(wasm: &[u8]) {
|
||||||
@@ -563,9 +568,11 @@ pub fn table_ops(
|
|||||||
/// conform to certain specifications: one exported function, one exported
|
/// conform to certain specifications: one exported function, one exported
|
||||||
/// memory.
|
/// memory.
|
||||||
#[derive(Default, Debug, Arbitrary, Clone)]
|
#[derive(Default, Debug, Arbitrary, Clone)]
|
||||||
pub struct SingleFunctionModuleConfig;
|
pub struct SingleFunctionModuleConfig<const SIMD: bool, const BULK: bool>;
|
||||||
|
|
||||||
impl wasm_smith::Config for SingleFunctionModuleConfig {
|
impl<const SIMD: bool, const BULK: bool> wasm_smith::Config
|
||||||
|
for SingleFunctionModuleConfig<SIMD, BULK>
|
||||||
|
{
|
||||||
fn allow_start_export(&self) -> bool {
|
fn allow_start_export(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -611,6 +618,14 @@ impl wasm_smith::Config for SingleFunctionModuleConfig {
|
|||||||
fn canonicalize_nans(&self) -> bool {
|
fn canonicalize_nans(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn simd_enabled(&self) -> bool {
|
||||||
|
SIMD
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bulk_memory_enabled(&self) -> bool {
|
||||||
|
BULK
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform differential execution between Cranelift and wasmi, diffing the
|
/// Perform differential execution between Cranelift and wasmi, diffing the
|
||||||
@@ -638,7 +653,7 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con
|
|||||||
|
|
||||||
// Introspect wasmtime module to find name of an exported function and of an
|
// Introspect wasmtime module to find name of an exported function and of an
|
||||||
// exported memory.
|
// exported memory.
|
||||||
let func_name = first_exported_function(&wasmtime_module)?;
|
let (func_name, _ty) = first_exported_function(&wasmtime_module)?;
|
||||||
let memory_name = first_exported_memory(&wasmtime_module)?;
|
let memory_name = first_exported_memory(&wasmtime_module)?;
|
||||||
|
|
||||||
let wasmi_mem_export = wasmi_instance.export_by_name(memory_name).unwrap();
|
let wasmi_mem_export = wasmi_instance.export_by_name(memory_name).unwrap();
|
||||||
@@ -816,7 +831,7 @@ fn run_in_wasmtime(
|
|||||||
.context("Wasmtime cannot instantiate module")?;
|
.context("Wasmtime cannot instantiate module")?;
|
||||||
|
|
||||||
// Find the first exported function.
|
// Find the first exported function.
|
||||||
let func_name =
|
let (func_name, _ty) =
|
||||||
first_exported_function(&wasmtime_module).context("Cannot find exported function")?;
|
first_exported_function(&wasmtime_module).context("Cannot find exported function")?;
|
||||||
let wasmtime_main = wasmtime_instance
|
let wasmtime_main = wasmtime_instance
|
||||||
.get_func(&mut wasmtime_store, &func_name[..])
|
.get_func(&mut wasmtime_store, &func_name[..])
|
||||||
@@ -828,10 +843,10 @@ fn run_in_wasmtime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Introspect wasmtime module to find the name of the first exported function.
|
// Introspect wasmtime module to find the name of the first exported function.
|
||||||
fn first_exported_function(module: &wasmtime::Module) -> Option<&str> {
|
fn first_exported_function(module: &wasmtime::Module) -> Option<(&str, FuncType)> {
|
||||||
for e in module.exports() {
|
for e in module.exports() {
|
||||||
match e.ty() {
|
match e.ty() {
|
||||||
wasmtime::ExternType::Func(..) => return Some(e.name()),
|
wasmtime::ExternType::Func(ty) => return Some((e.name(), ty)),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
293
crates/fuzzing/src/oracles/v8.rs
Normal file
293
crates/fuzzing/src/oracles/v8.rs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
use super::{first_exported_function, first_exported_memory, log_wasm};
|
||||||
|
use rusty_v8 as v8;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::sync::Once;
|
||||||
|
use wasmtime::*;
|
||||||
|
|
||||||
|
/// Performs differential execution between Wasmtime and V8.
|
||||||
|
///
|
||||||
|
/// This will instantiate the `wasm` provided, which should have no host
|
||||||
|
/// imports, and then run it in Wasmtime with the `config` specified and V8 with
|
||||||
|
/// default settings. The first export is executed and if memory is exported
|
||||||
|
/// it's compared as well.
|
||||||
|
///
|
||||||
|
/// Note that it's the caller's responsibility to ensure that the `wasm`
|
||||||
|
/// doesn't infinitely loop as no protections are done in v8 to prevent this
|
||||||
|
/// from happening.
|
||||||
|
pub fn differential_v8_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> {
|
||||||
|
// Wasmtime setup
|
||||||
|
crate::init_fuzzing();
|
||||||
|
log_wasm(wasm);
|
||||||
|
let (wasmtime_module, mut wasmtime_store) = super::differential_store(wasm, config);
|
||||||
|
log::trace!("compiled module with wasmtime");
|
||||||
|
|
||||||
|
// V8 setup
|
||||||
|
let mut isolate = isolate();
|
||||||
|
let mut scope = v8::HandleScope::new(&mut *isolate);
|
||||||
|
let context = v8::Context::new(&mut scope);
|
||||||
|
let global = context.global(&mut scope);
|
||||||
|
let mut scope = v8::ContextScope::new(&mut scope, context);
|
||||||
|
|
||||||
|
// V8: compile module
|
||||||
|
let buf = v8::ArrayBuffer::new_backing_store_from_boxed_slice(wasm.into());
|
||||||
|
let buf = v8::SharedRef::from(buf);
|
||||||
|
let name = v8::String::new(&mut scope, "WASM_BINARY").unwrap();
|
||||||
|
let buf = v8::ArrayBuffer::with_backing_store(&mut scope, &buf);
|
||||||
|
global.set(&mut scope, name.into(), buf.into());
|
||||||
|
let v8_module = eval(&mut scope, "new WebAssembly.Module(WASM_BINARY)").unwrap();
|
||||||
|
let name = v8::String::new(&mut scope, "WASM_MODULE").unwrap();
|
||||||
|
global.set(&mut scope, name.into(), v8_module);
|
||||||
|
log::trace!("compiled module with v8");
|
||||||
|
|
||||||
|
// Wasmtime: instantiate
|
||||||
|
let wasmtime_instance = wasmtime::Instance::new(&mut wasmtime_store, &wasmtime_module, &[]);
|
||||||
|
log::trace!("instantiated with wasmtime");
|
||||||
|
|
||||||
|
// V8: instantiate
|
||||||
|
let v8_instance = eval(&mut scope, "new WebAssembly.Instance(WASM_MODULE)");
|
||||||
|
log::trace!("instantiated with v8");
|
||||||
|
|
||||||
|
// Verify V8 and wasmtime match
|
||||||
|
let (wasmtime_instance, v8_instance) = match (wasmtime_instance, v8_instance) {
|
||||||
|
(Ok(i1), Ok(i2)) => (i1, i2),
|
||||||
|
(Ok(_), Err(msg)) => {
|
||||||
|
panic!("wasmtime succeeded at instantiation, v8 failed: {}", msg)
|
||||||
|
}
|
||||||
|
(Err(err), Ok(_)) => {
|
||||||
|
panic!("v8 succeeded at instantiation, wasmtime failed: {:?}", err)
|
||||||
|
}
|
||||||
|
(Err(err), Err(msg)) => {
|
||||||
|
log::trace!("instantiations failed");
|
||||||
|
assert_error_matches(&err, &msg);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
log::trace!("instantiations were successful");
|
||||||
|
|
||||||
|
let (func, ty) = first_exported_function(&wasmtime_module)?;
|
||||||
|
|
||||||
|
// not supported yet in V8
|
||||||
|
if ty.params().chain(ty.results()).any(|t| t == ValType::V128) {
|
||||||
|
log::trace!("exported function uses v128, skipping");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut wasmtime_params = Vec::new();
|
||||||
|
let mut v8_params = Vec::new();
|
||||||
|
for param in ty.params() {
|
||||||
|
wasmtime_params.push(match param {
|
||||||
|
ValType::I32 => Val::I32(0),
|
||||||
|
ValType::I64 => Val::I64(0),
|
||||||
|
ValType::F32 => Val::F32(0),
|
||||||
|
ValType::F64 => Val::F64(0),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
});
|
||||||
|
v8_params.push(match param {
|
||||||
|
ValType::I32 | ValType::F32 | ValType::F64 => v8::Number::new(&mut scope, 0.0).into(),
|
||||||
|
ValType::I64 => v8::BigInt::new_from_i64(&mut scope, 0).into(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wasmtime: call the first exported func
|
||||||
|
let wasmtime_main = wasmtime_instance
|
||||||
|
.get_func(&mut wasmtime_store, func)
|
||||||
|
.expect("function export is present");
|
||||||
|
let wasmtime_vals = wasmtime_main.call(&mut wasmtime_store, &wasmtime_params);
|
||||||
|
log::trace!("finished wasmtime invocation");
|
||||||
|
|
||||||
|
// V8: call the first exported func
|
||||||
|
let name = v8::String::new(&mut scope, "WASM_INSTANCE").unwrap();
|
||||||
|
global.set(&mut scope, name.into(), v8_instance);
|
||||||
|
let name = v8::String::new(&mut scope, "EXPORT_NAME").unwrap();
|
||||||
|
let func_name = v8::String::new(&mut scope, func).unwrap();
|
||||||
|
global.set(&mut scope, name.into(), func_name.into());
|
||||||
|
let name = v8::String::new(&mut scope, "ARGS").unwrap();
|
||||||
|
let v8_params = v8::Array::new_with_elements(&mut scope, &v8_params);
|
||||||
|
global.set(&mut scope, name.into(), v8_params.into());
|
||||||
|
let v8_vals = eval(
|
||||||
|
&mut scope,
|
||||||
|
&format!("WASM_INSTANCE.exports[EXPORT_NAME](...ARGS)"),
|
||||||
|
);
|
||||||
|
log::trace!("finished v8 invocation");
|
||||||
|
|
||||||
|
// Verify V8 and wasmtime match
|
||||||
|
match (wasmtime_vals, v8_vals) {
|
||||||
|
(Ok(wasmtime), Ok(v8)) => {
|
||||||
|
log::trace!("both executed successfully");
|
||||||
|
match wasmtime.len() {
|
||||||
|
0 => assert!(v8.is_undefined()),
|
||||||
|
1 => assert_val_match(&wasmtime[0], &v8, &mut scope),
|
||||||
|
_ => {
|
||||||
|
let array = v8::Local::<'_, v8::Array>::try_from(v8).unwrap();
|
||||||
|
for (i, wasmtime) in wasmtime.iter().enumerate() {
|
||||||
|
let v8 = array.get_index(&mut scope, i as u32).unwrap();
|
||||||
|
assert_val_match(wasmtime, &v8, &mut scope);
|
||||||
|
// ..
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Ok(_), Err(msg)) => {
|
||||||
|
panic!("wasmtime succeeded at invocation, v8 failed: {}", msg)
|
||||||
|
}
|
||||||
|
(Err(err), Ok(_)) => {
|
||||||
|
panic!("v8 succeeded at invocation, wasmtime failed: {:?}", err)
|
||||||
|
}
|
||||||
|
(Err(err), Err(msg)) => {
|
||||||
|
log::trace!("got two traps");
|
||||||
|
assert_error_matches(&err, &msg);
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify V8 and wasmtime match memories
|
||||||
|
if let Some(mem) = first_exported_memory(&wasmtime_module) {
|
||||||
|
log::trace!("comparing memories");
|
||||||
|
let wasmtime = wasmtime_instance
|
||||||
|
.get_memory(&mut wasmtime_store, mem)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let name = v8::String::new(&mut scope, "MEMORY_NAME").unwrap();
|
||||||
|
let func_name = v8::String::new(&mut scope, mem).unwrap();
|
||||||
|
global.set(&mut scope, name.into(), func_name.into());
|
||||||
|
let v8 = eval(
|
||||||
|
&mut scope,
|
||||||
|
&format!("WASM_INSTANCE.exports[MEMORY_NAME].buffer"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let v8 = v8::Local::<'_, v8::ArrayBuffer>::try_from(v8).unwrap();
|
||||||
|
let v8_data = v8.get_backing_store();
|
||||||
|
let wasmtime_data = wasmtime.data(&wasmtime_store);
|
||||||
|
assert_eq!(wasmtime_data.len(), v8_data.len());
|
||||||
|
for i in 0..v8_data.len() {
|
||||||
|
if wasmtime_data[i] != v8_data[i].get() {
|
||||||
|
panic!("memories differ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manufactures a new V8 Isolate to run within.
|
||||||
|
fn isolate() -> v8::OwnedIsolate {
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| {
|
||||||
|
let platform = v8::new_default_platform(0, false).make_shared();
|
||||||
|
v8::V8::initialize_platform(platform);
|
||||||
|
v8::V8::initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
v8::Isolate::new(Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates the JS `code` within `scope`, returning either the result of the
|
||||||
|
/// computation or the stringified exception if one happened.
|
||||||
|
fn eval<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
code: &str,
|
||||||
|
) -> Result<v8::Local<'s, v8::Value>, String> {
|
||||||
|
let mut tc = v8::TryCatch::new(scope);
|
||||||
|
let mut scope = v8::EscapableHandleScope::new(&mut tc);
|
||||||
|
let source = v8::String::new(&mut scope, code).unwrap();
|
||||||
|
let script = v8::Script::compile(&mut scope, source, None).unwrap();
|
||||||
|
match script.run(&mut scope) {
|
||||||
|
Some(val) => Ok(scope.escape(val)),
|
||||||
|
None => {
|
||||||
|
drop(scope);
|
||||||
|
assert!(tc.has_caught());
|
||||||
|
Err(tc
|
||||||
|
.message()
|
||||||
|
.unwrap()
|
||||||
|
.get(&mut tc)
|
||||||
|
.to_rust_string_lossy(&mut tc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the wasmtime value `a` matches the v8 value `b`.
|
||||||
|
///
|
||||||
|
/// For NaN values simply just asserts that they're both NaN.
|
||||||
|
fn assert_val_match(a: &Val, b: &v8::Local<'_, v8::Value>, scope: &mut v8::HandleScope<'_>) {
|
||||||
|
match *a {
|
||||||
|
Val::I32(wasmtime) => {
|
||||||
|
assert_eq!(i64::from(wasmtime), b.to_int32(scope).unwrap().value());
|
||||||
|
}
|
||||||
|
Val::I64(wasmtime) => {
|
||||||
|
assert_eq!((wasmtime, true), b.to_big_int(scope).unwrap().i64_value());
|
||||||
|
}
|
||||||
|
Val::F32(wasmtime) => {
|
||||||
|
same_float(
|
||||||
|
f64::from(f32::from_bits(wasmtime)),
|
||||||
|
b.to_number(scope).unwrap().value(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Val::F64(wasmtime) => {
|
||||||
|
same_float(
|
||||||
|
f64::from_bits(wasmtime),
|
||||||
|
b.to_number(scope).unwrap().value(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("unsupported match {:?}", a),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same_float(a: f64, b: f64) {
|
||||||
|
assert!(a == b || (a.is_nan() && b.is_nan()), "{} != {}", a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to assert that the `wasmtime` error matches the `v8` error string.
|
||||||
|
///
|
||||||
|
/// This is not a precise function. This will likely need updates over time as
|
||||||
|
/// v8 and/or wasmtime changes. The goal here is to generally make sure that
|
||||||
|
/// both engines fail for basically the same reason.
|
||||||
|
fn assert_error_matches(wasmtime: &anyhow::Error, v8: &str) {
|
||||||
|
let wasmtime_msg = match wasmtime.downcast_ref::<Trap>() {
|
||||||
|
Some(trap) => trap.display_reason().to_string(),
|
||||||
|
None => format!("{:?}", wasmtime),
|
||||||
|
};
|
||||||
|
let verify_wasmtime = |msg: &str| {
|
||||||
|
assert!(wasmtime_msg.contains(msg), "{}\n!=\n{}", wasmtime_msg, v8);
|
||||||
|
};
|
||||||
|
let verify_v8 = |msg: &[&str]| {
|
||||||
|
assert!(
|
||||||
|
msg.iter().any(|msg| v8.contains(msg)),
|
||||||
|
"{:?}\n\t!=\n{}",
|
||||||
|
wasmtime_msg,
|
||||||
|
v8
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if let Some(code) = wasmtime.downcast_ref::<Trap>().and_then(|t| t.trap_code()) {
|
||||||
|
match code {
|
||||||
|
TrapCode::MemoryOutOfBounds => {
|
||||||
|
return verify_v8(&[
|
||||||
|
"memory access out of bounds",
|
||||||
|
"data segment is out of bounds",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
TrapCode::UnreachableCodeReached => return verify_v8(&["unreachable"]),
|
||||||
|
TrapCode::IntegerDivisionByZero => {
|
||||||
|
return verify_v8(&["divide by zero", "remainder by zero"])
|
||||||
|
}
|
||||||
|
TrapCode::StackOverflow => return verify_v8(&["call stack size exceeded"]),
|
||||||
|
TrapCode::IndirectCallToNull => return verify_v8(&["null function"]),
|
||||||
|
TrapCode::TableOutOfBounds => {
|
||||||
|
return verify_v8(&[
|
||||||
|
"table initializer is out of bounds",
|
||||||
|
"table index is out of bounds",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
TrapCode::BadSignature => return verify_v8(&["function signature mismatch"]),
|
||||||
|
TrapCode::IntegerOverflow | TrapCode::BadConversionToInteger => {
|
||||||
|
return verify_v8(&[
|
||||||
|
"float unrepresentable in integer range",
|
||||||
|
"divide result unrepresentable",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
other => log::debug!("unknown code {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify_wasmtime("not possibly present in an error, just panic please");
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
// Note that `sigsetjmp` and `siglongjmp` are used here where possible to
|
// Note that `sigsetjmp` and `siglongjmp` are used here where possible to
|
||||||
// explicitly pass a 0 argument to `sigsetjmp` that we don't need to preserve
|
// explicitly pass a 0 argument to `sigsetjmp` that we don't need to preserve
|
||||||
@@ -32,3 +34,38 @@ void wasmtime_longjmp(void *JmpBuf) {
|
|||||||
platform_jmp_buf *buf = (platform_jmp_buf*) JmpBuf;
|
platform_jmp_buf *buf = (platform_jmp_buf*) JmpBuf;
|
||||||
platform_longjmp(*buf, 1);
|
platform_longjmp(*buf, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Just in case cross-language LTO is enabled we set the `noinline` attribute
|
||||||
|
// and also try to have some sort of side effect in this function with a dummy
|
||||||
|
// `asm` statement.
|
||||||
|
//
|
||||||
|
// Note the `weak` linkage here, though, which is intended to let other code
|
||||||
|
// override this symbol if it's defined elsewhere, since this definition doesn't
|
||||||
|
// matter.
|
||||||
|
#ifndef CFG_TARGET_OS_windows
|
||||||
|
__attribute__((weak, noinline))
|
||||||
|
#endif
|
||||||
|
void __jit_debug_register_code() {
|
||||||
|
#ifndef CFG_TARGET_OS_windows
|
||||||
|
asm("");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JITDescriptor {
|
||||||
|
uint32_t version_;
|
||||||
|
uint32_t action_flag_;
|
||||||
|
void* relevant_entry_;
|
||||||
|
void* first_entry_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note the `weak` linkage here which is the same purpose as above. We want to
|
||||||
|
// let other runtimes be able to override this since our own definition isn't
|
||||||
|
// important.
|
||||||
|
#ifndef CFG_TARGET_OS_windows
|
||||||
|
__attribute__((weak))
|
||||||
|
#endif
|
||||||
|
struct JITDescriptor __jit_debug_descriptor = {1, 0, NULL, NULL};
|
||||||
|
|
||||||
|
struct JITDescriptor* wasmtime_jit_debug_descriptor() {
|
||||||
|
return &__jit_debug_descriptor;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,23 +27,9 @@ struct JITDescriptor {
|
|||||||
first_entry: *mut JITCodeEntry,
|
first_entry: *mut JITCodeEntry,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
extern "C" {
|
||||||
#[used]
|
fn wasmtime_jit_debug_descriptor() -> *mut JITDescriptor;
|
||||||
static mut __jit_debug_descriptor: JITDescriptor = JITDescriptor {
|
fn __jit_debug_register_code();
|
||||||
version: 1,
|
|
||||||
action_flag: JIT_NOACTION,
|
|
||||||
relevant_entry: ptr::null_mut(),
|
|
||||||
first_entry: ptr::null_mut(),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
#[inline(never)]
|
|
||||||
extern "C" fn __jit_debug_register_code() {
|
|
||||||
// Hack to not allow inlining even when Rust wants to do it in release mode.
|
|
||||||
let x = 3;
|
|
||||||
unsafe {
|
|
||||||
std::ptr::read_volatile(&x);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -102,41 +88,43 @@ unsafe impl Sync for GdbJitImageRegistration {}
|
|||||||
|
|
||||||
unsafe fn register_gdb_jit_image(entry: *mut JITCodeEntry) {
|
unsafe fn register_gdb_jit_image(entry: *mut JITCodeEntry) {
|
||||||
let _lock = GDB_REGISTRATION.lock().unwrap();
|
let _lock = GDB_REGISTRATION.lock().unwrap();
|
||||||
|
let desc = &mut *wasmtime_jit_debug_descriptor();
|
||||||
|
|
||||||
// Add it to the linked list in the JIT descriptor.
|
// Add it to the linked list in the JIT descriptor.
|
||||||
(*entry).next_entry = __jit_debug_descriptor.first_entry;
|
(*entry).next_entry = desc.first_entry;
|
||||||
if !__jit_debug_descriptor.first_entry.is_null() {
|
if !desc.first_entry.is_null() {
|
||||||
(*__jit_debug_descriptor.first_entry).prev_entry = entry;
|
(*desc.first_entry).prev_entry = entry;
|
||||||
}
|
}
|
||||||
__jit_debug_descriptor.first_entry = entry;
|
desc.first_entry = entry;
|
||||||
// Point the relevant_entry field of the descriptor at the entry.
|
// Point the relevant_entry field of the descriptor at the entry.
|
||||||
__jit_debug_descriptor.relevant_entry = entry;
|
desc.relevant_entry = entry;
|
||||||
// Set action_flag to JIT_REGISTER and call __jit_debug_register_code.
|
// Set action_flag to JIT_REGISTER and call __jit_debug_register_code.
|
||||||
__jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
|
desc.action_flag = JIT_REGISTER_FN;
|
||||||
__jit_debug_register_code();
|
__jit_debug_register_code();
|
||||||
|
|
||||||
__jit_debug_descriptor.action_flag = JIT_NOACTION;
|
desc.action_flag = JIT_NOACTION;
|
||||||
__jit_debug_descriptor.relevant_entry = ptr::null_mut();
|
desc.relevant_entry = ptr::null_mut();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn unregister_gdb_jit_image(entry: *mut JITCodeEntry) {
|
unsafe fn unregister_gdb_jit_image(entry: *mut JITCodeEntry) {
|
||||||
let _lock = GDB_REGISTRATION.lock().unwrap();
|
let _lock = GDB_REGISTRATION.lock().unwrap();
|
||||||
|
let desc = &mut *wasmtime_jit_debug_descriptor();
|
||||||
|
|
||||||
// Remove the code entry corresponding to the code from the linked list.
|
// Remove the code entry corresponding to the code from the linked list.
|
||||||
if !(*entry).prev_entry.is_null() {
|
if !(*entry).prev_entry.is_null() {
|
||||||
(*(*entry).prev_entry).next_entry = (*entry).next_entry;
|
(*(*entry).prev_entry).next_entry = (*entry).next_entry;
|
||||||
} else {
|
} else {
|
||||||
__jit_debug_descriptor.first_entry = (*entry).next_entry;
|
desc.first_entry = (*entry).next_entry;
|
||||||
}
|
}
|
||||||
if !(*entry).next_entry.is_null() {
|
if !(*entry).next_entry.is_null() {
|
||||||
(*(*entry).next_entry).prev_entry = (*entry).prev_entry;
|
(*(*entry).next_entry).prev_entry = (*entry).prev_entry;
|
||||||
}
|
}
|
||||||
// Point the relevant_entry field of the descriptor at the code entry.
|
// Point the relevant_entry field of the descriptor at the code entry.
|
||||||
__jit_debug_descriptor.relevant_entry = entry;
|
desc.relevant_entry = entry;
|
||||||
// Set action_flag to JIT_UNREGISTER and call __jit_debug_register_code.
|
// Set action_flag to JIT_UNREGISTER and call __jit_debug_register_code.
|
||||||
__jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN;
|
desc.action_flag = JIT_UNREGISTER_FN;
|
||||||
__jit_debug_register_code();
|
__jit_debug_register_code();
|
||||||
|
|
||||||
__jit_debug_descriptor.action_flag = JIT_NOACTION;
|
desc.action_flag = JIT_NOACTION;
|
||||||
__jit_debug_descriptor.relevant_entry = ptr::null_mut();
|
desc.relevant_entry = ptr::null_mut();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ path = "fuzz_targets/differential_wasmi.rs"
|
|||||||
test = false
|
test = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "differential_v8"
|
||||||
|
path = "fuzz_targets/differential_v8.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "spectests"
|
name = "spectests"
|
||||||
path = "fuzz_targets/spectests.rs"
|
path = "fuzz_targets/spectests.rs"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ static EXECUTED: AtomicUsize = AtomicUsize::new(0);
|
|||||||
|
|
||||||
fuzz_target!(|data: (
|
fuzz_target!(|data: (
|
||||||
generators::Config,
|
generators::Config,
|
||||||
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig>
|
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig<false, false>>
|
||||||
)| {
|
)| {
|
||||||
let (config, mut wasm) = data;
|
let (config, mut wasm) = data;
|
||||||
wasm.module.ensure_termination(1000);
|
wasm.module.ensure_termination(1000);
|
||||||
|
|||||||
13
fuzz/fuzz_targets/differential_v8.rs
Normal file
13
fuzz/fuzz_targets/differential_v8.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use wasmtime_fuzzing::{generators, oracles};
|
||||||
|
|
||||||
|
fuzz_target!(|data: (
|
||||||
|
generators::Config,
|
||||||
|
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig<true, true>>
|
||||||
|
)| {
|
||||||
|
let (config, mut wasm) = data;
|
||||||
|
wasm.module.ensure_termination(1000);
|
||||||
|
oracles::differential_v8_execution(&wasm.module.to_bytes(), &config);
|
||||||
|
});
|
||||||
@@ -5,7 +5,7 @@ use wasmtime_fuzzing::{generators, oracles};
|
|||||||
|
|
||||||
fuzz_target!(|data: (
|
fuzz_target!(|data: (
|
||||||
generators::Config,
|
generators::Config,
|
||||||
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig>
|
wasm_smith::ConfiguredModule<oracles::SingleFunctionModuleConfig<false, false>>
|
||||||
)| {
|
)| {
|
||||||
let (config, mut wasm) = data;
|
let (config, mut wasm) = data;
|
||||||
wasm.module.ensure_termination(1000);
|
wasm.module.ensure_termination(1000);
|
||||||
|
|||||||
Reference in New Issue
Block a user