🔢Das Binärformat Byte für Byte

Ein .wasm-Modul ist kompakt und schnell zu dekodieren: ein 8-Byte-Kopf, danach Sections in fester Reihenfolge. Zahlen sind fast überall LEB128-kodiert, Listen sind „Anzahl + Einträge“, Zeichenketten „Länge + UTF-8“.

🧱Grundbausteine

Kopf (8 Byte)
0061736D01000000

Magic \0asm und Version 1 als u32 little endian. Komponenten (Component Model) nutzen hier 0D 00 01 00.

Section
┌────┬──────────────┬───────────┐
│ id │ size (u32)   │ Inhalt …  │
│ 1 B│ LEB128       │ size Byte │
└────┴──────────────┴───────────┘

Dank size kann ein Parser unbekannte Sections überspringen.

vec(T) und name
vec:  n (u32)  T₁ T₂ … Tₙ
name: n (u32)  n Byte UTF-8
"add" → 03 61 64 64
Werttypen (1 Byte)
7F i327E i647D f327C f647B v12870 funcref6F externref60 func-Typ
Reihenfolge der Sections:1 Type2 Import3 Function4 Table5 Memory13 Tag6 Global7 Export8 Start9 Element12 DataCount10 Code11 Data0 Custom (überall erlaubt)

🔍Hex-Explorer

Beispielmodule (mit „name“-Section) oder eine eigene .wasm-Datei – sie wird nur lokal im Browser gelesen. Jedes Byte ist seiner Section zugeordnet; Hover zeigt das Feld und den dekodierten Wert.
Modul:
📦 Demo-Modul.wasm487 ByteVersion 111 Sections8 Funktionen (1 importiert)8 Exporte
000000
0061736d01000000011b0560027f7f01
000010
7f60027f7f0060037f7f7f017f60017f
000020
017f600000020b0103656e76036c6f67
000030
00010308070000000203010404040170
000040
000305030100010606017f0141000b07
000050
520807746162656c6c650100066d656d
000060
6f727902000761756672756665030003
000070
616464000106726563686e6500040966
000080
6163746f7269616c00050e67726f7373
000090
73636872656962656e00060568616c6c
0000a0
6f00070909010041000b030102030a90
0000b0
01070700200020016a0b070020002001
0000c0
6b0b0700200020016c0b120023004101
0000d0
6a24002001200220001100000b170020
0000e0
00410248047f4101052000200041016b
0000f0
10056c0b0b4201027f200020016a2102
000100
02400340200020024f0d0120002d0000
000110
2103200341e1004f200341fa004d7104
000120
402000200341206b3a00000b20004101
000130
6a21000c000b0b0b08004110410c1000
000140
0b0b12010041100b0c48616c6c6f2c20
000150
5761736d21008f01046e616d65013f08
000160
00036c6f670103616464020373756203
000170
036d756c0406726563686e6505096661
000180
63746f7269616c060e67726f73737363
000190
6872656962656e070568616c6c6f0231
0001a0
080000010002000300040300026f7001
0001b0
0161020162050100016e060400037074
0001c0
7201036c656e0204656e646503016307
0001d0
00040801000562696e6f70070a010007
0001e0
61756672756665
Mit der Maus über ein Byte fahren: Feld, Bedeutung und dekodierter Wert erscheinen hier. Klick fixiert die Auswahl.
Aufbau

Legende: TypeImportFunctionTableMemoryGlobalExportElementCodeDataCustom

🧾add.wasm komplett erklärt (41 Byte)

Das Modul aus dem Kapitel „WAT ↔ Binär“, übersetzt mit wat2wasm ohne Namen. Die Tabelle wird vom eigenen Parser erzeugt.
OffsetBytesSectionFeldBedeutung
0x0000 61 73 6dKopfMagic-Nummer00 61 73 6D = "\0asm"
0x0401 00 00 00KopfVersion1 (little endian u32)
0x0801TypeSection-ID1 = Type
0x0907TypeSection-Größe (u32 LEB128)7 Byte
0x0a01TypeAnzahl Typen1
0x0b60TypeTyp-Form0x60 = Funktionstyp (func)
0x0c02TypeAnzahl Parameter2
0x0d7fTypeParametertypi32
0x0e7fTypeParametertypi32
0x0f01TypeAnzahl Ergebnisse1
0x107fTypeErgebnistypi32
0x1103FunctionSection-ID3 = Function
0x1202FunctionSection-Größe (u32 LEB128)2 Byte
0x1301FunctionAnzahl Funktionen1
0x1400FunctionFunktion 0: Typindex0
0x1507ExportSection-ID7 = Export
0x1607ExportSection-Größe (u32 LEB128)7 Byte
0x1701ExportAnzahl Exporte1
0x1803 61 64 64ExportExportname"add" (Länge 3)
0x1c00ExportExport-Art0 = func
0x1d00ExportIndex0
0x1e0aCodeSection-ID10 = Code
0x1f09CodeSection-Größe (u32 LEB128)9 Byte
0x2001CodeAnzahl Funktionskörper1
0x2107CodeKörpergröße func 07 Byte
0x2200CodeAnzahl Local-Gruppen0
0x2320Codelocal.get0x20
0x2400CodeLocal-Index0
0x2520Codelocal.get0x20
0x2601CodeLocal-Index1
0x276aCodei32.add0x6a
0x280bCodeend0x0b

🧮LEB128-Labor

Variable Länge: kleine Zahlen brauchen ein Byte, große bis zu fünf (u32) bzw. zehn (u64). Jedes Byte trägt 7 Nutzbits, das oberste Bit sagt, ob noch eins folgt.

Zahl → LEB128

e58e26= 3 Byte
ByteBits7 Nutzbits× 2^Beitrag
e5111001011010101
8e100011101471792
26001001103814622592

1 = „es folgt noch ein Byte“, 0 = letztes Byte. Die 7-Bit-Gruppen stehen niederwertigste zuerst (little endian).

LEB128 → Zahl

als unsigned
624485
3 Byte gelesen
als signed
624485
positiv (Bit 6 = 0)

Wasm nutzt unsigned LEB128 für Längen, Anzahlen und Indizes (u32, höchstens 5 Byte) und signed füri32.const/i64.const (höchstens 5 bzw. 10 Byte).

💡 Warum nicht einfach 4 Byte?
Die meisten Indizes und Längen in einem Modul sind klein. Mit LEB128 kostet local.get 0 nur zwei Byte (20 00) statt fünf – das spart Übertragungsgröße, und die Dekodierung bleibt trivial.