Move more logic into runtime-core and add an interactive shell.

This commit is contained in:
losfair 2019-06-27 00:41:07 +08:00
parent a792ac6a48
commit 63f9818cf6
9 changed files with 525 additions and 406 deletions

View File

@ -1,3 +1,5 @@
# NOTE: Keep this consistent with `alternative_stack.rs`.
.globl run_on_alternative_stack .globl run_on_alternative_stack
run_on_alternative_stack: run_on_alternative_stack:
# (stack_end, stack_begin) # (stack_end, stack_begin)

View File

@ -1,3 +1,5 @@
# NOTE: Keep this consistent with `alternative_stack.rs`.
.globl _run_on_alternative_stack .globl _run_on_alternative_stack
_run_on_alternative_stack: _run_on_alternative_stack:
# (stack_end, stack_begin) # (stack_end, stack_begin)

View File

@ -1,17 +1,69 @@
mod raw { mod raw {
use std::ffi::c_void;
extern "C" { extern "C" {
pub fn run_on_alternative_stack( pub fn run_on_alternative_stack(
stack_end: *mut u64, stack_end: *mut u64,
stack_begin: *mut u64, stack_begin: *mut u64,
userdata_arg2: *mut u8, userdata_arg2: *mut u8,
) -> u64; ) -> 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 { 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()) 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<Option<(SetJmpBuffer, Option<Box<Any>>)>> = UnsafeCell::new(None);
}
pub unsafe fn catch_unsafe_unwind<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<Any>> {
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<Any>) -> ! {
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, F: FnOnce() -> R>(size: usize, f: F) -> R { pub fn allocate_and_run<R, F: FnOnce() -> R>(size: usize, f: F) -> R {
struct Context<F: FnOnce() -> R, R> { struct Context<F: FnOnce() -> R, R> {
f: Option<F>, f: Option<F>,
@ -32,9 +84,11 @@ pub fn allocate_and_run<R, F: FnOnce() -> R>(size: usize, f: F) -> R {
assert!(size >= 4096); assert!(size >= 4096);
let mut stack: Vec<u64> = vec![0; size / 8]; let mut stack: Vec<u64> = vec![0; size / 8];
let mut end_offset = stack.len(); let end_offset = stack.len();
stack[end_offset - 4] = invoke::<F, R> as usize as u64; stack[end_offset - 4] = invoke::<F, R> 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_begin = stack.as_mut_ptr().offset((end_offset - 4 - 6) as isize);
let stack_end = stack.as_mut_ptr().offset(end_offset as isize); let stack_end = stack.as_mut_ptr().offset(end_offset as isize);
@ -46,3 +100,230 @@ pub fn allocate_and_run<R, F: FnOnce() -> R>(size: usize, f: F) -> R {
ctx.ret.take().unwrap() 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<u64>; 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<u64>; 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<u64>; 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,
}
}

View File

@ -226,14 +226,6 @@ impl MachineStateDiff {
} }
impl ExecutionStateImage { impl ExecutionStateImage {
pub fn from_bytes(input: &[u8]) -> Option<ExecutionStateImage> {
use bincode::deserialize;
match deserialize(input) {
Ok(x) => Some(x),
Err(_) => None,
}
}
pub fn print_backtrace_if_needed(&self) { pub fn print_backtrace_if_needed(&self) {
use std::env; use std::env;
@ -319,6 +311,21 @@ impl ExecutionStateImage {
} }
} }
impl InstanceImage {
pub fn from_bytes(input: &[u8]) -> Option<InstanceImage> {
use bincode::deserialize;
match deserialize(input) {
Ok(x) => Some(x),
Err(_) => None,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
use bincode::serialize;
serialize(self).unwrap()
}
}
#[cfg(all(unix, target_arch = "x86_64"))] #[cfg(all(unix, target_arch = "x86_64"))]
pub mod x64 { pub mod x64 {
use super::*; use super::*;
@ -341,19 +348,20 @@ pub mod x64 {
pub unsafe fn invoke_call_return_on_stack_raw_image( pub unsafe fn invoke_call_return_on_stack_raw_image(
msm: &ModuleStateMap, msm: &ModuleStateMap,
code_base: usize, code_base: usize,
image: &[u8], image_raw: Vec<u8>,
vmctx: &mut Ctx, vmctx: &mut Ctx,
) -> u64 { ) -> u64 {
use bincode::deserialize; use bincode::deserialize;
let image: InstanceImage = deserialize(image).unwrap(); let image: InstanceImage = deserialize(&image_raw).unwrap();
invoke_call_return_on_stack(msm, code_base, &image, vmctx) drop(image_raw); // free up memory
invoke_call_return_on_stack(msm, code_base, image, vmctx)
} }
#[warn(unused_variables)] #[warn(unused_variables)]
pub unsafe fn invoke_call_return_on_stack( pub unsafe fn invoke_call_return_on_stack(
msm: &ModuleStateMap, msm: &ModuleStateMap,
code_base: usize, code_base: usize,
image: &InstanceImage, image: InstanceImage,
vmctx: &mut Ctx, vmctx: &mut Ctx,
) -> u64 { ) -> u64 {
let mut stack: Vec<u64> = vec![0; 1048576 * 8 / 8]; // 8MB stack let mut stack: Vec<u64> = vec![0; 1048576 * 8 / 8]; // 8MB stack
@ -366,7 +374,7 @@ pub mod x64 {
let mut known_registers: [Option<u64>; 24] = [None; 24]; let mut known_registers: [Option<u64>; 24] = [None; 24];
let local_functions_vec: Vec<&FunctionStateMap> = 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 // Bottom to top
for f in image.execution_state.frames.iter().rev() { for f in image.execution_state.frames.iter().rev() {
@ -513,6 +521,8 @@ pub mod x64 {
image.globals[i]; image.globals[i];
} }
drop(image); // free up host memory
run_on_alternative_stack( run_on_alternative_stack(
stack.as_mut_ptr().offset(stack.len() as isize), stack.as_mut_ptr().offset(stack.len() as isize),
stack.as_mut_ptr().offset(stack_offset as isize), stack.as_mut_ptr().offset(stack_offset as isize),
@ -649,7 +659,7 @@ pub mod x64 {
known_registers[idx.0] = Some(*stack); known_registers[idx.0] = Some(*stack);
stack = stack.offset(1); stack = stack.offset(1);
} }
MachineValue::CopyStackBPRelative(offset) => { MachineValue::CopyStackBPRelative(_) => {
stack = stack.offset(1); stack = stack.offset(1);
} }
MachineValue::WasmStack(idx) => { MachineValue::WasmStack(idx) => {

View File

@ -1,76 +1,77 @@
use crate::alternative_stack::begin_unsafe_unwind;
use crate::import::{ImportObject, Namespace}; use crate::import::{ImportObject, Namespace};
use crate::trampoline::{CallContext, TrampolineBuffer, TrampolineBufferBuilder}; use crate::trampoline::{CallContext, TrampolineBuffer, TrampolineBufferBuilder};
use crate::vm::Ctx; 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::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct SuspendConfig { static INTERRUPTED: AtomicBool = AtomicBool::new(false);
pub image_path: String,
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 { struct ImportContext {
next: Option<(*mut c_void, fn(*mut c_void))>, _trampolines: Rc<TrampolineBuffer>,
trampolines: Rc<TrampolineBuffer>,
config: Rc<SuspendConfig>,
} }
impl ImportContext { impl ImportContext {
fn new(trampolines: Rc<TrampolineBuffer>, config: Rc<SuspendConfig>) -> ImportContext { fn new(trampolines: Rc<TrampolineBuffer>) -> ImportContext {
ImportContext { ImportContext {
trampolines, _trampolines: trampolines,
next: None,
config,
} }
} }
} }
fn destroy_import_context(x: *mut c_void) { pub fn patch_import_object(x: &mut ImportObject) {
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);
let mut builder = TrampolineBufferBuilder::new(); let mut builder = TrampolineBufferBuilder::new();
let config_ptr: &SuspendConfig = &*config; let idx_suspend =
let idx = builder.add_context_rsp_state_preserving_trampoline( builder.add_context_rsp_state_preserving_trampoline(suspend, ::std::ptr::null());
suspend, let idx_check_interrupt =
config_ptr as *const SuspendConfig as *const CallContext, builder.add_context_rsp_state_preserving_trampoline(check_interrupt, ::std::ptr::null());
);
let trampolines = builder.build(); let trampolines = builder.build();
let suspend_indirect: fn(&mut Ctx) = 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); let trampolines = Rc::new(trampolines);
// FIXME: Memory leak! // FIXME: Memory leak!
::std::mem::forget(ImportContext::new(trampolines.clone(), config.clone())); ::std::mem::forget(ImportContext::new(trampolines.clone()));
let mut ns = Namespace::new(); let mut ns = Namespace::new();
ns.insert("suspend", func!(suspend_indirect)); ns.insert("suspend", func!(suspend_indirect));
ns.insert("check_interrupt", func!(check_interrupt_indirect));
x.register("wasmer_suspend", ns); x.register("wasmer_suspend", ns);
} }
#[allow(clippy::cast_ptr_alignment)] #[allow(clippy::cast_ptr_alignment)]
unsafe extern "C" fn suspend( unsafe extern "C" fn check_interrupt(ctx: &mut Ctx, _: *const CallContext, stack: *const u64) {
ctx: &mut Ctx, if get_and_reset_interrupted() {
config_ptr_raw: *const CallContext, do_suspend(ctx, stack);
mut stack: *const u64, }
) { }
#[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}; use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR};
{ let image = {
let config = &*(config_ptr_raw as *const SuspendConfig);
let msm = (*ctx.module) let msm = (*ctx.module)
.runnable_module .runnable_module
.get_module_state_map() .get_module_state_map()
@ -99,11 +100,8 @@ unsafe extern "C" fn suspend(
eprintln!("\n{}", "Suspending instance.".green().bold()); eprintln!("\n{}", "Suspending instance.".green().bold());
} }
es_image.print_backtrace_if_needed(); es_image.print_backtrace_if_needed();
let image = build_instance_image(ctx, es_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();
}
::std::process::exit(0); begin_unsafe_unwind(Box::new(image));
} }

View File

@ -180,6 +180,10 @@ where
_phantom: PhantomData, _phantom: PhantomData,
} }
} }
pub fn get_vm_func(&self) -> NonNull<vm::Func> {
self.f
}
} }
impl<'a, Args, Rets> Func<'a, Args, Rets, Host> impl<'a, Args, Rets> Func<'a, Args, Rets, Host>

View File

@ -23,8 +23,8 @@ use wasmer_runtime_core::{
memory::MemoryType, memory::MemoryType,
module::{ModuleInfo, ModuleInner}, module::{ModuleInfo, ModuleInner},
state::{ state::{
x64::new_machine_state, x64::X64Register, FunctionStateMap, MachineState, MachineStateDiff, x64::new_machine_state, x64::X64Register, FunctionStateMap, MachineState, MachineValue,
MachineValue, ModuleStateMap, WasmAbstractValue, ModuleStateMap, WasmAbstractValue,
}, },
structures::{Map, TypedIndex}, structures::{Map, TypedIndex},
typed_func::Wasm, typed_func::Wasm,

View File

@ -9,110 +9,23 @@
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here //! 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. //! 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::any::Any;
use std::cell::{Cell, RefCell, UnsafeCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::ptr;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Once; use wasmer_runtime_core::alternative_stack::{
use wasmer_runtime_core::alternative_stack::allocate_and_run; begin_unsafe_unwind, catch_unsafe_unwind, ensure_sighandler,
};
use wasmer_runtime_core::codegen::BkptInfo; 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::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! { thread_local! {
pub static SETJMP_BUFFER: UnsafeCell<[c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]);
pub static CAUGHT_FAULTS: Cell<Option<FaultInfo>> = Cell::new(None);
pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null());
pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any>>> = Cell::new(None); pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any>>> = Cell::new(None);
pub static BKPT_MAP: RefCell<Vec<Arc<HashMap<usize, Box<Fn(BkptInfo) + Send + Sync + 'static>>>>> = RefCell::new(Vec::new()); pub static BKPT_MAP: RefCell<Vec<Arc<HashMap<usize, Box<Fn(BkptInfo) + Send + Sync + 'static>>>>> = RefCell::new(Vec::new());
} }
pub unsafe fn trigger_trap() -> ! { pub unsafe fn trigger_trap() -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); begin_unsafe_unwind(Box::new(()));
longjmp(jmp_buf as *mut c_void, 0)
} }
pub enum CallProtError { pub enum CallProtError {
@ -121,210 +34,22 @@ pub enum CallProtError {
} }
pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, CallProtError> { pub fn call_protected<T>(f: impl FnOnce() -> T) -> Result<T, CallProtError> {
ensure_sighandler();
unsafe { unsafe {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let ret = catch_unsafe_unwind(|| f());
let prev_jmp_buf = *jmp_buf; match ret {
Ok(x) => Ok(x),
SIGHANDLER_INIT.call_once(|| { Err(_) => {
install_sighandler(); if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
}); Err(CallProtError::Error(data))
} else {
let signum = setjmp(jmp_buf as *mut _); Err(CallProtError::Trap(WasmTrapInfo::Unknown))
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))
} }
} else {
let ret = f(); // TODO: Switch stack?
*jmp_buf = prev_jmp_buf;
Ok(ret)
} }
} }
} }
pub unsafe fn throw(payload: Box<dyn Any>) -> ! { pub unsafe fn throw(payload: Box<dyn Any>) -> ! {
let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); begin_unsafe_unwind(payload);
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<u64>; 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<u64>; 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<u64>; 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,
}
} }

View File

@ -19,7 +19,6 @@ use wasmer_clif_backend::CraneliftCompiler;
use wasmer_llvm_backend::LLVMCompiler; use wasmer_llvm_backend::LLVMCompiler;
use wasmer_runtime::{ use wasmer_runtime::{
cache::{Cache as BaseCache, FileSystemCache, WasmHash, WASMER_VERSION_HASH}, cache::{Cache as BaseCache, FileSystemCache, WasmHash, WASMER_VERSION_HASH},
error::RuntimeError,
Func, Value, Func, Value,
}; };
use wasmer_runtime_core::{ use wasmer_runtime_core::{
@ -112,13 +111,6 @@ struct Run {
)] )]
loader: Option<LoaderName>, loader: Option<LoaderName>,
#[cfg(feature = "backend:singlepass")]
#[structopt(long = "image-file")]
image_file: Option<String>,
#[structopt(long = "resume")]
resume: bool,
#[structopt(long = "command-name", hidden = true)] #[structopt(long = "command-name", hidden = true)]
command_name: Option<String>, command_name: Option<String>,
@ -158,7 +150,7 @@ impl FromStr for LoaderName {
} }
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug, Eq, PartialEq)]
enum Backend { enum Backend {
Cranelift, Cranelift,
Singlepass, Singlepass,
@ -511,14 +503,9 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
#[cfg(feature = "backend:singlepass")] #[cfg(feature = "backend:singlepass")]
{ {
if let Some(ref name) = options.image_file { if options.backend == Backend::Singlepass {
use wasmer_runtime_core::suspend::{patch_import_object, SuspendConfig}; use wasmer_runtime_core::suspend::patch_import_object;
patch_import_object( patch_import_object(&mut import_object);
&mut import_object,
SuspendConfig {
image_path: name.clone(),
},
);
} }
} }
@ -526,54 +513,81 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
.instantiate(&import_object) .instantiate(&import_object)
.map_err(|e| format!("Can't instantiate module: {:?}", e))?; .map_err(|e| format!("Can't instantiate module: {:?}", e))?;
let start: Func<(), ()> = instance.func("_start").map_err(|e| format!("{:?}", e))?;
#[cfg(feature = "backend:singlepass")] #[cfg(feature = "backend:singlepass")]
{ unsafe {
if let Some(ref name) = options.image_file { if options.backend == Backend::Singlepass {
if options.resume { use wasmer_runtime_core::alternative_stack::{
use wasmer_runtime_core::state::x64::invoke_call_return_on_stack_raw_image; catch_unsafe_unwind, ensure_sighandler,
use wasmer_singlepass_backend::protect_unix::call_protected; };
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"); ensure_sighandler();
let mut image: Vec<u8> = vec![];
file.read_to_end(&mut image).unwrap();
let msm = instance let start_raw: extern "C" fn(&mut Ctx) =
.module ::std::mem::transmute(start.get_vm_func());
.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();
return Ok(()); let mut image: Option<InstanceImage> = 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::<InstanceImage>() {
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 {
if let Err(ref err) = result { RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg),
match err { RuntimeError::Error { data } => {
RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg), if let Some(error_code) = data.downcast_ref::<wasmer_wasi::ExitCode>() {
RuntimeError::Error { data } => { std::process::exit(error_code.code as i32)
if let Some(error_code) = data.downcast_ref::<wasmer_wasi::ExitCode>() { }
std::process::exit(error_code.code as i32)
} }
} }
panic!("error: {:?}", err)
} }
panic!("error: {:?}", err)
} }
} else { } else {
let import_object = wasmer_runtime_core::import::ImportObject::new(); let import_object = wasmer_runtime_core::import::ImportObject::new();
@ -598,6 +612,89 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
Ok(()) Ok(())
} }
#[cfg(feature = "backend:singlepass")]
struct InteractiveShellContext {
image: Option<wasmer_runtime_core::state::InstanceImage>,
}
#[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) { fn run(options: Run) {
match execute_wasm(&options) { match execute_wasm(&options) {
Ok(()) => {} Ok(()) => {}