🔢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:
oder hierher ziehen – wird nur im Browser gelesen
📦 Demo-Modul.wasm487 ByteVersion 111 Sections8 Funktionen (1 importiert)8 Exporte
000000
0061736d01000000011b0560027f7f01
·asm·······`····000010
7f60027f7f0060037f7f7f017f60017f
·`····`······`··000020
017f600000020b0103656e76036c6f67
··`······env·log000030
00010308070000000203010404040170
···············p000040
000305030100010606017f0141000b07
············A···000050
520807746162656c6c650100066d656d
R··tabelle···mem000060
6f727902000761756672756665030003
ory···aufrufe···000070
616464000106726563686e6500040966
add···rechne···f000080
6163746f7269616c00050e67726f7373
actorial···gross000090
73636872656962656e00060568616c6c
schreiben···hall0000a0
6f00070909010041000b030102030a90
o······A········0000b0
01070700200020016a0b070020002001
···· · ·j··· · ·0000c0
6b0b0700200020016c0b120023004101
k··· · ·l···#·A·0000d0
6a24002001200220001100000b170020
j$· · · ······· 0000e0
00410248047f4101052000200041016b
·A·H··A·· · ·A·k0000f0
10056c0b0b4201027f200020016a2102
··l··B··· · ·j!·000100
02400340200020024f0d0120002d0000
·@·@ · ·O·· ·-··000110
2103200341e1004f200341fa004d7104
!· ·A··O ·A··Mq·000120
402000200341206b3a00000b20004101
@ · ·A k:··· ·A·000130
6a21000c000b0b0b08004110410c1000
j!········A·A···000140
0b0b12010041100b0c48616c6c6f2c20
·····A···Hallo, 000150
5761736d21008f01046e616d65013f08
Wasm!····name·?·000160
00036c6f670103616464020373756203
··log··add··sub·000170
036d756c0406726563686e6505096661
·mul··rechne··fa000180
63746f7269616c060e67726f73737363
ctorial··grosssc000190
6872656962656e070568616c6c6f0231
hreiben··hallo·10001a0
080000010002000300040300026f7001
·············op·0001b0
0161020162050100016e060400037074
·a··b····n····pt0001c0
7201036c656e0204656e646503016307
r··len··ende··c·0001d0
00040801000562696e6f70070a010007
······binop·····0001e0
61756672756665
aufrufeMit 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.
| Offset | Bytes | Section | Feld | Bedeutung |
|---|---|---|---|---|
| 0x00 | 00 61 73 6d | Kopf | Magic-Nummer | 00 61 73 6D = "\0asm" |
| 0x04 | 01 00 00 00 | Kopf | Version | 1 (little endian u32) |
| 0x08 | 01 | Type | Section-ID | 1 = Type |
| 0x09 | 07 | Type | Section-Größe (u32 LEB128) | 7 Byte |
| 0x0a | 01 | Type | Anzahl Typen | 1 |
| 0x0b | 60 | Type | Typ-Form | 0x60 = Funktionstyp (func) |
| 0x0c | 02 | Type | Anzahl Parameter | 2 |
| 0x0d | 7f | Type | Parametertyp | i32 |
| 0x0e | 7f | Type | Parametertyp | i32 |
| 0x0f | 01 | Type | Anzahl Ergebnisse | 1 |
| 0x10 | 7f | Type | Ergebnistyp | i32 |
| 0x11 | 03 | Function | Section-ID | 3 = Function |
| 0x12 | 02 | Function | Section-Größe (u32 LEB128) | 2 Byte |
| 0x13 | 01 | Function | Anzahl Funktionen | 1 |
| 0x14 | 00 | Function | Funktion 0: Typindex | 0 |
| 0x15 | 07 | Export | Section-ID | 7 = Export |
| 0x16 | 07 | Export | Section-Größe (u32 LEB128) | 7 Byte |
| 0x17 | 01 | Export | Anzahl Exporte | 1 |
| 0x18 | 03 61 64 64 | Export | Exportname | "add" (Länge 3) |
| 0x1c | 00 | Export | Export-Art | 0 = func |
| 0x1d | 00 | Export | Index | 0 |
| 0x1e | 0a | Code | Section-ID | 10 = Code |
| 0x1f | 09 | Code | Section-Größe (u32 LEB128) | 9 Byte |
| 0x20 | 01 | Code | Anzahl Funktionskörper | 1 |
| 0x21 | 07 | Code | Körpergröße func 0 | 7 Byte |
| 0x22 | 00 | Code | Anzahl Local-Gruppen | 0 |
| 0x23 | 20 | Code | local.get | 0x20 |
| 0x24 | 00 | Code | Local-Index | 0 |
| 0x25 | 20 | Code | local.get | 0x20 |
| 0x26 | 01 | Code | Local-Index | 1 |
| 0x27 | 6a | Code | i32.add | 0x6a |
| 0x28 | 0b | Code | end | 0x0b |
🧮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
| Byte | Bits | 7 Nutzbits | × 2^ | Beitrag |
|---|---|---|---|---|
| e5 | 11100101 | 101 | 0 | 101 |
| 8e | 10001110 | 14 | 7 | 1792 |
| 26 | 00100110 | 38 | 14 | 622592 |
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.