Inspecting Components with wasm-tools

This page answers one task: you have a .wasm file that is supposed to be a component — built by a teammate, downloaded from a registry, produced by a toolchain you do not know — and you need to see what it is: which interfaces it imports and exports, whether it is valid, what core modules it contains, and why a host refuses to instantiate it.

Prerequisites

  • [ ] wasm-tools installed (cargo install wasm-tools or a release binary), recent enough for the component features you use.
  • [ ] The component file, and ideally the WIT the host expects.
  • [ ] Optionally a host (Wasmtime) to reproduce instantiation errors.

What a component file contains

A core module is a flat list of sections: types, imports, functions, memory, exports, code. A component is a different binary format layered on top: it contains core modules (one or more), core instances that wire them together, component types describing the WIT-level interface, canonical functions that lift core functions into component functions (and lower imports the other way), and component-level imports and exports named with WIT interface names such as wasi:cli/run@0.2.0. It may also carry custom sections: producers (which toolchains built it), embedded WIT for documentation, and names.

Because of this nesting, tools for core modules (wasm-objdump, older versions of browser DevTools) cannot read components, and the first question with any unknown file is which kind it is.

The layers inside a component file The outer component layer declares WIT-level imports and exports by interface name. Canonical lift and lower functions adapt between component types and core functions. Core instances wire one or more embedded core modules together, which contain the actual code and linear memory. Custom sections carry producer and name metadata. component imports/exports wasi:cli/run@0.2.0, acme:… canonical lift / lower type adaptation core instances wiring between modules core modules code + linear memory custom sections producers, names, WIT

Step 1 — identify the file and print its world

wasm-tools print app.wasm | head -3
# (component              ← a component; a core module would start with (module

wasm-tools component wit app.wasm
# package root:component;
# world root {
#   import wasi:cli/environment@0.2.0;
#   import wasi:io/streams@0.2.0;
#   import acme:storage/blobs@1.2.0;
#   export wasi:cli/run@0.2.0;
# }

component wit reconstructs the component’s world from its type information: every import the host must satisfy and every export it offers, with full versions. This single command answers most “will this run in my host?” questions.

Step 2 — validate with the right features

wasm-tools validate app.wasm
wasm-tools validate --features all app.wasm          # if it uses newer proposals

Validation checks the component and every embedded core module. A failure names the section and offset. If validation fails only without --features, the component uses proposals (for example async or newer types) that your host may not support either — a useful early warning.

Step 3 — look at structure and metadata

wasm-tools print app.wasm > app.wat        # full text form, including core modules
wasm-tools objdump app.wasm                # section-level overview with sizes
wasm-tools metadata show app.wasm          # producers: language, toolchain versions

metadata show reports which toolchains produced the component and its modules (for example rustc 1.8x, wit-component 0.2xx), which matters when a bug is suspected in a particular toolchain version. objdump shows sizes per section and per nested module, so you can see whether size comes from your code, from an embedded interpreter, or from an adapter module.

Which wasm-tools command answers which question component wit shows the world of imports and exports. validate checks the binary and nested modules. print produces the text form. objdump shows sections and sizes. metadata show reports producing toolchains. component unbundle or core module extraction gives the inner core modules for core-level tools. question command what does it import and export? wasm-tools component wit is it valid? wasm-tools validate what is inside, as text? wasm-tools print where do the bytes go? wasm-tools objdump which toolchain built it? wasm-tools metadata show

Step 4 — extract the core modules

To use core-module tools — twiggy for size, wasm-opt for optimisation, wasm-objdump for disassembly — extract the modules inside:

wasm-tools component unbundle app.wasm --module-dir out/     # in versions that support it
# or print the component and inspect (core module ...) blocks directly

Most components built from one language contain one main core module plus, for components adapted from WASI Preview 1, a small adapter module. Size analysis on the main module then works as for any core module.

Step 5 — diagnose instantiation failures

When a host fails to instantiate a component, the error usually names an import: “component imports instance acme:storage/blobs@1.2.0, but a matching implementation was not found”. Compare the component’s world (component wit) with what the host provides:

  • Version mismatch. The component imports @1.2.0, the host provides @1.1.0 — the component uses something newer than the host implements.
  • Missing WASI interface. The component imports wasi:sockets/tcp, the host linked only CLI interfaces — add it, or rebuild the component without that dependency.
  • Wrong world kind. A library component (exporting a custom interface) run with wasmtime run, which expects wasi:cli/run — use a host that calls the custom export.
  • Unexpected imports from the toolchain. An interpreter-based component importing more WASI than expected — normal; link full WASI.

Inspecting composed components

Composed components contain other components. wasm-tools component wit shows only the outer world — what remains unsatisfied after composition — which is exactly what the host must provide. wasm-tools print shows nested (component ...) blocks, letting you confirm which sub-components were included and how their imports were wired. If a composed component still imports an interface you expected another component to satisfy, the composition did not connect them — often a version mismatch between the exporting and importing sides.

Using inspection in CI

Inspection commands are cheap and deterministic, which makes them good CI checks. Store the expected world of each component (component wit output) in the repository and fail the build when it changes unexpectedly — a new import may mean a dependency started using the network or file system. Check that release components contain no debug custom sections and that metadata show reports the expected toolchain versions. These checks catch supply-chain surprises and accidental capability creep before deployment.

Reviewing third-party components before use

Components downloaded from a registry or received from another team deserve the same review as any dependency, and inspection makes the review concrete. Start with the world: a component that claims to transform images should not import wasi:sockets or wasi:http/outgoing-handler; unexpected imports are the clearest signal that a component does more than advertised. Check metadata show for the producing toolchains and compare them with what the publisher documents. Compare the published digest with the file you have, and if the publisher provides signatures or provenance attestations, verify them. Finally, because the Component Model enforces capabilities at the boundary, the host decides what a component can actually reach: link only the interfaces its stated purpose requires, so even a component with surprising imports fails to instantiate rather than quietly gaining access.

Comparing builds and spotting regressions

Two builds of the same component should differ only where the source changed. Diffing their component wit output catches interface changes; diffing objdump section sizes catches size regressions and newly embedded modules; diffing metadata show catches toolchain changes that slipped in through a lockfile update. For reproducible-build workflows, identical source and toolchains should produce byte-identical components, and cmp on the two files is the simplest possible check; when they differ, wasm-tools print of both, diffed, shows where — often an embedded path or timestamp in a custom section.

Expected output

wasm-tools component wit app.wasm prints a world importing three WASI interfaces and acme:storage/blobs@1.2.0 and exporting wasi:cli/run@0.2.0; validation passes; metadata show lists the Rust and wit-component versions; the extracted core module analyses cleanly with twiggy; and a host failure is traced to the host implementing blobs@1.1.0.

Gotchas

  • Using core-module tools on components. They fail or mislead. Check the file kind first.
  • Old wasm-tools versions. Newer component features fail to parse. Keep wasm-tools current.
  • Validating without features. Proposal use goes unnoticed. Try --features all and compare.
  • Ignoring adapter modules. They add imports and size. Expect them in adapted components.
  • Reading only the outer world of composed components. Check nested wiring with print.
  • Linking every interface for third-party components. Unexpected imports then succeed. Grant only what the purpose requires.

Performance note

All inspection commands run in milliseconds even for large components; printing a 30 MB interpreter-based component to text takes a few seconds and produces a very large file, so prefer component wit and objdump for routine checks.

Time to inspect a 30 MB component Milliseconds for wasm-tools to print the world, validate, show section sizes and print the full text form of a 30 megabyte interpreter-based component. ms per command component wit 40 ms validate 180 ms objdump 60 ms print (full text) 3,100 ms

Frequently Asked Questions

Can browser DevTools inspect components? Browsers do not run components directly; inspect the transpiled core modules produced by jco transpile.

How do I see the exact WIT types of exports? component wit prints full type definitions for interfaces the component defines or uses.

Does wasm-tools strip work on components? Yes — it removes custom sections from the component and nested modules.

How do I compare two builds? Diff the component wit and objdump outputs of both.

How should I review a third-party component before using it? Check its imports against its stated purpose, verify producers and digests, and link only the interfaces it needs.

Why do two builds from the same source differ? Usually embedded paths, timestamps or toolchain versions in custom sections; diff the printed text forms to find where.

Can I tell which language a component was written in? Usually — metadata show lists producers such as rustc, TinyGo or componentize-py, and interpreter-based components are recognisable by their size.

← Back to Wasm Component Model & WIT Bindings