Reading the name Custom Section
This page answers one question: where do function names in WebAssembly stack traces and profiles come from, what exactly is in the name
section, and should you ship it?
Prerequisites
- [ ] A module built with names (most dev builds) and one without (a stripped release build) to compare.
- [ ] WABT’s
wasm-objdumpandwasm-strip, orwasm-tools.
Names are optional metadata
WebAssembly code refers to functions, locals and globals by index. The code section contains no names at all; a function is “function 412”,
and its third local is “local 2”. Names exist only in a custom section — a section with id 0 and a string name — called name. Engines are
required to ignore malformed custom sections and are not required to use them, but in practice every browser and runtime reads the name
section to label stack frames, profiler entries and debugger views.
That makes the name section the difference between a crash report that says at app::parser::parse_header and one that says
at wasm-function[412]. It is also pure metadata: removing it changes nothing about how the module runs, only how readable it is when
something goes wrong.
Step 1 — see whether a module has names
wasm-objdump -h app.wasm | grep -i 'custom.*name'
wasm-objdump -x -j name app.wasm | head
Custom start=0x0004f2a1 end=0x0005a8d0 (size=0x0000b62f) "name"
Custom:
- name: "name"
- module
- func[0]
- func[1]
- func[2]
Here the section is 46 KB — names are long in Rust and C++ because they include module paths and, in C++, demangled templates.
Step 2 — understand the subsections
The name section’s content is a sequence of subsections, each with an id byte and a LEB128 size, so readers can skip what they do not need:
00 module name a single name for the whole module
01 function names a map: function index → name
02 local names for each function, a map: local index → name
03 label names (extended name section) block labels
04 type names names for type indices
05 table names
06 memory names
07 global names
08 element segment names
09 data segment names
Each map is a LEB128 count followed by (index, name) pairs, where names are LEB128-length-prefixed UTF-8 strings — the same string encoding
as imports and exports, described in
reading the import and export sections.
Toolchains emit subsections 1 and 2 most of the time; the others come from the extended name section proposal and are used by some
toolchains for debugging.
Step 3 — decode a function-name entry
A fragment of the function-names subsection:
01 subsection id 1: function names
93 0b subsection size: 1427 (LEB128)
a2 01 count: 162 entries
00 function index 0
1b 77 62 67 3a 3a ... name length 27, "wbg::__wbg_log_1d3ae13c"
01 function index 1
1b 61 70 70 3a 3a ... name length 27, "app::parser::parse_header"
Indices refer to the full function index space, imports included — so imported functions can have names too, which is how stack traces label calls into JavaScript glue.
Step 4 — keep names in development, decide for release
Dev builds keep names by default. Release builds differ by toolchain: wasm-pack and wasm-bindgen keep the name section unless wasm-opt --strip-debug or --strip removes it; Emscripten drops names at -O2 and above unless you pass --profiling-funcs; wasm-ld --strip-all
removes them; wasm-strip removes every custom section.
# keep only names, drop DWARF and other debug data
wasm-opt -O3 --strip-dwarf app.wasm -o app.named.wasm
# drop names too
wasm-strip app.wasm -o app.stripped.wasm
The choice is a trade between size and diagnosability. A common compromise is to strip names from the shipped module and keep the unstripped
build as an artifact for each release, then translate wasm-function[412] back to parse_header when a crash report arrives — the process
described in symbolicating Wasm stack traces in production.
Step 5 — shorten names if you keep them
If you ship names, you can make them cheaper. Rust’s -C symbol-mangling-version=v0 changes little here because the name section stores
demangled names, but limiting generic instantiations reduces the number of distinct functions. For C++, names of template-heavy code can be
hundreds of bytes each; wasm-opt --strip-dwarf keeps names while dropping debug info, and some teams post-process the section to trim
namespace prefixes. Measure before bothering: in many modules, names compress well under Brotli, and the on-the-wire cost is a fraction of the
raw size.
Custom sections in general
The name section is the best-known example of a general mechanism. Any section with id 0 is a custom section: a name string followed by
arbitrary bytes. Engines must accept custom sections they do not understand and ignore them; they can only use ones they recognise, and must not
let a malformed custom section affect execution. That rule is what lets toolchains attach metadata freely without breaking anything.
The ecosystem uses this extensively. DWARF debug information lives in custom sections named .debug_info, .debug_line and so on. The
producers section records which languages and tools built the module. target_features records which proposals the code uses, which linkers
read when combining objects. wasm-bindgen stores its interface description in a custom section that its CLI consumes and removes. The
sourceMappingURL section points debuggers at a source map, and external_debug_info points at a separate DWARF file. The linking and
reloc.* sections in object files carry what the linker needs and disappear from the final module.
All of them follow the same rules as name: optional, removable and irrelevant to execution. When a module is larger than expected, listing
its custom sections with wasm-objdump -h is a quick check — debug sections left in a release build are a common and easily fixed cause, and
the technique for adding your own is in
adding custom sections to a Wasm binary.
How tools use the section
Several tools you already use read the name section, and knowing that helps explain their behaviour. Browser DevTools label stack frames and
the Sources panel’s function list from it. Profilers — Chrome’s Performance panel, the Firefox Profiler, perf with wasmtime’s perfmap — use it
to name samples. wasm-objdump and wasm2wat use it to print $parse_header instead of $func412. twiggy uses it to attribute size to named
functions. When any of these shows indices instead of names, the explanation is always the same: the module you are looking at has no name
section, or a different build’s section that does not match.
Expected output
The same trap in a module with and without names:
RuntimeError: unreachable
at app::parser::parse_header (wasm://wasm/8c1e2f1a:wasm-function[1]:0x2a3f)
RuntimeError: unreachable
at wasm://wasm/8c1e2f1a:wasm-function[1]:0x2a3f
Gotchas
- Names from a different build. Mapping indices with an unstripped artifact from another build gives wrong names. Archive the exact build.
wasm-optsilently stripping names. Some optimization flags remove the section. Pass--debuginfo(-g) towasm-optto keep it.- Huge C++ names. Template-heavy code can make the section larger than you expect. Measure it.
- Names on imported functions mistaken for your code. Import entries carry names too, typically the glue’s. Check the import section before assuming a named frame is in your module.
- Confusing names with DWARF. The
namesection gives function names only; source lines and variable types come from DWARF sections.
Performance note
For a Rust application, the name section was 46 KB raw and 11 KB after Brotli on a 610 KB module — about 2% of the compressed transfer. For a C++ application it was 410 KB raw and 96 KB compressed, about 7%. Engines parse it lazily or not at all until a name is needed, so it does not affect compile time.
Frequently Asked Questions
Does the name section affect performance? No. It is not executed and engines read it only when they need a name.
Can I add names to a stripped module? Only by copying the section from an unstripped build of the same code. Names cannot be recovered from the code itself.
Can I rename functions in the section after building?
Yes — tools such as wasm-tools can rewrite custom sections, and some teams shorten names this way. Make sure symbolication tools use the same
renamed table.
Do names leak information? They reveal function and module names from your source, which some teams consider sensitive. Stripping is the remedy if that matters.
Is this the same as DWARF? No. DWARF is a separate set of custom sections with full debugging information; see debugging Wasm with DWARF and source maps.
Related
- Adding custom sections to a Wasm binary — the general mechanism.
- Reading Wasm stack traces — where names show up first.
- Analyzing Wasm size with twiggy — a tool that depends on names.
- Generating flame graphs for Wasm — profiles that need names.
← Back to Wasm Binary Format Deep Dive