disasm.c (7270B)
1 /* Wasm disassembler. 2 * 3 * Renders the code section of a .wasm module (as exposed by read_wasm) into 4 * WAT instruction text. The code section payload is framed — a function count, 5 * then per function a body size, a locals declaration, an instruction stream, 6 * and a terminating `end` — so unlike the flat ISAs this decoder is stateful: 7 * it walks the framing across successive decode calls, emits one ".locals" 8 * line per function body (at the offset read_wasm records as that function's 9 * symbol value, so objdump labels each body), then decodes the body's 10 * instructions one per call. Instruction decoding reuses the shared 11 * wasm_decode_one_insn so the opcode mapping has a single source of truth. */ 12 13 #include "arch/wasm/disasm.h" 14 15 #include <stdio.h> 16 #include <string.h> 17 18 #include "core/heap.h" 19 #include "core/strbuf.h" 20 #include "wasm/wasm.h" 21 #include "wasm/wasm_insn_table.h" 22 23 #define WASM_DASM_MNEM_CAP 32u 24 #define WASM_DASM_OPS_CAP 192u 25 26 typedef struct WasmDisasm { 27 ArchDisasm base; 28 Compiler* c; 29 Heap* heap; 30 WasmModule scratch; /* reusable decode buffer for wasm_decode_one_insn */ 31 int inited; /* read the function count yet? */ 32 u32 funcs_left; /* function bodies not yet started */ 33 int in_body; /* currently emitting a body's instructions */ 34 u32 depth; /* block/loop/if nesting, for indentation */ 35 char mnem_buf[WASM_DASM_MNEM_CAP]; 36 char ops_buf[WASM_DASM_OPS_CAP]; 37 StrBuf mnem; 38 StrBuf ops; 39 } WasmDisasm; 40 41 /* Bounds-checked uleb over [*p, end); leaves *p past end and returns 0 on 42 * overrun (caller treats a 0-length decode as truncated). */ 43 static u32 dis_uleb(const u8** p, const u8* end) { 44 u32 result = 0, shift = 0; 45 while (*p < end) { 46 u8 b = *(*p)++; 47 result |= (u32)(b & 0x7fu) << shift; 48 if (!(b & 0x80u)) return result; 49 shift += 7u; 50 if (shift >= 32u) break; 51 } 52 return result; 53 } 54 55 static const char* valtype_name(i64 b) { 56 switch ((u8)b) { 57 case 0x7f: 58 return "i32"; 59 case 0x7e: 60 return "i64"; 61 case 0x7d: 62 return "f32"; 63 case 0x7c: 64 return "f64"; 65 case 0x70: 66 return "funcref"; 67 case 0x6f: 68 return "externref"; 69 default: 70 return "?"; 71 } 72 } 73 74 /* Render an instruction's immediate operands into d->ops, dispatched on the 75 * shared operand class from WASM_INSN_TABLE rather than re-listing kinds. */ 76 static void render_operands(WasmDisasm* d, const WasmInsn* in) { 77 const WasmInsnInfo* info = wasm_insn_info((WasmInsnKind)in->kind); 78 WasmOperandClass oc = 79 info ? (WasmOperandClass)info->operand_class : WASM_OC_NONE; 80 switch (oc) { 81 case WASM_OC_SLEB: 82 strbuf_put_i64(&d->ops, in->imm); 83 break; 84 case WASM_OC_FP: { 85 char buf[40]; 86 (void)snprintf(buf, sizeof buf, "%g", in->fp); 87 strbuf_puts(&d->ops, buf); 88 break; 89 } 90 case WASM_OC_IDX: 91 case WASM_OC_TYPED_REF: 92 strbuf_put_u64(&d->ops, (u64)in->imm); 93 break; 94 case WASM_OC_CALL_INDIRECT: 95 strbuf_put_u64(&d->ops, (u64)in->imm); 96 strbuf_puts(&d->ops, " "); 97 strbuf_put_u64(&d->ops, (u64)in->aux_idx); 98 break; 99 case WASM_OC_BR_TABLE: { 100 u32 i; 101 for (i = 0; i < in->ntargets; ++i) { 102 if (i) strbuf_putc(&d->ops, ' '); 103 strbuf_put_u64(&d->ops, (u64)in->targets[i]); 104 } 105 break; 106 } 107 case WASM_OC_REF_NULL: 108 strbuf_puts(&d->ops, valtype_name(in->imm)); 109 break; 110 case WASM_OC_MEMARG: { 111 int wrote = 0; 112 if (in->offset64) { 113 strbuf_puts(&d->ops, "offset="); 114 strbuf_put_u64(&d->ops, in->offset64); 115 wrote = 1; 116 } 117 if (in->align) { 118 if (wrote) strbuf_putc(&d->ops, ' '); 119 strbuf_puts(&d->ops, "align="); 120 strbuf_put_u64(&d->ops, (u64)(1u << in->align)); 121 } 122 break; 123 } 124 default: 125 break; 126 } 127 } 128 129 /* Indentation prefix: two spaces per nesting level, baked into the mnemonic so 130 * objdump's column layout reads as nested WAT. */ 131 static void put_indent(WasmDisasm* d, u32 depth) { 132 u32 i; 133 for (i = 0; i < depth; ++i) strbuf_puts(&d->mnem, " "); 134 } 135 136 static u32 wasm_decode(ArchDisasm* base, const u8* bytes, size_t len, u64 vaddr, 137 KitInsn* out) { 138 WasmDisasm* d = (WasmDisasm*)base; 139 const u8* p = bytes; 140 const u8* end = bytes + len; 141 142 strbuf_reset(&d->mnem); 143 strbuf_reset(&d->ops); 144 out->annotation = SLICE_LIT(""); 145 146 if (len == 0) return 0; 147 148 if (!d->inited) { 149 d->inited = 1; 150 d->funcs_left = dis_uleb(&p, end); 151 d->in_body = 0; 152 } 153 154 if (!d->in_body) { 155 /* Start of a function body: consume the body size LEB and the locals 156 * vector, emitting one ".locals" line whose address is the locals-vector 157 * start (matching read_wasm's function symbol value). */ 158 size_t header; 159 u64 body_vaddr; 160 u32 ngroups, g; 161 if (d->funcs_left == 0) return 0; 162 (void)dis_uleb(&p, end); /* body size; body end tracked via depth */ 163 header = (size_t)(p - bytes); 164 body_vaddr = vaddr + header; 165 ngroups = dis_uleb(&p, end); 166 strbuf_puts(&d->mnem, ".locals"); 167 for (g = 0; g < ngroups && p < end; ++g) { 168 u32 n = dis_uleb(&p, end); 169 u8 vt = (p < end) ? *p++ : 0; 170 u32 k; 171 for (k = 0; k < n; ++k) { 172 strbuf_putc(&d->ops, ' '); 173 strbuf_puts(&d->ops, valtype_name(vt)); 174 } 175 } 176 d->in_body = 1; 177 d->depth = 0; 178 d->funcs_left--; 179 out->vaddr = body_vaddr; 180 out->bytes = bytes + header; 181 out->nbytes = (u32)((size_t)(p - bytes) - header); 182 out->mnemonic = strbuf_slice(&d->mnem); 183 out->operands = strbuf_slice(&d->ops); 184 return (u32)(p - bytes); 185 } 186 187 /* Inside a body: decode one instruction. */ 188 { 189 WasmInsn insn; 190 size_t n = wasm_decode_one_insn(d->c, &d->scratch, bytes, len, 0, &insn); 191 WasmInsnKind k; 192 if (n == 0) return 0; 193 k = (WasmInsnKind)insn.kind; 194 /* Dedent for the closing/middle keywords before printing them. */ 195 if (k == WASM_INSN_ELSE && d->depth) { 196 put_indent(d, d->depth - 1u); 197 } else if (k == WASM_INSN_END && d->depth) { 198 put_indent(d, d->depth - 1u); 199 } else { 200 put_indent(d, d->depth); 201 } 202 strbuf_puts(&d->mnem, wasm_insn_mnemonic(k)); 203 render_operands(d, &insn); 204 205 if (k == WASM_INSN_BLOCK || k == WASM_INSN_LOOP || k == WASM_INSN_IF) { 206 d->depth++; 207 } else if (k == WASM_INSN_END) { 208 if (d->depth == 0) 209 d->in_body = 0; /* body-terminating end */ 210 else 211 d->depth--; 212 } 213 214 out->vaddr = vaddr; 215 out->bytes = bytes; 216 out->nbytes = (u32)n; 217 out->mnemonic = strbuf_slice(&d->mnem); 218 out->operands = strbuf_slice(&d->ops); 219 return (u32)n; 220 } 221 } 222 223 static void wasm_disasm_destroy(ArchDisasm* base) { 224 WasmDisasm* d = (WasmDisasm*)base; 225 Heap* h = d->heap; 226 wasm_module_free(&d->scratch); 227 h->free(h, d, sizeof *d); 228 } 229 230 ArchDisasm* wasm_disasm_new(Compiler* c) { 231 Heap* h = (Heap*)c->ctx->heap; 232 WasmDisasm* d = (WasmDisasm*)h->alloc(h, sizeof *d, _Alignof(WasmDisasm)); 233 if (!d) return NULL; 234 memset(d, 0, sizeof *d); 235 d->c = c; 236 d->heap = h; 237 d->base.decode = wasm_decode; 238 d->base.destroy = wasm_disasm_destroy; 239 wasm_module_init(&d->scratch, h); 240 strbuf_init(&d->mnem, d->mnem_buf, sizeof d->mnem_buf); 241 strbuf_init(&d->ops, d->ops_buf, sizeof d->ops_buf); 242 return &d->base; 243 }