wasmdbg 0.1.0

Debugger for wasm binaries
Documentation
#[macro_use]
extern crate failure;

use std::cell::{Ref, RefCell};
use std::rc::Rc;

pub mod breakpoints;
pub mod nan_preserving_float;
pub mod value;
pub mod vm;
pub mod wasi;
pub mod wasm;
use breakpoints::{Breakpoint, Breakpoints};
use value::Value;
use vm::{CodePosition, InitError, Memory, Trap, VM};
use wasm::{LoadError, Module};

#[derive(Debug, Fail)]
pub enum DebuggerError {
    #[fail(display = "Failed to initialize wasm instance: {}", _0)]
    InitError(#[fail(cause)] InitError),
    #[fail(display = "No binary file loaded")]
    NoFileLoaded,
    #[fail(display = "The binary is not being run")]
    NoRunningInstance,
    #[fail(display = "Invalid brekapoint position")]
    InvalidBreakpointPosition,
    #[fail(display = "Invalid global for watchpoint")]
    InvalidWatchpointGlobal,
    #[fail(display = "This feature is still unimplemented")]
    Unimplemented,
}

pub type DebuggerResult<T> = Result<T, DebuggerError>;

pub struct File {
    file_path: String,
    module: Rc<Module>,
    breakpoints: Rc<RefCell<Breakpoints>>,
}

#[derive(Default)]
pub struct Debugger {
    file: Option<File>,
    vm: Option<VM>,
}

impl File {
    pub fn file_path(&self) -> &String {
        &self.file_path
    }

    pub fn module(&self) -> &Module {
        &self.module
    }

    pub fn breakpoints(&self) -> Ref<'_, Breakpoints> {
        self.breakpoints.borrow()
    }
}

impl Debugger {
    pub fn new() -> Self {
        Debugger {
            file: None,
            vm: None,
        }
    }

    pub fn file(&self) -> Option<&File> {
        self.file.as_ref()
    }

    pub fn vm(&self) -> Option<&VM> {
        self.vm.as_ref()
    }

    pub fn load_file(&mut self, file_path: &str) -> Result<(), LoadError> {
        let module = Module::from_file(file_path)?;

        self.file = Some(File {
            file_path: file_path.to_owned(),
            module: Rc::new(module),
            breakpoints: Rc::new(RefCell::new(Breakpoints::new())),
        });
        self.vm = None;

        Ok(())
    }

    pub fn backtrace(&self) -> DebuggerResult<Vec<CodePosition>> {
        let vm = self.get_vm()?;
        let mut backtrace = vec![vm.ip()];
        for frame in vm.function_stack().iter().skip(1).rev() {
            backtrace.push(frame.ret_addr);
        }
        Ok(backtrace)
    }

    pub fn globals(&self) -> DebuggerResult<&[Value]> {
        Ok(self.get_vm()?.globals())
    }

    pub fn memory(&self) -> DebuggerResult<&Memory> {
        Ok(self.get_vm()?.memory())
    }

    pub fn breakpoints(&self) -> DebuggerResult<Ref<'_, Breakpoints>> {
        Ok(self.get_file()?.breakpoints())
    }

    pub fn add_breakpoint(&mut self, breakpoint: Breakpoint) -> DebuggerResult<u32> {
        let file = self.get_file_mut()?;
        match breakpoint {
            Breakpoint::Code(pos) => {
                if file
                    .module()
                    .get_func(pos.func_index)
                    .and_then(|func| func.instructions().get(pos.instr_index as usize))
                    .is_none()
                {
                    return Err(DebuggerError::InvalidBreakpointPosition);
                }
            }
            Breakpoint::Memory(..) => (),
            Breakpoint::Global(_, index) => {
                if index as usize >= file.module().globals().len() {
                    return Err(DebuggerError::InvalidWatchpointGlobal);
                }
            }
        }
        Ok(file.breakpoints.borrow_mut().add_breakpoint(breakpoint))
    }

    pub fn delete_breakpoint(&mut self, index: u32) -> DebuggerResult<bool> {
        Ok(self
            .get_file()?
            .breakpoints
            .borrow_mut()
            .delete_breakpoint(index))
    }

    pub fn clear_breakpoints(&mut self) -> DebuggerResult<()> {
        self.get_file()?.breakpoints.borrow_mut().clear();
        Ok(())
    }

    pub fn run(&mut self) -> DebuggerResult<Trap> {
        Ok(self.create_vm()?.run())
    }

    pub fn start(&mut self) -> DebuggerResult<Option<Trap>> {
        Ok(self.create_vm()?.start().err())
    }

    pub fn call(&mut self, index: u32, args: &[Value]) -> DebuggerResult<Trap> {
        Ok(self.ensure_vm()?.run_func(index, args))
    }

    pub fn reset_vm(&mut self) -> DebuggerResult<()> {
        self.vm = None;
        Ok(())
    }

    pub fn continue_execution(&mut self) -> DebuggerResult<Trap> {
        Ok(self.get_vm_mut()?.continue_execution())
    }

    pub fn single_instruction(&mut self) -> DebuggerResult<Option<Trap>> {
        Ok(self.get_vm_mut()?.execute_step().err())
    }

    pub fn next_instruction(&mut self) -> DebuggerResult<Option<Trap>> {
        Ok(self.get_vm_mut()?.execute_step_over().err())
    }

    pub fn execute_until_return(&mut self) -> DebuggerResult<Option<Trap>> {
        Ok(self.get_vm_mut()?.execute_step_out().err())
    }

    fn create_vm(&mut self) -> DebuggerResult<&mut VM> {
        let file = self.file.as_ref().ok_or(DebuggerError::NoFileLoaded)?;
        let module = file.module.clone();
        let breakpoints = file.breakpoints.clone();
        self.vm = Some(VM::new(module, breakpoints).map_err(DebuggerError::InitError)?);
        Ok(self.vm.as_mut().unwrap())
    }

    fn ensure_vm(&mut self) -> DebuggerResult<&mut VM> {
        if let Some(ref mut vm) = self.vm {
            Ok(vm)
        } else {
            self.create_vm()
        }
    }

    pub fn get_vm(&self) -> DebuggerResult<&VM> {
        if let Some(ref vm) = self.vm {
            Ok(vm)
        } else {
            Err(DebuggerError::NoRunningInstance)
        }
    }

    pub fn get_vm_mut(&mut self) -> DebuggerResult<&mut VM> {
        if let Some(ref mut vm) = self.vm {
            Ok(vm)
        } else {
            Err(DebuggerError::NoRunningInstance)
        }
    }

    pub fn get_file(&self) -> DebuggerResult<&File> {
        if let Some(ref file) = self.file {
            Ok(file)
        } else {
            Err(DebuggerError::NoFileLoaded)
        }
    }

    fn get_file_mut(&mut self) -> DebuggerResult<&mut File> {
        if let Some(ref mut file) = self.file {
            Ok(file)
        } else {
            Err(DebuggerError::NoFileLoaded)
        }
    }
}