Inspecting Modules with wasm-tools
This page answers one task: you have a .wasm file — your own build, a vendor’s binary, a component — and you want to see what is inside,
check that it is valid for the features you target, and make small changes, using one command-line tool.
Prerequisites
- [ ]
wasm-tools(cargo install wasm-tools --locked, or a release binary). - [ ] Optionally WABT and Binaryen, for comparison.
- [ ] A module to inspect.
One tool for the format
The WebAssembly tool ecosystem grew in layers. WABT, the WebAssembly Binary Toolkit, offered the original assembler and disassembler —
wat2wasm, wasm2wat, wasm-objdump, wasm-validate. Binaryen offered an optimizer, wasm-opt, and related tools. Later, the Bytecode
Alliance built wasm-tools, a single CLI with subcommands that tracks new proposals quickly — including the component model, which the older
tools do not cover.
Day to day, wasm-tools is a convenient single entry point: print a module as text, validate it against a specific feature set, inspect
sections, strip custom data, read or add metadata, and work with components. The other toolkits remain the right choice for some jobs —
Binaryen for optimization, WABT’s interpreter for tracing — but most inspection questions can be answered with one tool.
Step 1 — print a module as text
wasm-tools print app.wasm | head -30
wasm-tools print app.wasm --skeleton | head # signatures only, no function bodies
print produces the WebAssembly text format with names from the name section where available, and it understands every proposal the
tool supports, so modules using GC types, exceptions or memory64 print correctly where older disassemblers fail. --skeleton omits function bodies, which turns
a 200,000-line printout into a readable list of imports, exports, types and function signatures — the fastest way to get an overview of a large
module. For reading compiled code in detail, the techniques in
converting Wasm back to WAT with wasm2wat
apply equally to wasm-tools print output.
Step 2 — validate against the features you support
validate checks that a module is well-formed and type-correct, and lets you specify which proposals are allowed. That answers a question the
browser cannot answer for you in advance: will this module load in an engine that supports only these features?
wasm-tools validate app.wasm # current default feature set
wasm-tools validate --features=-simd,-reference-types app.wasm # pretend SIMD and reftypes are unavailable
error: SIMD support is not enabled (at offset 0x1a3f)
Running the second form in CI against the oldest feature set you support catches a toolchain upgrade that silently started emitting new instructions. It complements the runtime detection described in detecting proposal support at runtime.
Step 3 — look at sections and sizes
wasm-tools objdump app.wasm
types | 0xb - 0x4c7 | 1212 bytes | 48 count
imports | 0x4ca - 0x2b32 | 9832 bytes | 340 count
functions | 0x2b35 - 0x3195 | 1632 bytes | 1630 count
code | 0x37a1 - 0x7e9b8 | 512535 bytes | 1630 count
data | 0x7e9bb - 0x8a1e3 | 47144 bytes | 6 count
custom "name" | 0x8a1e6 - 0x96615 | 50223 bytes | 1 count
custom "producers" | 0x96618 - 0x96672 | 90 bytes | 1 count
The counts make it easy to spot oddities: hundreds of imports point to a large glue surface; a big data section suggests embedded assets;
custom sections larger than the code section mean debug information was left in.
Step 4 — strip and annotate
wasm-tools strip app.wasm -o app.stripped.wasm # remove custom sections except "name"
wasm-tools strip --all app.wasm -o app.min.wasm # remove all custom sections
wasm-tools metadata show app.wasm # producers, name, version
wasm-tools metadata add --name app --version 2.4.0 app.wasm -o app.tagged.wasm
strip is useful for vendor binaries delivered with debug sections, and for checking how much of a module is metadata. metadata add stamps
standard name and version information, read back by metadata show and by registries; for your own payloads, custom sections work as described
in adding custom sections to a Wasm binary.
Step 5 — inspect components
Components — the output of cargo component, jco and similar tools — are a different binary layer, and wasm-tools is the main tool that
understands them:
wasm-tools component wit greeter.wasm # the component's WIT interface
wasm-tools print greeter.wasm | head # the component structure, with nested core modules
wasm-tools validate greeter.wasm # validates components too
component wit prints the imports and exports of a component as WIT, which is the most useful single view of what a component needs and
provides. Building and running components is covered in
building a component in Rust with cargo-component.
Comparing two builds
One of the most useful things to do with these commands is compare two builds of the same module — before and after a dependency upgrade, a toolchain change or a suspicious commit. Text diffs of full disassemblies are too noisy to read, but the structural outputs diff well:
diff <(wasm-tools objdump old.wasm) <(wasm-tools objdump new.wasm)
diff <(wasm-tools print --skeleton old.wasm | grep -E '^\s*\((import|export)') \
<(wasm-tools print --skeleton new.wasm | grep -E '^\s*\((import|export)')
The first shows which sections changed size; the second shows any change to the module’s interface — a new import, a renamed export — which is
exactly the kind of change that breaks hosts without breaking the build. A new import from env after a dependency upgrade, for instance, often
means a C dependency started calling a libc function the build does not provide, which would otherwise surface only as a LinkError in a
browser. Running both diffs as part of reviewing toolchain or dependency upgrades turns those surprises into review comments.
For finer-grained size comparisons, twiggy has a diff subcommand that attributes growth to individual functions, which pairs well with the
section-level view here.
Fitting it into a workflow
A small amount of habit makes these commands pay off. Keep wasm-tools in the build image next to the compiler, pinned like any other tool, so
everyone’s output matches. Add two or three commands to CI — validate against the minimum feature set, check the section table for unexpected
debug sections, and record the size of the code section — so problems are caught at review time. And when something strange happens in a browser,
reach for print --skeleton before a debugger: knowing exactly what a module imports and exports, and what its signatures are, resolves a
surprising share of loading problems before you set a single breakpoint.
Expected output
For a typical Rust module, wasm-tools validate prints nothing and exits zero; print --skeleton lists something like:
(module $app.wasm
(type (;0;) (func (param i32)))
(import "wbg" "__wbg_log_1d3ae13c" (func (;0;) (type 0)))
(memory (;0;) 17)
(export "memory" (memory 0))
(export "parse" (func 412))
...
Gotchas
- Different defaults between versions. The default feature set changes as proposals standardise. Pass
--featuresexplicitly in CI. stripremoving the name section.--allremovesnametoo, which makes stack traces unreadable. Strip it deliberately or not at all.- Expecting optimization.
wasm-toolsinspects and transforms; it is not an optimizer. Usewasm-optfor that. - Printing huge modules to the terminal. A full
printof a large module can be millions of lines. Redirect to a file and search it, or start with--skeleton. - Validation passing locally, failing in CI. Different
wasm-toolsversions enable different defaults. Pin the version. - Mixing up modules and components. Core-module tools reject components. Check with
objdumpor the header version first.
Performance note
wasm-tools print --skeleton on a 12 MB module took about 0.4 s and produced a 900-line overview; a full print took 6 s and produced 2.1
million lines. validate ran in about 0.3 s. All are fast enough to run on every CI build.
Frequently Asked Questions
Is wasm-tools a replacement for WABT?
For inspection and validation, largely yes. WABT still offers an interpreter with tracing and some tools that wasm-tools does not.
Can wasm-tools assemble WAT?
Yes — wasm-tools parse file.wat -o file.wasm assembles text into binary, with support for the newest proposals.
Does it help with size analysis?
objdump shows section sizes; for per-function attribution use twiggy, as in
analyzing Wasm size with twiggy.
Does wasm-tools work on Windows?
Yes — release binaries are available for Windows, macOS and Linux, and cargo install builds it anywhere Rust does.
Can I use it as a library?
The same functionality is available as Rust crates — wasmparser, wasmprinter, wasm-encoder — which many other tools embed. They are a
good foundation if you need custom inspection inside your own build tooling.
Can it generate random modules for testing?
Yes — wasm-tools smith generates random valid modules, which is useful for fuzzing engines and tools.
Related
- Validating binaries with wasm-validate — validation in CI.
- Auditing third-party Wasm binaries — an audit built on these commands.
- Reading the import and export sections — what
print --skeletonshows. - Running components in wasmtime — running what you inspected.
← Back to Wasm Binary Format Deep Dive