To build the wazero binary, execute:
just build
To run the test suite, execute:
just test
This delegates to the Makefile which runs the full Go test suite.
To run only the trace writer tests (pure Go, no FFI):
just test-tracewriter-go
To run trace writer tests including Rust FFI:
just test-tracewriter
This is a fork of wazero (WebAssembly runtime for Go) with CodeTracer tracing capabilities added. The tracing instruments WASM execution to produce Step/Call/Function/Event records that CodeTracer loads for time-travel debugging.
cmd/wazero/— CLI entry point. Therunsubcommand accepts--out-dirto produce a CodeTracer trace, and--stylusto load an EVM trace for Stylus replay.internal/engine/interpreter/interpreter.go— Core interpreter loop with DWARF-based source-level stepping (lines 723-876). This is where Step/Call/Function trace events are generated by mapping WASM program counter offsets to source lines via DWARF.internal/wasmdebug/dwarf_indexing.go— DWARF index: maps PC ranges to source lines, functions, local variables, and inlined subroutines. Built at module compile time.internal/stylus/— Stylus (Arbitrum WASM) replay harness. Creates host function stubs that replay pre-recorded EVM interaction data.tracewriter/— Trace output. Supports a pure-Go JSON writer and a Rust FFI writer (via cgo, usingcodetracer_trace_writer_ffi).experimental/— Upstream wazero experimental features (logging, sockets).
For regular WASM programs (targeting wasm32-wasip1), the flow is:
CompileModule()decodes the WASM binary including DWARF custom sections (.debug_info,.debug_line,.debug_str,.debug_abbrev,.debug_ranges)IndexDwarfData()builds a PC-indexed lookup structure mapping WASM byte offsets to source file/line, function entries, local variables, and inlined subroutines- During execution, the interpreter loop (lines 786-876) checks each instruction's
PC offset against the DWARF index. When the source line changes, it emits a
RegisterStep. On function entry, it emitsRegisterCall+RegisterFunction. - Local variable values are read from the WASM locals array and emitted as
RegisterVariableevents.
Stylus tracing does NOT debug the actual on-chain binary. Instead, it replays the execution using a debug-built WASM binary with DWARF symbols. The pipeline is:
-
ct arb deploy(in thecodetracerrepo,src/ct/stylus/deploy.nim):- Runs
cargo build --target wasm32-unknown-unknown(debug mode, preserves DWARF) - Runs
cargo stylus deployto deploy the optimized on-chain binary - Saves the debug WASM to
~/.local/share/codetracer/contract-debug-wasm/<addr>/debug.wasm - This creates a mapping: on-chain contract address → debug-built WASM
- Runs
-
Transaction execution: Users send transactions to the deployed contract on a local Arbitrum devnode (nitro-testnode). The EVM records all host function calls (read_args, storage ops, msg_value, etc.) with their inputs and outputs.
-
ct arb record(in thecodetracerrepo,src/ct/stylus/record.nim):- Looks up the contract address from the tx hash via
eth_getTransactionByHash - Retrieves the debug WASM from the mapping at
contract-debug-wasm/<addr>/debug.wasm - Gets the EVM trace via
cargo stylus trace --use-native-tracer --tx <hash> - Invokes the wazero binary with
--stylus <evm_trace.json>and the debug WASM
- Looks up the contract address from the tx hash via
-
Wazero Stylus replay (this repo,
cmd/wazero/wazero.go):- Loads the debug WASM through the same
CompileModule()path as regular WASM, which parses DWARF sections and builds the PC index stylus.Instantiate()(internal/stylus/stylus.go) creates a"vm_hooks"host module with Go-implemented stubs for all Stylus host functions- Each stub replays pre-recorded data from the EVM trace: when the WASM calls
read_args, the stub writes the recorded calldata into WASM memory; when it callsstorage_load_bytes32, the stub returns the recorded storage value; etc. - The WASM's
user_entrypointfunction is called with the entrypoint arg from the EVM trace, and its return value is validated against the recorded result - Since the debug WASM executes through the same interpreter, DWARF-based stepping SHOULD produce Step/Call/Function events alongside the EVM Event records
- Loads the debug WASM through the same
-
CodeTracer loads the trace: The db-backend parses the trace containing both source-level stepping events (from DWARF) and EVM interaction events (from the Stylus stubs). The EVM events appear in the Event Log panel.
The EVM trace is a JSON array consumed by internal/stylus/stylus_trace.go:
[
{"name": "user_entrypoint", "startInk": 100, "endInk": 90, "args": "0x...", "outs": "0x00000000"},
{"name": "read_args", "startInk": 90, "endInk": 88, "args": "0x", "outs": "0xca1d209d..."},
{"name": "storage_load_bytes32", "startInk": 88, "endInk": 85, "args": "0x...", "outs": "0x..."},
...
{"name": "user_returned", "startInk": 10, "endInk": 8, "args": "0x", "outs": "0x00000000"}
]Each event has: name (host function), startInk/endInk (gas metering), args
(hex-encoded input bytes), outs (hex-encoded output bytes to inject into WASM memory).
The DWARF stepping code only generates trace events for source files matching:
- Filename ends with
.rs - Does NOT start with
/rustc(standard library internals) - Does NOT contain
.rustup,.cargo, or/rust/deps
This means only user-authored Rust code generates stepping events. Library code from crates.io or the Rust standard library is stepped through silently.
There is a second filter at line 820 with a false || prefix that currently disables
line-level tracking even when function-level tracking is active. This appears to be
a debug leftover that needs investigation — changing it to true || or removing the
false || would enable full line-level stepping.
The interpreter tracks inlined function calls using DWARF DW_TAG_inlined_subroutine
entries. When the PC enters an inlined region, a synthetic call/function event is
generated; when it exits, a return event is generated.
The codetracer repo's db_backend_record.nim:109 passes --trace-dir to the wazero
binary, but wazero's CLI (cmd/wazero/wazero.go:227) expects --out-dir. This means
ct arb record invocations produce NO trace output — the recorder is nil, so no
Step/Call/Function/Event records are written. This must be fixed in either the Nim
caller or the Go CLI (or both, to accept the old flag as an alias).
The condition if false || (strings.HasSuffix(...)) disables line-level stepping.
This appears to be a debugging leftover. Should be investigated and either removed
(to enable full stepping) or documented with a clear rationale.
Because of the --trace-dir/--out-dir mismatch (when invoked via ct arb record)
and/or because integration tests build with --release (stripping DWARF), current
Stylus traces in practice contain only EVM Event entries, not Step/Call/Function entries.
The db-backend has been hardened to handle this event-only format without panicking, but
the goal is to have full source-level stepping alongside EVM events.
stylus_flow_integration.rs:105 builds with cargo build --release --target wasm32-unknown-unknown,
which strips DWARF debug info. For testing DWARF-based stepping with Stylus, the build
should either use debug mode or add [profile.release] debug = true to the contract's
Cargo.toml.
Tests the end-to-end Stylus recording pipeline: build WASM, deploy, send tx, get EVM trace, record with wazero. Requires a running Arbitrum devnode.
cd ../codetracer && just test-stylus-flow # Quick smoke test
cd ../codetracer && just test-stylus-flow-full # Full content verificationTests that the db-backend DAP server handles Stylus traces correctly. Uses pre-recorded fixtures — no devnode needed.
cd ../codetracer/src/db-backend && cargo test test_stylus_dap_event_only_traceWebdriverIO tests that launch VS Code Insiders with the extension and verify UI behavior.
cd ../codetracer-vscode-extension && npm run test:wdioDuring development, certain commands will fail because you don't have access to the internet.
The script .agents/download_internet_resources.sh is executed before
your development session starts while your computer is still connected
to the internet.
You can examine this script to see what kind of internet resources have been downloaded for offline use. If it's difficult for you to achieve a task without access to additional internet resources, you can always propose a PR that modifies the download.sh script instead of completing your main task.
Downloading development dependencies may also fail due to the lack of
internet connectivity. We are trying to maintain the script .agents/codex-setup
that is also executed before your development session starts while
your computer is still connected to the internet. It tries to run
all build commands that need development dependencies in order to
cache the dependencies for offline use. Please propose changes to
this script when you introduce new build targets with dependencies.
When you need to consult the documentation or source code modules for a particular dependency, always try to find where this dependency have been downloaded and try to access the necessary files through the file system (i.e. depending on the programming language, the operating system and the package manager being used, they should be in their standard location).
In the .agents/codebase-insights.txt file, we try to maintain useful tips that may help
you in your development tasks. When you discover something important or surprising about
the codebase, add a remark in a comment near the relevant code or in the codebase-insights
file. ALWAYS remove older remarks if they are no longer true.
You can consult this file before starting your coding tasks.
- ALWAYS strive to achieve high code quality.
- ALWAYS write secure code.
- ALWAYS make sure the code is well tested and edge cases are covered. Design the code for testability and be extremely thorough.
- ALWAYS write defensive code and make sure all potential errors are handled.
- ALWAYS strive to write highly reusable code with routines that have high fan in and low fan out.
- ALWAYS keep the code DRY.
- Aim for low coupling and high cohesion. Encapsulate and hide implementation details.
- Go code should pass
go vetandgofmtwithout issues. - Nix files are formatted with
nixfmt.
- Document public APIs and complex modules using standard code documentation conventions.
- Comment the intention behind your code extensively. Omit comments only for very obvious facts that almost any developer would know.
- Maintain the comments together with the code to keep them meaningful and current.
- When the code is based on specific formats, standards or well-specified behavior of other software, always make sure to include relevant links (URLs) that provide the necessary technical details.
- You MUST use multiline git commit messages.
- Use the conventional commits style for the first line of the commit message.
- Use the summary section of your final response as the remaining lines in the commit message.