diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7110856..05c2dcd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,10 +10,8 @@ jobs: name: Build and Test runs-on: ubuntu-latest env: - WASM_TOOLS_VERSION: 1.245.1 WIT_BINDGEN_VERSION: 0.53.1 WAC_VERSION: 0.9.0 - WASMTIME_VERSION: 41.0.3 VICEROY_VERSION: 0.16.4 steps: - uses: actions/checkout@v5 @@ -21,28 +19,24 @@ jobs: with: target: wasm32-unknown-unknown, wasm32-wasip2 components: rustfmt, clippy - - name: Install wasm-tools, wit-bindgen and wac + - uses: bytecodealliance/actions/wasmtime/setup@v1 + - uses: bytecodealliance/actions/wasm-tools/setup@v1 + - uses: bytecodealliance/actions/wit-bindgen/setup@v1 + with: + version: ${{ env.WIT_BINDGEN_VERSION }} + - name: Install wac run: | tmp=$(mktemp -d) - curl -sLo "$tmp/wasm-tools.tar.gz" https://github.com/bytecodealliance/wasm-tools/releases/download/v${{ env.WASM_TOOLS_VERSION }}/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-x86_64-linux.tar.gz - tar -xz -f "$tmp/wasm-tools.tar.gz" -C "$tmp" - mv "$tmp/wasm-tools-${{ env.WASM_TOOLS_VERSION }}-x86_64-linux/wasm-tools" /usr/local/bin/ - curl -sLo "$tmp/wit-bindgen.tar.gz" https://github.com/bytecodealliance/wit-bindgen/releases/download/v${{ env.WIT_BINDGEN_VERSION }}/wit-bindgen-${{ env.WIT_BINDGEN_VERSION }}-x86_64-linux.tar.gz - tar -xz -f "$tmp/wit-bindgen.tar.gz" -C "$tmp" - mv "$tmp/wit-bindgen-${{ env.WIT_BINDGEN_VERSION }}-x86_64-linux/wit-bindgen" /usr/local/bin/ curl -sLo "$tmp/wac-cli" https://github.com/bytecodealliance/wac/releases/download/v${{ env.WAC_VERSION }}/wac-cli-x86_64-unknown-linux-musl chmod a+x "$tmp/wac-cli" mv "$tmp/wac-cli" /usr/local/bin/wac - - name: Install viceroy and wasmtime + - name: Install viceroy run: | tmp=$(mktemp -d) curl -sLo "$tmp/viceroy.tar.gz" https://github.com/fastly/Viceroy/releases/download/v${{ env.VICEROY_VERSION }}/viceroy_v${{ env.VICEROY_VERSION }}_linux-amd64.tar.gz tar -xz -f "$tmp/viceroy.tar.gz" -C "$tmp" chmod a+x "$tmp/viceroy" mv "$tmp/viceroy" /usr/local/bin/ - curl -sLo "$tmp/wasmtime.tar.xz" https://github.com/bytecodealliance/wasmtime/releases/download/v${{ env.WASMTIME_VERSION }}/wasmtime-v${{ env.WASMTIME_VERSION }}-x86_64-linux.tar.xz - tar -xJ -f "$tmp/wasmtime.tar.xz" -C "$tmp" - mv "$tmp/wasmtime-v${{ env.WASMTIME_VERSION }}-x86_64-linux/wasmtime" /usr/local/bin/ - uses: actions/cache@v5 with: path: | diff --git a/Makefile b/Makefile index 4f03613..793d55a 100644 --- a/Makefile +++ b/Makefile @@ -15,9 +15,12 @@ test-fuzz: RUSTFLAGS="" $(MAKE) run-fuzz WASM=tests/calculator.wasm test-record: - $(MAKE) run-record WASM=tests/rust.wasm $(MAKE) run-record WASM=tests/go.wasm $(MAKE) run-record WASM=tests/python.wasm + $(MAKE) run-record WASM=tests/rust.wasm + # test the same trace with a different wasm replay + target/release/proxy-component instrument -m replay tests/rust.debug.wasm + wasmtime --invoke 'start()' composed.wasm < trace.out run-fuzz: target/release/proxy-component instrument -m fuzz $(WASM) diff --git a/README.md b/README.md index fdaa121..31a5175 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository explores the concept of virtualizing and/or instrumenting WIT components. Given a Wasm component, we synthesize a new component that virtualizes the host interface and can optionally call the original host when necessary. This synthesized component can be linked, via `wac`, to produce a resulting component with the exact same WIT interface as the original, -but with the added side effects from the virtualized component. This allows us to instrument or virtualize the Wasm component without modifying the user code. +but with the added side effects from the virtualized component. This allows us to instrument or virtualize the Wasm component without modifying the user code, nor the host runtime. Currently, the tool focuses on using this technique to perform fuzzing, and record & replay for Wasm components. In the future, we can apply the same technique to other use cases, such as generating adapters. @@ -20,8 +20,9 @@ To test record and fuzzing, run `make test`. ``` $ proxy-component instrument -m record +$ composed.wasm > trace.out # store the stdout trace to trace.out ``` -Run `composed.wasm` in the host runtime which the original wasm is supposed to run. The tool provides a guest implementation for record and replay APIs, which outputs the trace to stdout while recording, and reads the trace from stdin while replay. +Run `composed.wasm` in the host runtime which the original wasm is supposed to run. The tool provides a [guest implementation](components/recorder/) for record and replay APIs, which outputs the trace to stdout while recording, and reads the trace from stdin while replay. The host runtime can also choose to implement the [`record` interface](https://github.com/chenyan2002/proxy-component/blob/main/assets/recorder.wit#L3). Then we can use the `--use-host-recorder` flag to skip composing the guest-side record implementation. @@ -34,16 +35,18 @@ $ proxy-component instrument -m replay $ wasmtime --invoke 'start()' composed.wasm < trace.out ``` -Note that the trace is self-contained, and `composed.wasm` doesn't have any imports. This means that we can run `composed.wasm` in a regular `wasmtime` without the host interface. +Note that the trace is self-contained, and `composed.wasm` doesn't have any imports. This means that we can run `composed.wasm` in a regular `wasmtime`. Another interesting use case is that we can replay the trace with a different Wasm binary, likely with a different compiler flag, or -a different optimization strategy, to compare the performance. We have assertions in the replay phase to make sure that the trace +a different optimization strategy, to compare the performance. + +We provide a [Debug component](components/debug/) that does not go through instrumentation. You can use the Debug component in your code to perform I/O operations while in the replay mode. We have assertions in the replay phase to make sure that the trace is still valid with the new binary. ### Fuzzing ``` -$ cargo run instrument -m fuzz +$ proxy-component instrument -m fuzz $ wasmtime --invoke 'start()' composed.wasm ``` diff --git a/assets/debug.wasm b/assets/debug.wasm index d212d70..adde5e1 100644 Binary files a/assets/debug.wasm and b/assets/debug.wasm differ diff --git a/assets/util.wit b/assets/util.wit index 9b23fdb..661dd04 100644 --- a/assets/util.wit +++ b/assets/util.wit @@ -2,6 +2,7 @@ package proxy:util; interface debug { print: func(x: string); + eprint: func(x: string); get-random: func() -> list; } diff --git a/assets/workspace_cargo.toml b/assets/workspace_cargo.toml index c3bb820..273dbf3 100644 --- a/assets/workspace_cargo.toml +++ b/assets/workspace_cargo.toml @@ -3,7 +3,7 @@ members = ["record_imports", "record_exports"] resolver = "2" [workspace.dependencies] -wit-bindgen = { version = "0.52.0", default-features = false, features = ["bitflags", "std"] } +wit-bindgen = { version = "0.53.1", default-features = false, features = ["bitflags", "std"] } wasm-wave = { git = "https://github.com/chenyan2002/wasm-tools.git", branch = "extend-wave", version = "0.239.0", default-features = false } #wasm-wave = { path = "/Users/chenyan/src/bytecodealliance/wasm-tools/crates/wasm-wave", default-features = false } arbitrary = "1.4.2" diff --git a/components/debug/src/lib.rs b/components/debug/src/lib.rs index e638a2d..2c8467c 100644 --- a/components/debug/src/lib.rs +++ b/components/debug/src/lib.rs @@ -11,6 +11,9 @@ impl Guest for Component { fn print(s: String) { println!("{}", s); } + fn eprint(s: String) { + eprintln!("{}", s); + } fn get_random() -> Vec { let mut data = vec![0u8; 1024]; getrandom::fill(&mut data).unwrap(); diff --git a/src/codegen.rs b/src/codegen.rs index e372e7d..048f3cd 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -201,8 +201,8 @@ impl State { __params.push(wasm_wave::to_string(&ToValue::to_value(&#arg_names)).unwrap()); )* let mut __buf = __params.join(","); + proxy::util::debug::print(&format!("import: {}({})", #display_name, __buf)); __buf += #display_name; - proxy::util::debug::print(&format!("import: {}", __buf)); let mut u = Unstructured::new(&__buf.as_bytes()); let res = u.arbitrary().unwrap(); let res_str = wasm_wave::to_string(&ToValue::to_value(&res)).unwrap(); diff --git a/tests/rust.debug.wasm b/tests/rust.debug.wasm new file mode 100644 index 0000000..0b8ef55 Binary files /dev/null and b/tests/rust.debug.wasm differ