Implemented protected call and floating point traps; passing all spectests!

This commit is contained in:
losfair 2019-03-17 10:27:14 +08:00
parent 1f8c644855
commit f8fe999015
7 changed files with 497 additions and 20 deletions

View File

@ -14,3 +14,5 @@ dynasm = "0.3.0"
dynasmrt = "0.3.1"
lazy_static = "1.2.0"
byteorder = "1"
nix = "0.13.0"
libc = "0.2.49"

View File

@ -9,6 +9,7 @@ use wasmer_runtime_core::{
use wasmparser::{Operator, Type as WpType};
pub trait ModuleCodeGenerator<FCG: FunctionCodeGenerator, PC: ProtectedCaller, FR: FuncResolver> {
fn check_precondition(&mut self, module_info: &ModuleInfo) -> Result<(), CodegenError>;
fn next_function(&mut self) -> Result<&mut FCG, CodegenError>;
fn finalize(self, module_info: &ModuleInfo) -> Result<(PC, FR), CodegenError>;
fn feed_signatures(

View File

@ -24,6 +24,7 @@ use wasmer_runtime_core::{
vm::{self, ImportBacking, LocalGlobal, LocalTable, LocalMemory},
};
use wasmparser::{Operator, Type as WpType};
use crate::protect_unix;
thread_local! {
static CURRENT_EXECUTION_CONTEXT: RefCell<Vec<*const X64ExecutionContext>> = RefCell::new(Vec::new());
@ -384,6 +385,7 @@ impl ProtectedCaller for X64ExecutionContext {
CURRENT_EXECUTION_CONTEXT.with(|x| x.borrow_mut().push(self));
let ret = unsafe {
protect_unix::call_protected(|| {
CALL_WASM(
param_buf.as_ptr(),
param_buf.len(),
@ -391,10 +393,13 @@ impl ProtectedCaller for X64ExecutionContext {
memory_base,
_vmctx,
)
})
};
CURRENT_EXECUTION_CONTEXT.with(|x| x.borrow_mut().pop().unwrap());
let ret = ret?;
Ok(if let Some(ty) = return_ty {
vec![match ty {
WpType::I32 => Value::I32(ret as i32),
@ -524,6 +529,19 @@ impl X64ModuleCodeGenerator {
}
impl ModuleCodeGenerator<X64FunctionCode, X64ExecutionContext, X64RuntimeResolver> for X64ModuleCodeGenerator {
fn check_precondition(&mut self, module_info: &ModuleInfo) -> Result<(), CodegenError> {
for mem in module_info.memories.iter().map(|(_, v)| v).chain(module_info.imported_memories.iter().map(|(_, v)| &v.1)) {
match mem.memory_type() {
MemoryType::Dynamic => return Err(CodegenError {
message: "dynamic memory isn't supported yet"
}),
_ => {}
}
}
Ok(())
}
fn next_function(&mut self) -> Result<&mut X64FunctionCode, CodegenError> {
let (mut assembler, mut function_labels, br_table_data) = match self.functions.last_mut() {
Some(x) => (
@ -1428,6 +1446,98 @@ impl X64FunctionCode {
Ok(())
}
fn emit_f32_int_conv_check(
assembler: &mut Assembler,
reg: Register,
lower_bound: f32,
upper_bound: f32,
) {
let lower_bound = f32::to_bits(lower_bound);
let upper_bound = f32::to_bits(upper_bound);
dynasm!(
assembler
; movq xmm5, r15
// underflow
; movd xmm1, Rd(reg as u8)
; mov r15d, lower_bound as i32
; movd xmm2, r15d
; vcmpltss xmm0, xmm1, xmm2
; movd r15d, xmm0
; cmp r15d, 1
; je >trap
// overflow
; mov r15d, upper_bound as i32
; movd xmm2, r15d
; vcmpgtss xmm0, xmm1, xmm2
; movd r15d, xmm0
; cmp r15d, 1
; je >trap
// NaN
; vcmpeqss xmm0, xmm1, xmm1
; movd r15d, xmm0
; cmp r15d, 0
; je >trap
; movq r15, xmm5
; jmp >ok
; trap:
; ud2
; ok:
);
}
fn emit_f64_int_conv_check(
assembler: &mut Assembler,
reg: Register,
lower_bound: f64,
upper_bound: f64,
) {
let lower_bound = f64::to_bits(lower_bound);
let upper_bound = f64::to_bits(upper_bound);
dynasm!(
assembler
; movq xmm5, r15
// underflow
; movq xmm1, Rq(reg as u8)
; mov r15, QWORD lower_bound as i64
; movq xmm2, r15
; vcmpltsd xmm0, xmm1, xmm2
; movd r15d, xmm0
; cmp r15d, 1
; je >trap
// overflow
; mov r15, QWORD upper_bound as i64
; movq xmm2, r15
; vcmpgtsd xmm0, xmm1, xmm2
; movd r15d, xmm0
; cmp r15d, 1
; je >trap
// NaN
; vcmpeqsd xmm0, xmm1, xmm1
; movd r15d, xmm0
; cmp r15d, 0
; je >trap
; movq r15, xmm5
; jmp >ok
; trap:
; ud2
; ok:
);
}
fn emit_native_call_trampoline<A: Copy + Sized, B: Copy + Sized>(
assembler: &mut Assembler,
target: unsafe extern "C" fn(
@ -4137,11 +4247,17 @@ impl FunctionCodeGenerator for X64FunctionCode {
WpType::F32
)?;
}
Operator::I32TruncUF32 | Operator::I32TruncSF32 => {
Operator::I32TruncUF32 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f32_int_conv_check(
assembler,
reg,
-1.0,
4294967296.0,
);
dynasm!(
assembler
; movd xmm1, Rd(reg as u8)
@ -4153,11 +4269,61 @@ impl FunctionCodeGenerator for X64FunctionCode {
WpType::I32
)?;
}
Operator::I64TruncUF32 | Operator::I64TruncSF32 => {
Operator::I32TruncSF32 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f32_int_conv_check(
assembler,
reg,
-2147483904.0,
2147483648.0
);
dynasm!(
assembler
; movd xmm1, Rd(reg as u8)
; roundss xmm1, xmm1, 3
; cvtss2si Rd(reg as u8), xmm1
);
},
WpType::F32,
WpType::I32
)?;
}
Operator::I64TruncUF32 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f32_int_conv_check(
assembler,
reg,
-1.0,
18446744073709551616.0,
);
dynasm!(
assembler
; movd xmm1, Rd(reg as u8)
; roundss xmm1, xmm1, 3
; cvtss2si Rq(reg as u8), xmm1
);
},
WpType::F32,
WpType::I64
)?;
}
Operator::I64TruncSF32 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f32_int_conv_check(
assembler,
reg,
-9223373136366403584.0,
9223372036854775808.0,
);
dynasm!(
assembler
; movd xmm1, Rd(reg as u8)
@ -4514,11 +4680,18 @@ impl FunctionCodeGenerator for X64FunctionCode {
WpType::F64
)?;
}
Operator::I32TruncUF64 | Operator::I32TruncSF64 => {
Operator::I32TruncUF64 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f64_int_conv_check(
assembler,
reg,
-1.0,
4294967296.0,
);
dynasm!(
assembler
; movq xmm1, Rq(reg as u8)
@ -4530,11 +4703,64 @@ impl FunctionCodeGenerator for X64FunctionCode {
WpType::I32
)?;
}
Operator::I64TruncUF64 | Operator::I64TruncSF64 => {
Operator::I32TruncSF64 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f64_int_conv_check(
assembler,
reg,
-2147483649.0,
2147483648.0,
);
dynasm!(
assembler
; movq xmm1, Rq(reg as u8)
; roundsd xmm1, xmm1, 3
; cvtsd2si Rd(reg as u8), xmm1
);
},
WpType::F64,
WpType::I32
)?;
}
Operator::I64TruncUF64 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f64_int_conv_check(
assembler,
reg,
-1.0,
18446744073709551616.0,
);
dynasm!(
assembler
; movq xmm1, Rq(reg as u8)
; roundsd xmm1, xmm1, 3
; cvtsd2si Rq(reg as u8), xmm1
);
},
WpType::F64,
WpType::I64
)?;
}
Operator::I64TruncSF64 => {
Self::emit_unop(
assembler,
&mut self.value_stack,
|assembler, value_stack, reg| {
Self::emit_f64_int_conv_check(
assembler,
reg,
-9223372036854777856.0,
9223372036854775808.0,
);
dynasm!(
assembler
; movq xmm1, Rq(reg as u8)
@ -4553,7 +4779,8 @@ impl FunctionCodeGenerator for X64FunctionCode {
LocalOrImport::Local(local_mem_index) => {
let mem_desc = &module_info.memories[local_mem_index];
match mem_desc.memory_type() {
MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_local,
//MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_local,
MemoryType::Dynamic => unimplemented!(),
MemoryType::Static => self.native_trampolines.memory_size_static_local,
MemoryType::SharedStatic => self.native_trampolines.memory_size_shared_local,
}
@ -4561,7 +4788,8 @@ impl FunctionCodeGenerator for X64FunctionCode {
LocalOrImport::Import(import_mem_index) => {
let mem_desc = &module_info.imported_memories[import_mem_index].1;
match mem_desc.memory_type() {
MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_import,
//MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_import,
MemoryType::Dynamic => unimplemented!(),
MemoryType::Static => self.native_trampolines.memory_size_static_import,
MemoryType::SharedStatic => self.native_trampolines.memory_size_shared_import,
}
@ -4581,7 +4809,8 @@ impl FunctionCodeGenerator for X64FunctionCode {
LocalOrImport::Local(local_mem_index) => {
let mem_desc = &module_info.memories[local_mem_index];
match mem_desc.memory_type() {
MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_local,
//MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_local,
MemoryType::Dynamic => unimplemented!(),
MemoryType::Static => self.native_trampolines.memory_grow_static_local,
MemoryType::SharedStatic => self.native_trampolines.memory_grow_shared_local,
}
@ -4589,7 +4818,8 @@ impl FunctionCodeGenerator for X64FunctionCode {
LocalOrImport::Import(import_mem_index) => {
let mem_desc = &module_info.imported_memories[import_mem_index].1;
match mem_desc.memory_type() {
MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_import,
//MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_import,
MemoryType::Dynamic => unimplemented!(),
MemoryType::Static => self.native_trampolines.memory_grow_static_import,
MemoryType::SharedStatic => self.native_trampolines.memory_grow_shared_import,
}
@ -4725,13 +4955,20 @@ unsafe extern "C" fn call_indirect(
CallIndirectLocalOrImport::Import => &*(*(*vmctx).imported_tables),
} ;
if elem_index >= table.count as usize {
panic!("element index out of bounds");
eprintln!("element index out of bounds");
unsafe { protect_unix::trigger_trap(); }
}
let anyfunc = &*(table.base as *mut vm::Anyfunc).offset(elem_index as isize);
let ctx: &X64ExecutionContext =
&*CURRENT_EXECUTION_CONTEXT.with(|x| *x.borrow().last().unwrap());
let func_index = anyfunc.func_index.unwrap();
let func_index = match anyfunc.func_index {
Some(x) => x,
None => {
eprintln!("empty table entry");
unsafe { protect_unix::trigger_trap(); }
}
};
/*println!(
"SIG INDEX = {}, FUNC INDEX = {:?}, ELEM INDEX = {}",
@ -4741,7 +4978,8 @@ unsafe extern "C" fn call_indirect(
if ctx.signatures[SigIndex::new(sig_index)]
!= ctx.signatures[ctx.function_signatures[func_index]]
{
panic!("signature mismatch");
eprintln!("signature mismatch");
unsafe { protect_unix::trigger_trap(); }
}
let func = ctx.function_pointers[func_index.index() as usize].0;

View File

@ -1,5 +1,11 @@
#![feature(proc_macro_hygiene)]
#[cfg(not(any(
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "x86_64"),
)))]
compile_error!("This crate doesn't yet support compiling on operating systems other than linux and macos and architectures other than x86_64");
#[macro_use]
extern crate dynasmrt;
@ -15,6 +21,7 @@ mod codegen;
mod codegen_x64;
mod parse;
mod stack;
mod protect_unix;
use crate::codegen::{CodegenError, ModuleCodeGenerator};
use crate::parse::LoadError;

View File

@ -18,6 +18,7 @@ use wasmparser::{
BinaryReaderError, CodeSectionReader, Data, DataKind, Element, ElementKind, Export,
ExternalKind, FuncType, Import, ImportSectionEntryType, InitExpr, ModuleReader, Operator,
SectionCode, Type as WpType,
WasmDecoder,
};
#[derive(Debug)]
@ -38,6 +39,30 @@ impl From<CodegenError> for LoadError {
}
}
fn validate(bytes: &[u8]) -> Result<(), LoadError> {
let mut parser = wasmparser::ValidatingParser::new(
bytes,
Some(wasmparser::ValidatingParserConfig {
operator_config: wasmparser::OperatorValidatorConfig {
enable_threads: false,
enable_reference_types: false,
enable_simd: false,
enable_bulk_memory: false,
},
mutable_global_imports: false,
}),
);
loop {
let state = parser.read();
match *state {
wasmparser::ParserState::EndWasm => break Ok(()),
wasmparser::ParserState::Error(err) => Err(LoadError::Parse(err))?,
_ => {}
}
}
}
pub fn read_module<
MCG: ModuleCodeGenerator<FCG, PC, FR>,
FCG: FunctionCodeGenerator,
@ -48,6 +73,7 @@ pub fn read_module<
backend: Backend,
mcg: &mut MCG,
) -> Result<ModuleInfo, LoadError> {
validate(wasm)?;
let mut info = ModuleInfo {
memories: Map::new(),
globals: Map::new(),
@ -279,6 +305,7 @@ pub fn read_module<
}
.into());
}
mcg.check_precondition(&info)?;
for i in 0..code_reader.get_count() {
let item = code_reader.read()?;
let mut fcg = mcg.next_function()?;

View File

@ -0,0 +1,202 @@
//! Installing signal handlers allows us to handle traps and out-of-bounds memory
//! accesses that occur when runniing webassembly.
//!
//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622
//!
//! When a WebAssembly module triggers any traps, we perform recovery here.
//!
//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
//! unless you have memory unsafety elsewhere in your code.
//!
use libc::{c_int, c_void, siginfo_t};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
};
use std::cell::{Cell, UnsafeCell};
use std::ptr;
use std::sync::Once;
use wasmer_runtime_core::error::{RuntimeError, RuntimeResult};
extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
siginfo: *mut siginfo_t,
ucontext: *mut c_void,
) {
unsafe {
do_unwind(signum, siginfo as _, ucontext);
}
}
extern "C" {
pub fn setjmp(env: *mut c_void) -> c_int;
fn longjmp(env: *mut c_void, val: c_int) -> !;
}
pub unsafe fn install_sighandler() {
let sa = SigAction::new(
SigHandler::SigAction(signal_trap_handler),
SaFlags::SA_ONSTACK,
SigSet::empty(),
);
sigaction(SIGFPE, &sa).unwrap();
sigaction(SIGILL, &sa).unwrap();
sigaction(SIGSEGV, &sa).unwrap();
sigaction(SIGBUS, &sa).unwrap();
}
const SETJMP_BUFFER_LEN: usize = 27;
pub static SIGHANDLER_INIT: Once = Once::new();
thread_local! {
pub static SETJMP_BUFFER: UnsafeCell<[c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
pub static CAUGHT_ADDRESSES: Cell<(*const c_void, *const c_void)> = Cell::new((ptr::null(), ptr::null()));
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
}
pub unsafe fn trigger_trap() -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
longjmp(jmp_buf as *mut c_void, 0)
}
pub fn call_protected<T>(f: impl FnOnce() -> T) -> RuntimeResult<T> {
unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
let prev_jmp_buf = *jmp_buf;
SIGHANDLER_INIT.call_once(|| {
install_sighandler();
});
let signum = setjmp(jmp_buf as *mut _);
if signum != 0 {
*jmp_buf = prev_jmp_buf;
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
Ok(SIGILL) => "illegal instruction",
Ok(SIGSEGV) => "segmentation violation",
Ok(SIGBUS) => "bus error",
Err(_) => "error while getting the Signal",
_ => "unkown trapped signal",
};
// When the trap-handler is fully implemented, this will return more information.
Err(RuntimeError::Trap {
msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(),
}
.into())
} else {
let ret = f(); // TODO: Switch stack?
*jmp_buf = prev_jmp_buf;
Ok(ret)
}
}
}
/// Unwinds to last protected_call.
pub unsafe fn do_unwind(signum: i32, siginfo: *const c_void, ucontext: *const c_void) -> ! {
// Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.)
// itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should
// temporarily disable the signal handlers to debug it.
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get());
if *jmp_buf == [0; SETJMP_BUFFER_LEN] {
::std::process::abort();
}
CAUGHT_ADDRESSES.with(|cell| cell.set(get_faulting_addr_and_ip(siginfo, ucontext)));
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip(
siginfo: *const c_void,
ucontext: *const c_void,
) -> (*const c_void, *const c_void) {
use libc::{ucontext_t, RIP};
#[allow(dead_code)]
#[repr(C)]
struct siginfo_t {
si_signo: i32,
si_errno: i32,
si_code: i32,
si_addr: u64,
// ...
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*ucontext).uc_mcontext.gregs[RIP as usize];
(si_addr as _, rip as _)
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip(
siginfo: *const c_void,
ucontext: *const c_void,
) -> (*const c_void, *const c_void) {
#[allow(dead_code)]
#[repr(C)]
struct ucontext_t {
uc_onstack: u32,
uc_sigmask: u32,
uc_stack: libc::stack_t,
uc_link: *const ucontext_t,
uc_mcsize: u64,
uc_mcontext: *const mcontext_t,
}
#[repr(C)]
struct exception_state {
trapno: u16,
cpu: u16,
err: u32,
faultvaddr: u64,
}
#[repr(C)]
struct regs {
rax: u64,
rbx: u64,
rcx: u64,
rdx: u64,
rdi: u64,
rsi: u64,
rbp: u64,
rsp: u64,
r8: u64,
r9: u64,
r10: u64,
r11: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
rip: u64,
rflags: u64,
cs: u64,
fs: u64,
gs: u64,
}
#[allow(dead_code)]
#[repr(C)]
struct mcontext_t {
es: exception_state,
ss: regs,
// ...
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*(*ucontext).uc_mcontext).ss.rip;
(si_addr, rip as _)
}

View File

@ -251,8 +251,8 @@ impl MemoryDescriptor {
pub fn memory_type(self) -> MemoryType {
match (self.maximum.is_some(), self.shared) {
(true, true) => MemoryType::SharedStatic,
(true, false) => MemoryType::Static,
(false, false) => MemoryType::Dynamic,
(true, false) | (false, false) => MemoryType::Static,
//(false, false) => MemoryType::Dynamic,
(false, true) => panic!("shared memory without a max is not allowed"),
}
}