├── tests ├── simple-example │ ├── .gitignore │ ├── c-dwarf │ │ ├── .gitignore │ │ ├── dummy.c │ │ ├── main.c │ │ └── Makefile │ ├── foo.wat │ ├── swift │ │ └── wasminspect_init_swift │ ├── Makefile │ └── calc.wat ├── spectest.rs ├── debugger.rs └── simple_example.rs ├── .gitignore ├── assets └── demo.gif ├── rust-toolchain.toml ├── .gitmodules ├── crates ├── vm │ ├── src │ │ ├── config.rs │ │ ├── global.rs │ │ ├── interceptor.rs │ │ ├── address.rs │ │ ├── inst.rs │ │ ├── data.rs │ │ ├── host.rs │ │ ├── export.rs │ │ ├── elem.rs │ │ ├── instance.rs │ │ ├── lib.rs │ │ ├── memory.rs │ │ ├── table.rs │ │ ├── func.rs │ │ ├── linker.rs │ │ ├── module.rs │ │ └── stack.rs │ ├── Cargo.toml │ └── macro │ │ ├── Cargo.toml │ │ └── src │ │ ├── lib.rs │ │ └── inst.rs ├── wasi │ ├── build.rs │ ├── macro │ │ ├── src │ │ │ ├── lib.rs │ │ │ ├── utils.rs │ │ │ └── wasi.rs │ │ └── Cargo.toml │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── borrow.rs ├── swift-runtime │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs ├── wast-spec │ ├── Cargo.toml │ └── src │ │ ├── spectest.rs │ │ └── lib.rs ├── debugger │ ├── src │ │ ├── dwarf │ │ │ ├── utils.rs │ │ │ ├── format.rs │ │ │ └── types.rs │ │ ├── commands │ │ │ ├── mod.rs │ │ │ ├── run.rs │ │ │ ├── backtrace.rs │ │ │ ├── symbol.rs │ │ │ ├── stack.rs │ │ │ ├── sourcemap.rs │ │ │ ├── command.rs │ │ │ ├── subroutine.rs │ │ │ ├── settings.rs │ │ │ ├── global.rs │ │ │ ├── local.rs │ │ │ ├── frame.rs │ │ │ ├── debugger.rs │ │ │ ├── disassemble.rs │ │ │ ├── breakpoint.rs │ │ │ ├── list.rs │ │ │ ├── process.rs │ │ │ ├── memory.rs │ │ │ ├── expression.rs │ │ │ └── thread.rs │ │ ├── lib.rs │ │ └── process.rs │ └── Cargo.toml └── debugger-server │ ├── Cargo.toml │ └── src │ ├── serialization.rs │ ├── lib.rs │ ├── rpc.rs │ └── socket.rs ├── shell.nix ├── src └── bin │ ├── wasminspect_server.rs │ └── wasminspect.rs ├── .github └── workflows │ ├── check.yml │ └── release.yml ├── Makefile ├── LICENSE ├── README.md ├── Cargo.toml └── docs └── tutorial.md /tests/simple-example/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.o 3 | -------------------------------------------------------------------------------- /tests/simple-example/c-dwarf/.gitignore: -------------------------------------------------------------------------------- 1 | .o 2 | .wasm 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .wabt 4 | .wasi-sdk 5 | /vendor 6 | .vscode 7 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kateinoigakukun/wasminspect/HEAD/assets/demo.gif -------------------------------------------------------------------------------- /tests/simple-example/c-dwarf/dummy.c: -------------------------------------------------------------------------------- 1 | int inc(int input) { 2 | return input + 1; 3 | } 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.69.0" 3 | components = [] 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /tests/simple-example/foo.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (func (export "foo") (result i32) 3 | (i32.const 123) 4 | ) 5 | ) 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/testsuite"] 2 | path = tests/testsuite 3 | url = https://github.com/WebAssembly/testsuite.git 4 | -------------------------------------------------------------------------------- /crates/vm/src/config.rs: -------------------------------------------------------------------------------- 1 | use wasmparser::WasmFeatures; 2 | 3 | #[derive(Default)] 4 | pub struct Config { 5 | pub features: WasmFeatures, 6 | } 7 | -------------------------------------------------------------------------------- /crates/wasi/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let wasi_root = std::env::var("DEP_WASI_COMMON_19_WASI").unwrap(); 3 | println!("cargo:rustc-env=WASI_ROOT={}", wasi_root); 4 | } 5 | -------------------------------------------------------------------------------- /crates/swift-runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-swift-runtime" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | links = "swiftCore" 7 | -------------------------------------------------------------------------------- /tests/simple-example/c-dwarf/main.c: -------------------------------------------------------------------------------- 1 | extern int inc(int input); 2 | int dec(int input) { 3 | return input - 1; 4 | } 5 | int main(void) { 6 | int foo = 0; 7 | foo++; 8 | foo = inc(foo); 9 | foo = dec(foo); 10 | return foo; 11 | } 12 | -------------------------------------------------------------------------------- /crates/wasi/macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | mod utils; 3 | mod wasi; 4 | 5 | use proc_macro::TokenStream; 6 | 7 | #[proc_macro] 8 | pub fn define_wasi_fn_for_wasminspect(args: TokenStream) -> TokenStream { 9 | wasi::define_wasi_fn_for_wasminspect(args.into()).into() 10 | } 11 | -------------------------------------------------------------------------------- /crates/wast-spec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wast-spec" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | wasminspect-vm = { path = "../vm" } 9 | wast = "68.0.0" 10 | anyhow = "1.0.26" 11 | wasmparser = "0.95.0" 12 | -------------------------------------------------------------------------------- /crates/vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-vm" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | wasmparser = "0.95.0" 9 | thiserror = "1.0.9" 10 | anyhow = "1.0.26" 11 | wasminspect-vm-macro = { path = "./macro" } 12 | -------------------------------------------------------------------------------- /crates/wasi/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-wasi-macro" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | proc-macro2 = "1.0.6" 12 | witx = "0.9.0" 13 | quote = "1.0" 14 | -------------------------------------------------------------------------------- /crates/vm/macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-vm-macro" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | proc-macro2 = "1.0.6" 12 | syn = "1.0" 13 | witx = "0.9.0" 14 | quote = "1.0" 15 | anyhow = "1.0.0" -------------------------------------------------------------------------------- /tests/simple-example/swift/wasminspect_init_swift: -------------------------------------------------------------------------------- 1 | settings set directory.map /home/katei/swiftwasm-source /Users/katei/projects/swiftwasm-source 2 | breakpoint set swift_getAssociatedTypeWitnessSlowImpl 3 | run 4 | process continue 5 | process continue 6 | process continue 7 | thread step-over 8 | thread step-over 9 | thread step-in 10 | -------------------------------------------------------------------------------- /tests/simple-example/Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 2 | WABT_DIR ?= $(MAKEFILE_DIR)/../../.wabt 3 | WAT2WASM := $(WABT_DIR)/wat2wasm 4 | 5 | FIXTURES := calc.wasm 6 | 7 | .PHONY: all 8 | all: $(FIXTURES) 9 | 10 | %.wasm: %.wat 11 | "$(WAT2WASM)" $< -o $@ 12 | .PHONY: clean 13 | clean: 14 | rm *.wasm 15 | -------------------------------------------------------------------------------- /crates/debugger/src/dwarf/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | pub(crate) fn clone_string_attribute( 4 | dwarf: &gimli::Dwarf, 5 | unit: &gimli::Unit, 6 | attr: gimli::AttributeValue, 7 | ) -> Result { 8 | Ok(dwarf 9 | .attr_string(unit, attr)? 10 | .to_string()? 11 | .as_ref() 12 | .to_string()) 13 | } 14 | -------------------------------------------------------------------------------- /crates/wasi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-wasi" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | wasi-common = "0.25.0" 9 | wiggle = "0.25.0" 10 | wasi-cap-std-sync = "0.25.0" 11 | log = "0.4" 12 | wasminspect-vm = { path = "../vm" } 13 | wasminspect-wasi-macro = { path = "./macro" } 14 | wasmparser = "0.95.0" 15 | cap-std = "0.13.0" 16 | anyhow = "1.0.0" 17 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod debugger; 3 | pub mod sourcemap; 4 | pub mod subroutine; 5 | pub mod symbol; 6 | 7 | // commands 8 | pub mod backtrace; 9 | pub mod breakpoint; 10 | pub mod disassemble; 11 | pub mod expression; 12 | pub mod frame; 13 | pub mod global; 14 | pub mod list; 15 | pub mod local; 16 | pub mod memory; 17 | pub mod process; 18 | pub mod run; 19 | pub mod settings; 20 | pub mod stack; 21 | pub mod thread; 22 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/run.rs: -------------------------------------------------------------------------------- 1 | use super::command::AliasCommand; 2 | use anyhow::Result; 3 | 4 | pub struct RunCommand {} 5 | 6 | impl RunCommand { 7 | pub fn new() -> Self { 8 | Self {} 9 | } 10 | } 11 | 12 | impl AliasCommand for RunCommand { 13 | fn name(&self) -> &'static str { 14 | "run" 15 | } 16 | 17 | fn run(&self, _args: Vec<&str>) -> Result { 18 | Ok("process launch".to_string()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/backtrace.rs: -------------------------------------------------------------------------------- 1 | use super::command::AliasCommand; 2 | use anyhow::Result; 3 | 4 | pub struct BacktraceCommand {} 5 | 6 | impl BacktraceCommand { 7 | pub fn new() -> Self { 8 | Self {} 9 | } 10 | } 11 | 12 | impl AliasCommand for BacktraceCommand { 13 | fn name(&self) -> &'static str { 14 | "bt" 15 | } 16 | 17 | fn run(&self, _args: Vec<&str>) -> Result { 18 | Ok("thread backtrace".to_string()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | oxalica_rust_overlay = import (builtins.fetchTarball https://github.com/oxalica/rust-overlay/archive/e17bfe3baa0487f0671c9ed0e9057d10987ba7f7.tar.gz); 3 | pkgs = import { 4 | overlays = [ oxalica_rust_overlay ]; 5 | }; 6 | rust_toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml); 7 | in 8 | with pkgs; 9 | 10 | pkgs.mkShell rec { 11 | nativeBuildInputs = lib.optionals stdenv.isDarwin [ 12 | darwin.libiconv 13 | ] ++ [ 14 | rust_toolchain 15 | cacert 16 | curl 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /crates/vm/macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | mod inst; 3 | 4 | use proc_macro::TokenStream; 5 | use syn::DeriveInput; 6 | 7 | #[proc_macro_derive(TryFromWasmParserOperator)] 8 | pub fn try_from_wasmparser_operator(args: TokenStream) -> TokenStream { 9 | inst::try_from_wasmparser_operator(syn::parse_macro_input!(args as DeriveInput)) 10 | .unwrap() 11 | .into() 12 | } 13 | 14 | #[proc_macro] 15 | pub fn define_instr_kind(args: TokenStream) -> TokenStream { 16 | inst::define_instr_kind(syn::parse_macro_input!(args)) 17 | .unwrap() 18 | .into() 19 | } 20 | -------------------------------------------------------------------------------- /crates/swift-runtime/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | static SWIFT_RUNTIME_LIB_DIR: &str = "SWIFT_RUNTIME_LIB_DIR"; 4 | 5 | fn main() { 6 | let runtime_lib_dir = match env::var(SWIFT_RUNTIME_LIB_DIR) { 7 | Ok(val) => val, 8 | Err(_) => { 9 | println!("Environment variable {} not found", SWIFT_RUNTIME_LIB_DIR); 10 | "/usr/lib/swift".to_string() 11 | } 12 | }; 13 | println!("cargo:rerun-if-env-changed={}", SWIFT_RUNTIME_LIB_DIR); 14 | println!("cargo:rustc-link-search=native={}", runtime_lib_dir); 15 | println!("cargo:rustc-link-lib=dylib=swiftCore"); 16 | } 17 | -------------------------------------------------------------------------------- /src/bin/wasminspect_server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::str::FromStr; 3 | use structopt::StructOpt; 4 | 5 | #[derive(StructOpt)] 6 | struct Opts { 7 | /// The listen address 8 | #[structopt(default_value = "127.0.0.1:4000")] 9 | listen_addr: String, 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() -> anyhow::Result<()> { 14 | env_logger::init_from_env(env_logger::Env::default().default_filter_or("warn")); 15 | 16 | let opts = Opts::from_args(); 17 | let addr = SocketAddr::from_str(&opts.listen_addr)?; 18 | wasminspect_debugger_server::start(addr).await; 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/symbol.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "swift-extension")] 2 | use wasminspect_swift_runtime::demangle; 3 | 4 | pub fn demangle_symbol(symbol: &str) -> &str { 5 | if is_swift_symbol(symbol) { 6 | demangle_swift_symbol(symbol) 7 | } else { 8 | symbol 9 | } 10 | } 11 | 12 | fn is_swift_symbol(symbol: &str) -> bool { 13 | symbol.starts_with("$s") 14 | } 15 | 16 | #[cfg(feature = "swift-extension")] 17 | fn demangle_swift_symbol(symbol: &str) -> &str { 18 | demangle(symbol).unwrap_or(symbol) 19 | } 20 | #[cfg(not(feature = "swift-extension"))] 21 | fn demangle_swift_symbol(symbol: &str) -> &str { 22 | symbol 23 | } 24 | -------------------------------------------------------------------------------- /crates/vm/src/global.rs: -------------------------------------------------------------------------------- 1 | use crate::value::Value; 2 | use wasmparser::GlobalType; 3 | 4 | pub struct GlobalInstance { 5 | ty: GlobalType, 6 | value: Value, 7 | } 8 | 9 | impl GlobalInstance { 10 | pub fn new(value: Value, ty: GlobalType) -> Self { 11 | Self { value, ty } 12 | } 13 | 14 | pub fn value(&self) -> Value { 15 | self.value 16 | } 17 | 18 | pub fn set_value(&mut self, value: Value) { 19 | assert!(self.is_mutable()); 20 | self.value = value 21 | } 22 | 23 | pub fn is_mutable(&self) -> bool { 24 | self.ty.mutable 25 | } 26 | 27 | pub fn ty(&self) -> &GlobalType { 28 | &self.ty 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/simple-example/c-dwarf/Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 2 | WASI_SDK_DIR ?= $(MAKEFILE_DIR)/../../../.wasi-sdk 3 | CLANG := $(WASI_SDK_DIR)/bin/clang 4 | 5 | all: main.wasm 6 | main.wasm: dummy.o main.o 7 | $(CLANG) $^ --sysroot=$(WASI_SDK_DIR)/share/wasi-sysroot -o $@ 8 | main.o: main.c 9 | $(CLANG) -c \ 10 | -g -v \ 11 | -target wasm32-wasi \ 12 | --sysroot=$(WASI_SDK_DIR)/share/wasi-sysroot \ 13 | -o $@ \ 14 | $< 15 | dummy.o: dummy.c 16 | $(CLANG) -c -v \ 17 | -target wasm32-wasi \ 18 | --sysroot=$(WASI_SDK_DIR)/share/wasi-sysroot \ 19 | -o $@ \ 20 | $< 21 | .PHONY: clean 22 | clean: 23 | rm main dummy.o main.o 24 | -------------------------------------------------------------------------------- /crates/debugger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-debugger" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | wasminspect-vm = { path = "../vm" } 9 | wasminspect-wasi = { path = "../wasi" } 10 | wasminspect-swift-runtime = { path = "../swift-runtime", optional = true } 11 | linefeed = "0.6.0" 12 | clap = "2.33.0" 13 | structopt = "0.3" 14 | thiserror = "1.0.9" 15 | anyhow = "1.0.26" 16 | wasmparser = "0.95.0" 17 | gimli = "0.21.0" 18 | log = "0.4.8" 19 | num-bigint = "0.4" 20 | shell-words = "1.0.0" 21 | cap-std = "0.13.0" 22 | signal-hook = "0.3.0" 23 | 24 | [features] 25 | default = [] 26 | swift-extension = ["wasminspect-swift-runtime"] 27 | remote-api = [] 28 | -------------------------------------------------------------------------------- /tests/simple-example/calc.wat: -------------------------------------------------------------------------------- 1 | (module 2 | (import "spectest" "print_i32" (func $print_i32 (param i32))) 3 | (func $add (export "add") (param i32) (param i32) (result i32) 4 | get_local 0 5 | get_local 1 6 | i32.add) 7 | (func $mul (export "mul") (param $n i32) (param $m i32) (result i32) (local $i i32) (local $sum i32) 8 | (block $exit 9 | (loop $loop 10 | (br_if $exit (i32.lt_s (get_local $n) (get_local $i))) 11 | (set_local $sum (i32.add (get_local $sum) (get_local $n))) 12 | (set_local $i (i32.add (get_local $i) (i32.const 1))) 13 | (br $loop))) 14 | (return (get_local $sum))) 15 | (func $call_add (export "call_add") (param i32) (param i32) (result i32) 16 | (call $add (get_local 0) (get_local 1))) 17 | (func $print_added (export "print_added") (param i32) (param i32) 18 | (call $print_i32 (call $add (get_local 0) (get_local 1)))) 19 | ) 20 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@master 9 | - name: Checkout submodules 10 | shell: bash 11 | run: | 12 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 13 | git submodule sync --recursive 14 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 15 | - name: Install wabt and fixtures 16 | run: make .wabt fixtures 17 | - uses: cachix/install-nix-action@v13 18 | with: 19 | nix_path: nixpkgs=channel:nixos-21.05 20 | - name: Run cargo build 21 | run: nix-shell --pure --run "cargo build" 22 | - name: Run cargo test 23 | run: nix-shell --pure --run "cargo test" 24 | -------------------------------------------------------------------------------- /crates/debugger-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect-debugger-server" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | wasminspect-debugger = { path = "../debugger" } 9 | wasminspect-vm = { path = "../vm" } 10 | wasmparser = "0.95.0" 11 | anyhow = "1.0.26" 12 | hyper = { version = "0.14.0", features = ["full"] } 13 | serde = { version = "1.0.0", features = ["derive"] } 14 | serde_derive = "1.0" 15 | bytes = "1" 16 | serde_json = "1.0" 17 | log = "0.4.8" 18 | headers = "0.3" 19 | futures = { version = "0.3", default-features = false, features = ["alloc"] } 20 | tokio = { version = "1", features = ["full"] } 21 | tokio-tungstenite = { version = "0.13", default-features = false } 22 | tower-service = "0.3" 23 | num-traits = "0.2" 24 | num-derive = "0.3" 25 | lazy_static = "1.4.0" 26 | 27 | [dev-dependencies] 28 | env_logger = "0.7.1" 29 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/stack.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use anyhow::Result; 4 | 5 | pub struct StackCommand {} 6 | 7 | impl StackCommand { 8 | pub fn new() -> Self { 9 | Self {} 10 | } 11 | } 12 | 13 | impl Command for StackCommand { 14 | fn name(&self) -> &'static str { 15 | "stack" 16 | } 17 | 18 | fn description(&self) -> &'static str { 19 | "Commands for operating stack." 20 | } 21 | 22 | fn run( 23 | &self, 24 | debugger: &mut D, 25 | context: &CommandContext, 26 | _args: Vec<&str>, 27 | ) -> Result> { 28 | for (index, value) in debugger.stack_values().iter().enumerate() { 29 | let output = format!("{}: {:?}", index, value); 30 | context.printer.println(&output); 31 | } 32 | Ok(None) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/vm/src/interceptor.rs: -------------------------------------------------------------------------------- 1 | use crate::executor::{ExecResult, Signal}; 2 | use crate::inst::Instruction; 3 | use crate::{Executor, Store}; 4 | 5 | pub trait Interceptor { 6 | fn invoke_func(&self, name: &str, executor: &Executor, store: &Store) -> ExecResult; 7 | fn execute_inst(&self, inst: &Instruction) -> ExecResult; 8 | fn after_store(&self, addr: usize, bytes: &[u8]) -> ExecResult; 9 | } 10 | 11 | #[derive(Default)] 12 | pub struct NopInterceptor {} 13 | impl NopInterceptor { 14 | pub fn new() -> Self { 15 | Default::default() 16 | } 17 | } 18 | impl Interceptor for NopInterceptor { 19 | fn invoke_func(&self, _name: &str, _executor: &Executor, _store: &Store) -> ExecResult { 20 | Ok(Signal::Next) 21 | } 22 | fn execute_inst(&self, _inst: &Instruction) -> ExecResult { 23 | Ok(Signal::Next) 24 | } 25 | 26 | fn after_store(&self, _addr: usize, _bytes: &[u8]) -> ExecResult { 27 | Ok(Signal::Next) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/sourcemap.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 2 | pub enum ColumnType { 3 | LeftEdge, 4 | Column(u64), 5 | } 6 | 7 | impl From for u64 { 8 | fn from(val: ColumnType) -> Self { 9 | match val { 10 | ColumnType::Column(c) => c, 11 | ColumnType::LeftEdge => 0, 12 | } 13 | } 14 | } 15 | 16 | #[derive(Clone)] 17 | pub struct LineInfo { 18 | pub filepath: String, 19 | pub line: Option, 20 | pub column: ColumnType, 21 | } 22 | 23 | pub trait SourceMap { 24 | fn find_line_info(&self, offset: usize) -> Option; 25 | fn set_directory_map(&self, from: String, to: String); 26 | } 27 | 28 | pub struct EmptySourceMap {} 29 | 30 | impl EmptySourceMap { 31 | pub fn new() -> Self { 32 | Self {} 33 | } 34 | } 35 | impl SourceMap for EmptySourceMap { 36 | fn find_line_info(&self, _: usize) -> Option { 37 | None 38 | } 39 | fn set_directory_map(&self, _: String, _: String) {} 40 | } 41 | -------------------------------------------------------------------------------- /tests/spectest.rs: -------------------------------------------------------------------------------- 1 | extern crate wast_spec; 2 | use std::path::Path; 3 | use wast_spec::WastContext; 4 | 5 | include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs")); 6 | 7 | fn run_wast(wast: &str) -> anyhow::Result<()> { 8 | let wast = Path::new(wast); 9 | 10 | let mut cfg = wasminspect_vm::Config::default(); 11 | 12 | cfg.features.simd = feature_found(wast, "simd"); 13 | cfg.features.memory64 = feature_found(wast, "memory64"); 14 | cfg.features.multi_memory = feature_found(wast, "multi-memory"); 15 | cfg.features.component_model = feature_found(wast, "component-model"); 16 | cfg.features.threads = feature_found(wast, "threads"); 17 | 18 | let mut context = WastContext::new(cfg); 19 | match context.run_file(wast) { 20 | Ok(_) => (), 21 | Err(err) => panic!("{}", err), 22 | } 23 | Ok(()) 24 | } 25 | 26 | fn feature_found(path: &Path, name: &str) -> bool { 27 | path.iter().any(|part| match part.to_str() { 28 | Some(s) => s.contains(name), 29 | None => false, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST))) 2 | WABT_DIR ?= $(MAKEFILE_DIR)/.wabt 3 | WASI_SDK_DIR ?= $(MAKEFILE_DIR)/.wasi-sdk 4 | 5 | ifeq ($(shell uname),Darwin) 6 | WABT_DOWNLOAD_URL="https://github.com/WebAssembly/wabt/releases/download/1.0.12/wabt-1.0.12-osx.tar.gz" 7 | WASI_SDK_DOWNLOAD_URL="https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-8/wasi-sdk-8.0-macos.tar.gz" 8 | else 9 | WABT_DOWNLOAD_URL="https://github.com/WebAssembly/wabt/releases/download/1.0.12/wabt-1.0.12-linux.tar.gz" 10 | WASI_SDK_DOWNLOAD_URL="https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-8/wasi-sdk-8.0-linux.tar.gz" 11 | endif 12 | 13 | .PHONY: fixtures 14 | fixtures: .wabt .wasi-sdk 15 | cd tests/simple-example; make all; 16 | cd tests/simple-example/c-dwarf; make all; 17 | 18 | .wabt: 19 | mkdir -p $(WABT_DIR) && cd $(WABT_DIR) && \ 20 | curl -L $(WABT_DOWNLOAD_URL) | tar xz --strip-components 1 21 | .wasi-sdk: 22 | mkdir -p $(WASI_SDK_DIR) && cd $(WASI_SDK_DIR) && \ 23 | curl -L $(WASI_SDK_DOWNLOAD_URL) | tar xz --strip-components 1 24 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/command.rs: -------------------------------------------------------------------------------- 1 | use super::debugger::{Debugger, OutputPrinter}; 2 | use super::sourcemap::SourceMap; 3 | use super::subroutine::SubroutineMap; 4 | use anyhow::Result; 5 | use wasminspect_vm::WasmValue; 6 | 7 | pub struct CommandContext { 8 | pub sourcemap: Box, 9 | pub subroutine: Box, 10 | pub printer: Box, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub enum CommandResult { 15 | ProcessFinish(Vec), 16 | Exit, 17 | } 18 | 19 | pub trait Command { 20 | fn name(&self) -> &'static str; 21 | fn description(&self) -> &'static str { 22 | "No description yet" 23 | } 24 | fn run( 25 | &self, 26 | debugger: &mut D, 27 | context: &CommandContext, 28 | args: Vec<&str>, 29 | ) -> Result>; 30 | } 31 | 32 | pub trait AliasCommand { 33 | fn name(&self) -> &'static str; 34 | fn description(&self) -> &'static str { 35 | "No description yet" 36 | } 37 | fn run(&self, args: Vec<&str>) -> Result; 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yuta Saito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/subroutine.rs: -------------------------------------------------------------------------------- 1 | use crate::dwarf::{FrameBase, WasmLoc}; 2 | use anyhow::Result; 3 | 4 | pub struct Variable { 5 | pub name: String, 6 | pub type_name: String, 7 | } 8 | 9 | pub trait SubroutineMap { 10 | fn variable_name_list(&self, code_offset: usize) -> Result>; 11 | fn get_frame_base(&self, code_offset: usize) -> Result>; 12 | fn display_variable( 13 | &self, 14 | code_offset: usize, 15 | frame_base: FrameBase, 16 | memory: &[u8], 17 | name: String, 18 | ) -> Result<()>; 19 | } 20 | 21 | pub struct EmptySubroutineMap {} 22 | 23 | impl EmptySubroutineMap { 24 | pub fn new() -> Self { 25 | Self {} 26 | } 27 | } 28 | impl SubroutineMap for EmptySubroutineMap { 29 | fn variable_name_list(&self, _code_offset: usize) -> Result> { 30 | Ok(vec![]) 31 | } 32 | fn get_frame_base(&self, _: usize) -> Result> { 33 | Ok(Some(WasmLoc::Global(0))) 34 | } 35 | fn display_variable(&self, _: usize, _: FrameBase, _: &[u8], _: String) -> Result<()> { 36 | Ok(()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /crates/vm/src/address.rs: -------------------------------------------------------------------------------- 1 | use crate::linker::{GlobalAddress, LinkableAddress}; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | 5 | // Internal representation of global function address to reference same function instances 6 | 7 | use crate::func::FunctionInstance; 8 | pub type FuncAddr = LinkableAddress; 9 | pub type ExecutableFuncAddr = GlobalAddress; 10 | 11 | use crate::table::TableInstance; 12 | pub type TableAddr = LinkableAddress>>; 13 | pub type ResolvedTableAddr = GlobalAddress>>; 14 | 15 | use crate::memory::MemoryInstance; 16 | pub type MemoryAddr = LinkableAddress>>; 17 | pub type ResolvedMemoryAddr = GlobalAddress>>; 18 | 19 | use crate::global::GlobalInstance; 20 | pub type GlobalAddr = LinkableAddress>>; 21 | pub type ResolvedGlobalAddr = GlobalAddress>>; 22 | 23 | use crate::elem::ElementInstance; 24 | pub type ElemAddr = LinkableAddress>>; 25 | 26 | use crate::data::DataInstance; 27 | pub type DataAddr = LinkableAddress>>; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wasminspect: An Interactive Debugger for WebAssembly 2 | 3 | Wasminspect is an interactive debugger for WebAssembly like lldb. It can be used for WebAssembly code and WASI applications also. 4 | 5 | ![Check](https://github.com/kateinoigakukun/wasminspect/workflows/Check/badge.svg) 6 | 7 | ![demo](./assets/demo.gif) 8 | 9 | ## [Tutorial](./docs/tutorial.md) 10 | 11 | Let's try to debug your WebAssembly binary! 12 | 13 | ## Features 14 | 15 | - Full WASI supports 16 | - Breakpoints 17 | - Process control 18 | - step-in, step-over and step-out 19 | - Dump memory space 20 | - Parse and evaluate DWARF debug information 21 | - [more detail](./docs/tutorial.md) 22 | 23 | ## Swift Extension 24 | 25 | wasminspect support some Swift specific features. To enable these features, please build on your machine because it requires swift runtime library. 26 | 27 | On macOS: 28 | 29 | ```sh 30 | $ export SWIFT_RUNTIME_LIB_DIR=$(xcrun -show-sdk-path)/usr/lib/swift 31 | $ cargo build --features swift-extension 32 | ``` 33 | 34 | On Linux: 35 | 36 | ```sh 37 | $ export SWIFT_RUNTIME_LIB_DIR=/path/to/lib/swift/linux # e.g. $HOME/.swiftenv/versions/5.2-RELEASE/usr/lib/swift/linux 38 | $ RUSTFLAGS="-C link-args=-Wl,-rpath,$SWIFT_RUNTIME_LIB_DIR" cargo +nightly build --features swift-extension 39 | ``` 40 | -------------------------------------------------------------------------------- /tests/debugger.rs: -------------------------------------------------------------------------------- 1 | extern crate wasminspect_debugger; 2 | extern crate wasminspect_vm; 3 | use std::{collections::HashMap, io::Read}; 4 | use wasminspect_debugger::*; 5 | use wasminspect_vm::*; 6 | use wast_spec::instantiate_spectest; 7 | 8 | fn load_file(filename: &str) -> anyhow::Result> { 9 | let mut f = ::std::fs::File::open(filename)?; 10 | let mut buffer = Vec::new(); 11 | f.read_to_end(&mut buffer)?; 12 | Ok(buffer) 13 | } 14 | 15 | #[test] 16 | fn test_load_and_execute() -> anyhow::Result<()> { 17 | let (mut process, _) = start_debugger(None, vec![], vec![])?; 18 | let example_dir = std::path::Path::new(file!()) 19 | .parent() 20 | .unwrap() 21 | .join("simple-example"); 22 | let bytes = load_file(example_dir.join("calc.wasm").to_str().unwrap())?; 23 | let spectest = instantiate_spectest(); 24 | let mut host_modules = HashMap::new(); 25 | let args = vec![]; 26 | host_modules.insert("spectest".to_string(), spectest); 27 | process 28 | .debugger 29 | .load_main_module(&bytes, String::from("calc.wasm"))?; 30 | process.debugger.instantiate(host_modules, Some(&args))?; 31 | process 32 | .debugger 33 | .run(Some("add"), vec![WasmValue::I32(1), WasmValue::I32(2)])?; 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /crates/debugger-server/src/serialization.rs: -------------------------------------------------------------------------------- 1 | use crate::rpc; 2 | use tokio_tungstenite::tungstenite::Message; 3 | 4 | pub fn deserialize_request(message: &Message) -> Result { 5 | match message { 6 | Message::Binary(bytes) => rpc::BinaryRequest::from_bytes(bytes).map(rpc::Request::Binary), 7 | Message::Text(text) => match serde_json::from_str::(text) { 8 | Ok(req) => Ok(rpc::Request::Text(req)), 9 | Err(e) => Err(rpc::RequestError::InvalidTextRequestJSON(Box::new(e))), 10 | }, 11 | msg => Err(rpc::RequestError::InvalidMessageType(format!("{:?}", msg))), 12 | } 13 | } 14 | pub fn serialize_response(response: rpc::Response) -> Message { 15 | match response { 16 | rpc::Response::Text(response) => { 17 | let json = match serde_json::to_string(&response) { 18 | Ok(json) => json, 19 | Err(e) => { 20 | log::error!("Failed to serialize error response: {}", e); 21 | return Message::Close(None); 22 | } 23 | }; 24 | Message::Text(json) 25 | } 26 | rpc::Response::Binary { kind, bytes } => { 27 | let mut bin = vec![kind as u8]; 28 | bin.extend(bytes); 29 | Message::binary(bin) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/wasi/macro/src/utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Literal, TokenTree}; 2 | use std::path::PathBuf; 3 | 4 | pub(crate) fn witx_path_from_arg(arg: TokenTree) -> PathBuf { 5 | let string; 6 | 7 | if let TokenTree::Literal(literal) = arg { 8 | let parsed = parse_string_literal(literal); 9 | 10 | string = parsed; 11 | } else { 12 | panic!("arguments must be string literals"); 13 | } 14 | 15 | let root = PathBuf::from(std::env::var("WASI_ROOT").expect("WASI_ROOT")); 16 | root.join(&string) 17 | } 18 | 19 | fn parse_string_literal(literal: Literal) -> String { 20 | let s = literal.to_string(); 21 | assert!( 22 | s.starts_with('"') && s.ends_with('"'), 23 | "string literal must be enclosed in double-quotes" 24 | ); 25 | 26 | let trimmed = s[1..s.len() - 1].to_owned(); 27 | assert!( 28 | !trimmed.contains('"'), 29 | "string literal must not contain embedded quotes for now" 30 | ); 31 | assert!( 32 | !trimmed.contains('\\'), 33 | "string literal must not contain embedded backslashes for now" 34 | ); 35 | 36 | trimmed 37 | } 38 | 39 | pub(crate) fn witx_target_module_map_ident(arg: TokenTree) -> String { 40 | if let TokenTree::Ident(id) = arg { 41 | id.to_string() 42 | } else { 43 | panic!("arguments must be string literals"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasminspect" 3 | version = "0.2.0" 4 | authors = ["Yuta Saito "] 5 | edition = "2018" 6 | description = "An Interactive Debugger for WebAssembly" 7 | license-file = "LICENSE" 8 | repository = "https://github.com/kateinoigakukun/wasminspect" 9 | documentation = "https://github.com/kateinoigakukun/wasminspect/blob/main/docs/tutorial.md" 10 | 11 | [[bin]] 12 | name = "wasminspect" 13 | path = "src/bin/wasminspect.rs" 14 | 15 | [[bin]] 16 | name = "wasminspect-server" 17 | path = "src/bin/wasminspect_server.rs" 18 | required-features = ["remote-api"] 19 | 20 | [dependencies] 21 | wasminspect-debugger = { path = "crates/debugger" } 22 | wasminspect-vm = { path = "crates/vm" } 23 | wasminspect-debugger-server = { path = "crates/debugger-server", optional = true } 24 | wast-spec = { path = "crates/wast-spec" } 25 | clap = "2.33.0" 26 | structopt = "0.3" 27 | env_logger = "0.7.1" 28 | anyhow = "1.0.26" 29 | tokio = { version = "1", features = ["full"], optional = true } 30 | 31 | [workspace] 32 | members = [ 33 | "crates/debugger", 34 | "crates/vm", 35 | "crates/wast-spec", 36 | "crates/wasi", 37 | "crates/swift-runtime", 38 | ] 39 | [features] 40 | default = [] 41 | swift-extension = ["wasminspect-debugger/swift-extension"] 42 | remote-api = [ 43 | "wasminspect-debugger-server", 44 | "wasminspect-debugger/remote-api", 45 | "tokio", 46 | ] 47 | -------------------------------------------------------------------------------- /crates/vm/src/inst.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use wasminspect_vm_macro::{define_instr_kind, TryFromWasmParserOperator}; 3 | use wasmparser::*; 4 | #[derive(Debug, Clone)] 5 | pub struct Instruction { 6 | pub kind: InstructionKind, 7 | pub offset: usize, 8 | } 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct BrTableData { 12 | pub table: Vec, 13 | pub default: u32, 14 | } 15 | 16 | trait WasmInstPayloadFrom: Sized { 17 | type Error; 18 | fn from_payload(_: T) -> Result; 19 | } 20 | 21 | impl WasmInstPayloadFrom for U 22 | where 23 | U: From, 24 | { 25 | type Error = wasmparser::BinaryReaderError; 26 | fn from_payload(from: T) -> Result { 27 | Ok(From::::from(from)) 28 | } 29 | } 30 | 31 | impl WasmInstPayloadFrom> for BrTableData { 32 | type Error = wasmparser::BinaryReaderError; 33 | fn from_payload(table: BrTable) -> Result { 34 | Ok(BrTableData { 35 | table: table.targets().collect::, _>>()?, 36 | default: table.default(), 37 | }) 38 | } 39 | } 40 | 41 | for_each_operator!(define_instr_kind); 42 | 43 | pub fn transform_inst( 44 | reader: &mut OperatorsReader, 45 | base_offset: usize, 46 | ) -> anyhow::Result { 47 | let (op, offset) = reader.read_with_offset()?; 48 | let kind = TryFrom::try_from(op)?; 49 | Ok(Instruction { 50 | kind, 51 | offset: offset - base_offset, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /crates/swift-runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi; 2 | 3 | extern "C" { 4 | fn swift_demangle( 5 | mangledName: *const u8, 6 | mangledNameLength: usize, 7 | outputBuffer: *mut u8, 8 | outputBufferSize: *mut usize, 9 | flags: u32, 10 | ) -> *const i8; 11 | } 12 | 13 | #[derive(Debug)] 14 | pub enum DemangleError { 15 | Utf8Error(std::str::Utf8Error), 16 | Null, 17 | } 18 | 19 | impl std::error::Error for DemangleError {} 20 | 21 | impl std::fmt::Display for DemangleError { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | match self { 24 | DemangleError::Utf8Error(err) => { 25 | write!(f, "Error while interpolating C string: {:?}", err) 26 | } 27 | DemangleError::Null => write!(f, "swift_demangle returns null"), 28 | } 29 | } 30 | } 31 | 32 | pub fn demangle(symbol: &str) -> Result<&str, DemangleError> { 33 | unsafe { 34 | let demangled = swift_demangle( 35 | symbol.as_ptr(), 36 | symbol.len(), 37 | std::ptr::null_mut(), 38 | std::ptr::null_mut(), 39 | 0, 40 | ); 41 | if demangled.is_null() { 42 | Err(DemangleError::Null) 43 | } else { 44 | ffi::CStr::from_ptr(demangled) 45 | .to_str() 46 | .map_err(DemangleError::Utf8Error) 47 | } 48 | } 49 | } 50 | 51 | #[test] 52 | fn test_demangle() { 53 | let input = "$sSi"; 54 | assert_eq!(demangle(input).unwrap(), "Swift.Int"); 55 | } 56 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/settings.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use anyhow::Result; 4 | 5 | use structopt::StructOpt; 6 | 7 | pub struct SettingsCommand {} 8 | 9 | impl SettingsCommand { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | #[derive(StructOpt)] 16 | enum Opts { 17 | #[structopt(name = "set")] 18 | Set { 19 | key: String, 20 | operand1: String, 21 | operand2: String, 22 | }, 23 | } 24 | 25 | impl Command for SettingsCommand { 26 | fn name(&self) -> &'static str { 27 | "settings" 28 | } 29 | 30 | fn description(&self) -> &'static str { 31 | "Commands for setting environment" 32 | } 33 | 34 | fn run( 35 | &self, 36 | _debugger: &mut D, 37 | context: &CommandContext, 38 | args: Vec<&str>, 39 | ) -> Result> { 40 | let opts = Opts::from_iter_safe(args)?; 41 | match opts { 42 | Opts::Set { 43 | key, 44 | operand1, 45 | operand2, 46 | } => match key.as_str() { 47 | "directory.map" => { 48 | context.sourcemap.set_directory_map(operand1, operand2); 49 | } 50 | _ => { 51 | let output = format!("'{}' is not valid key", key); 52 | context.printer.eprintln(&output); 53 | } 54 | }, 55 | } 56 | Ok(None) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/simple_example.rs: -------------------------------------------------------------------------------- 1 | extern crate wasminspect_vm; 2 | use wasminspect_vm::*; 3 | use wast_spec::instantiate_spectest; 4 | 5 | use std::path::Path; 6 | 7 | fn run_wasm(filename: &str, func: &str, args: Vec, results: Vec) { 8 | let example_dir = Path::new(file!()).parent().unwrap().join("simple-example"); 9 | let mut instance = WasmInstance::new(); 10 | let config = Config::default(); 11 | let spectest = instantiate_spectest(); 12 | instance.load_host_module("spectest".to_string(), spectest); 13 | let module_index = instance 14 | .load_module_from_file( 15 | None, 16 | example_dir.join(filename).to_str().unwrap().to_string(), 17 | ) 18 | .ok() 19 | .unwrap(); 20 | match instance.run(module_index, Some(func.to_string()), args, &config) { 21 | Ok(result) => assert_eq!(result, results), 22 | Err(err) => panic!("{}", err), 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_calc_add() { 28 | run_wasm( 29 | "calc.wasm", 30 | "add", 31 | vec![WasmValue::I32(1), WasmValue::I32(2)], 32 | vec![WasmValue::I32(3)], 33 | ); 34 | } 35 | 36 | #[test] 37 | fn test_calc_mul() { 38 | run_wasm( 39 | "calc.wasm", 40 | "mul", 41 | vec![WasmValue::I32(2), WasmValue::I32(3)], 42 | vec![WasmValue::I32(6)], 43 | ); 44 | } 45 | 46 | #[test] 47 | fn test_calc_call() { 48 | run_wasm( 49 | "calc.wasm", 50 | "call_add", 51 | vec![WasmValue::I32(3), WasmValue::I32(4)], 52 | vec![WasmValue::I32(7)], 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/global.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use anyhow::{anyhow, Result}; 4 | 5 | use structopt::StructOpt; 6 | 7 | pub struct GlobalCommand {} 8 | 9 | impl GlobalCommand { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | #[derive(StructOpt)] 16 | enum Opts { 17 | #[structopt(name = "read")] 18 | Read { 19 | #[structopt(name = "INDEX")] 20 | index: usize, 21 | }, 22 | } 23 | 24 | impl Command for GlobalCommand { 25 | fn name(&self) -> &'static str { 26 | "global" 27 | } 28 | 29 | fn description(&self) -> &'static str { 30 | "Commands for operating globals." 31 | } 32 | 33 | fn run( 34 | &self, 35 | debugger: &mut D, 36 | context: &CommandContext, 37 | args: Vec<&str>, 38 | ) -> Result> { 39 | let opts = Opts::from_iter_safe(args)?; 40 | use wasminspect_vm::*; 41 | match opts { 42 | Opts::Read { index } => { 43 | let store: &Store = debugger.store()?; 44 | let mod_index = match debugger.current_frame() { 45 | Some(frame) => frame.module_index, 46 | None => return Err(anyhow!("function frame not found")), 47 | }; 48 | let global = store.global(GlobalAddr::new_unsafe(mod_index, index)); 49 | let output = format!("{:?}", global.borrow().value()); 50 | context.printer.println(&output); 51 | Ok(None) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/local.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use anyhow::Result; 4 | 5 | use structopt::StructOpt; 6 | 7 | pub struct LocalCommand {} 8 | 9 | impl LocalCommand { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | #[derive(StructOpt)] 16 | enum Opts { 17 | #[structopt(name = "read")] 18 | Read { 19 | #[structopt(name = "INDEX")] 20 | index: Option, 21 | }, 22 | } 23 | 24 | impl Command for LocalCommand { 25 | fn name(&self) -> &'static str { 26 | "local" 27 | } 28 | 29 | fn description(&self) -> &'static str { 30 | "Commands for operating locals." 31 | } 32 | 33 | fn run( 34 | &self, 35 | debugger: &mut D, 36 | context: &CommandContext, 37 | args: Vec<&str>, 38 | ) -> Result> { 39 | let opts = Opts::from_iter_safe(args)?; 40 | match opts { 41 | Opts::Read { index: None } => { 42 | for (index, value) in debugger.locals().iter().enumerate() { 43 | let output = format!("{: <3}: {:?}", index, value); 44 | context.printer.println(&output); 45 | } 46 | } 47 | Opts::Read { index: Some(index) } => { 48 | let locals = debugger.locals(); 49 | if index >= locals.len() { 50 | return Err(anyhow::anyhow!("{:?} is out of range, locals length is {:?}", index, locals.len())); 51 | } 52 | let output = format!("{:?}", locals[index]); 53 | context.printer.println(&output); 54 | } 55 | } 56 | Ok(None) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/frame.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use anyhow::Result; 4 | 5 | use structopt::StructOpt; 6 | 7 | pub struct FrameCommand {} 8 | 9 | impl FrameCommand { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | #[derive(StructOpt)] 16 | enum Opts { 17 | #[structopt(name = "variable")] 18 | Variable, 19 | #[structopt(name = "select")] 20 | Select { 21 | #[structopt(name = "index")] 22 | frame_index: usize, 23 | }, 24 | } 25 | 26 | impl Command for FrameCommand { 27 | fn name(&self) -> &'static str { 28 | "frame" 29 | } 30 | 31 | fn description(&self) -> &'static str { 32 | "Commands for selecting current stack frame." 33 | } 34 | 35 | fn run( 36 | &self, 37 | debugger: &mut D, 38 | context: &CommandContext, 39 | args: Vec<&str>, 40 | ) -> Result> { 41 | let opts = Opts::from_iter_safe(args)?; 42 | match opts { 43 | Opts::Variable => { 44 | let (insts, next_index) = debugger.selected_instructions()?; 45 | let current_index = if next_index == 0 { 0 } else { next_index - 1 }; 46 | let current_inst = insts[current_index].clone(); 47 | let variable_names = context.subroutine.variable_name_list(current_inst.offset)?; 48 | for variable in variable_names { 49 | let output = format!("{}: {}", variable.name, variable.type_name); 50 | context.printer.println(&output); 51 | } 52 | Ok(None) 53 | } 54 | Opts::Select { frame_index } => { 55 | debugger.select_frame(Some(frame_index))?; 56 | Ok(None) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/debugger.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use wasminspect_vm::{HostValue, Instruction, ModuleIndex, Signal, Store, WasmValue}; 3 | 4 | #[derive(Default, Clone)] 5 | pub struct DebuggerOpts { 6 | pub watch_memory: bool, 7 | } 8 | 9 | pub enum Breakpoint { 10 | Function { name: String }, 11 | Instruction { inst_offset: usize }, 12 | } 13 | 14 | pub enum RunResult { 15 | Finish(Vec), 16 | Breakpoint, 17 | } 18 | 19 | #[derive(Clone, Copy)] 20 | pub enum StepStyle { 21 | InstIn, 22 | InstOver, 23 | Out, 24 | } 25 | 26 | pub struct FunctionFrame { 27 | pub module_index: ModuleIndex, 28 | pub argument_count: usize, 29 | } 30 | 31 | pub trait OutputPrinter { 32 | fn println(&self, _: &str); 33 | fn eprintln(&self, _: &str); 34 | } 35 | pub type RawHostModule = std::collections::HashMap; 36 | 37 | pub trait Debugger { 38 | fn get_opts(&self) -> DebuggerOpts; 39 | fn set_opts(&mut self, opts: DebuggerOpts); 40 | fn instantiate( 41 | &mut self, 42 | host_modules: std::collections::HashMap, 43 | wasi_args: Option<&[String]>, 44 | ) -> Result<()>; 45 | fn run(&mut self, name: Option<&str>, args: Vec) -> Result; 46 | fn is_running(&self) -> bool; 47 | fn frame(&self) -> Vec; 48 | fn current_frame(&self) -> Option; 49 | fn locals(&self) -> Vec; 50 | fn memory(&self) -> Result>; 51 | fn store(&self) -> Result<&Store>; 52 | fn set_breakpoint(&mut self, breakpoint: Breakpoint); 53 | fn stack_values(&self) -> Vec; 54 | fn selected_instructions(&self) -> Result<(&[Instruction], usize)>; 55 | fn step(&self, style: StepStyle) -> Result; 56 | fn process(&mut self) -> Result; 57 | fn select_frame(&mut self, frame_index: Option) -> Result<()>; 58 | } 59 | -------------------------------------------------------------------------------- /crates/vm/src/data.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | AccessOutOfBounds { 4 | try_to_access: Option, 5 | memory_size: usize, 6 | }, 7 | } 8 | 9 | impl std::fmt::Display for Error { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | match self { 12 | Self::AccessOutOfBounds { try_to_access: Some(addr), memory_size } => write!( 13 | f, 14 | "out of bounds memory access, try to access {} but size of memory is {}", 15 | addr, memory_size 16 | ), 17 | Self::AccessOutOfBounds { try_to_access: None, memory_size } => write!( 18 | f, 19 | "out of bounds memory access, try to access over size of usize but size of memory is {}", 20 | memory_size 21 | ), 22 | } 23 | } 24 | } 25 | 26 | type Result = std::result::Result; 27 | 28 | pub struct DataInstance { 29 | bytes: Vec, 30 | } 31 | 32 | impl DataInstance { 33 | pub fn new(bytes: Vec) -> Self { 34 | Self { bytes } 35 | } 36 | pub fn validate_region(&self, offset: usize, size: usize) -> Result<()> { 37 | let len = self.bytes.len(); 38 | if let Some(max_addr) = offset.checked_add(size) { 39 | if max_addr > len { 40 | return Err(Error::AccessOutOfBounds { 41 | try_to_access: Some(max_addr), 42 | memory_size: len, 43 | }); 44 | } 45 | } else { 46 | return Err(Error::AccessOutOfBounds { 47 | try_to_access: None, 48 | memory_size: len, 49 | }); 50 | } 51 | Ok(()) 52 | } 53 | 54 | pub fn raw(&self) -> &[u8] { 55 | &self.bytes 56 | } 57 | 58 | pub fn drop_bytes(&mut self) { 59 | self.bytes = vec![]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/debugger-server/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod debugger_proxy; 2 | mod rpc; 3 | mod serialization; 4 | mod socket; 5 | 6 | use hyper::{ 7 | service::{make_service_fn, service_fn}, 8 | Method, Request, StatusCode, 9 | }; 10 | use hyper::{Body, Response, Server}; 11 | 12 | use std::net::SocketAddr; 13 | 14 | pub async fn start(addr: SocketAddr) { 15 | run(addr).await; 16 | } 17 | 18 | async fn remote_api(req: Request) -> Result, anyhow::Error> { 19 | match (req.method(), req.uri().path()) { 20 | (&Method::GET, "/debugger") => { 21 | let res = socket::socket_handshake(req, socket::establish_connection).await; 22 | match res { 23 | Ok(res) => Ok(res), 24 | Err(e) => Ok(Response::builder() 25 | .status(StatusCode::INTERNAL_SERVER_ERROR) 26 | .body(Body::from(e.to_string())) 27 | .unwrap()), 28 | } 29 | } 30 | _ => { 31 | // Return 404 not found response. 32 | Ok(Response::builder() 33 | .status(StatusCode::NOT_FOUND) 34 | .body(Body::empty()) 35 | .unwrap()) 36 | } 37 | } 38 | } 39 | 40 | async fn shutdown_signal() { 41 | // Wait for the CTRL+C signal 42 | tokio::signal::ctrl_c() 43 | .await 44 | .expect("failed to install CTRL+C signal handler"); 45 | } 46 | 47 | async fn run(addr: SocketAddr) { 48 | let make_service = make_service_fn(move |_| async move { 49 | Ok::<_, anyhow::Error>(service_fn(move |req| { 50 | log::trace!("request: {:?}", req); 51 | remote_api(req) 52 | })) 53 | }); 54 | 55 | let server = Server::bind(&addr).serve(make_service); 56 | 57 | println!("Listening on http://{}", server.local_addr()); 58 | let graceful = server.with_graceful_shutdown(shutdown_signal()); 59 | 60 | if let Err(e) = graceful.await { 61 | eprintln!("server error: {}", e); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/vm/src/host.rs: -------------------------------------------------------------------------------- 1 | use crate::address::MemoryAddr; 2 | use crate::executor::Trap; 3 | use crate::global::GlobalInstance; 4 | use crate::memory::MemoryInstance; 5 | use crate::module::ModuleIndex; 6 | use crate::store::Store; 7 | use crate::table::TableInstance; 8 | use crate::value::Value; 9 | use std::cell::RefCell; 10 | use std::rc::Rc; 11 | use wasmparser::FuncType; 12 | 13 | type Ref = Rc>; 14 | 15 | pub struct HostContext<'a> { 16 | pub mem: &'a mut [u8], 17 | } 18 | 19 | pub enum HostValue { 20 | Func(HostFuncBody), 21 | Global(Rc>), 22 | Mem(Ref), 23 | Table(Ref), 24 | } 25 | 26 | type HostCode = dyn Fn(&[Value], &mut Vec, &mut HostContext, &Store) -> Result<(), Trap>; 27 | 28 | pub struct HostFuncBody { 29 | ty: FuncType, 30 | code: Box, 31 | } 32 | 33 | impl HostFuncBody { 34 | pub fn new(ty: FuncType, code: F) -> Self 35 | where 36 | F: Fn(&[Value], &mut Vec, &mut HostContext, &Store) -> Result<(), Trap>, 37 | F: 'static, 38 | { 39 | Self { 40 | ty, 41 | code: Box::new(code), 42 | } 43 | } 44 | 45 | pub fn call( 46 | &self, 47 | param: &[Value], 48 | results: &mut Vec, 49 | store: &Store, 50 | module_index: ModuleIndex, 51 | ) -> Result<(), Trap> { 52 | if store.memory_count(module_index) > 0 { 53 | let mem_addr = MemoryAddr::new_unsafe(module_index, 0); 54 | let mem = store.memory(mem_addr); 55 | let mem = &mut mem.borrow_mut(); 56 | let raw_mem = mem.raw_data_mut(); 57 | let mut ctx = HostContext { mem: raw_mem }; 58 | (self.code)(param, results, &mut ctx, store) 59 | } else { 60 | let mut ctx = HostContext { mem: &mut [] }; 61 | (self.code)(param, results, &mut ctx, store) 62 | } 63 | } 64 | 65 | pub fn ty(&self) -> &FuncType { 66 | &self.ty 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /crates/wasi/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cap_std::fs::Dir; 2 | use std::cell::RefCell; 3 | use std::collections::HashMap; 4 | use wasi_cap_std_sync::WasiCtxBuilder; 5 | use wasi_common::WasiCtx; 6 | use wasminspect_vm::*; 7 | use wasmparser::{FuncType, ValType}; 8 | mod borrow; 9 | 10 | pub struct WasiContext { 11 | ctx: RefCell, 12 | } 13 | 14 | #[derive(Debug)] 15 | struct WasiError(std::string::String); 16 | impl std::error::Error for WasiError {} 17 | impl std::fmt::Display for WasiError { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | write!(f, "{:?}", self) 20 | } 21 | } 22 | 23 | fn wasi_proc_exit(status: i32) -> Result<(), Trap> { 24 | std::process::exit(status); 25 | } 26 | 27 | pub fn instantiate_wasi( 28 | args: &[String], 29 | preopen_dirs: Vec<(String, Dir)>, 30 | envs: &[(String, String)], 31 | ) -> anyhow::Result<(WasiContext, HashMap)> { 32 | let builder = WasiCtxBuilder::new(); 33 | let mut builder = builder.inherit_stdio().args(args)?.envs(envs)?; 34 | 35 | for (name, dir) in preopen_dirs.into_iter() { 36 | builder = builder.preopened_dir(dir, name)?; 37 | } 38 | 39 | let wasi_ctx = builder.build()?; 40 | 41 | let mut module: HashMap = HashMap::new(); 42 | 43 | wasminspect_wasi_macro::define_wasi_fn_for_wasminspect!( 44 | module, 45 | "phases/snapshot/witx/wasi_snapshot_preview1.witx" 46 | ); 47 | 48 | module.insert( 49 | "sock_accept".to_string(), 50 | HostValue::Func(HostFuncBody::new( 51 | FuncType::new(vec![ValType::I32, ValType::I32, ValType::I32], vec![ValType::I32]), 52 | move |_, _, _, _| { 53 | Err(Trap::HostFunctionError(Box::new(WasiError( 54 | "sock_accept is not supported".to_string(), 55 | )))) 56 | } 57 | )), 58 | ); 59 | 60 | let context = WasiContext { 61 | ctx: RefCell::new(wasi_ctx), 62 | }; 63 | Ok((context, module)) 64 | } 65 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/disassemble.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::{Debugger, OutputPrinter}; 3 | use anyhow::Result; 4 | use structopt::StructOpt; 5 | 6 | pub struct DisassembleCommand {} 7 | 8 | impl DisassembleCommand { 9 | pub fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | #[derive(StructOpt)] 15 | struct Opts { 16 | #[structopt(short, long)] 17 | count: Option, 18 | #[structopt(short, long)] 19 | pc: bool, 20 | } 21 | 22 | impl Command for DisassembleCommand { 23 | fn name(&self) -> &'static str { 24 | "disassemble" 25 | } 26 | 27 | fn description(&self) -> &'static str { 28 | "Disassemble instructions in the current function." 29 | } 30 | 31 | fn run( 32 | &self, 33 | debugger: &mut D, 34 | context: &CommandContext, 35 | args: Vec<&str>, 36 | ) -> Result> { 37 | let opts: Opts = Opts::from_iter_safe(args)?; 38 | let count = if opts.pc { 39 | Some(opts.count.unwrap_or(4)) 40 | } else { 41 | opts.count 42 | }; 43 | display_asm(debugger, context.printer.as_ref(), count, opts.pc)?; 44 | Ok(None) 45 | } 46 | } 47 | 48 | pub fn display_asm( 49 | debugger: &D, 50 | printer: &dyn OutputPrinter, 51 | count: Option, 52 | pc_rel: bool, 53 | ) -> Result<()> { 54 | let (insts, inst_index) = debugger.selected_instructions()?; 55 | let begin = if pc_rel { inst_index } else { 0 }; 56 | let end = if let Some(count) = count { 57 | begin + count 58 | } else { 59 | insts.len() 60 | }; 61 | for (index, inst) in insts.iter().enumerate() { 62 | if !(begin..end).contains(&index) { 63 | continue; 64 | } 65 | let prefix = if index == inst_index { "->" } else { " " }; 66 | let output = format!("{} 0x{:>08x}: {:?}", prefix, inst.offset, inst.kind); 67 | printer.println(&output); 68 | } 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /crates/vm/src/export.rs: -------------------------------------------------------------------------------- 1 | use crate::address::*; 2 | use crate::module::ModuleIndex; 3 | 4 | pub struct ExportInstance { 5 | name: String, 6 | value: ExternalValue, 7 | } 8 | 9 | impl ExportInstance { 10 | pub fn name(&self) -> &String { 11 | &self.name 12 | } 13 | 14 | pub fn value(&self) -> &ExternalValue { 15 | &self.value 16 | } 17 | 18 | pub fn new_from_entry(entry: wasmparser::Export, module_index: ModuleIndex) -> Self { 19 | use wasmparser::ExternalKind; 20 | Self { 21 | name: entry.name.to_string(), 22 | value: match entry.kind { 23 | ExternalKind::Func => { 24 | let addr = FuncAddr::new_unsafe(module_index, entry.index as usize); 25 | ExternalValue::Func(addr) 26 | } 27 | ExternalKind::Global => { 28 | let addr = GlobalAddr::new_unsafe(module_index, entry.index as usize); 29 | ExternalValue::Global(addr) 30 | } 31 | ExternalKind::Memory => { 32 | let addr = MemoryAddr::new_unsafe(module_index, entry.index as usize); 33 | ExternalValue::Memory(addr) 34 | } 35 | ExternalKind::Table => { 36 | let addr = TableAddr::new_unsafe(module_index, entry.index as usize); 37 | ExternalValue::Table(addr) 38 | } 39 | ExternalKind::Tag => { 40 | panic!("event is not supported yet") 41 | } 42 | }, 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug)] 48 | pub enum ExternalValue { 49 | Func(FuncAddr), 50 | Global(GlobalAddr), 51 | Memory(MemoryAddr), 52 | Table(TableAddr), 53 | } 54 | 55 | impl ExternalValue { 56 | pub(crate) fn type_name(&self) -> &str { 57 | match self { 58 | Self::Func(_) => "function", 59 | Self::Global(_) => "global", 60 | Self::Memory(_) => "memory", 61 | Self::Table(_) => "table", 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/breakpoint.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::{Breakpoint, Debugger}; 3 | use anyhow::{anyhow, Result}; 4 | use structopt::StructOpt; 5 | 6 | pub struct BreakpointCommand {} 7 | 8 | impl BreakpointCommand { 9 | pub fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | #[derive(StructOpt)] 15 | enum Opts { 16 | /// Sets a breakpoint for the given symbol in executable 17 | #[structopt(name = "set")] 18 | Set(SetOpts), 19 | } 20 | 21 | #[derive(StructOpt)] 22 | struct SetOpts { 23 | #[structopt(short, long)] 24 | name: Option, 25 | #[structopt(short, long)] 26 | address: Option, 27 | } 28 | 29 | impl SetOpts { 30 | fn breakpoint(self) -> Result { 31 | if let Some(name) = self.name { 32 | Ok(Breakpoint::Function { name }) 33 | } else if let Some(address) = self.address { 34 | let address = if address.starts_with("0x") { 35 | let raw = address.trim_start_matches("0x"); 36 | usize::from_str_radix(raw, 16)? 37 | } else { 38 | address.parse::()? 39 | }; 40 | Ok(Breakpoint::Instruction { 41 | inst_offset: address, 42 | }) 43 | } else { 44 | Err(anyhow!("no breakpoint option")) 45 | } 46 | } 47 | } 48 | 49 | impl Command for BreakpointCommand { 50 | fn name(&self) -> &'static str { 51 | "breakpoint" 52 | } 53 | 54 | fn description(&self) -> &'static str { 55 | "Commands for operating on breakpoints." 56 | } 57 | 58 | fn run( 59 | &self, 60 | debugger: &mut D, 61 | _context: &CommandContext, 62 | args: Vec<&str>, 63 | ) -> Result> { 64 | let opts = Opts::from_iter_safe(args)?; 65 | match opts { 66 | Opts::Set(opts) => { 67 | debugger.set_breakpoint(opts.breakpoint()?); 68 | Ok(None) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/vm/src/elem.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{RefType, RefVal}; 2 | 3 | #[derive(Debug)] 4 | pub enum Error { 5 | AccessOutOfBounds { 6 | try_to_access: Option, 7 | size: usize, 8 | }, 9 | } 10 | 11 | impl std::fmt::Display for Error { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | Self::AccessOutOfBounds { try_to_access: Some(addr), size } => write!( 15 | f, 16 | "out of bounds table access, try to access {} but size of memory is {}", 17 | addr, size 18 | ), 19 | Self::AccessOutOfBounds { try_to_access: None, size } => write!( 20 | f, 21 | "out of bounds table access, try to access over size of usize but size of memory is {}", 22 | size 23 | ), 24 | } 25 | } 26 | } 27 | 28 | type Result = std::result::Result; 29 | 30 | pub struct ElementInstance { 31 | _ty: RefType, 32 | elem: Vec, 33 | } 34 | 35 | impl ElementInstance { 36 | pub fn new(ty: RefType, elem: Vec) -> Self { 37 | Self { _ty: ty, elem } 38 | } 39 | 40 | pub fn validate_region(&self, offset: usize, size: usize) -> Result<()> { 41 | let len = self.elem.len(); 42 | if let Some(max_addr) = offset.checked_add(size) { 43 | if max_addr > len { 44 | return Err(Error::AccessOutOfBounds { 45 | try_to_access: Some(max_addr), 46 | size: len, 47 | }); 48 | } 49 | } else { 50 | return Err(Error::AccessOutOfBounds { 51 | try_to_access: None, 52 | size: len, 53 | }); 54 | } 55 | Ok(()) 56 | } 57 | 58 | pub fn get_at(&self, index: usize) -> Result { 59 | self.elem 60 | .get(index) 61 | .ok_or_else(|| Error::AccessOutOfBounds { 62 | try_to_access: Some(index), 63 | size: self.elem.len(), 64 | }) 65 | .map(|addr| *addr) 66 | } 67 | 68 | pub fn drop_elem(&mut self) { 69 | self.elem = vec![]; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/bin/wasminspect.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | 3 | use std::io::Read; 4 | use structopt::StructOpt; 5 | use wasminspect_debugger::{self, ModuleInput}; 6 | 7 | fn parse_env_var(s: &str) -> anyhow::Result<(String, String)> { 8 | let parts: Vec<_> = s.splitn(2, '=').collect(); 9 | if parts.len() != 2 { 10 | return Err(anyhow!("must be of the form `key=value")); 11 | } 12 | Ok((parts[0].to_owned(), parts[1].to_owned())) 13 | } 14 | 15 | fn parse_map_dirs(s: &str) -> anyhow::Result<(String, String)> { 16 | let parts: Vec<&str> = s.split("::").collect(); 17 | if parts.len() != 2 { 18 | return Err(anyhow!("must contain exactly one double colon ('::')")); 19 | } 20 | Ok((parts[0].into(), parts[1].into())) 21 | } 22 | 23 | #[derive(StructOpt)] 24 | struct Opts { 25 | /// The wasm binary file 26 | #[structopt(name = "FILE")] 27 | filepath: Option, 28 | /// Tells the debugger to read in and execute the debugger commands in given file, after wasm file has been loaded 29 | #[structopt(short, long)] 30 | source: Option, 31 | /// Grant access to a guest directory mapped as a host directory 32 | #[structopt(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", parse(try_from_str = parse_map_dirs))] 33 | map_dirs: Vec<(String, String)>, 34 | 35 | /// Pass an environment variable to the program 36 | #[structopt(long = "env", number_of_values = 1, value_name = "NAME=VAL", parse(try_from_str = parse_env_var))] 37 | envs: Vec<(String, String)>, 38 | } 39 | 40 | fn main() -> anyhow::Result<()> { 41 | env_logger::init_from_env(env_logger::Env::default().default_filter_or("warn")); 42 | 43 | let opts = Opts::from_args(); 44 | let module_input = match opts.filepath { 45 | Some(filepath) => { 46 | let mut buffer = Vec::new(); 47 | let filepath = std::path::Path::new(&filepath); 48 | let basename = filepath 49 | .file_name() 50 | .expect("invalid file path") 51 | .to_str() 52 | .expect("invalid file name encoding") 53 | .to_string(); 54 | let mut f = std::fs::File::open(filepath)?; 55 | f.read_to_end(&mut buffer)?; 56 | Some(ModuleInput { 57 | bytes: buffer, 58 | basename, 59 | }) 60 | } 61 | None => None, 62 | }; 63 | if let Err(err) = 64 | wasminspect_debugger::run_loop(module_input, opts.source, opts.map_dirs, opts.envs) 65 | { 66 | println!("{:?}", err) 67 | } 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /crates/vm/src/instance.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::executor::WasmError; 3 | use crate::host::HostValue; 4 | use crate::invoke_func_ignoring_break; 5 | use crate::module::ModuleIndex; 6 | use crate::store::Store; 7 | use crate::value::Value; 8 | use std::collections::HashMap; 9 | 10 | use anyhow::Result; 11 | use std::io::Read; 12 | 13 | #[derive(Default)] 14 | pub struct WasmInstance { 15 | pub store: Store, 16 | } 17 | 18 | impl WasmInstance { 19 | pub fn load_module_from_file( 20 | &mut self, 21 | name: Option, 22 | module_filename: String, 23 | ) -> Result { 24 | let mut f = ::std::fs::File::open(module_filename)?; 25 | let mut buffer = Vec::new(); 26 | f.read_to_end(&mut buffer)?; 27 | self.load_module_from_module(name, &mut buffer) 28 | } 29 | 30 | pub fn load_module_from_module( 31 | &mut self, 32 | name: Option, 33 | reader: &mut [u8], 34 | ) -> Result { 35 | self.store.load_module(name, reader) 36 | } 37 | 38 | pub fn load_host_module(&mut self, name: String, module: HashMap) { 39 | self.store.load_host_module(name, module) 40 | } 41 | 42 | pub fn register_name(&mut self, name: String, module_index: ModuleIndex) { 43 | self.store.register_name(name, module_index) 44 | } 45 | 46 | pub fn add_embed_context(&mut self, ctx: T) { 47 | self.store.add_embed_context(Box::new(ctx)) 48 | } 49 | } 50 | 51 | impl WasmInstance { 52 | pub fn new() -> Self { 53 | Default::default() 54 | } 55 | 56 | pub fn get_global(&self, module_index: ModuleIndex, field: &str) -> Option { 57 | self.store 58 | .scan_global_by_name(module_index, field) 59 | .map(|g| g.borrow().value()) 60 | } 61 | 62 | pub fn run( 63 | &mut self, 64 | module_index: ModuleIndex, 65 | func_name: Option, 66 | arguments: Vec, 67 | config: &Config, 68 | ) -> Result, WasmError> { 69 | let module = self.store.module(module_index).defined().unwrap(); 70 | let func_addr = if let Some(func_name) = func_name { 71 | if let Ok(Some(func_addr)) = module.exported_func(&func_name) { 72 | func_addr 73 | } else { 74 | return Err(WasmError::EntryFunctionNotFound(func_name.clone())); 75 | } 76 | } else if let Some(start_func_addr) = module.start_func_addr() { 77 | *start_func_addr 78 | } else if let Ok(Some(func_addr)) = module.exported_func("_start") { 79 | func_addr 80 | } else { 81 | return Err(WasmError::EntryFunctionNotFound("_start".to_string())); 82 | }; 83 | invoke_func_ignoring_break(func_addr, arguments, &mut self.store, config) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/vm/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod address; 2 | mod config; 3 | mod data; 4 | mod elem; 5 | mod executor; 6 | mod export; 7 | mod func; 8 | mod global; 9 | mod host; 10 | mod inst; 11 | mod instance; 12 | mod interceptor; 13 | mod linker; 14 | mod memory; 15 | mod module; 16 | mod stack; 17 | mod store; 18 | mod table; 19 | mod value; 20 | 21 | pub use self::address::*; 22 | pub use self::config::Config; 23 | pub use self::executor::{Executor, Signal, Trap, WasmError}; 24 | pub use self::func::{FunctionInstance, InstIndex}; 25 | pub use self::global::GlobalInstance; 26 | pub use self::host::{HostContext, HostFuncBody, HostValue}; 27 | pub use self::inst::{Instruction, InstructionKind}; 28 | pub use self::instance::WasmInstance; 29 | pub use self::interceptor::{Interceptor, NopInterceptor}; 30 | pub use self::memory::MemoryInstance as HostMemory; 31 | pub use self::module::{DefinedModuleInstance, ModuleIndex}; 32 | pub use self::stack::{CallFrame, ProgramCounter}; 33 | pub use self::store::Store; 34 | pub use self::table::TableInstance as HostTable; 35 | pub use self::value::Value as WasmValue; 36 | pub use self::value::*; 37 | 38 | pub const WASM_PAGE_SIZE: usize = 0x10000; 39 | 40 | pub fn invoke_func_ignoring_break( 41 | func_addr: FuncAddr, 42 | arguments: Vec, 43 | store: &mut Store, 44 | config: &Config, 45 | ) -> Result, WasmError> { 46 | match store 47 | .func(func_addr) 48 | .ok_or(WasmError::ExecutionError(Trap::UndefinedFunc(func_addr.1)))? 49 | { 50 | (FunctionInstance::Native(host), _) => { 51 | let mut results = Vec::new(); 52 | match host 53 | .code() 54 | .call(&arguments, &mut results, store, func_addr.module_index()) 55 | { 56 | Ok(_) => Ok(results), 57 | Err(_) => Err(WasmError::HostExecutionError), 58 | } 59 | } 60 | (FunctionInstance::Defined(func), exec_addr) => { 61 | let (frame, ret_types) = { 62 | let ret_types = func.ty().results(); 63 | let frame = CallFrame::new_from_func(exec_addr, func, arguments, None); 64 | (frame, ret_types) 65 | }; 66 | let pc = ProgramCounter::new(func.module_index(), exec_addr, InstIndex::zero()); 67 | let interceptor = NopInterceptor::new(); 68 | let mut executor = Executor::new(frame, ret_types.len(), pc); 69 | loop { 70 | let result = executor.execute_step(store, &interceptor, config); 71 | match result { 72 | Ok(Signal::Next) => continue, 73 | Ok(Signal::Breakpoint) => continue, 74 | Ok(Signal::End) => match executor.pop_result(ret_types.to_vec()) { 75 | Ok(values) => return Ok(values), 76 | Err(err) => return Err(WasmError::ReturnValueError(err)), 77 | }, 78 | Err(err) => return Err(WasmError::ExecutionError(err)), 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/list.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::{Debugger, OutputPrinter}; 3 | use super::sourcemap::{ColumnType, LineInfo, SourceMap}; 4 | use anyhow::{anyhow, Result}; 5 | 6 | pub struct ListCommand {} 7 | 8 | impl ListCommand { 9 | pub fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | impl Command for ListCommand { 15 | fn name(&self) -> &'static str { 16 | "list" 17 | } 18 | 19 | fn description(&self) -> &'static str { 20 | "List relevant source code." 21 | } 22 | 23 | fn run( 24 | &self, 25 | debugger: &mut D, 26 | context: &CommandContext, 27 | _args: Vec<&str>, 28 | ) -> Result> { 29 | let line_info = next_line_info(debugger, context.sourcemap.as_ref())?; 30 | display_source(line_info, context.printer.as_ref())?; 31 | Ok(None) 32 | } 33 | } 34 | 35 | pub fn next_line_info(debugger: &D, sourcemap: &dyn SourceMap) -> Result { 36 | let (insts, next_index) = debugger.selected_instructions()?; 37 | match sourcemap.find_line_info(insts[next_index].offset) { 38 | Some(info) => Ok(info), 39 | None => Err(anyhow!("Source info not found")), 40 | } 41 | } 42 | 43 | pub fn display_source(line_info: LineInfo, printer: &dyn OutputPrinter) -> Result<()> { 44 | use std::fs::File; 45 | use std::io::{BufRead, BufReader}; 46 | let source = BufReader::new(File::open(line_info.filepath)?); 47 | // In case compiler can't determine source code location. Page 151. 48 | if line_info.line == Some(0) || line_info.line == None { 49 | return Ok(()); 50 | } 51 | let range = line_info.line.map(|l| { 52 | if l < 20 { 53 | 0..(l + 20) 54 | } else { 55 | (l - 20)..(l + 20) 56 | } 57 | }); 58 | for (index, line) in source.lines().enumerate() { 59 | // line_info.line begin with 1 60 | let index = index + 1; 61 | let line = line?; 62 | 63 | let should_display = range.as_ref().map(|r| r.contains(&(index as u64))); 64 | if !(should_display.unwrap_or(true)) { 65 | continue; 66 | } 67 | let out = if Some(index as u64) == line_info.line { 68 | let mut out = format!("-> {: <4} ", index); 69 | match line_info.column { 70 | ColumnType::Column(col) => { 71 | for (col_index, col_char) in line.chars().enumerate() { 72 | if (col_index + 1) as u64 == col { 73 | out = format!("{}\x1B[4m{}\x1B[0m", out, col_char); 74 | } else { 75 | out = format!("{}{}", out, col_char); 76 | } 77 | } 78 | } 79 | ColumnType::LeftEdge => { 80 | out = format!("{}{}", out, line); 81 | } 82 | } 83 | out 84 | } else { 85 | format!(" {: <4} {}", index, line) 86 | }; 87 | printer.println(&out); 88 | } 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/process.rs: -------------------------------------------------------------------------------- 1 | use crate::RunResult; 2 | 3 | use super::command::{Command, CommandContext, CommandResult}; 4 | use super::debugger::Debugger; 5 | use anyhow::Result; 6 | 7 | use structopt::StructOpt; 8 | 9 | pub struct ProcessCommand {} 10 | 11 | impl ProcessCommand { 12 | pub fn new() -> Self { 13 | Self {} 14 | } 15 | } 16 | 17 | #[derive(StructOpt)] 18 | enum Opts { 19 | #[structopt(name = "continue")] 20 | Continue, 21 | 22 | /// Start WASI entry point 23 | #[structopt(name = "launch")] 24 | Launch { 25 | /// Entry point to start 26 | start: Option, 27 | 28 | /// Arguments to pass to the WASI entry point 29 | #[structopt(name = "ARGS", last = true)] 30 | args: Vec, 31 | }, 32 | } 33 | 34 | impl Command for ProcessCommand { 35 | fn name(&self) -> &'static str { 36 | "process" 37 | } 38 | 39 | fn description(&self) -> &'static str { 40 | "Commands for interacting with processes." 41 | } 42 | 43 | fn run( 44 | &self, 45 | debugger: &mut D, 46 | context: &CommandContext, 47 | args: Vec<&str>, 48 | ) -> Result> { 49 | let opts = Opts::from_iter_safe(args)?; 50 | match opts { 51 | Opts::Continue => match debugger.process()? { 52 | RunResult::Finish(result) => { 53 | return Ok(Some(CommandResult::ProcessFinish(result))); 54 | } 55 | RunResult::Breakpoint => { 56 | context.printer.println("Hit breakpoint"); 57 | } 58 | }, 59 | Opts::Launch { start, args } => { 60 | return self.start_debugger(debugger, context, start, args); 61 | } 62 | } 63 | Ok(None) 64 | } 65 | } 66 | impl ProcessCommand { 67 | fn start_debugger( 68 | &self, 69 | debugger: &mut D, 70 | context: &CommandContext, 71 | start: Option, 72 | wasi_args: Vec, 73 | ) -> Result> { 74 | use std::io::Write; 75 | if debugger.is_running() { 76 | print!("There is a running process, kill it and restart?: [Y/n] "); 77 | std::io::stdout().flush().unwrap(); 78 | let stdin = std::io::stdin(); 79 | let mut input = String::new(); 80 | stdin.read_line(&mut input).unwrap(); 81 | if input != "Y\n" && input != "y\n" { 82 | return Ok(None); 83 | } 84 | } 85 | debugger.instantiate(std::collections::HashMap::new(), Some(&wasi_args))?; 86 | 87 | match debugger.run(start.as_deref(), vec![]) { 88 | Ok(RunResult::Finish(values)) => { 89 | let output = format!("{:?}", values); 90 | context.printer.println(&output); 91 | return Ok(Some(CommandResult::ProcessFinish(values))); 92 | } 93 | Ok(RunResult::Breakpoint) => { 94 | context.printer.println("Hit breakpoint"); 95 | } 96 | Err(msg) => { 97 | let output = format!("{}", msg); 98 | context.printer.eprintln(&output); 99 | } 100 | } 101 | Ok(None) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/memory.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use anyhow::{anyhow, Result}; 4 | 5 | use structopt::StructOpt; 6 | 7 | pub struct MemoryCommand {} 8 | 9 | impl MemoryCommand { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | #[derive(StructOpt)] 16 | enum Opts { 17 | #[structopt(name = "read")] 18 | Read { 19 | #[structopt(name = "ADDRESS")] 20 | address: String, 21 | #[structopt(short, long, default_value = "32")] 22 | count: u32, 23 | }, 24 | #[structopt(name = "enable-watch")] 25 | EnableWatch, 26 | } 27 | 28 | impl Command for MemoryCommand { 29 | fn name(&self) -> &'static str { 30 | "memory" 31 | } 32 | 33 | fn description(&self) -> &'static str { 34 | "Commands for operating on memory." 35 | } 36 | fn run( 37 | &self, 38 | debugger: &mut D, 39 | context: &CommandContext, 40 | args: Vec<&str>, 41 | ) -> Result> { 42 | let opts = Opts::from_iter_safe(args)?; 43 | match opts { 44 | Opts::Read { address, count } => { 45 | let address = if address.starts_with("0x") { 46 | let raw = address.trim_start_matches("0x"); 47 | i64::from_str_radix(raw, 16)? 48 | } else { 49 | address.parse::()? 50 | }; 51 | let memory = debugger.memory()?; 52 | 53 | let begin = address as usize; 54 | let end = begin + (count as usize); 55 | let chunk_size = 16; 56 | if memory.len() <= end { 57 | return Err(anyhow!( 58 | "index {} out of range for slice of length {}", 59 | end, 60 | memory.len() 61 | )); 62 | } 63 | for (offset, bytes) in memory[begin..end].chunks(chunk_size).enumerate() { 64 | let bytes_str = bytes 65 | .iter() 66 | .map(|b| format!("{:>02x}", b)) 67 | .collect::>(); 68 | let output = format!( 69 | "0x{:>08x}: {} {}", 70 | begin + offset * chunk_size, 71 | bytes_str.join(" "), 72 | dump_memory_as_str(bytes) 73 | ); 74 | context.printer.println(&output); 75 | } 76 | Ok(None) 77 | } 78 | Opts::EnableWatch => { 79 | let mut opts = debugger.get_opts(); 80 | opts.watch_memory = true; 81 | debugger.set_opts(opts); 82 | Ok(None) 83 | } 84 | } 85 | } 86 | } 87 | 88 | use std::str; 89 | fn dump_memory_as_str(bytes: &[u8]) -> String { 90 | let mut v = Vec::new(); 91 | for byte in bytes.iter() { 92 | let byte = *byte; 93 | let byte = if byte > 0x1f && byte < 0x7f { 94 | let byte = vec![byte]; 95 | str::from_utf8(&byte).unwrap_or(".").to_string() 96 | } else { 97 | ".".to_string() 98 | }; 99 | v.push(byte) 100 | } 101 | v.join("") 102 | } 103 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | build: 8 | strategy: 9 | matrix: 10 | target: 11 | - x86_64-unknown-linux-gnu 12 | - x86_64-pc-windows-gnu 13 | - x86_64-apple-darwin 14 | include: 15 | - target: x86_64-unknown-linux-gnu 16 | os: ubuntu-latest 17 | - target: x86_64-pc-windows-gnu 18 | os: ubuntu-latest 19 | - target: x86_64-apple-darwin 20 | os: macos-latest 21 | 22 | runs-on: ${{ matrix.os }} 23 | 24 | steps: 25 | - uses: actions/checkout@v1 26 | 27 | # https://github.com/actions/cache/blob/master/examples.md#rust---cargo 28 | - name: Cache cargo registry 29 | uses: actions/cache@v1 30 | with: 31 | path: ~/.cargo/registry 32 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 33 | - name: Cache cargo index 34 | uses: actions/cache@v1 35 | with: 36 | path: ~/.cargo/git 37 | key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} 38 | - name: Cache cargo build 39 | uses: actions/cache@v1 40 | with: 41 | path: target 42 | key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} 43 | 44 | - uses: actions-rs/toolchain@v1 45 | with: 46 | toolchain: stable 47 | override: true 48 | - uses: actions-rs/cargo@v1.0.1 49 | with: 50 | command: build 51 | args: --release --target=${{ matrix.target }} 52 | use-cross: true 53 | 54 | - run: | 55 | zip --junk-paths wasminspect-${{ matrix.target }} target/${{ matrix.target }}/release/wasminspect{,.exe} 56 | - uses: actions/upload-artifact@v1 57 | with: 58 | name: build-${{ matrix.target }} 59 | path: wasminspect-${{ matrix.target }}.zip 60 | create-release: 61 | needs: [build] 62 | runs-on: ubuntu-latest 63 | steps: 64 | - id: create-release 65 | uses: actions/create-release@v1.0.0 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | with: 69 | tag_name: ${{ github.ref }} 70 | release_name: Release ${{ github.ref }} 71 | draft: false 72 | prerelease: false 73 | - run: | 74 | echo '${{ steps.create-release.outputs.upload_url }}' > release_upload_url.txt 75 | - uses: actions/upload-artifact@v1 76 | with: 77 | name: create-release 78 | path: release_upload_url.txt 79 | upload-release: 80 | strategy: 81 | matrix: 82 | target: 83 | - x86_64-unknown-linux-gnu 84 | - x86_64-pc-windows-gnu 85 | - x86_64-apple-darwin 86 | needs: [create-release] 87 | runs-on: ubuntu-latest 88 | steps: 89 | - uses: actions/download-artifact@v1 90 | with: 91 | name: create-release 92 | - id: upload-url 93 | run: | 94 | echo "::set-output name=url::$(cat create-release/release_upload_url.txt)" 95 | - uses: actions/download-artifact@v1 96 | with: 97 | name: build-${{ matrix.target }} 98 | - uses: actions/upload-release-asset@v1.0.1 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | with: 102 | upload_url: ${{ steps.upload-url.outputs.url }} 103 | asset_path: ./build-${{ matrix.target }}/wasminspect-${{ matrix.target }}.zip 104 | asset_name: wasminspect-${{ matrix.target }}.zip 105 | asset_content_type: application/zip 106 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/expression.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::Debugger; 3 | use crate::dwarf::{FrameBase, WasmLoc}; 4 | use anyhow::{anyhow, Context, Result}; 5 | 6 | pub struct ExpressionCommand {} 7 | 8 | impl ExpressionCommand { 9 | pub fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | use structopt::StructOpt; 15 | #[derive(StructOpt)] 16 | struct Opts { 17 | #[structopt(name = "SYMBOL")] 18 | symbol: String, 19 | } 20 | 21 | impl Command for ExpressionCommand { 22 | fn name(&self) -> &'static str { 23 | "expression" 24 | } 25 | 26 | fn description(&self) -> &'static str { 27 | "Evaluate an expression on the process (only support variable name now)." 28 | } 29 | 30 | fn run( 31 | &self, 32 | debugger: &mut D, 33 | context: &CommandContext, 34 | args: Vec<&str>, 35 | ) -> Result> { 36 | let opts = Opts::from_iter_safe(args)?; 37 | let (insts, next_index) = debugger.selected_instructions()?; 38 | let current_index = if next_index == 0 { 0 } else { next_index - 1 }; 39 | let current_inst = insts[current_index].clone(); 40 | let locals = debugger.locals(); 41 | use wasminspect_vm::*; 42 | let store: &Store = debugger.store()?; 43 | let mod_index = match debugger.current_frame() { 44 | Some(frame) => frame.module_index, 45 | None => return Err(anyhow!("function frame not found")), 46 | }; 47 | let frame_base = match context.subroutine.get_frame_base(current_inst.offset)? { 48 | Some(loc) => { 49 | let offset = match loc { 50 | WasmLoc::Global(idx) => store 51 | .global(GlobalAddr::new_unsafe(mod_index, idx as usize)) 52 | .borrow() 53 | .value(), 54 | WasmLoc::Local(idx) => *locals 55 | .get(idx as usize) 56 | .with_context(|| "failed to get base local".to_string())?, 57 | WasmLoc::Stack(idx) => *debugger 58 | .stack_values() 59 | .get(idx as usize) 60 | .with_context(|| "failed to get base local".to_string())?, 61 | }; 62 | let offset = match offset { 63 | WasmValue::Num(NumVal::I32(v)) => v as u64, 64 | WasmValue::Num(NumVal::I64(v)) => v as u64, 65 | _ => return Err(anyhow!("unexpected frame base value: {:?}", offset)), 66 | }; 67 | FrameBase::WasmFrameBase(offset) 68 | } 69 | None => { 70 | let argument_count = debugger 71 | .current_frame() 72 | .with_context(|| "function frame not found".to_string())? 73 | .argument_count; 74 | let offset = *locals 75 | .get(argument_count + 2) 76 | .with_context(|| "failed to get rbp".to_string())?; 77 | let offset = match offset { 78 | WasmValue::Num(NumVal::I32(v)) => v as u64, 79 | _ => return Err(anyhow!("unexpected frame base value: {:?}", offset)), 80 | }; 81 | FrameBase::Rbp(offset) 82 | } 83 | }; 84 | log::debug!("frame_base is {:?}", frame_base); 85 | context.subroutine.display_variable( 86 | current_inst.offset, 87 | frame_base, 88 | &debugger.memory()?, 89 | opts.symbol, 90 | )?; 91 | Ok(None) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/vm/src/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::value::FromLittleEndian; 2 | use crate::WASM_PAGE_SIZE; 3 | 4 | pub struct MemoryInstance { 5 | data: Vec, 6 | pub max: Option, 7 | pub initial: usize, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub enum Error { 12 | GrowOverMaximumSize(usize), 13 | GrowOverMaximumPageSize(usize), 14 | AccessOutOfBounds { 15 | try_to_access: Option, 16 | memory_size: usize, 17 | }, 18 | } 19 | 20 | impl std::fmt::Display for Error { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | match self { 23 | Self::AccessOutOfBounds { try_to_access: Some(addr), memory_size } => write!( 24 | f, 25 | "out of bounds memory access, try to access {} but size of memory is {}", 26 | addr, memory_size 27 | ), 28 | Self::AccessOutOfBounds { try_to_access: None, memory_size } => write!( 29 | f, 30 | "out of bounds memory access, try to access over size of usize but size of memory is {}", 31 | memory_size 32 | ), 33 | _ => write!(f, "{:?}", self), 34 | } 35 | } 36 | } 37 | 38 | type Result = std::result::Result; 39 | 40 | impl MemoryInstance { 41 | pub fn new(initial: usize, maximum: Option) -> Self { 42 | Self { 43 | data: std::iter::repeat(0) 44 | .take(initial * WASM_PAGE_SIZE) 45 | .collect(), 46 | initial, 47 | max: maximum, 48 | } 49 | } 50 | 51 | pub fn validate_region(&self, offset: usize, size: usize) -> Result<()> { 52 | if let Some(max_addr) = offset.checked_add(size) { 53 | if max_addr > self.data_len() { 54 | return Err(Error::AccessOutOfBounds { 55 | try_to_access: Some(max_addr), 56 | memory_size: self.data_len(), 57 | }); 58 | } 59 | } else { 60 | return Err(Error::AccessOutOfBounds { 61 | try_to_access: None, 62 | memory_size: self.data_len(), 63 | }); 64 | } 65 | Ok(()) 66 | } 67 | 68 | pub fn store(&mut self, offset: usize, data: &[u8]) -> Result<()> { 69 | self.validate_region(offset, data.len())?; 70 | for (index, byte) in data.iter().enumerate() { 71 | self.data[offset + index] = *byte; 72 | } 73 | Ok(()) 74 | } 75 | pub fn data_len(&self) -> usize { 76 | self.data.len() 77 | } 78 | 79 | pub fn load_as(&self, offset: usize) -> Result { 80 | self.validate_region(offset, std::mem::size_of::())?; 81 | let buf = &self.data[offset..offset + std::mem::size_of::()]; 82 | Ok(T::from_le(buf)) 83 | } 84 | 85 | pub fn page_count(&self) -> usize { 86 | self.data_len() / WASM_PAGE_SIZE 87 | } 88 | 89 | pub fn grow(&mut self, n: usize) -> Result<()> { 90 | let len = self.page_count() + n; 91 | if len > (u32::MAX as usize / WASM_PAGE_SIZE) { 92 | return Err(Error::GrowOverMaximumPageSize(len)); 93 | } 94 | 95 | if let Some(max) = self.max { 96 | if len > max { 97 | return Err(Error::GrowOverMaximumSize(max)); 98 | } 99 | } 100 | let zero_len = n * WASM_PAGE_SIZE; 101 | self.data.resize(self.data.len() + zero_len, 0); 102 | self.initial = len; 103 | Ok(()) 104 | } 105 | pub fn raw_data_mut(&mut self) -> &mut [u8] { 106 | &mut self.data 107 | } 108 | 109 | pub fn raw_data(&self) -> &[u8] { 110 | &self.data 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /crates/debugger/src/dwarf/format.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{AddAssign, SubAssign}; 2 | 3 | use super::utils::*; 4 | 5 | use anyhow::{anyhow, Context, Result}; 6 | use gimli::Unit; 7 | use num_bigint::{BigInt, BigUint, Sign}; 8 | 9 | pub fn format_object( 10 | node: gimli::EntriesTreeNode, 11 | memory: &[u8], 12 | _encoding: gimli::Encoding, 13 | dwarf: &gimli::Dwarf, 14 | unit: &Unit, 15 | ) -> Result { 16 | match node.entry().tag() { 17 | gimli::DW_TAG_base_type => { 18 | let entry = node.entry(); 19 | let name = match entry.attr_value(gimli::DW_AT_name)? { 20 | Some(attr) => clone_string_attribute(dwarf, unit, attr)?, 21 | None => "".to_string(), 22 | }; 23 | let byte_size = entry 24 | .attr_value(gimli::DW_AT_byte_size)? 25 | .and_then(|attr| attr.udata_value()) 26 | .with_context(|| "Failed to get byte_size".to_string())?; 27 | let encoding = entry 28 | .attr_value(gimli::DW_AT_encoding)? 29 | .and_then(|attr| match attr { 30 | gimli::AttributeValue::Encoding(encoding) => Some(encoding), 31 | _ => None, 32 | }) 33 | .with_context(|| "Failed to get type encoding".to_string())?; 34 | let mut bytes = Vec::new(); 35 | bytes.extend_from_slice(&memory[0..(byte_size as usize)]); 36 | 37 | match encoding { 38 | gimli::DW_ATE_signed => { 39 | let v = from_signed_bytes_le(&bytes); 40 | Ok(format!("{}({})", name, v)) 41 | } 42 | gimli::DW_ATE_unsigned => { 43 | let value = BigUint::from_bytes_le(&bytes); 44 | Ok(format!("{}({})", name, value)) 45 | } 46 | _ => unimplemented!(), 47 | } 48 | } 49 | gimli::DW_TAG_class_type | gimli::DW_TAG_structure_type => { 50 | let entry = node.entry(); 51 | let type_name = match entry.attr_value(gimli::DW_AT_name)? { 52 | Some(attr) => clone_string_attribute(dwarf, unit, attr)?, 53 | None => "".to_string(), 54 | }; 55 | let mut children = node.children(); 56 | let mut members = vec![]; 57 | while let Some(child) = children.next()? { 58 | match child.entry().tag() { 59 | gimli::DW_TAG_member => { 60 | let name = match child.entry().attr_value(gimli::DW_AT_name)? { 61 | Some(attr) => clone_string_attribute(dwarf, unit, attr)?, 62 | None => "".to_string(), 63 | }; 64 | // let ty = match entry.attr_value(gimli::DW_AT_type)? { 65 | // Some(gimli::AttributeValue::UnitRef(ref offset)) => offset.0, 66 | // _ => return Err(anyhow!("Failed to get type offset")), 67 | // }; 68 | members.push(name); 69 | } 70 | _ => continue, 71 | } 72 | } 73 | Ok(format!("{} {{\n{}\n}}", type_name, members.join(",\n"))) 74 | } 75 | _ => Err(anyhow!("unsupported DIE type")), 76 | } 77 | } 78 | 79 | fn from_signed_bytes_le(bytes: &[u8]) -> BigInt { 80 | assert!(!bytes.is_empty()); 81 | let is_negate = (bytes.last().unwrap() >> 7) == 1; 82 | let mut result = Vec::new(); 83 | for byte in bytes { 84 | let flipped = byte ^ !0; 85 | result.push(flipped); 86 | } 87 | let mut v = BigInt::from_bytes_le(if is_negate { Sign::Minus } else { Sign::Plus }, &result); 88 | if is_negate { 89 | v.sub_assign(1); 90 | } else { 91 | v.add_assign(1); 92 | } 93 | v 94 | } 95 | -------------------------------------------------------------------------------- /crates/wast-spec/src/spectest.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::rc::Rc; 4 | use wasminspect_vm::*; 5 | use wasmparser::{FuncType, GlobalType, ValType}; 6 | 7 | pub fn instantiate_spectest() -> HashMap { 8 | let mut module = HashMap::new(); 9 | let ty = FuncType::new(vec![], vec![]); 10 | let func = HostValue::Func(HostFuncBody::new(ty, |_, _, _, _| Ok(()))); 11 | module.insert("print".to_string(), func); 12 | 13 | let ty = FuncType::new(vec![ValType::I32], vec![]); 14 | let func = HostValue::Func(HostFuncBody::new(ty, |params, _, _, _| { 15 | println!("{}: i32", params[0].as_i32().unwrap()); 16 | Ok(()) 17 | })); 18 | module.insert("print_i32".to_string(), func); 19 | 20 | let ty = FuncType::new(vec![ValType::I64], vec![]); 21 | let func = HostValue::Func(HostFuncBody::new(ty, |params, _, _, _| { 22 | println!("{}: i64", params[0].as_i64().unwrap()); 23 | Ok(()) 24 | })); 25 | module.insert("print_i64".to_string(), func); 26 | 27 | let ty = FuncType::new(vec![ValType::F32], vec![]); 28 | let func = HostValue::Func(HostFuncBody::new(ty, |params, _, _, _| { 29 | println!("{}: f32", params[0].as_f32().unwrap()); 30 | Ok(()) 31 | })); 32 | module.insert("print_f32".to_string(), func); 33 | 34 | let ty = FuncType::new(vec![ValType::F64], vec![]); 35 | let func = HostValue::Func(HostFuncBody::new(ty, |params, _, _, _| { 36 | println!("{}: f64", params[0].as_f64().unwrap()); 37 | Ok(()) 38 | })); 39 | module.insert("print_f64".to_string(), func); 40 | 41 | let ty = FuncType::new(vec![ValType::I32, ValType::F32], vec![]); 42 | let func = HostValue::Func(HostFuncBody::new(ty, |params, _, _, _| { 43 | println!("{}: i32", params[0].as_i32().unwrap()); 44 | println!("{}: f32", params[1].as_f32().unwrap()); 45 | Ok(()) 46 | })); 47 | module.insert("print_i32_f32".to_string(), func); 48 | 49 | let ty = FuncType::new(vec![ValType::F64, ValType::F64], vec![]); 50 | let func = HostValue::Func(HostFuncBody::new(ty, |params, _, _, _| { 51 | println!("{}: f64", params[0].as_f64().unwrap()); 52 | println!("{}: f64", params[1].as_f64().unwrap()); 53 | Ok(()) 54 | })); 55 | module.insert("print_f64_f64".to_string(), func); 56 | 57 | let create_glbal = |value, ty| Rc::new(RefCell::new(GlobalInstance::new(value, ty))); 58 | module.insert( 59 | "global_i32".to_string(), 60 | HostValue::Global(create_glbal( 61 | WasmValue::I32(666), 62 | GlobalType { 63 | content_type: ValType::I32, 64 | mutable: false, 65 | }, 66 | )), 67 | ); 68 | module.insert( 69 | "global_i64".to_string(), 70 | HostValue::Global(create_glbal( 71 | WasmValue::I64(666), 72 | GlobalType { 73 | content_type: ValType::I64, 74 | mutable: false, 75 | }, 76 | )), 77 | ); 78 | module.insert( 79 | "global_f32".to_string(), 80 | HostValue::Global(create_glbal( 81 | WasmValue::F32(0x44268000), 82 | GlobalType { 83 | content_type: ValType::F32, 84 | mutable: false, 85 | }, 86 | )), 87 | ); 88 | module.insert( 89 | "global_f64".to_string(), 90 | HostValue::Global(create_glbal( 91 | WasmValue::F64(0x4084d00000000000), 92 | GlobalType { 93 | content_type: ValType::F64, 94 | mutable: false, 95 | }, 96 | )), 97 | ); 98 | 99 | let table = Rc::new(RefCell::new(HostTable::new(10, Some(20), RefType::FuncRef))); 100 | module.insert("table".to_string(), HostValue::Table(table)); 101 | 102 | let mem = Rc::new(RefCell::new(HostMemory::new(1, Some(2)))); 103 | module.insert("memory".to_string(), HostValue::Mem(mem)); 104 | module 105 | } 106 | -------------------------------------------------------------------------------- /crates/debugger-server/src/rpc.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use num_derive::FromPrimitive; 4 | use num_traits::FromPrimitive; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] 8 | #[serde(tag = "type")] 9 | pub enum WasmValue { 10 | I32 { value: i32 }, 11 | I64 { value: i64 }, 12 | F32 { value: f32 }, 13 | F64 { value: f64 }, 14 | } 15 | 16 | pub type JSNumber = f64; 17 | 18 | #[derive(Debug, Serialize, Deserialize)] 19 | pub enum WasmImport { 20 | Func { name: String }, 21 | Global { name: String }, 22 | Mem { name: String }, 23 | Table { name: String }, 24 | } 25 | 26 | #[derive(Debug, Serialize, Deserialize)] 27 | #[serde(tag = "type")] 28 | pub enum WasmExport { 29 | Memory { 30 | name: String, 31 | #[serde(rename = "memorySize")] 32 | memory_size: usize, 33 | }, 34 | Function { 35 | name: String, 36 | }, 37 | Global { 38 | name: String, 39 | }, 40 | Table { 41 | name: String, 42 | }, 43 | } 44 | 45 | #[derive(Debug)] 46 | pub enum RequestError { 47 | InvalidBinaryRequestKind(u8), 48 | InvalidTextRequestJSON(Box), 49 | InvalidMessageType(String), 50 | CallArgumentLengthMismatch, 51 | } 52 | 53 | impl std::fmt::Display for RequestError { 54 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 55 | write!(f, "{:?}", self) 56 | } 57 | } 58 | impl std::error::Error for RequestError {} 59 | 60 | #[derive(Debug, Serialize, Deserialize)] 61 | #[serde(tag = "type")] 62 | pub enum TextRequest { 63 | Version, 64 | InitMemory, 65 | CallExported { 66 | name: String, 67 | args: Vec, 68 | }, 69 | CallResult { 70 | values: Vec, 71 | }, 72 | LoadMemory { 73 | name: String, 74 | offset: usize, 75 | length: usize, 76 | }, 77 | StoreMemory { 78 | name: String, 79 | offset: usize, 80 | bytes: Vec, 81 | }, 82 | } 83 | 84 | #[derive(FromPrimitive, Debug)] 85 | pub enum BinaryRequestKind { 86 | Init = 0, 87 | } 88 | 89 | const WASM_MAGIC: [u8; 4] = [0x00, 0x61, 0x73, 0x6d]; 90 | 91 | #[derive(Debug)] 92 | pub struct BinaryRequest<'a> { 93 | pub kind: BinaryRequestKind, 94 | pub bytes: &'a [u8], 95 | } 96 | 97 | impl<'a> BinaryRequest<'a> { 98 | pub fn from_bytes(bytes: &'a [u8]) -> Result { 99 | if bytes.len() >= 4 && bytes[0..4].eq(&WASM_MAGIC) { 100 | Ok(Self { 101 | kind: BinaryRequestKind::Init, 102 | bytes, 103 | }) 104 | } else if let Some(kind) = FromPrimitive::from_u8(bytes[0]) { 105 | Ok(Self { 106 | kind, 107 | bytes: &bytes[1..], 108 | }) 109 | } else { 110 | Err(RequestError::InvalidBinaryRequestKind(bytes[0])) 111 | } 112 | } 113 | } 114 | 115 | #[derive(Debug)] 116 | pub enum Request<'a> { 117 | Text(TextRequest), 118 | Binary(BinaryRequest<'a>), 119 | } 120 | 121 | #[derive(Debug, Serialize, Deserialize)] 122 | #[serde(tag = "type")] 123 | pub enum TextResponse { 124 | Version { 125 | value: String, 126 | }, 127 | Init { 128 | exports: Vec, 129 | }, 130 | CallResult { 131 | values: Vec, 132 | }, 133 | CallHost { 134 | module: String, 135 | field: String, 136 | args: Vec, 137 | }, 138 | LoadMemoryResult { 139 | bytes: Vec, 140 | }, 141 | StoreMemoryResult, 142 | Error { 143 | message: String, 144 | }, 145 | } 146 | #[derive(Debug)] 147 | #[repr(u8)] 148 | pub enum BinaryResponseKind { 149 | InitMemory = 0, 150 | } 151 | 152 | #[derive(Debug)] 153 | pub enum Response { 154 | Text(TextResponse), 155 | Binary { 156 | kind: BinaryResponseKind, 157 | bytes: Vec, 158 | }, 159 | } 160 | 161 | impl From for Response { 162 | fn from(val: TextResponse) -> Self { 163 | Response::Text(val) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /crates/vm/src/table.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{RefType, RefVal}; 2 | 3 | #[derive(Debug)] 4 | pub enum Error { 5 | AccessOutOfBounds { 6 | try_to_access: Option, 7 | size: usize, 8 | }, 9 | UninitializedElement(usize), 10 | GrowOverMaximumSize { 11 | base: usize, 12 | growing: usize, 13 | }, 14 | } 15 | 16 | impl std::fmt::Display for Error { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | match self { 19 | Self::AccessOutOfBounds { try_to_access: Some(addr), size } => write!( 20 | f, 21 | "undefined element: out of bounds table access, try to access {} but size of memory is {}", 22 | addr, size 23 | ), 24 | Self::AccessOutOfBounds { try_to_access: None, size } => write!( 25 | f, 26 | "out of bounds table access, try to access over size of usize but size of memory is {}", 27 | size 28 | ), 29 | Self::UninitializedElement(addr) => { 30 | write!(f, "uninitialized element, try to access {}", addr) 31 | } 32 | other => write!(f, "{:?}", other) 33 | } 34 | } 35 | } 36 | 37 | type Result = std::result::Result; 38 | 39 | /// Runtime representation of a table. It records its type and holds a vector of `RefVal` 40 | /// https://webassembly.github.io/spec/core/exec/runtime.html#table-instances 41 | pub struct TableInstance { 42 | buffer: Vec, 43 | pub max: Option, 44 | pub initial: usize, 45 | pub ty: RefType, 46 | } 47 | 48 | impl TableInstance { 49 | pub fn new(initial: usize, maximum: Option, ty: RefType) -> Self { 50 | Self { 51 | buffer: std::iter::repeat(RefVal::NullRef(ty)) 52 | .take(initial) 53 | .collect(), 54 | initial, 55 | max: maximum, 56 | ty, 57 | } 58 | } 59 | 60 | pub fn validate_region(&self, offset: usize, size: usize) -> Result<()> { 61 | if let Some(max_addr) = offset.checked_add(size) { 62 | if max_addr > self.buffer_len() { 63 | return Err(Error::AccessOutOfBounds { 64 | try_to_access: Some(max_addr), 65 | size: self.buffer_len(), 66 | }); 67 | } 68 | } else { 69 | return Err(Error::AccessOutOfBounds { 70 | try_to_access: None, 71 | size: self.buffer_len(), 72 | }); 73 | } 74 | Ok(()) 75 | } 76 | 77 | pub fn initialize(&mut self, offset: usize, data: Vec) -> Result<()> { 78 | self.validate_region(offset, data.len())?; 79 | for (index, func_addr) in data.into_iter().enumerate() { 80 | self.buffer[offset + index] = func_addr; 81 | } 82 | Ok(()) 83 | } 84 | 85 | pub fn buffer_len(&self) -> usize { 86 | self.buffer.len() 87 | } 88 | 89 | pub fn get_at(&self, index: usize) -> Result { 90 | self.buffer 91 | .get(index) 92 | .ok_or_else(|| Error::AccessOutOfBounds { 93 | try_to_access: Some(index), 94 | size: self.buffer_len(), 95 | }) 96 | .map(|addr| *addr) 97 | } 98 | 99 | pub fn set_at(&mut self, index: usize, val: RefVal) -> Result<()> { 100 | let buffer_len = self.buffer_len(); 101 | let entry = self.buffer.get_mut(index).ok_or(Error::AccessOutOfBounds { 102 | try_to_access: Some(index), 103 | size: buffer_len, 104 | })?; 105 | *entry = val; 106 | Ok(()) 107 | } 108 | 109 | /// https://webassembly.github.io/spec/core/exec/modules.html#growing-tables 110 | pub fn grow(&mut self, n: usize, val: RefVal) -> Result<()> { 111 | let base_len = self.buffer_len(); 112 | let len = base_len.checked_add(n).ok_or(Error::GrowOverMaximumSize { 113 | base: base_len, 114 | growing: n, 115 | })?; 116 | 117 | if let Some(max) = self.max { 118 | if len > max { 119 | return Err(Error::GrowOverMaximumSize { 120 | base: base_len, 121 | growing: n, 122 | }); 123 | } 124 | } 125 | let mut extra = std::iter::repeat(val).take(n).collect(); 126 | self.buffer.append(&mut extra); 127 | Ok(()) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /crates/vm/macro/src/inst.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use quote::quote; 3 | use syn::{Data, DeriveInput, Variant}; 4 | 5 | pub fn try_from_wasmparser_operator(ast: DeriveInput) -> Result { 6 | let variants = match &ast.data { 7 | Data::Enum(v) => &v.variants, 8 | _ => return Err(anyhow!("unexpected non enum type")), 9 | }; 10 | let name = &ast.ident; 11 | let translate_arms = variants.into_iter().map(|v| build_translate_arm(name, v)); 12 | 13 | Ok(quote! { 14 | impl TryFrom> for #name { 15 | type Error = wasmparser::BinaryReaderError; 16 | fn try_from(op: wasmparser::Operator<'_>) -> Result { 17 | Ok(match op { 18 | #(#translate_arms),* 19 | }) 20 | } 21 | } 22 | }) 23 | } 24 | 25 | pub fn define_instr_kind(ast: proc_macro2::TokenStream) -> Result { 26 | // Accept ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) 27 | 28 | let mut tokens = proc_macro2::TokenStream::new(); 29 | let mut iter = ast.into_iter(); 30 | 31 | loop { 32 | let at = match iter.next() { 33 | Some(t) => t, 34 | None => break, 35 | }; 36 | assert_eq!(at.to_string(), "@"); 37 | 38 | let _proposal = iter.next().expect("unexpected end of input"); 39 | 40 | let op = iter.next().expect("unexpected end of input"); 41 | let op = match op { 42 | proc_macro2::TokenTree::Ident(i) => i, 43 | _ => panic!("unexpected token: {}", op), 44 | }; 45 | 46 | let mut payload = None; 47 | if let Some(proc_macro2::TokenTree::Group(g)) = iter.clone().next() { 48 | iter.next(); 49 | payload = Some(g.stream()); 50 | } 51 | 52 | assert_eq!( 53 | iter.next().expect("unexpected end of input").to_string(), 54 | "=" 55 | ); 56 | assert_eq!( 57 | iter.next().expect("unexpected end of input").to_string(), 58 | ">" 59 | ); 60 | iter.next().expect("unexpected end of input"); 61 | 62 | tokens.extend(build_instr_kind_case(op, payload)); 63 | } 64 | 65 | Ok(quote! { 66 | #[derive(Debug, Clone, TryFromWasmParserOperator)] 67 | pub enum InstructionKind { 68 | #tokens 69 | } 70 | }) 71 | } 72 | 73 | fn build_instr_kind_case( 74 | op: proc_macro2::Ident, 75 | payload: Option, 76 | ) -> proc_macro2::TokenStream { 77 | if let Some(payload) = payload { 78 | // BrTable is a special case because it has lifetime in its payload 79 | if op == "BrTable" { 80 | return quote! { 81 | #op { 82 | targets: BrTableData 83 | }, 84 | }; 85 | } 86 | quote! { 87 | #op { #payload }, 88 | } 89 | } else { 90 | quote! { 91 | #op, 92 | } 93 | } 94 | } 95 | 96 | fn build_translate_arm( 97 | enum_name: &proc_macro2::Ident, 98 | variant: &Variant, 99 | ) -> proc_macro2::TokenStream { 100 | let variant_name = variant.ident.clone(); 101 | match &variant.fields { 102 | syn::Fields::Named(fields) => { 103 | let fields = fields 104 | .named 105 | .iter() 106 | .filter_map(|f| f.ident.as_ref()) 107 | .collect::>(); 108 | let fields_and_values = fields.iter().map(|field| { 109 | quote! { 110 | #field: WasmInstPayloadFrom::from_payload(#field)? 111 | } 112 | }); 113 | quote! { 114 | wasmparser::Operator::#variant_name { #(#fields),* } => #enum_name::#variant_name { #(#fields_and_values),* } 115 | } 116 | } 117 | syn::Fields::Unnamed(fields) => { 118 | let fields = fields 119 | .unnamed 120 | .iter() 121 | .enumerate() 122 | .map(|(i, _)| proc_macro2::Ident::new(&format!("field{}", i), variant.ident.span())) 123 | .collect::>(); 124 | quote! { 125 | wasmparser::Operator::#variant_name ( #(#fields),* ) => #enum_name::#variant_name 126 | } 127 | } 128 | syn::Fields::Unit => { 129 | quote! { 130 | wasmparser::Operator::#variant_name => #enum_name::#variant_name 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /crates/debugger/src/commands/thread.rs: -------------------------------------------------------------------------------- 1 | use super::command::{Command, CommandContext, CommandResult}; 2 | use super::debugger::{Debugger, StepStyle}; 3 | use super::disassemble::display_asm; 4 | use super::list::{display_source, next_line_info}; 5 | use super::symbol::demangle_symbol; 6 | 7 | pub struct ThreadCommand {} 8 | 9 | impl ThreadCommand { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | use anyhow::Result; 16 | use structopt::StructOpt; 17 | 18 | #[derive(StructOpt)] 19 | enum Opts { 20 | #[structopt(name = "info")] 21 | Info, 22 | #[structopt(name = "backtrace")] 23 | Backtrace, 24 | #[structopt(name = "step-in")] 25 | StepIn, 26 | #[structopt(name = "step-over")] 27 | StepOver, 28 | #[structopt(name = "step-out")] 29 | StepOut, 30 | #[structopt(name = "step-inst-in")] 31 | StepInstIn, 32 | #[structopt(name = "step-inst-over")] 33 | StepInstOver, 34 | } 35 | 36 | impl Command for ThreadCommand { 37 | fn name(&self) -> &'static str { 38 | "thread" 39 | } 40 | 41 | fn description(&self) -> &'static str { 42 | "Commands for operating the thread." 43 | } 44 | 45 | fn run( 46 | &self, 47 | debugger: &mut D, 48 | context: &CommandContext, 49 | args: Vec<&str>, 50 | ) -> Result> { 51 | let opts = Opts::from_iter_safe(args.clone())?; 52 | match opts { 53 | Opts::Info => { 54 | let frames = debugger.frame(); 55 | let frame_name = frames.last().unwrap(); 56 | let (insts, next_index) = debugger.selected_instructions()?; 57 | let current_index = if next_index == 0 { 0 } else { next_index - 1 }; 58 | let current_inst = insts[current_index].clone(); 59 | let code_offset = current_inst.offset; 60 | let output = if let Some(line_info) = context.sourcemap.find_line_info(code_offset) 61 | { 62 | format!( 63 | "0x{:x} `{} at {}:{}:{}`", 64 | code_offset, 65 | frame_name, 66 | line_info.filepath, 67 | line_info 68 | .line 69 | .map(|l| format!("{}", l)) 70 | .unwrap_or_else(|| "".to_string()), 71 | Into::::into(line_info.column) 72 | ) 73 | } else { 74 | format!("0x{:x} `{}`", code_offset, frame_name) 75 | }; 76 | context.printer.println(&output); 77 | } 78 | Opts::Backtrace => { 79 | for (index, frame) in debugger.frame().iter().rev().enumerate() { 80 | let output = format!("{}: {}", index, demangle_symbol(frame)); 81 | context.printer.println(&output); 82 | } 83 | } 84 | Opts::StepIn | Opts::StepOver => { 85 | let style = match opts { 86 | Opts::StepIn => StepStyle::InstIn, 87 | Opts::StepOver => StepStyle::InstOver, 88 | _ => panic!(), 89 | }; 90 | let initial_line_info = next_line_info(debugger, context.sourcemap.as_ref())?; 91 | while { 92 | debugger.step(style)?; 93 | let line_info = next_line_info(debugger, context.sourcemap.as_ref())?; 94 | initial_line_info.filepath == line_info.filepath 95 | && initial_line_info.line == line_info.line 96 | } {} 97 | let line_info = next_line_info(debugger, context.sourcemap.as_ref())?; 98 | display_source(line_info, context.printer.as_ref())?; 99 | } 100 | Opts::StepOut => { 101 | debugger.step(StepStyle::Out)?; 102 | let line_info = next_line_info(debugger, context.sourcemap.as_ref())?; 103 | display_source(line_info, context.printer.as_ref())?; 104 | } 105 | Opts::StepInstIn | Opts::StepInstOver => { 106 | let style = match opts { 107 | Opts::StepInstIn => StepStyle::InstIn, 108 | Opts::StepInstOver => StepStyle::InstOver, 109 | _ => panic!(), 110 | }; 111 | debugger.step(style)?; 112 | display_asm(debugger, context.printer.as_ref(), Some(4), true)?; 113 | } 114 | } 115 | Ok(None) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/debugger/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod commands; 2 | mod debugger; 3 | mod dwarf; 4 | mod process; 5 | 6 | use std::{cell::RefCell, rc::Rc}; 7 | 8 | pub use commands::command::CommandContext; 9 | pub use commands::command::CommandResult; 10 | pub use commands::debugger::{Debugger, RunResult}; 11 | pub use debugger::MainDebugger; 12 | pub use linefeed; 13 | pub use process::Interactive; 14 | pub use process::Process; 15 | 16 | use anyhow::{anyhow, Result}; 17 | use commands::command; 18 | use log::warn; 19 | 20 | pub fn try_load_dwarf( 21 | buffer: &[u8], 22 | context: &mut commands::command::CommandContext, 23 | ) -> Result<()> { 24 | use dwarf::transform_dwarf; 25 | let debug_info = transform_dwarf(buffer)?; 26 | context.sourcemap = Box::new(debug_info.sourcemap); 27 | context.subroutine = Box::new(debug_info.subroutine); 28 | Ok(()) 29 | } 30 | 31 | struct ConsolePrinter {} 32 | impl commands::debugger::OutputPrinter for ConsolePrinter { 33 | fn println(&self, output: &str) { 34 | println!("{}", output); 35 | } 36 | fn eprintln(&self, output: &str) { 37 | eprintln!("{}", output); 38 | } 39 | } 40 | 41 | pub struct ModuleInput { 42 | pub bytes: Vec, 43 | pub basename: String, 44 | } 45 | 46 | pub fn start_debugger( 47 | module_input: Option, 48 | preopen_dirs: Vec<(String, String)>, 49 | envs: Vec<(String, String)>, 50 | ) -> Result<( 51 | process::Process, 52 | command::CommandContext, 53 | )> { 54 | let mut debugger = debugger::MainDebugger::new(preopen_dirs, envs)?; 55 | let mut context = commands::command::CommandContext { 56 | sourcemap: Box::new(commands::sourcemap::EmptySourceMap::new()), 57 | subroutine: Box::new(commands::subroutine::EmptySubroutineMap::new()), 58 | printer: Box::new(ConsolePrinter {}), 59 | }; 60 | 61 | if let Some(ref module_input) = module_input { 62 | debugger.load_main_module(&module_input.bytes, module_input.basename.clone())?; 63 | match try_load_dwarf(&module_input.bytes, &mut context) { 64 | Ok(_) => (), 65 | Err(err) => { 66 | warn!("Failed to load dwarf info: {}", err); 67 | } 68 | } 69 | } 70 | let process = process::Process::new( 71 | debugger, 72 | vec![ 73 | Box::new(commands::thread::ThreadCommand::new()), 74 | Box::new(commands::list::ListCommand::new()), 75 | Box::new(commands::memory::MemoryCommand::new()), 76 | Box::new(commands::stack::StackCommand::new()), 77 | Box::new(commands::breakpoint::BreakpointCommand::new()), 78 | Box::new(commands::disassemble::DisassembleCommand::new()), 79 | Box::new(commands::expression::ExpressionCommand::new()), 80 | Box::new(commands::global::GlobalCommand::new()), 81 | Box::new(commands::local::LocalCommand::new()), 82 | Box::new(commands::frame::FrameCommand::new()), 83 | Box::new(commands::settings::SettingsCommand::new()), 84 | Box::new(commands::process::ProcessCommand::new()), 85 | ], 86 | vec![ 87 | Box::new(commands::run::RunCommand::new()), 88 | Box::new(commands::backtrace::BacktraceCommand::new()), 89 | ], 90 | )?; 91 | Ok((process, context)) 92 | } 93 | 94 | pub fn run_loop( 95 | module_input: Option, 96 | init_source: Option, 97 | preopen_dirs: Vec<(String, String)>, 98 | envs: Vec<(String, String)>, 99 | ) -> Result<()> { 100 | let (mut process, context) = start_debugger(module_input, preopen_dirs, envs)?; 101 | 102 | { 103 | let is_default = init_source.is_none(); 104 | let lines = match { 105 | let init_source = init_source.unwrap_or_else(|| "~/.wasminspect_init".to_string()); 106 | use std::fs::File; 107 | use std::io::{BufRead, BufReader}; 108 | File::open(init_source).map(|file| BufReader::new(file).lines()) 109 | } { 110 | Ok(lines) => lines.map(|l| l.unwrap()).collect::>(), 111 | Err(err) => { 112 | if is_default { 113 | vec![] 114 | } else { 115 | return Err(anyhow!("{}", err)); 116 | } 117 | } 118 | }; 119 | for line in lines { 120 | process.dispatch_command(&line, &context)?; 121 | } 122 | } 123 | let mut interactive = Interactive::new_with_loading_history()?; 124 | let process = Rc::new(RefCell::new(process)); 125 | while let CommandResult::ProcessFinish(_) = interactive.run_loop(&context, process.clone())? {} 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /crates/vm/src/func.rs: -------------------------------------------------------------------------------- 1 | use crate::value::RefVal; 2 | use crate::RefType; 3 | 4 | use crate::host::HostFuncBody; 5 | use crate::inst::*; 6 | use crate::module::*; 7 | use crate::value::Value; 8 | use anyhow::Result; 9 | use std::iter; 10 | use wasmparser::ValType; 11 | use wasmparser::{FuncType, FunctionBody}; 12 | 13 | #[derive(Clone, Copy, Debug)] 14 | pub struct InstIndex(pub u32); 15 | 16 | impl InstIndex { 17 | pub fn zero() -> InstIndex { 18 | InstIndex(0) 19 | } 20 | } 21 | 22 | pub enum FunctionInstance { 23 | Defined(DefinedFunctionInstance), 24 | Native(NativeFunctionInstance), 25 | } 26 | 27 | impl FunctionInstance { 28 | pub fn ty(&self) -> &FuncType { 29 | match self { 30 | Self::Defined(defined) => defined.ty(), 31 | Self::Native(host) => host.ty(), 32 | } 33 | } 34 | 35 | pub fn defined(&self) -> Option<&DefinedFunctionInstance> { 36 | match self { 37 | Self::Defined(defined) => Some(defined), 38 | _ => None, 39 | } 40 | } 41 | 42 | pub fn name(&self) -> &String { 43 | match self { 44 | Self::Defined(defined) => &defined.name, 45 | Self::Native(host) => host.field_name(), 46 | } 47 | } 48 | } 49 | 50 | pub struct DefinedFunctionInstance { 51 | name: String, 52 | ty: FuncType, 53 | module_index: ModuleIndex, 54 | instructions: Vec, 55 | default_locals: Vec, 56 | } 57 | 58 | impl DefinedFunctionInstance { 59 | pub(crate) fn new( 60 | name: String, 61 | ty: FuncType, 62 | module_index: ModuleIndex, 63 | body: FunctionBody, 64 | base_offset: usize, 65 | ) -> Result { 66 | let mut locals = Vec::new(); 67 | let reader = body.get_locals_reader()?; 68 | for local in reader { 69 | let (count, value_type) = local?; 70 | let elements = iter::repeat(value_type).take(count as usize); 71 | locals.append(&mut elements.collect()); 72 | } 73 | let mut reader = body.get_operators_reader()?; 74 | let mut instructions = Vec::new(); 75 | while !reader.eof() { 76 | let inst = transform_inst(&mut reader, base_offset)?; 77 | instructions.push(inst); 78 | } 79 | 80 | // Compute default local values here instead of frame initialization 81 | // to avoid re-computation 82 | let mut local_tys = ty.params().to_vec(); 83 | local_tys.append(&mut locals.to_vec()); 84 | let mut default_locals = Vec::new(); 85 | for ty in local_tys { 86 | let v = match ty { 87 | ValType::I32 => Value::I32(0), 88 | ValType::I64 => Value::I64(0), 89 | ValType::F32 => Value::F32(0), 90 | ValType::F64 => Value::F64(0), 91 | ValType::ExternRef => Value::Ref(RefVal::NullRef(RefType::ExternRef)), 92 | ValType::FuncRef => Value::Ref(RefVal::NullRef(RefType::FuncRef)), 93 | _ => unimplemented!("local initialization of type {:?}", ty), 94 | }; 95 | default_locals.push(v); 96 | } 97 | 98 | Ok(Self { 99 | name, 100 | ty, 101 | module_index, 102 | instructions, 103 | default_locals, 104 | }) 105 | } 106 | 107 | pub fn name(&self) -> &String { 108 | &self.name 109 | } 110 | 111 | pub fn ty(&self) -> &FuncType { 112 | &self.ty 113 | } 114 | 115 | pub fn module_index(&self) -> ModuleIndex { 116 | self.module_index 117 | } 118 | 119 | pub fn instructions(&self) -> &[Instruction] { 120 | &self.instructions 121 | } 122 | 123 | pub(crate) fn inst(&self, index: InstIndex) -> Option<&Instruction> { 124 | self.instructions.get(index.0 as usize) 125 | } 126 | 127 | pub(crate) fn default_locals(&self) -> &[Value] { 128 | &self.default_locals 129 | } 130 | } 131 | 132 | pub struct NativeFunctionInstance { 133 | ty: FuncType, 134 | module_name: String, 135 | field_name: String, 136 | code: HostFuncBody, 137 | } 138 | 139 | impl NativeFunctionInstance { 140 | pub fn ty(&self) -> &FuncType { 141 | &self.ty 142 | } 143 | 144 | pub fn module_name(&self) -> &String { 145 | &self.module_name 146 | } 147 | 148 | pub fn field_name(&self) -> &String { 149 | &self.field_name 150 | } 151 | 152 | pub fn code(&self) -> &HostFuncBody { 153 | &self.code 154 | } 155 | 156 | pub fn new(ty: FuncType, module_name: String, field_name: String, code: HostFuncBody) -> Self { 157 | Self { 158 | ty, 159 | module_name, 160 | field_name, 161 | code, 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | ## Command Structure 2 | 3 | Most of wasminspect commands are similar to LLDB commands. The commands are all of the form: 4 | 5 | ``` 6 | [-options [option-value]] [argument [argument...]] 7 | ``` 8 | 9 | You can display help for each commands by `--help` flag. 10 | 11 | ## Getting started 12 | 13 | Let's try to debug your WebAssembly binary! 14 | 15 | Before debugging with wasminspect, please make sure that your WebAssembly binary has [DWARF debug information](http://dwarfstd.org/). 16 | 17 | Popular compilers like `clang` produces DWARF when `-g` flag is given. 18 | 19 | wasminspect just loads the given binary file, not execute it immediately. 20 | 21 | ```sh 22 | $ wasminspect awesome.wasm 23 | (wasminspect) 24 | ``` 25 | 26 | If you give commands playbook file with `--source` flag, wasminspect execute the commands after loading binary file automatically. 27 | 28 | ```sh 29 | $ cat init_playbook 30 | breakpoint set main 31 | run 32 | $ wasminspect awesome.wasm --source init_playbook 33 | (wasminspect) 34 | ``` 35 | 36 | ### Process your WebAssembly application 37 | 38 | `run` command just starts the process. If there is another process, it confirms whether it starts new process or not. 39 | 40 | ```sh 41 | (wasminspect) run 42 | ... 43 | (wasminspect) run 44 | There is a running process, kill it and restart?: [Y/n] Y 45 | ``` 46 | 47 | ### Setting breakpoints 48 | 49 | wasminspect stops the process when called function contains symbols set by breakpoints. 50 | 51 | ```sh 52 | (wasminspect) breakpoint set __original_main 53 | (wasminspect) run 54 | Hit breakpoint 55 | ``` 56 | 57 | ### Display corresponding source file 58 | 59 | wasminspect lists relevant source code from DWARF information. 60 | 61 | ```sh 62 | (wasminspect) list 63 | 1 int fib(int n) { 64 | 2 switch (n) { 65 | 3 case 0: 66 | 4 case 1: 67 | 5 return n; 68 | 6 default: 69 | 7 return fib(n - 2) + fib(n - 1); 70 | 8 } 71 | 9 } 72 | 10 73 | 11 int main(void) { 74 | 12 int x = 4; 75 | -> 13 x = fib(x); 76 | 14 return x; 77 | 15 } 78 | ``` 79 | 80 | ### Controlling Your Program 81 | 82 | After breakpoint hit, you can control your program by step-in, step-over, and step-out. 83 | 84 | ```sh 85 | (wasminspect) thread step-in 86 | (wasminspect) thread step-over 87 | (wasminspect) thread step-out 88 | (wasminspect) thread step-inst-in 89 | (wasminspect) thread step-inst-over 90 | ``` 91 | 92 | You can resume the process by `process continue` command. 93 | 94 | ```sh 95 | (wasminspect) process continue 96 | ``` 97 | 98 | ### Examining Thread State 99 | 100 | Once you’ve stopped, you can get thread information from wasminspect. 101 | 102 | ```sh 103 | (wasminspect) thread info 104 | 0x197 `__original_main at /Users/katei/.ghq/github.com/kateinoigakukun/wasminspect/tests/simple-example/c-dwarf/main.c:5:0` 105 | ``` 106 | 107 | This result shows the instruction address, function name and source code location. 108 | 109 | And you can examine call frame backtrace. 110 | ```sh 111 | (wasminspect) thread backtrace 112 | 0: fib 113 | 1: fib 114 | 2: fib 115 | 3: __original_main 116 | 4: _start 117 | ``` 118 | 119 | ## Experimental 120 | 121 | ### Dump frame variables 122 | 123 | wasminspect can dump local frame variables and print their contents. 124 | 125 | Now v0.1.0 only supports a few primitive types for `expression` command. But you can see the content by `memory` command if the content are in the linear memory. 126 | 127 | ```sh 128 | (wasminspect) frame variable 129 | conformance: const ProtocolConformanceDescriptor* 130 | protocol: const ProtocolDescriptor* 131 | requirements: ArrayRef > 132 | 133 | (wasminspect) expression protocol 134 | const ProtocolDescriptor* (0xe8fe8) 135 | 136 | (wasminspect) memory read 0xe8fe8 137 | 0x000e8fe8: b4 c1 03 00 d4 a5 00 00 00 00 00 00 00 00 00 00 ................ 138 | 0x000e8ff8: 94 2d 00 00 d4 a1 00 00 00 00 00 00 78 8f 0e 00 .-..........x... 139 | ``` 140 | 141 | 142 | ## Advanced 143 | 144 | ### Examine WebAssembly machine status 145 | 146 | If you're working on developing compiler, this is very useful to check the compiler emits correct instruction. 147 | 148 | ```sh 149 | (wasminspect) global read 0 150 | I32(67040) 151 | (wasminspect) local read 3 152 | I32(138) 153 | (wasminspect) stack 154 | 0: I32(953712) 155 | 1: I32(204436) 156 | (wasminspect) disassemble 157 | 0x00000197: GlobalGet { global_index: 0 } 158 | 0x0000019d: LocalSet { local_index: 0 } 159 | -> 0x0000019f: I32Const { value: 16 } 160 | 0x000001a1: LocalSet { local_index: 1 } 161 | 0x000001a3: LocalGet { local_index: 0 } 162 | 0x000001a5: LocalGet { local_index: 1 } 163 | ``` 164 | 165 | 166 | ### Source Directory mapping for the binary built by other machine 167 | 168 | If the binary is built in remote machine, DWARF records remote source directory path. 169 | If you have same structure source directory in debugging machine, you can map the source directory. 170 | 171 | This is similar feature to [LLDB's source-map.](https://lldb.llvm.org/use/map.html#miscellaneous) 172 | 173 | > Remap source file pathnames for the debug session. If your source files are no longer located in the same location as when the program was built --- maybe the program was built on a different computer --- you need to tell the debugger how to find the sources at their local file path instead of the build system's file path. 174 | 175 | 176 | ```sh 177 | (wasminspect) settings set directory.map /home/katei/swiftwasm-source /Users/katei/projects/swiftwasm-source 178 | ``` 179 | 180 | -------------------------------------------------------------------------------- /crates/debugger/src/process.rs: -------------------------------------------------------------------------------- 1 | use crate::commands::command::{self, AliasCommand, Command, CommandResult}; 2 | use crate::commands::debugger::Debugger; 3 | use anyhow::{Context, Result}; 4 | use linefeed::{DefaultTerminal, Interface, ReadResult}; 5 | use std::{cell::RefCell, io, rc::Rc}; 6 | use std::{collections::HashMap, time::Duration}; 7 | 8 | pub struct Process { 9 | pub debugger: D, 10 | commands: HashMap>>, 11 | aliases: HashMap>, 12 | } 13 | 14 | impl Process { 15 | pub fn new( 16 | debugger: D, 17 | commands: Vec>>, 18 | aliases: Vec>, 19 | ) -> anyhow::Result { 20 | let mut cmd_map = HashMap::new(); 21 | for cmd in commands { 22 | cmd_map.insert(cmd.name().to_string().clone(), cmd); 23 | } 24 | let mut alias_map = HashMap::new(); 25 | for cmd in aliases { 26 | alias_map.insert(cmd.name().to_string().clone(), cmd); 27 | } 28 | Ok(Self { 29 | debugger, 30 | commands: cmd_map, 31 | aliases: alias_map, 32 | }) 33 | } 34 | 35 | pub fn dispatch_command( 36 | &mut self, 37 | line: &str, 38 | context: &command::CommandContext, 39 | ) -> Result> { 40 | let cmd_name = extract_command_name(line); 41 | let args = shell_words::split(line)?; 42 | // FIXME 43 | let args = args.iter().map(AsRef::as_ref).collect(); 44 | if let Some(cmd) = self.commands.get(cmd_name) { 45 | match cmd.run(&mut self.debugger, context, args) { 46 | Ok(result) => Ok(result), 47 | Err(err) => { 48 | eprintln!("{}", err); 49 | Ok(None) 50 | } 51 | } 52 | } else if let Some(alias) = self.aliases.get(cmd_name) { 53 | let line = alias.run(args)?; 54 | self.dispatch_command(&line, context) 55 | } else if cmd_name == "help" { 56 | println!("Available commands:"); 57 | for command in self.commands.values() { 58 | println!(" {} -- {}", command.name(), command.description()); 59 | } 60 | Ok(None) 61 | } else if cfg!(feature = "remote-api") && cmd_name == "start-server" { 62 | Ok(Some(CommandResult::Exit)) 63 | } else { 64 | eprintln!("'{}' is not a valid command.", cmd_name); 65 | Ok(None) 66 | } 67 | } 68 | } 69 | 70 | pub struct Interactive { 71 | pub interface: Interface, 72 | 73 | history_file: String, 74 | } 75 | 76 | fn history_file_path() -> String { 77 | format!( 78 | "{}/.wasminspect-history", 79 | std::env::var_os("HOME").unwrap().to_str().unwrap() 80 | ) 81 | } 82 | 83 | impl Interactive { 84 | pub fn new_with_loading_history() -> anyhow::Result { 85 | Self::new(&history_file_path()) 86 | } 87 | 88 | pub fn new(history_file: &str) -> anyhow::Result { 89 | let interface = Interface::new("wasminspect").with_context(|| "new Interface")?; 90 | interface 91 | .set_prompt("(wasminspect) ") 92 | .with_context(|| "set prompt")?; 93 | if let Err(e) = interface.load_history(history_file) { 94 | if e.kind() == io::ErrorKind::NotFound { 95 | } else { 96 | eprintln!("Could not load history file {}: {}", history_file, e); 97 | } 98 | } 99 | Ok(Self { 100 | interface, 101 | history_file: history_file.to_string(), 102 | }) 103 | } 104 | pub fn run_step( 105 | &mut self, 106 | context: &command::CommandContext, 107 | process: Rc>>, 108 | last_line: &mut Option, 109 | timeout: Option, 110 | ) -> Result> { 111 | let line = match self.interface.read_line_step(timeout)? { 112 | Some(ReadResult::Input(line)) => line, 113 | Some(_) => return Ok(Some(CommandResult::Exit)), 114 | None => return Ok(None), 115 | }; 116 | let result = if !line.trim().is_empty() { 117 | self.interface.add_history_unique(line.clone()); 118 | *last_line = Some(line.clone()); 119 | process.borrow_mut().dispatch_command(&line, context)? 120 | } else if let Some(last_line) = last_line.as_ref() { 121 | process.borrow_mut().dispatch_command(last_line, context)? 122 | } else { 123 | None 124 | }; 125 | Ok(result) 126 | } 127 | 128 | pub fn run_loop( 129 | &mut self, 130 | context: &command::CommandContext, 131 | process: Rc>>, 132 | ) -> Result { 133 | let mut last_line: Option = None; 134 | loop { 135 | if let Some(result) = self.run_step(context, process.clone(), &mut last_line, None)? { 136 | return Ok(result); 137 | } 138 | } 139 | } 140 | } 141 | 142 | fn extract_command_name(s: &str) -> &str { 143 | let s = s.trim(); 144 | 145 | match s.find(|ch: char| ch.is_whitespace()) { 146 | Some(pos) => &s[..pos], 147 | None => s, 148 | } 149 | } 150 | 151 | impl Drop for Interactive { 152 | fn drop(&mut self) { 153 | if let Err(error) = self.interface.save_history(&self.history_file) { 154 | println!("Error while saving command history: {}", error); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /crates/wasi/src/borrow.rs: -------------------------------------------------------------------------------- 1 | /// Taken from https://github.com/bytecodealliance/wasmtime/blob/main/crates/wiggle/borrow/src/lib.rs 2 | /// Wasmtime is distributed under Apache License 3 | use std::cell::RefCell; 4 | use std::collections::HashMap; 5 | use wiggle::{BorrowHandle, GuestError, Region}; 6 | 7 | pub struct BorrowChecker { 8 | /// Unfortunately, since the terminology of std::cell and the problem domain of borrow checking 9 | /// overlap, the method calls on this member will be confusing. 10 | bc: RefCell, 11 | } 12 | 13 | impl BorrowChecker { 14 | /// A `BorrowChecker` manages run-time validation of borrows from a 15 | /// `GuestMemory`. It keeps track of regions of guest memory which are 16 | /// possible to alias with Rust references (via the `GuestSlice` and 17 | /// `GuestStr` structs, which implement `std::ops::Deref` and 18 | /// `std::ops::DerefMut`. It also enforces that `GuestPtr::read` 19 | /// does not access memory with an outstanding mutable borrow, and 20 | /// `GuestPtr::write` does not access memory with an outstanding 21 | /// shared or mutable borrow. 22 | pub fn new() -> Self { 23 | BorrowChecker { 24 | bc: RefCell::new(InnerBorrowChecker::new()), 25 | } 26 | } 27 | /// Indicates whether any outstanding shared or mutable borrows are known 28 | /// to the `BorrowChecker`. This function must be `false` in order for it 29 | /// to be safe to recursively call into a WebAssembly module, or to 30 | /// manipulate the WebAssembly memory by any other means. 31 | pub fn has_outstanding_borrows(&self) -> bool { 32 | self.bc.borrow().has_outstanding_borrows() 33 | } 34 | pub fn shared_borrow(&self, r: Region) -> Result { 35 | self.bc.borrow_mut().shared_borrow(r) 36 | } 37 | pub fn mut_borrow(&self, r: Region) -> Result { 38 | self.bc.borrow_mut().mut_borrow(r) 39 | } 40 | pub fn shared_unborrow(&self, h: BorrowHandle) { 41 | self.bc.borrow_mut().shared_unborrow(h) 42 | } 43 | pub fn mut_unborrow(&self, h: BorrowHandle) { 44 | self.bc.borrow_mut().mut_unborrow(h) 45 | } 46 | pub fn is_shared_borrowed(&self, r: Region) -> bool { 47 | self.bc.borrow().is_shared_borrowed(r) 48 | } 49 | pub fn is_mut_borrowed(&self, r: Region) -> bool { 50 | self.bc.borrow().is_mut_borrowed(r) 51 | } 52 | } 53 | 54 | #[derive(Debug)] 55 | /// This is a pretty naive way to account for borrows. This datastructure 56 | /// could be made a lot more efficient with some effort. 57 | struct InnerBorrowChecker { 58 | /// Maps from handle to region borrowed. A HashMap is probably not ideal 59 | /// for this but it works. It would be more efficient if we could 60 | /// check `is_borrowed` without an O(n) iteration, by organizing borrows 61 | /// by an ordering of Region. 62 | shared_borrows: HashMap, 63 | mut_borrows: HashMap, 64 | /// Handle to give out for the next borrow. This is the bare minimum of 65 | /// bookkeeping of free handles, and in a pathological case we could run 66 | /// out, hence [`GuestError::BorrowCheckerOutOfHandles`] 67 | next_handle: BorrowHandle, 68 | } 69 | 70 | impl InnerBorrowChecker { 71 | fn new() -> Self { 72 | InnerBorrowChecker { 73 | shared_borrows: HashMap::new(), 74 | mut_borrows: HashMap::new(), 75 | next_handle: BorrowHandle(0), 76 | } 77 | } 78 | 79 | fn has_outstanding_borrows(&self) -> bool { 80 | !(self.shared_borrows.is_empty() && self.mut_borrows.is_empty()) 81 | } 82 | 83 | fn is_shared_borrowed(&self, r: Region) -> bool { 84 | self.shared_borrows.values().any(|b| b.overlaps(r)) 85 | } 86 | fn is_mut_borrowed(&self, r: Region) -> bool { 87 | self.mut_borrows.values().any(|b| b.overlaps(r)) 88 | } 89 | 90 | fn new_handle(&mut self) -> Result { 91 | // Reset handles to 0 if all handles have been returned. 92 | if self.shared_borrows.is_empty() && self.mut_borrows.is_empty() { 93 | self.next_handle = BorrowHandle(0); 94 | } 95 | let h = self.next_handle; 96 | // Get the next handle. Since we don't recycle handles until all of 97 | // them have been returned, there is a pathological case where a user 98 | // may make a Very Large (usize::MAX) number of valid borrows and 99 | // unborrows while always keeping at least one borrow outstanding, and 100 | // we will run out of borrow handles. 101 | self.next_handle = BorrowHandle( 102 | h.0.checked_add(1) 103 | .ok_or(GuestError::BorrowCheckerOutOfHandles)?, 104 | ); 105 | Ok(h) 106 | } 107 | 108 | fn shared_borrow(&mut self, r: Region) -> Result { 109 | if self.is_mut_borrowed(r) { 110 | return Err(GuestError::PtrBorrowed(r)); 111 | } 112 | let h = self.new_handle()?; 113 | self.shared_borrows.insert(h, r); 114 | Ok(h) 115 | } 116 | 117 | fn mut_borrow(&mut self, r: Region) -> Result { 118 | if self.is_shared_borrowed(r) || self.is_mut_borrowed(r) { 119 | return Err(GuestError::PtrBorrowed(r)); 120 | } 121 | let h = self.new_handle()?; 122 | self.mut_borrows.insert(h, r); 123 | Ok(h) 124 | } 125 | 126 | fn shared_unborrow(&mut self, h: BorrowHandle) { 127 | let removed = self.shared_borrows.remove(&h); 128 | debug_assert!(removed.is_some(), "double-freed shared borrow"); 129 | } 130 | 131 | fn mut_unborrow(&mut self, h: BorrowHandle) { 132 | let removed = self.mut_borrows.remove(&h); 133 | debug_assert!(removed.is_some(), "double-freed mut borrow"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /crates/wasi/macro/src/wasi.rs: -------------------------------------------------------------------------------- 1 | use crate::utils; 2 | use proc_macro2::{Ident, Literal, Span, TokenStream}; 3 | use quote::quote; 4 | use utils::witx_target_module_map_ident; 5 | use witx::WasmType; 6 | 7 | fn emit_func_extern( 8 | name: &str, 9 | params: &[WasmType], 10 | returns: &[WasmType], 11 | module_map_id: &Ident, 12 | module_id: &Ident, 13 | ) -> TokenStream { 14 | let to_wasmparser_ty = |abi_ty: &WasmType| match abi_ty { 15 | WasmType::I32 => quote! { ::wasmparser::ValType::I32 }, 16 | WasmType::I64 => quote! { ::wasmparser::ValType::I64 }, 17 | WasmType::F32 => quote! { ::wasmparser::ValType::F32 }, 18 | WasmType::F64 => quote! { ::wasmparser::ValType::F64 }, 19 | }; 20 | 21 | let mut param_types = Vec::new(); 22 | for param in params { 23 | let param = to_wasmparser_ty(param); 24 | param_types.push(quote! { #param }); 25 | } 26 | 27 | let mut arg_values = Vec::new(); 28 | for (idx, param) in params.iter().enumerate() { 29 | let cast_fn = match param { 30 | WasmType::I32 => quote! { as_i32 }, 31 | WasmType::I64 => quote! { as_i64 }, 32 | WasmType::F32 => quote! { as_f32 }, 33 | WasmType::F64 => quote! { as_f64 }, 34 | }; 35 | let idx_lit = Literal::usize_unsuffixed(idx); 36 | arg_values.push(quote! { args[#idx_lit].#cast_fn().unwrap() }); 37 | } 38 | 39 | let mut return_types = Vec::new(); 40 | for ret in returns { 41 | let ret = to_wasmparser_ty(ret); 42 | return_types.push(quote! { #ret }); 43 | } 44 | 45 | let mut ret_value = quote! {}; 46 | if let Some(ret_ty) = returns.first() { 47 | assert!(returns.len() == 1); 48 | let (primitive_ty, ty_case) = match ret_ty { 49 | WasmType::I32 => (quote! { i32 }, quote! { WasmValue::I32 }), 50 | WasmType::I64 => (quote! { i64 }, quote! { WasmValue::I64 }), 51 | WasmType::F32 => (quote! { f32 }, quote! { WasmValue::F32 }), 52 | WasmType::F64 => (quote! { f64 }, quote! { WasmValue::F64 }), 53 | }; 54 | ret_value = quote! { ret.push(#ty_case(result as #primitive_ty)); }; 55 | } 56 | 57 | let name_id = Ident::new(name, Span::call_site()); 58 | let name_str = name; 59 | let call_expr = if name == "proc_exit" { 60 | quote! { 61 | let result = crate::wasi_proc_exit( 62 | #(#arg_values),* 63 | ); 64 | } 65 | } else { 66 | quote! { 67 | let result = match wasi_common::snapshots::preview_1::#module_id::#name_id( 68 | &*wasi_ctx, 69 | &mem, 70 | #(#arg_values),* 71 | ) { 72 | Ok(result) => result, 73 | Err(e) => return Err(Trap::HostFunctionError(Box::new(WasiError(format!("{:?}", e))))), 74 | }; 75 | #ret_value 76 | } 77 | }; 78 | quote! { 79 | let ty = ::wasmparser::FuncType::new( 80 | vec![#(#param_types),*], 81 | vec![#(#return_types),*], 82 | ); 83 | let func = HostValue::Func(HostFuncBody::new(ty, move |args, ret, ctx, store| { 84 | log::debug!("{}({:?})", #name, args); 85 | let wasi_ctx = store.get_embed_context::().unwrap(); 86 | let mut wasi_ctx = wasi_ctx.ctx.borrow_mut(); 87 | let bc = unsafe { borrow::BorrowChecker::new() }; 88 | let mem = WasiMemory { 89 | mem: ctx.mem.as_mut_ptr(), 90 | mem_size: ctx.mem.len() as u32, 91 | bc, 92 | }; 93 | #call_expr 94 | Ok(()) 95 | })); 96 | #module_map_id.insert(#name_str.to_string(), func); 97 | } 98 | } 99 | 100 | pub fn define_wasi_fn_for_wasminspect(args: TokenStream) -> TokenStream { 101 | let mut args = args.into_iter(); 102 | let module_map_id = witx_target_module_map_ident(args.next().expect("module map id")); 103 | args.next(); // consume "," 104 | let module_map_id = Ident::new(&module_map_id, Span::call_site()); 105 | let path = utils::witx_path_from_arg(args.next().expect("witx path")); 106 | let doc = match witx::load(&[&path]) { 107 | Ok(doc) => doc, 108 | Err(e) => { 109 | panic!("error opening file {}: {}", path.display(), e); 110 | } 111 | }; 112 | 113 | let mut ctor_externs = Vec::new(); 114 | 115 | for module in doc.modules() { 116 | let module_name = module.name.as_str(); 117 | let module_id = Ident::new(module_name, Span::call_site()); 118 | 119 | for func in module.funcs() { 120 | let name = func.name.as_str(); 121 | let (params, returns) = func.wasm_signature(); 122 | 123 | ctor_externs.push(emit_func_extern( 124 | name, 125 | ¶ms, 126 | &returns, 127 | &module_map_id, 128 | &module_id, 129 | )); 130 | } 131 | } 132 | quote! { 133 | struct WasiMemory { 134 | mem: *mut u8, 135 | mem_size: u32, 136 | bc: borrow::BorrowChecker, 137 | } 138 | unsafe impl ::wiggle::GuestMemory for WasiMemory { 139 | fn base(&self) -> (*mut u8, u32) { 140 | return (self.mem, self.mem_size); 141 | } 142 | fn has_outstanding_borrows(&self) -> bool { 143 | self.bc.has_outstanding_borrows() 144 | } 145 | fn is_shared_borrowed(&self, r: ::wiggle::Region) -> bool { 146 | self.bc.is_shared_borrowed(r) 147 | } 148 | fn is_mut_borrowed(&self, r: ::wiggle::Region) -> bool { 149 | self.bc.is_mut_borrowed(r) 150 | } 151 | fn shared_borrow(&self, r: ::wiggle::Region) -> Result<::wiggle::BorrowHandle, ::wiggle::GuestError> { 152 | self.bc.shared_borrow(r) 153 | } 154 | fn mut_borrow(&self, r: ::wiggle::Region) -> Result<::wiggle::BorrowHandle, ::wiggle::GuestError> { 155 | self.bc.mut_borrow(r) 156 | } 157 | fn shared_unborrow(&self, h: ::wiggle::BorrowHandle) { 158 | self.bc.shared_unborrow(h) 159 | } 160 | fn mut_unborrow(&self, h: ::wiggle::BorrowHandle) { 161 | self.bc.mut_unborrow(h) 162 | } 163 | } 164 | #(#ctor_externs)* 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /crates/vm/src/linker.rs: -------------------------------------------------------------------------------- 1 | use crate::module::ModuleIndex; 2 | use std::collections::HashMap; 3 | use std::fmt; 4 | use std::hash::Hash; 5 | 6 | /// An address value which points an `Item` in `LinkableCollection` 7 | /// The pointee item must be exists in the collection. 8 | #[derive(PartialEq, Eq, Hash)] 9 | pub struct GlobalAddress(usize, std::marker::PhantomData); 10 | 11 | impl Clone for GlobalAddress { 12 | fn clone(&self) -> Self { 13 | Self(self.0, self.1) 14 | } 15 | } 16 | 17 | impl Copy for GlobalAddress {} 18 | 19 | impl fmt::Debug for GlobalAddress { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!(f, "GlobalAddress({})", self.0) 22 | } 23 | } 24 | 25 | /// An address value which *may* points an `Item` in `LinkableCollection` 26 | /// or another `LinkableAddress`. 27 | /// To access the pointee, resolve it by `LinkableCollection`, 28 | /// and get a real address `GlobalAddress`. 29 | /// Note that different `LinkableAddress`es can points the same item. 30 | pub struct LinkableAddress( 31 | ModuleIndex, 32 | pub(crate) usize, 33 | std::marker::PhantomData Item>, 34 | ); 35 | 36 | impl LinkableAddress { 37 | pub fn new_unsafe(module: ModuleIndex, index: usize) -> Self { 38 | Self(module, index, std::marker::PhantomData) 39 | } 40 | 41 | pub fn module_index(&self) -> ModuleIndex { 42 | self.0 43 | } 44 | } 45 | 46 | impl Clone for LinkableAddress { 47 | fn clone(&self) -> Self { 48 | Self::new_unsafe(self.0, self.1) 49 | } 50 | } 51 | 52 | impl Copy for LinkableAddress {} 53 | 54 | impl fmt::Debug for LinkableAddress { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | write!(f, "{:?}, func_index: {}", self.0, self.1) 57 | } 58 | } 59 | 60 | impl PartialEq for LinkableAddress { 61 | fn eq(&self, other: &Self) -> bool { 62 | self.0 == other.0 && self.1 == other.1 63 | } 64 | } 65 | 66 | impl Eq for LinkableAddress {} 67 | impl Hash for LinkableAddress { 68 | fn hash(&self, state: &mut H) { 69 | self.0.hash(state); 70 | state.write_usize(self.1); 71 | } 72 | } 73 | 74 | /// A collection of items and address spaces that can be linked to each other. 75 | /// 76 | /// ## Concept 77 | /// 78 | /// `LinkableCollection` holds a collection of `Item`s and two addres spaces. 79 | /// One of the two types of address, `LinkableAddress`, points to another 80 | /// `LinkableAddress` or a `GlobalAddress`. The other address `GlobalAddress` 81 | /// points to an `Item` held by the collection. 82 | /// 83 | /// ```text 84 | /// ┌──────────────── LinkableCollection ──────────────────────┐ 85 | /// │ │ 86 | /// │ LA = `LinkableAddress`, GA = `GlobalAddress` │ 87 | /// │ │ 88 | /// │ ┌─── Module A ───┐ ┌── Module B ──┐ ┌─── Module C ───┐ │ 89 | /// │ │ │ │ │ │ │ │ 90 | /// │ │ LA 0 LA 1 │ │ LA 2 ◀───┼──┼─ LA 3 ◀─ LA 4 │ │ 91 | /// │ │ │ │ │ │ │ │ │ ▲ │ │ 92 | /// │ └────┼──────┼────┘ └───────┼──────┘ └────┼───────────┘ │ 93 | /// │ │ └───────────────│──────────────┘ │ 94 | /// │ │ │ │ 95 | /// │ ┌────┼─── GlobalAddresses ──┼──────────────────────────┐ │ 96 | /// │ │ ▼ ▼ │ │ 97 | /// │ │ ┌──────────┐ ┌──────────┐ │ │ 98 | /// │ │ │ GA 0 │ │ GA 1 │ │ │ 99 | /// │ │ └──────────┘ └──────────┘ │ │ 100 | /// │ └────┼──────────────────────┼──────────────────────────┘ │ 101 | /// │ │ │ │ 102 | /// │ ┌────┼─────── Items ────────┼──────────────────────────┐ │ 103 | /// │ │ ▼ ▼ │ │ 104 | /// │ │ ┌──────────┐ ┌──────────┐ │ │ 105 | /// │ │ │ Item X │ │ Item Y │ │ │ 106 | /// │ │ └──────────┘ └──────────┘ │ │ 107 | /// │ └──────────────────────────────────────────────────────┘ │ 108 | /// └──────────────────────────────────────────────────────────┘ 109 | /// ``` 110 | /// 111 | pub(crate) struct LinkableCollection { 112 | items: Vec, 113 | item_addrs_by_module: HashMap>, 114 | } 115 | 116 | impl Default for LinkableCollection { 117 | fn default() -> Self { 118 | Self { 119 | items: Vec::new(), 120 | item_addrs_by_module: HashMap::new(), 121 | } 122 | } 123 | } 124 | 125 | impl LinkableCollection { 126 | pub(crate) fn resolve(&self, address: LinkableAddress) -> Option> { 127 | let raw_address = self.item_addrs_by_module.get(&address.0)?.get(address.1)?; 128 | Some(GlobalAddress(*raw_address, std::marker::PhantomData)) 129 | } 130 | 131 | pub(crate) fn link( 132 | &mut self, 133 | source: GlobalAddress, 134 | dist: ModuleIndex, 135 | ) -> LinkableAddress { 136 | let index = self 137 | .item_addrs_by_module 138 | .get(&dist) 139 | .map(|c| c.len()) 140 | .unwrap_or(0); 141 | self.item_addrs_by_module 142 | .entry(dist) 143 | .or_insert_with(Vec::new) 144 | .push(source.0); 145 | LinkableAddress::new_unsafe(dist, index) 146 | } 147 | 148 | pub(crate) fn get_global(&self, address: GlobalAddress) -> &Item { 149 | // Never panic because GlobalAddress is always valid 150 | self.items.get(address.0).unwrap() 151 | } 152 | 153 | pub(crate) fn get( 154 | &self, 155 | address: LinkableAddress, 156 | ) -> Option<(&Item, GlobalAddress)> { 157 | let addr = self.resolve(address)?; 158 | Some((self.items.get(addr.0)?, addr)) 159 | } 160 | 161 | pub(crate) fn push_global(&mut self, item: Item) -> GlobalAddress { 162 | let index = self.items.len(); 163 | self.items.push(item); 164 | GlobalAddress(index, std::marker::PhantomData) 165 | } 166 | 167 | pub(crate) fn push(&mut self, module_index: ModuleIndex, item: Item) -> LinkableAddress { 168 | let globa_index = self.items.len(); 169 | self.items.push(item); 170 | let addrs = self 171 | .item_addrs_by_module 172 | .entry(module_index) 173 | .or_insert_with(Vec::new); 174 | let index = addrs.len(); 175 | addrs.push(globa_index); 176 | LinkableAddress::new_unsafe(module_index, index) 177 | } 178 | 179 | pub(crate) fn items(&self, module_index: ModuleIndex) -> Option>> { 180 | let item_addrs = self.item_addrs_by_module.get(&module_index)?; 181 | Some( 182 | item_addrs 183 | .iter() 184 | .map(|index| GlobalAddress(*index, std::marker::PhantomData)) 185 | .collect(), 186 | ) 187 | } 188 | 189 | pub(crate) fn is_empty(&self, module_index: ModuleIndex) -> bool { 190 | self.item_addrs_by_module 191 | .get(&module_index) 192 | .map(|v| v.is_empty()) 193 | .unwrap_or(true) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /crates/vm/src/module.rs: -------------------------------------------------------------------------------- 1 | use crate::address::*; 2 | use crate::export::{ExportInstance, ExternalValue}; 3 | 4 | use std::collections::HashMap; 5 | use std::hash::Hash; 6 | 7 | #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] 8 | pub struct ModuleIndex(pub u32); 9 | 10 | pub enum ModuleInstance { 11 | Defined(DefinedModuleInstance), 12 | Host(HostModuleInstance), 13 | } 14 | 15 | impl ModuleInstance { 16 | pub fn defined(&self) -> Option<&DefinedModuleInstance> { 17 | match self { 18 | ModuleInstance::Defined(defined) => Some(defined), 19 | _ => None, 20 | } 21 | } 22 | } 23 | 24 | pub struct DefinedModuleInstance { 25 | types: Vec, 26 | pub exports: Vec, 27 | start_func: Option, 28 | } 29 | 30 | #[derive(Debug)] 31 | pub enum DefinedModuleError { 32 | TypeMismatch(&'static str, String), 33 | } 34 | 35 | impl std::fmt::Display for DefinedModuleError { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | match self { 38 | Self::TypeMismatch(expected, actual) => write!( 39 | f, 40 | "incompatible import type, expected {} but actual {}", 41 | expected, actual 42 | ), 43 | } 44 | } 45 | } 46 | 47 | impl std::error::Error for DefinedModuleError {} 48 | 49 | type DefinedModuleResult = std::result::Result; 50 | 51 | impl DefinedModuleInstance { 52 | pub fn new_from_module( 53 | module_index: ModuleIndex, 54 | types: Vec, 55 | exports: Vec, 56 | start_func: Option, 57 | ) -> Self { 58 | Self { 59 | types, 60 | exports: exports 61 | .iter() 62 | .map(|e| ExportInstance::new_from_entry(*e, module_index)) 63 | .collect(), 64 | start_func, 65 | } 66 | } 67 | 68 | pub fn exported_by_name(&self, name: &str) -> Option<&ExportInstance> { 69 | self.exports.iter().find(|e| *e.name() == name) 70 | } 71 | 72 | pub fn exported_global(&self, name: &str) -> DefinedModuleResult> { 73 | let export = self.exported_by_name(name); 74 | match export { 75 | Some(e) => match e.value() { 76 | ExternalValue::Global(addr) => Ok(Some(*addr)), 77 | _ => Err(DefinedModuleError::TypeMismatch( 78 | "global", 79 | e.value().type_name().to_string(), 80 | )), 81 | }, 82 | None => Ok(None), 83 | } 84 | } 85 | 86 | pub fn exported_func(&self, name: &str) -> DefinedModuleResult> { 87 | let export = self.exported_by_name(name); 88 | match export { 89 | Some(e) => match e.value() { 90 | ExternalValue::Func(addr) => Ok(Some(*addr)), 91 | _ => Err(DefinedModuleError::TypeMismatch( 92 | "function", 93 | e.value().type_name().to_string(), 94 | )), 95 | }, 96 | None => Ok(None), 97 | } 98 | } 99 | 100 | pub fn exported_table(&self, name: &str) -> DefinedModuleResult> { 101 | let export = self.exported_by_name(name); 102 | match export { 103 | Some(e) => match e.value() { 104 | ExternalValue::Table(addr) => Ok(Some(*addr)), 105 | _ => Err(DefinedModuleError::TypeMismatch( 106 | "table", 107 | e.value().type_name().to_string(), 108 | )), 109 | }, 110 | None => Ok(None), 111 | } 112 | } 113 | 114 | pub fn exported_memory(&self, name: &str) -> DefinedModuleResult> { 115 | let export = self.exported_by_name(name); 116 | match export { 117 | Some(e) => match e.value() { 118 | ExternalValue::Memory(addr) => Ok(Some(*addr)), 119 | _ => Err(DefinedModuleError::TypeMismatch( 120 | "memory", 121 | e.value().type_name().to_string(), 122 | )), 123 | }, 124 | None => Ok(None), 125 | } 126 | } 127 | 128 | pub fn start_func_addr(&self) -> &Option { 129 | &self.start_func 130 | } 131 | 132 | pub fn get_type(&self, index: usize) -> &wasmparser::FuncType { 133 | &self.types[index] 134 | } 135 | } 136 | 137 | pub struct HostModuleInstance { 138 | values: HashMap, 139 | } 140 | 141 | #[derive(Debug)] 142 | pub enum HostModuleError { 143 | TypeMismatch(&'static str, String), 144 | } 145 | 146 | impl std::fmt::Display for HostModuleError { 147 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 148 | match self { 149 | Self::TypeMismatch(expected, actual) => write!( 150 | f, 151 | "incompatible import type, expected {} but actual {}", 152 | expected, actual 153 | ), 154 | } 155 | } 156 | } 157 | 158 | type HostModuleResult = std::result::Result; 159 | 160 | pub enum HostExport { 161 | Func(ExecutableFuncAddr), 162 | Global(ResolvedGlobalAddr), 163 | Mem(ResolvedMemoryAddr), 164 | Table(ResolvedTableAddr), 165 | } 166 | 167 | impl HostExport { 168 | pub(crate) fn type_name(&self) -> &str { 169 | match self { 170 | Self::Func(_) => "function", 171 | Self::Global(_) => "global", 172 | Self::Mem(_) => "memory", 173 | Self::Table(_) => "table", 174 | } 175 | } 176 | } 177 | impl HostModuleInstance { 178 | pub fn new(values: HashMap) -> Self { 179 | Self { values } 180 | } 181 | } 182 | 183 | impl HostModuleInstance { 184 | pub(crate) fn global_by_name( 185 | &self, 186 | name: String, 187 | ) -> HostModuleResult> { 188 | match &self.values.get(&name) { 189 | Some(HostExport::Global(global)) => Ok(Some(global)), 190 | Some(v) => Err(HostModuleError::TypeMismatch( 191 | "global", 192 | v.type_name().to_string(), 193 | )), 194 | _ => Ok(None), 195 | } 196 | } 197 | pub(crate) fn func_by_name( 198 | &self, 199 | name: String, 200 | ) -> HostModuleResult> { 201 | match self.values.get(&name) { 202 | Some(HostExport::Func(ref func)) => Ok(Some(func)), 203 | Some(v) => Err(HostModuleError::TypeMismatch( 204 | "function", 205 | v.type_name().to_string(), 206 | )), 207 | _ => Ok(None), 208 | } 209 | } 210 | 211 | pub(crate) fn table_by_name( 212 | &self, 213 | name: String, 214 | ) -> HostModuleResult> { 215 | match &self.values.get(&name) { 216 | Some(HostExport::Table(table)) => Ok(Some(table)), 217 | Some(v) => Err(HostModuleError::TypeMismatch( 218 | "table", 219 | v.type_name().to_string(), 220 | )), 221 | _ => Ok(None), 222 | } 223 | } 224 | 225 | pub(crate) fn memory_by_name( 226 | &self, 227 | name: String, 228 | ) -> HostModuleResult> { 229 | match &self.values.get(&name) { 230 | Some(HostExport::Mem(mem)) => Ok(Some(mem)), 231 | Some(v) => Err(HostModuleError::TypeMismatch( 232 | "memory", 233 | v.type_name().to_string(), 234 | )), 235 | _ => Ok(None), 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /crates/debugger/src/dwarf/types.rs: -------------------------------------------------------------------------------- 1 | // FIXME: Cleanup after refactoring 2 | #![allow(dead_code)] 3 | 4 | use anyhow::{anyhow, Result}; 5 | use log::debug; 6 | use std::collections::HashMap; 7 | 8 | use super::utils::*; 9 | 10 | #[derive(Debug)] 11 | pub struct BaseTypeInfo { 12 | pub name: String, 13 | pub byte_size: u64, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub enum ModifierKind { 18 | Atomic, 19 | Const, 20 | Immutable, 21 | Packed, 22 | Pointer, 23 | Reference, 24 | Restrict, 25 | RvalueReference, 26 | Shared, 27 | Volatile, 28 | } 29 | #[derive(Debug)] 30 | pub struct ModifiedTypeInfo { 31 | pub content_ty_offset: Option, 32 | pub kind: ModifierKind, 33 | } 34 | 35 | #[derive(Debug)] 36 | pub struct Member { 37 | pub name: Option, 38 | pub ty: R::Offset, 39 | pub location: MemberLocation, 40 | } 41 | 42 | #[derive(Debug)] 43 | pub enum MemberLocation { 44 | LocationDescription(gimli::Expression), 45 | ConstOffset(u64), 46 | } 47 | 48 | #[derive(Debug)] 49 | pub struct StructTypeInfo { 50 | pub name: Option, 51 | pub members: Vec>, 52 | pub byte_size: u64, 53 | pub declaration: bool, 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct TypeDef { 58 | pub name: Option, 59 | pub ty: Option, 60 | } 61 | 62 | #[derive(Debug)] 63 | pub struct EnumerationTypeInfo { 64 | pub name: Option, 65 | pub ty: Option, 66 | pub enumerators: Vec, 67 | } 68 | 69 | #[derive(Debug)] 70 | pub struct Enumerator { 71 | pub name: Option, 72 | pub value: Option, 73 | } 74 | 75 | #[derive(Debug)] 76 | pub enum TypeInfo { 77 | BaseType(BaseTypeInfo), 78 | ModifiedType(ModifiedTypeInfo), 79 | StructType(StructTypeInfo), 80 | TypeDef(TypeDef), 81 | EnumerationType(EnumerationTypeInfo), 82 | } 83 | 84 | pub fn get_types( 85 | dwarf: &gimli::Dwarf, 86 | unit: &gimli::Unit, 87 | out_type_hash: &mut HashMap>, 88 | ) -> Result<()> { 89 | let mut tree = unit.entries_tree(None)?; 90 | let root = tree.root()?; 91 | parse_types_rec(root, dwarf, unit, out_type_hash)?; 92 | Ok(()) 93 | } 94 | pub fn parse_types_rec( 95 | node: gimli::EntriesTreeNode, 96 | dwarf: &gimli::Dwarf, 97 | unit: &gimli::Unit, 98 | out_type_hash: &mut HashMap>, 99 | ) -> Result<()> { 100 | let offset = node.entry().offset(); 101 | let mut ty = match node.entry().tag() { 102 | gimli::DW_TAG_base_type => Some(TypeInfo::::BaseType(parse_base_type( 103 | &node, dwarf, unit, 104 | )?)), 105 | gimli::DW_TAG_class_type | gimli::DW_TAG_structure_type => Some(TypeInfo::::StructType( 106 | parse_partial_struct_type(&node, dwarf, unit)?, 107 | )), 108 | gimli::DW_TAG_enumeration_type => Some(TypeInfo::::EnumerationType( 109 | parse_partial_enum_type(&node, dwarf, unit)?, 110 | )), 111 | gimli::DW_TAG_typedef => Some(TypeInfo::::TypeDef(parse_typedef(&node, dwarf, unit)?)), 112 | gimli::DW_TAG_atomic_type 113 | | gimli::DW_TAG_const_type 114 | | gimli::DW_TAG_immutable_type 115 | | gimli::DW_TAG_packed_type 116 | | gimli::DW_TAG_pointer_type 117 | | gimli::DW_TAG_reference_type 118 | | gimli::DW_TAG_restrict_type 119 | | gimli::DW_TAG_rvalue_reference_type 120 | | gimli::DW_TAG_shared_type 121 | | gimli::DW_TAG_volatile_type => { 122 | let kind = match node.entry().tag() { 123 | gimli::DW_TAG_atomic_type => ModifierKind::Atomic, 124 | gimli::DW_TAG_const_type => ModifierKind::Const, 125 | gimli::DW_TAG_immutable_type => ModifierKind::Immutable, 126 | gimli::DW_TAG_packed_type => ModifierKind::Packed, 127 | gimli::DW_TAG_pointer_type => ModifierKind::Pointer, 128 | gimli::DW_TAG_reference_type => ModifierKind::Reference, 129 | gimli::DW_TAG_restrict_type => ModifierKind::Restrict, 130 | gimli::DW_TAG_rvalue_reference_type => ModifierKind::RvalueReference, 131 | gimli::DW_TAG_shared_type => ModifierKind::Shared, 132 | gimli::DW_TAG_volatile_type => ModifierKind::Volatile, 133 | _ => unreachable!(), 134 | }; 135 | Some(TypeInfo::ModifiedType(parse_modified_type(kind, &node)?)) 136 | } 137 | gimli::DW_TAG_member => unreachable!(), 138 | _ => None, 139 | }; 140 | 141 | let mut children = node.children(); 142 | let mut members = vec![]; 143 | let mut enumerators = vec![]; 144 | while let Some(child) = children.next()? { 145 | match child.entry().tag() { 146 | gimli::DW_TAG_member => members.push(parse_member(&child, dwarf, unit)?), 147 | gimli::DW_TAG_enumerator => enumerators.push(parse_enumerator(&child, dwarf, unit)?), 148 | _ => parse_types_rec(child, dwarf, unit, out_type_hash)?, 149 | } 150 | } 151 | 152 | if let Some(TypeInfo::StructType(ty)) = ty.as_mut() { 153 | ty.members.append(&mut members); 154 | } 155 | if let Some(TypeInfo::EnumerationType(ty)) = ty.as_mut() { 156 | ty.enumerators.append(&mut enumerators); 157 | } 158 | if let Some(ty) = ty { 159 | out_type_hash.insert(offset.0, ty); 160 | } 161 | Ok(()) 162 | } 163 | 164 | fn parse_base_type( 165 | node: &gimli::EntriesTreeNode, 166 | dwarf: &gimli::Dwarf, 167 | unit: &gimli::Unit, 168 | ) -> Result { 169 | let name = match node.entry().attr_value(gimli::DW_AT_name)? { 170 | Some(attr) => clone_string_attribute(dwarf, unit, attr)?, 171 | None => "".to_string(), // return Err(anyhow!("Failed to get name")), 172 | }; 173 | let byte_size = node 174 | .entry() 175 | .attr_value(gimli::DW_AT_byte_size)? 176 | .and_then(|attr| attr.udata_value()) 177 | .unwrap_or(0); 178 | Ok(BaseTypeInfo { name, byte_size }) 179 | } 180 | 181 | fn parse_modified_type( 182 | kind: ModifierKind, 183 | node: &gimli::EntriesTreeNode, 184 | ) -> Result> { 185 | let ty = match node.entry().attr_value(gimli::DW_AT_type)? { 186 | Some(gimli::AttributeValue::UnitRef(ref offset)) => Some(offset.0), 187 | x => { 188 | debug!( 189 | "Failed to get pointee type: {:?} {:?} {:?}", 190 | node.entry().offset(), 191 | x, 192 | kind 193 | ); 194 | let mut attrs = node.entry().attrs(); 195 | while let Some(attr) = attrs.next()? { 196 | debug!("The entry has '{}'", attr.name()); 197 | } 198 | None 199 | } 200 | }; 201 | Ok(ModifiedTypeInfo { 202 | content_ty_offset: ty, 203 | kind, 204 | }) 205 | } 206 | 207 | fn parse_partial_struct_type( 208 | node: &gimli::EntriesTreeNode, 209 | dwarf: &gimli::Dwarf, 210 | unit: &gimli::Unit, 211 | ) -> Result> { 212 | let mut ty = StructTypeInfo { 213 | name: None, 214 | members: vec![], 215 | byte_size: 0, 216 | declaration: false, 217 | }; 218 | if let Some(attr) = node.entry().attr_value(gimli::DW_AT_name)? { 219 | ty.name = Some(clone_string_attribute(dwarf, unit, attr)?); 220 | } 221 | 222 | if let Some(byte_size) = node 223 | .entry() 224 | .attr_value(gimli::DW_AT_byte_size)? 225 | .and_then(|attr| attr.udata_value()) 226 | { 227 | ty.byte_size = byte_size; 228 | }; 229 | if let Some(gimli::AttributeValue::Flag(flag)) = 230 | node.entry().attr_value(gimli::DW_AT_declaration)? 231 | { 232 | ty.declaration = flag; 233 | } 234 | Ok(ty) 235 | } 236 | 237 | fn parse_member( 238 | node: &gimli::EntriesTreeNode, 239 | dwarf: &gimli::Dwarf, 240 | unit: &gimli::Unit, 241 | ) -> Result> { 242 | let name = match node.entry().attr_value(gimli::DW_AT_name)? { 243 | Some(attr) => Some(clone_string_attribute(dwarf, unit, attr)?), 244 | None => None, 245 | }; 246 | let ty = match node.entry().attr_value(gimli::DW_AT_type)? { 247 | Some(gimli::AttributeValue::UnitRef(ref offset)) => offset.0, 248 | _ => return Err(anyhow!("Failed to get type offset")), 249 | }; 250 | // DWARF v5 Page 118 251 | let mut member_location = MemberLocation::ConstOffset(0); 252 | if let Some(loc_attr) = node.entry().attr_value(gimli::DW_AT_data_member_location)? { 253 | match loc_attr { 254 | gimli::AttributeValue::Udata(offset) => { 255 | member_location = MemberLocation::ConstOffset(offset); 256 | } 257 | gimli::AttributeValue::Exprloc(expr) => { 258 | member_location = MemberLocation::LocationDescription(expr); 259 | } 260 | _ => unimplemented!(), 261 | } 262 | } 263 | Ok(Member { 264 | name, 265 | location: member_location, 266 | ty, 267 | }) 268 | } 269 | 270 | fn parse_typedef( 271 | node: &gimli::EntriesTreeNode, 272 | dwarf: &gimli::Dwarf, 273 | unit: &gimli::Unit, 274 | ) -> Result> { 275 | let name = match node.entry().attr_value(gimli::DW_AT_name)? { 276 | Some(attr) => Some(clone_string_attribute(dwarf, unit, attr)?), 277 | None => None, 278 | }; 279 | let ty = match node.entry().attr_value(gimli::DW_AT_type)? { 280 | Some(gimli::AttributeValue::UnitRef(ref offset)) => Some(offset.0), 281 | _ => None, 282 | }; 283 | Ok(TypeDef { name, ty }) 284 | } 285 | 286 | fn parse_partial_enum_type( 287 | node: &gimli::EntriesTreeNode, 288 | dwarf: &gimli::Dwarf, 289 | unit: &gimli::Unit, 290 | ) -> Result> { 291 | let name = match node.entry().attr_value(gimli::DW_AT_name)? { 292 | Some(attr) => Some(clone_string_attribute(dwarf, unit, attr)?), 293 | None => None, 294 | }; 295 | let ty = match node.entry().attr_value(gimli::DW_AT_type)? { 296 | Some(gimli::AttributeValue::UnitRef(ref offset)) => Some(offset.0), 297 | _ => None, 298 | }; 299 | Ok(EnumerationTypeInfo { 300 | name, 301 | ty, 302 | enumerators: vec![], 303 | }) 304 | } 305 | 306 | fn parse_enumerator( 307 | node: &gimli::EntriesTreeNode, 308 | dwarf: &gimli::Dwarf, 309 | unit: &gimli::Unit, 310 | ) -> Result { 311 | let mut enumerator = Enumerator { 312 | name: None, 313 | value: None, 314 | }; 315 | enumerator.name = match node.entry().attr_value(gimli::DW_AT_name)? { 316 | Some(attr) => Some(clone_string_attribute(dwarf, unit, attr)?), 317 | None => None, 318 | }; 319 | enumerator.value = node 320 | .entry() 321 | .attr_value(gimli::DW_AT_const_value)? 322 | .and_then(|attr| attr.sdata_value()); 323 | Ok(enumerator) 324 | } 325 | -------------------------------------------------------------------------------- /crates/vm/src/stack.rs: -------------------------------------------------------------------------------- 1 | use crate::address::*; 2 | use crate::func::{DefinedFunctionInstance, InstIndex}; 3 | use crate::module::ModuleIndex; 4 | use crate::value::Value; 5 | 6 | #[derive(Debug)] 7 | pub enum StackValueType { 8 | Label, 9 | Value, 10 | Activation, 11 | } 12 | 13 | const DEFAULT_CALL_STACK_LIMIT: usize = 1024; 14 | 15 | #[derive(Debug)] 16 | pub enum Error { 17 | PopEmptyStack, 18 | MismatchStackValueType { 19 | expected: StackValueType, 20 | actual: StackValueType, 21 | }, 22 | NoCallFrame, 23 | NotEnoughFrames, 24 | Overflow, 25 | } 26 | 27 | impl std::fmt::Display for Error { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | match self { 30 | Self::Overflow => write!(f, "call stack exhausted"), 31 | _ => write!(f, "{:?}", self), 32 | } 33 | } 34 | } 35 | 36 | type Result = std::result::Result; 37 | 38 | #[derive(Clone, Copy, Debug)] 39 | pub enum Label { 40 | If { arity: usize }, 41 | Block { arity: usize }, 42 | Loop { arity: usize, label: LoopLabel }, 43 | Return { arity: usize }, 44 | } 45 | 46 | #[derive(Clone, Copy, Debug)] 47 | pub struct LoopLabel { 48 | inst_index: InstIndex, 49 | } 50 | 51 | impl Label { 52 | pub fn new_loop(inst_index: InstIndex, arity: usize) -> Self { 53 | Self::Loop { 54 | arity, 55 | label: LoopLabel { inst_index }, 56 | } 57 | } 58 | 59 | pub fn arity(&self) -> usize { 60 | match self { 61 | Label::If { arity } => *arity, 62 | Label::Block { arity } => *arity, 63 | Label::Loop { arity, .. } => *arity, 64 | Label::Return { arity } => *arity, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Clone, Copy)] 70 | pub struct ProgramCounter { 71 | module_index: ModuleIndex, 72 | exec_addr: ExecutableFuncAddr, 73 | inst_index: InstIndex, 74 | } 75 | 76 | impl ProgramCounter { 77 | pub fn new( 78 | module_index: ModuleIndex, 79 | exec_addr: ExecutableFuncAddr, 80 | inst_index: InstIndex, 81 | ) -> Self { 82 | Self { 83 | module_index, 84 | exec_addr, 85 | inst_index, 86 | } 87 | } 88 | 89 | pub fn module_index(&self) -> ModuleIndex { 90 | self.module_index 91 | } 92 | 93 | pub fn exec_addr(&self) -> ExecutableFuncAddr { 94 | self.exec_addr 95 | } 96 | 97 | pub fn inst_index(&self) -> InstIndex { 98 | self.inst_index 99 | } 100 | 101 | pub fn inc_inst_index(&mut self) { 102 | self.inst_index.0 += 1; 103 | } 104 | 105 | pub fn loop_jump(&mut self, loop_label: &LoopLabel) { 106 | self.inst_index = loop_label.inst_index; 107 | } 108 | } 109 | 110 | #[derive(Clone)] 111 | pub struct CallFrame { 112 | pub module_index: ModuleIndex, 113 | pub locals: Vec, 114 | pub ret_pc: Option, 115 | 116 | // Only for debug use 117 | pub exec_addr: ExecutableFuncAddr, 118 | } 119 | 120 | impl CallFrame { 121 | fn new( 122 | module_index: ModuleIndex, 123 | exec_addr: ExecutableFuncAddr, 124 | default_locals: &[Value], 125 | args: Vec, 126 | pc: Option, 127 | ) -> Self { 128 | let mut locals = default_locals.to_vec(); 129 | for (i, arg) in args.into_iter().enumerate() { 130 | locals[i] = arg; 131 | } 132 | Self { 133 | module_index, 134 | exec_addr, 135 | locals, 136 | ret_pc: pc, 137 | } 138 | } 139 | 140 | pub fn new_from_func( 141 | exec_addr: ExecutableFuncAddr, 142 | func: &DefinedFunctionInstance, 143 | args: Vec, 144 | pc: Option, 145 | ) -> Self { 146 | Self::new( 147 | func.module_index(), 148 | exec_addr, 149 | func.default_locals(), 150 | args, 151 | pc, 152 | ) 153 | } 154 | 155 | pub fn set_local(&mut self, index: usize, value: Value) { 156 | self.locals[index] = value; 157 | } 158 | 159 | pub fn local(&self, index: usize) -> Value { 160 | self.locals[index] 161 | } 162 | 163 | pub fn module_index(&self) -> ModuleIndex { 164 | self.module_index 165 | } 166 | } 167 | 168 | #[derive(Clone)] 169 | pub enum StackValue { 170 | Value(Value), 171 | Label(Label), 172 | Activation(CallFrame), 173 | } 174 | 175 | impl StackValue { 176 | pub fn value_type(&self) -> StackValueType { 177 | match self { 178 | Self::Value(_) => StackValueType::Value, 179 | Self::Label(_) => StackValueType::Label, 180 | Self::Activation(_) => StackValueType::Activation, 181 | } 182 | } 183 | pub fn into_value(self) -> Result { 184 | match self { 185 | Self::Value(val) => Ok(val), 186 | _ => Err(Error::MismatchStackValueType { 187 | expected: StackValueType::Value, 188 | actual: self.value_type(), 189 | }), 190 | } 191 | } 192 | fn into_label(self) -> Result