kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }