diff --git a/lib/runtime-core/image-loading-linux-x86-64.s b/lib/runtime-core/image-loading-linux-x86-64.s index e9893968c..c357d8354 100644 --- a/lib/runtime-core/image-loading-linux-x86-64.s +++ b/lib/runtime-core/image-loading-linux-x86-64.s @@ -1,3 +1,5 @@ +# NOTE: Keep this consistent with `alternative_stack.rs`. + .globl run_on_alternative_stack run_on_alternative_stack: # (stack_end, stack_begin) diff --git a/lib/runtime-core/image-loading-macos-x86-64.s b/lib/runtime-core/image-loading-macos-x86-64.s index d2f5f761c..64e279f1f 100644 --- a/lib/runtime-core/image-loading-macos-x86-64.s +++ b/lib/runtime-core/image-loading-macos-x86-64.s @@ -1,3 +1,5 @@ +# NOTE: Keep this consistent with `alternative_stack.rs`. + .globl _run_on_alternative_stack _run_on_alternative_stack: # (stack_end, stack_begin) diff --git a/lib/runtime-core/src/alternative_stack.rs b/lib/runtime-core/src/alternative_stack.rs index 8b06b0176..aa44eb1de 100644 --- a/lib/runtime-core/src/alternative_stack.rs +++ b/lib/runtime-core/src/alternative_stack.rs @@ -1,17 +1,69 @@ mod raw { + use std::ffi::c_void; + extern "C" { pub fn run_on_alternative_stack( stack_end: *mut u64, stack_begin: *mut u64, userdata_arg2: *mut u8, ) -> u64; + pub fn setjmp(env: *mut c_void) -> i32; + pub fn longjmp(env: *mut c_void, val: i32) -> !; } } +use crate::state::x64::{read_stack, X64Register, GPR}; +use crate::suspend; +use crate::vm; +use libc::siginfo_t; +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGSEGV, + SIGTRAP, +}; +use std::any::Any; +use std::cell::UnsafeCell; +use std::ffi::c_void; +use std::process; +use std::sync::Once; + pub(crate) unsafe fn run_on_alternative_stack(stack_end: *mut u64, stack_begin: *mut u64) -> u64 { raw::run_on_alternative_stack(stack_end, stack_begin, ::std::ptr::null_mut()) } +const SETJMP_BUFFER_LEN: usize = 27; +type SetJmpBuffer = [i32; SETJMP_BUFFER_LEN]; + +thread_local! { + static UNWIND: UnsafeCell>)>> = UnsafeCell::new(None); +} + +pub unsafe fn catch_unsafe_unwind R>(f: F) -> Result> { + let unwind = UNWIND.with(|x| x.get()); + let old = (*unwind).take(); + *unwind = Some(([0; SETJMP_BUFFER_LEN], None)); + + if raw::setjmp(&mut (*unwind).as_mut().unwrap().0 as *mut SetJmpBuffer as *mut _) != 0 { + // error + let ret = (*unwind).as_mut().unwrap().1.take().unwrap(); + *unwind = old; + Err(ret) + } else { + let ret = f(); + // implicit control flow to the error case... + *unwind = old; + Ok(ret) + } +} + +pub unsafe fn begin_unsafe_unwind(e: Box) -> ! { + let unwind = UNWIND.with(|x| x.get()); + let inner = (*unwind) + .as_mut() + .expect("not within a catch_unsafe_unwind scope"); + inner.1 = Some(e); + raw::longjmp(&mut inner.0 as *mut SetJmpBuffer as *mut _, 0xffff); +} + pub fn allocate_and_run R>(size: usize, f: F) -> R { struct Context R, R> { f: Option, @@ -32,9 +84,11 @@ pub fn allocate_and_run R>(size: usize, f: F) -> R { assert!(size >= 4096); let mut stack: Vec = vec![0; size / 8]; - let mut end_offset = stack.len(); + let end_offset = stack.len(); stack[end_offset - 4] = invoke:: as usize as u64; + + // NOTE: Keep this consistent with `image-loading-*.s`. let stack_begin = stack.as_mut_ptr().offset((end_offset - 4 - 6) as isize); let stack_end = stack.as_mut_ptr().offset(end_offset as isize); @@ -46,3 +100,230 @@ pub fn allocate_and_run R>(size: usize, f: F) -> R { ctx.ret.take().unwrap() } } + +extern "C" fn signal_trap_handler( + _signum: ::nix::libc::c_int, + siginfo: *mut siginfo_t, + ucontext: *mut c_void, +) { + unsafe { + let fault = get_fault_info(siginfo as _, ucontext); + + allocate_and_run(65536, || { + // TODO: make this safer + let ctx = &*(fault.known_registers[X64Register::GPR(GPR::R15).to_index().0].unwrap() + as *mut vm::Ctx); + let rsp = fault.known_registers[X64Register::GPR(GPR::RSP).to_index().0].unwrap(); + + let msm = (*ctx.module) + .runnable_module + .get_module_state_map() + .unwrap(); + let code_base = (*ctx.module).runnable_module.get_code().unwrap().as_ptr() as usize; + let image = read_stack( + &msm, + code_base, + rsp as usize as *const u64, + fault.known_registers, + Some(fault.ip as usize as u64), + ); + + use colored::*; + eprintln!( + "\n{}", + "Wasmer encountered an error while running your WebAssembly program." + .bold() + .red() + ); + image.print_backtrace_if_needed(); + }); + + begin_unsafe_unwind(Box::new(())); + } +} + +extern "C" fn sigint_handler( + _signum: ::nix::libc::c_int, + _siginfo: *mut siginfo_t, + _ucontext: *mut c_void, +) { + if suspend::get_interrupted() { + eprintln!( + "Got another SIGINT before interrupt is handled by WebAssembly program, aborting" + ); + process::abort(); + } + suspend::set_interrupted(true); + eprintln!("Notified WebAssembly program to exit"); +} + +pub fn ensure_sighandler() { + INSTALL_SIGHANDLER.call_once(|| unsafe { + install_sighandler(); + }); +} + +static INSTALL_SIGHANDLER: Once = Once::new(); + +unsafe fn install_sighandler() { + let sa_trap = SigAction::new( + SigHandler::SigAction(signal_trap_handler), + SaFlags::SA_ONSTACK, + SigSet::empty(), + ); + sigaction(SIGFPE, &sa_trap).unwrap(); + sigaction(SIGILL, &sa_trap).unwrap(); + sigaction(SIGSEGV, &sa_trap).unwrap(); + sigaction(SIGBUS, &sa_trap).unwrap(); + sigaction(SIGTRAP, &sa_trap).unwrap(); + + let sa_interrupt = SigAction::new( + SigHandler::SigAction(sigint_handler), + SaFlags::SA_ONSTACK, + SigSet::empty(), + ); + sigaction(SIGINT, &sa_interrupt).unwrap(); +} + +pub struct FaultInfo { + pub faulting_addr: *const c_void, + pub ip: *const c_void, + pub known_registers: [Option; 24], +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo { + use libc::{ + ucontext_t, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9, REG_RAX, + REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP, + }; + + #[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 gregs = &(*ucontext).uc_mcontext.gregs; + + let mut known_registers: [Option; 24] = [None; 24]; + known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs[REG_R15 as usize] as _); + known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs[REG_R14 as usize] as _); + known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs[REG_R13 as usize] as _); + known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(gregs[REG_R12 as usize] as _); + known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(gregs[REG_R11 as usize] as _); + known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(gregs[REG_R10 as usize] as _); + known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(gregs[REG_R9 as usize] as _); + known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(gregs[REG_R8 as usize] as _); + known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(gregs[REG_RSI as usize] as _); + known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(gregs[REG_RDI as usize] as _); + known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(gregs[REG_RDX as usize] as _); + known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(gregs[REG_RCX as usize] as _); + known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(gregs[REG_RBX as usize] as _); + known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(gregs[REG_RAX as usize] as _); + + known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs[REG_RBP as usize] as _); + known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs[REG_RSP as usize] as _); + + // TODO: XMM registers + + FaultInfo { + faulting_addr: si_addr as usize as _, + ip: gregs[REG_RIP as usize] as _, + known_registers, + } +} + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo { + #[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 ss = &(*(*ucontext).uc_mcontext).ss; + + let mut known_registers: [Option; 24] = [None; 24]; + + known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(ss.r15); + known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(ss.r14); + known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(ss.r13); + known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(ss.r12); + known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(ss.r11); + known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(ss.r10); + known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(ss.r9); + known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(ss.r8); + known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(ss.rsi); + known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(ss.rdi); + known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(ss.rdx); + known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(ss.rcx); + known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(ss.rbx); + known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(ss.rax); + + known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(ss.rbp); + known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(ss.rsp); + + // TODO: XMM registers + + FaultInfo { + faulting_addr: si_addr, + ip: ss.rip as _, + known_registers, + } +} diff --git a/lib/runtime-core/src/state.rs b/lib/runtime-core/src/state.rs index 9b697d083..195303383 100644 --- a/lib/runtime-core/src/state.rs +++ b/lib/runtime-core/src/state.rs @@ -226,14 +226,6 @@ impl MachineStateDiff { } impl ExecutionStateImage { - pub fn from_bytes(input: &[u8]) -> Option { - use bincode::deserialize; - match deserialize(input) { - Ok(x) => Some(x), - Err(_) => None, - } - } - pub fn print_backtrace_if_needed(&self) { use std::env; @@ -319,6 +311,21 @@ impl ExecutionStateImage { } } +impl InstanceImage { + pub fn from_bytes(input: &[u8]) -> Option { + use bincode::deserialize; + match deserialize(input) { + Ok(x) => Some(x), + Err(_) => None, + } + } + + pub fn to_bytes(&self) -> Vec { + use bincode::serialize; + serialize(self).unwrap() + } +} + #[cfg(all(unix, target_arch = "x86_64"))] pub mod x64 { use super::*; @@ -341,19 +348,20 @@ pub mod x64 { pub unsafe fn invoke_call_return_on_stack_raw_image( msm: &ModuleStateMap, code_base: usize, - image: &[u8], + image_raw: Vec, vmctx: &mut Ctx, ) -> u64 { use bincode::deserialize; - let image: InstanceImage = deserialize(image).unwrap(); - invoke_call_return_on_stack(msm, code_base, &image, vmctx) + let image: InstanceImage = deserialize(&image_raw).unwrap(); + drop(image_raw); // free up memory + invoke_call_return_on_stack(msm, code_base, image, vmctx) } #[warn(unused_variables)] pub unsafe fn invoke_call_return_on_stack( msm: &ModuleStateMap, code_base: usize, - image: &InstanceImage, + image: InstanceImage, vmctx: &mut Ctx, ) -> u64 { let mut stack: Vec = vec![0; 1048576 * 8 / 8]; // 8MB stack @@ -366,7 +374,7 @@ pub mod x64 { let mut known_registers: [Option; 24] = [None; 24]; let local_functions_vec: Vec<&FunctionStateMap> = - msm.local_functions.iter().map(|(k, v)| v).collect(); + msm.local_functions.iter().map(|(_, v)| v).collect(); // Bottom to top for f in image.execution_state.frames.iter().rev() { @@ -513,6 +521,8 @@ pub mod x64 { image.globals[i]; } + drop(image); // free up host memory + run_on_alternative_stack( stack.as_mut_ptr().offset(stack.len() as isize), stack.as_mut_ptr().offset(stack_offset as isize), @@ -649,7 +659,7 @@ pub mod x64 { known_registers[idx.0] = Some(*stack); stack = stack.offset(1); } - MachineValue::CopyStackBPRelative(offset) => { + MachineValue::CopyStackBPRelative(_) => { stack = stack.offset(1); } MachineValue::WasmStack(idx) => { diff --git a/lib/runtime-core/src/suspend.rs b/lib/runtime-core/src/suspend.rs index 955df1cf6..e3b133beb 100644 --- a/lib/runtime-core/src/suspend.rs +++ b/lib/runtime-core/src/suspend.rs @@ -1,76 +1,77 @@ +use crate::alternative_stack::begin_unsafe_unwind; use crate::import::{ImportObject, Namespace}; use crate::trampoline::{CallContext, TrampolineBuffer, TrampolineBufferBuilder}; use crate::vm::Ctx; -use bincode::serialize; -use std::ffi::c_void; -use std::fs::File; -use std::io::Write; use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; -pub struct SuspendConfig { - pub image_path: String, +static INTERRUPTED: AtomicBool = AtomicBool::new(false); + +pub fn set_interrupted(x: bool) { + INTERRUPTED.store(x, Ordering::SeqCst); +} + +pub fn get_interrupted() -> bool { + INTERRUPTED.load(Ordering::SeqCst) +} + +pub fn get_and_reset_interrupted() -> bool { + INTERRUPTED.swap(false, Ordering::SeqCst) } struct ImportContext { - next: Option<(*mut c_void, fn(*mut c_void))>, - trampolines: Rc, - config: Rc, + _trampolines: Rc, } impl ImportContext { - fn new(trampolines: Rc, config: Rc) -> ImportContext { + fn new(trampolines: Rc) -> ImportContext { ImportContext { - trampolines, - next: None, - config, + _trampolines: trampolines, } } } -fn destroy_import_context(x: *mut c_void) { - unsafe { - let ctx = Box::from_raw(x as *mut ImportContext); - if let Some(x) = ctx.next { - (x.1)(x.0); - } - } -} - -pub fn patch_import_object(x: &mut ImportObject, config: SuspendConfig) { - let config = Rc::new(config); +pub fn patch_import_object(x: &mut ImportObject) { let mut builder = TrampolineBufferBuilder::new(); - let config_ptr: &SuspendConfig = &*config; - let idx = builder.add_context_rsp_state_preserving_trampoline( - suspend, - config_ptr as *const SuspendConfig as *const CallContext, - ); + let idx_suspend = + builder.add_context_rsp_state_preserving_trampoline(suspend, ::std::ptr::null()); + let idx_check_interrupt = + builder.add_context_rsp_state_preserving_trampoline(check_interrupt, ::std::ptr::null()); let trampolines = builder.build(); let suspend_indirect: fn(&mut Ctx) = - unsafe { ::std::mem::transmute(trampolines.get_trampoline(idx)) }; + unsafe { ::std::mem::transmute(trampolines.get_trampoline(idx_suspend)) }; + let check_interrupt_indirect: fn(&mut Ctx) = + unsafe { ::std::mem::transmute(trampolines.get_trampoline(idx_check_interrupt)) }; let trampolines = Rc::new(trampolines); // FIXME: Memory leak! - ::std::mem::forget(ImportContext::new(trampolines.clone(), config.clone())); + ::std::mem::forget(ImportContext::new(trampolines.clone())); let mut ns = Namespace::new(); ns.insert("suspend", func!(suspend_indirect)); + ns.insert("check_interrupt", func!(check_interrupt_indirect)); x.register("wasmer_suspend", ns); } #[allow(clippy::cast_ptr_alignment)] -unsafe extern "C" fn suspend( - ctx: &mut Ctx, - config_ptr_raw: *const CallContext, - mut stack: *const u64, -) { +unsafe extern "C" fn check_interrupt(ctx: &mut Ctx, _: *const CallContext, stack: *const u64) { + if get_and_reset_interrupted() { + do_suspend(ctx, stack); + } +} + +#[allow(clippy::cast_ptr_alignment)] +unsafe extern "C" fn suspend(ctx: &mut Ctx, _: *const CallContext, stack: *const u64) { + do_suspend(ctx, stack); +} + +unsafe fn do_suspend(ctx: &mut Ctx, mut stack: *const u64) -> ! { use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR}; - { - let config = &*(config_ptr_raw as *const SuspendConfig); - + let image = { let msm = (*ctx.module) .runnable_module .get_module_state_map() @@ -99,11 +100,8 @@ unsafe extern "C" fn suspend( eprintln!("\n{}", "Suspending instance.".green().bold()); } es_image.print_backtrace_if_needed(); - let image = build_instance_image(ctx, es_image); - let image_bin = serialize(&image).unwrap(); - let mut f = File::create(&config.image_path).unwrap(); - f.write_all(&image_bin).unwrap(); - } + build_instance_image(ctx, es_image) + }; - ::std::process::exit(0); + begin_unsafe_unwind(Box::new(image)); } diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index fc0d457d7..76cf452d8 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -180,6 +180,10 @@ where _phantom: PhantomData, } } + + pub fn get_vm_func(&self) -> NonNull { + self.f + } } impl<'a, Args, Rets> Func<'a, Args, Rets, Host> diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 6e7fad317..41922746a 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -23,8 +23,8 @@ use wasmer_runtime_core::{ memory::MemoryType, module::{ModuleInfo, ModuleInner}, state::{ - x64::new_machine_state, x64::X64Register, FunctionStateMap, MachineState, MachineStateDiff, - MachineValue, ModuleStateMap, WasmAbstractValue, + x64::new_machine_state, x64::X64Register, FunctionStateMap, MachineState, MachineValue, + ModuleStateMap, WasmAbstractValue, }, structures::{Map, TypedIndex}, typed_func::Wasm, diff --git a/lib/singlepass-backend/src/protect_unix.rs b/lib/singlepass-backend/src/protect_unix.rs index f17823eac..9854ef458 100644 --- a/lib/singlepass-backend/src/protect_unix.rs +++ b/lib/singlepass-backend/src/protect_unix.rs @@ -9,110 +9,23 @@ //! 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, - SIGTRAP, -}; use std::any::Any; -use std::cell::{Cell, RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell}; use std::collections::HashMap; -use std::ptr; use std::sync::Arc; -use std::sync::Once; -use wasmer_runtime_core::alternative_stack::allocate_and_run; +use wasmer_runtime_core::alternative_stack::{ + begin_unsafe_unwind, catch_unsafe_unwind, ensure_sighandler, +}; use wasmer_runtime_core::codegen::BkptInfo; -use wasmer_runtime_core::state::x64::{read_stack, X64Register, GPR}; use wasmer_runtime_core::typed_func::WasmTrapInfo; -use wasmer_runtime_core::vm; - -extern "C" fn signal_trap_handler( - signum: ::nix::libc::c_int, - siginfo: *mut siginfo_t, - ucontext: *mut c_void, -) { - unsafe { - let fault = get_fault_info(siginfo as _, ucontext); - - match Signal::from_c_int(signum) { - Ok(SIGTRAP) => { - let bkpt_map = BKPT_MAP.with(|x| x.borrow().last().map(|x| x.clone())); - if let Some(bkpt_map) = bkpt_map { - if let Some(ref x) = bkpt_map.get(&(fault.ip as usize)) { - (x)(BkptInfo { throw: throw }); - return; - } - } - } - _ => {} - } - - allocate_and_run(65536, || { - // TODO: make this safer - let ctx = &*(fault.known_registers[X64Register::GPR(GPR::R15).to_index().0].unwrap() - as *mut vm::Ctx); - let rsp = fault.known_registers[X64Register::GPR(GPR::RSP).to_index().0].unwrap(); - - let msm = (*ctx.module) - .runnable_module - .get_module_state_map() - .unwrap(); - let code_base = (*ctx.module).runnable_module.get_code().unwrap().as_ptr() as usize; - let image = self::read_stack( - &msm, - code_base, - rsp as usize as *const u64, - fault.known_registers, - Some(fault.ip as usize as u64), - ); - - use colored::*; - eprintln!( - "\n{}", - "Wasmer encountered an error while running your WebAssembly program." - .bold() - .red() - ); - image.print_backtrace_if_needed(); - }); - - 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(); - sigaction(SIGTRAP, &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_FAULTS: Cell> = Cell::new(None); - pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null()); pub static TRAP_EARLY_DATA: Cell>> = Cell::new(None); pub static BKPT_MAP: RefCell>>>> = RefCell::new(Vec::new()); } pub unsafe fn trigger_trap() -> ! { - let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); - - longjmp(jmp_buf as *mut c_void, 0) + begin_unsafe_unwind(Box::new(())); } pub enum CallProtError { @@ -121,210 +34,22 @@ pub enum CallProtError { } pub fn call_protected(f: impl FnOnce() -> T) -> Result { + ensure_sighandler(); 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; - - if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { - Err(CallProtError::Error(data)) - } else { - // 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", - // _ => "unknown 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()) - Err(CallProtError::Trap(WasmTrapInfo::Unknown)) + let ret = catch_unsafe_unwind(|| f()); + match ret { + Ok(x) => Ok(x), + Err(_) => { + if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) { + Err(CallProtError::Error(data)) + } else { + Err(CallProtError::Trap(WasmTrapInfo::Unknown)) + } } - } else { - let ret = f(); // TODO: Switch stack? - *jmp_buf = prev_jmp_buf; - Ok(ret) } } } pub unsafe fn throw(payload: Box) -> ! { - let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); - if *jmp_buf == [0; SETJMP_BUFFER_LEN] { - ::std::process::abort(); - } - TRAP_EARLY_DATA.with(|cell| cell.replace(Some(payload))); - longjmp(jmp_buf as *mut ::nix::libc::c_void, 0xffff); -} - -/// 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_FAULTS.with(|cell| cell.set(Some(get_fault_info(siginfo, ucontext)))); - - longjmp(jmp_buf as *mut ::nix::libc::c_void, signum) -} - -pub struct FaultInfo { - faulting_addr: *const c_void, - ip: *const c_void, - known_registers: [Option; 24], -} - -#[cfg(all(target_os = "linux", target_arch = "x86_64"))] -unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo { - use libc::{ - ucontext_t, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9, REG_RAX, - REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP, - }; - - #[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 gregs = &(*ucontext).uc_mcontext.gregs; - - let mut known_registers: [Option; 24] = [None; 24]; - known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs[REG_R15 as usize] as _); - known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs[REG_R14 as usize] as _); - known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs[REG_R13 as usize] as _); - known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(gregs[REG_R12 as usize] as _); - known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(gregs[REG_R11 as usize] as _); - known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(gregs[REG_R10 as usize] as _); - known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(gregs[REG_R9 as usize] as _); - known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(gregs[REG_R8 as usize] as _); - known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(gregs[REG_RSI as usize] as _); - known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(gregs[REG_RDI as usize] as _); - known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(gregs[REG_RDX as usize] as _); - known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(gregs[REG_RCX as usize] as _); - known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(gregs[REG_RBX as usize] as _); - known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(gregs[REG_RAX as usize] as _); - - known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs[REG_RBP as usize] as _); - known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs[REG_RSP as usize] as _); - - // TODO: XMM registers - - FaultInfo { - faulting_addr: si_addr as usize as _, - ip: gregs[REG_RIP as usize] as _, - known_registers, - } -} - -#[cfg(all(target_os = "macos", target_arch = "x86_64"))] -unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo { - #[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 ss = &(*(*ucontext).uc_mcontext).ss; - - let mut known_registers: [Option; 24] = [None; 24]; - - known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(ss.r15); - known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(ss.r14); - known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(ss.r13); - known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(ss.r12); - known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(ss.r11); - known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(ss.r10); - known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(ss.r9); - known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(ss.r8); - known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(ss.rsi); - known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(ss.rdi); - known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(ss.rdx); - known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(ss.rcx); - known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(ss.rbx); - known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(ss.rax); - - known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(ss.rbp); - known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(ss.rsp); - - // TODO: XMM registers - - FaultInfo { - faulting_addr: si_addr, - ip: ss.rip as _, - known_registers, - } + begin_unsafe_unwind(payload); } diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index bdd63033e..f8c2d5dbb 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -19,7 +19,6 @@ use wasmer_clif_backend::CraneliftCompiler; use wasmer_llvm_backend::LLVMCompiler; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash, WASMER_VERSION_HASH}, - error::RuntimeError, Func, Value, }; use wasmer_runtime_core::{ @@ -112,13 +111,6 @@ struct Run { )] loader: Option, - #[cfg(feature = "backend:singlepass")] - #[structopt(long = "image-file")] - image_file: Option, - - #[structopt(long = "resume")] - resume: bool, - #[structopt(long = "command-name", hidden = true)] command_name: Option, @@ -158,7 +150,7 @@ impl FromStr for LoaderName { } #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] enum Backend { Cranelift, Singlepass, @@ -511,14 +503,9 @@ fn execute_wasm(options: &Run) -> Result<(), String> { #[cfg(feature = "backend:singlepass")] { - if let Some(ref name) = options.image_file { - use wasmer_runtime_core::suspend::{patch_import_object, SuspendConfig}; - patch_import_object( - &mut import_object, - SuspendConfig { - image_path: name.clone(), - }, - ); + if options.backend == Backend::Singlepass { + use wasmer_runtime_core::suspend::patch_import_object; + patch_import_object(&mut import_object); } } @@ -526,54 +513,81 @@ fn execute_wasm(options: &Run) -> Result<(), String> { .instantiate(&import_object) .map_err(|e| format!("Can't instantiate module: {:?}", e))?; + let start: Func<(), ()> = instance.func("_start").map_err(|e| format!("{:?}", e))?; + #[cfg(feature = "backend:singlepass")] - { - if let Some(ref name) = options.image_file { - if options.resume { - use wasmer_runtime_core::state::x64::invoke_call_return_on_stack_raw_image; - use wasmer_singlepass_backend::protect_unix::call_protected; + unsafe { + if options.backend == Backend::Singlepass { + use wasmer_runtime_core::alternative_stack::{ + catch_unsafe_unwind, ensure_sighandler, + }; + use wasmer_runtime_core::state::{ + x64::invoke_call_return_on_stack, InstanceImage, + }; + use wasmer_runtime_core::vm::Ctx; - let mut file = File::open(name).expect("cannot open image file"); - let mut image: Vec = vec![]; - file.read_to_end(&mut image).unwrap(); + ensure_sighandler(); - let msm = instance - .module - .runnable_module - .get_module_state_map() - .unwrap(); - let code_base = - instance.module.runnable_module.get_code().unwrap().as_ptr() as usize; - call_protected(|| unsafe { - invoke_call_return_on_stack_raw_image( - &msm, - code_base, - &image, - instance.context_mut(), - ); - }) - .map_err(|_| "ERROR") - .unwrap(); + let start_raw: extern "C" fn(&mut Ctx) = + ::std::mem::transmute(start.get_vm_func()); - return Ok(()); + let mut image: Option = None; + loop { + let ret = if let Some(image) = image.take() { + let msm = instance + .module + .runnable_module + .get_module_state_map() + .unwrap(); + let code_base = + instance.module.runnable_module.get_code().unwrap().as_ptr() + as usize; + catch_unsafe_unwind(|| { + invoke_call_return_on_stack( + &msm, + code_base, + image, + instance.context_mut(), + ); + }) + } else { + catch_unsafe_unwind(|| start_raw(instance.context_mut())) + }; + if let Err(e) = ret { + if let Some(new_image) = e.downcast_ref::() { + let op = interactive_shell(InteractiveShellContext { + image: Some(new_image.clone()), + }); + match op { + ShellExitOperation::ContinueWith(new_image) => { + image = Some(new_image); + } + } + } else { + return Err("Error while executing WebAssembly".into()); + } + } else { + return Ok(()); + } } } } - let start: Func<(), ()> = instance.func("_start").map_err(|e| format!("{:?}", e))?; + { + use wasmer_runtime::error::RuntimeError; + let result = start.call(); - let result = start.call(); - - if let Err(ref err) = result { - match err { - RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg), - RuntimeError::Error { data } => { - if let Some(error_code) = data.downcast_ref::() { - std::process::exit(error_code.code as i32) + if let Err(ref err) = result { + match err { + RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg), + RuntimeError::Error { data } => { + if let Some(error_code) = data.downcast_ref::() { + std::process::exit(error_code.code as i32) + } } } + panic!("error: {:?}", err) } - panic!("error: {:?}", err) } } else { let import_object = wasmer_runtime_core::import::ImportObject::new(); @@ -598,6 +612,89 @@ fn execute_wasm(options: &Run) -> Result<(), String> { Ok(()) } +#[cfg(feature = "backend:singlepass")] +struct InteractiveShellContext { + image: Option, +} + +#[cfg(feature = "backend:singlepass")] +#[derive(Debug)] +enum ShellExitOperation { + ContinueWith(wasmer_runtime_core::state::InstanceImage), +} + +#[cfg(feature = "backend:singlepass")] +fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation { + use std::io::Write; + + let mut stdout = ::std::io::stdout(); + let stdin = ::std::io::stdin(); + + loop { + print!("Wasmer> "); + stdout.flush().unwrap(); + let mut line = String::new(); + stdin.read_line(&mut line).unwrap(); + let mut parts = line.split(" ").filter(|x| x.len() > 0).map(|x| x.trim()); + + let cmd = parts.next(); + if cmd.is_none() { + println!("Command required"); + continue; + } + let cmd = cmd.unwrap(); + + match cmd { + "snapshot" => { + let path = parts.next(); + if path.is_none() { + println!("Usage: snapshot [out_path]"); + continue; + } + let path = path.unwrap(); + + if let Some(ref image) = ctx.image { + let buf = image.to_bytes(); + let mut f = match File::create(path) { + Ok(x) => x, + Err(e) => { + println!("Cannot open output file at {}: {:?}", path, e); + continue; + } + }; + if let Err(e) = f.write_all(&buf) { + println!("Cannot write to output file at {}: {:?}", path, e); + continue; + } + println!("Done"); + } else { + println!("Program state not available"); + } + } + "continue" | "c" => { + if let Some(image) = ctx.image.take() { + return ShellExitOperation::ContinueWith(image); + } else { + println!("Program state not available, cannot continue execution"); + } + } + "backtrace" | "bt" => { + if let Some(ref image) = ctx.image { + println!("{}", image.execution_state.colored_output()); + } else { + println!("State not available"); + } + } + "exit" | "quit" => { + exit(0); + } + _ => { + println!("Unknown command: {}", cmd); + } + } + } +} + fn run(options: Run) { match execute_wasm(&options) { Ok(()) => {}