kit

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

disas.c (8675B)


      1 #include <kit/core.h>
      2 #include <kit/disasm.h>
      3 #include <stddef.h>
      4 #include <stdint.h>
      5 #include <string.h>
      6 
      7 #include "driver.h"
      8 #include "env.h"
      9 
     10 /* `kit disas` — disassemble raw machine-code bytes for a target arch. Unlike
     11  * `objdump -d`, the input is a headerless byte buffer (a file, stdin, or an
     12  * inline hex string), not a parsed object — handy for inspecting a snippet of
     13  * codegen or an emitted instruction. The arch comes from -target (host
     14  * default); --base sets the address shown for the first byte. */
     15 
     16 #define DISAS_TOOL "disas"
     17 
     18 typedef struct DisasOpts {
     19   KitTargetSpec target;
     20   uint64_t base;   /* --base: vaddr of the first byte */
     21   const char* hex; /* -x: inline hex string, or NULL */
     22   const char* in;  /* positional file, "-" for stdin, or NULL */
     23 } DisasOpts;
     24 
     25 void driver_help_disas(void) {
     26   driver_printf(
     27       "%.*s",
     28       KIT_SLICE_ARG(KIT_SLICE_LIT(
     29           "kit disas — disassemble raw machine-code bytes\n"
     30           "\n"
     31           "USAGE\n"
     32           "  kit disas [-target TRIPLE] [--base ADDR] -x \"HEX...\"\n"
     33           "  kit disas [-target TRIPLE] [--base ADDR] [FILE|-]\n"
     34           "\n"
     35           "DESCRIPTION\n"
     36           "  Decodes a headerless buffer of machine code. Bytes come from an\n"
     37           "  inline hex string (-x), a FILE, or stdin (no FILE, or `-`).\n"
     38           "  Hex may contain spaces. The buffer is treated as code for the\n"
     39           "  -target architecture (the host arch by default).\n"
     40           "\n"
     41           "OPTIONS\n"
     42           "  -target TRIPLE   architecture to decode for (e.g. aarch64,\n"
     43           "                   x86_64, riscv64). See `kit cc --help`.\n"
     44           "  -x \"HEX...\"      disassemble these hex bytes (spaces allowed)\n"
     45           "  --base ADDR      address of the first byte (default 0)\n"
     46           "  -h, --help       show this help\n"
     47           "\n"
     48           "EXIT CODES\n"
     49           "  0   success           1   error                2   bad usage\n")));
     50 }
     51 
     52 static int disas_is_hex(int c) {
     53   return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
     54          (c >= 'A' && c <= 'F');
     55 }
     56 static int disas_hexval(int c) {
     57   if (c >= '0' && c <= '9') return c - '0';
     58   if (c >= 'a' && c <= 'f') return c - 'a' + 10;
     59   return c - 'A' + 10;
     60 }
     61 
     62 /* Decode a whitespace-tolerant hex string into freshly-allocated bytes.
     63  * Returns 0 on success (caller frees via driver_free(env, *out, *outlen)). */
     64 static int disas_parse_hex(DriverEnv* env, const char* s, uint8_t** out,
     65                            size_t* outlen) {
     66   size_t ndig = 0, n, bi = 0;
     67   const char* p;
     68   uint8_t* buf;
     69   int hi = -1;
     70   for (p = s; *p; ++p) {
     71     if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') continue;
     72     if (!disas_is_hex((unsigned char)*p)) {
     73       driver_errf(DISAS_TOOL, "invalid hex digit: '%c'", *p);
     74       return 1;
     75     }
     76     ++ndig;
     77   }
     78   if (ndig == 0) {
     79     driver_errf(DISAS_TOOL, "no hex bytes given");
     80     return 1;
     81   }
     82   if (ndig & 1) {
     83     driver_errf(DISAS_TOOL, "hex string has an odd number of digits");
     84     return 1;
     85   }
     86   n = ndig / 2;
     87   buf = (uint8_t*)driver_alloc(env, n);
     88   if (!buf) {
     89     driver_errf(DISAS_TOOL, "out of memory");
     90     return 1;
     91   }
     92   for (p = s; *p; ++p) {
     93     int v;
     94     if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') continue;
     95     v = disas_hexval((unsigned char)*p);
     96     if (hi < 0)
     97       hi = v;
     98     else {
     99       buf[bi++] = (uint8_t)((hi << 4) | v);
    100       hi = -1;
    101     }
    102   }
    103   *out = buf;
    104   *outlen = n;
    105   return 0;
    106 }
    107 
    108 static void disas_run(const KitDisasmContext* dctx, const uint8_t* data,
    109                       size_t len, uint64_t base) {
    110   KitDisasmIter* it = NULL;
    111   KitInsn insn;
    112   if (kit_disasm_iter_new(dctx, data, len, base, NULL, &it) != KIT_OK) {
    113     driver_errf(DISAS_TOOL, "no disassembler for the selected target");
    114     return;
    115   }
    116   while (kit_disasm_iter_next(it, &insn) == KIT_ITER_ITEM) {
    117     uint32_t b;
    118     driver_printf("%8llx:\t", (unsigned long long)insn.vaddr);
    119     for (b = 0; b < insn.nbytes; ++b) driver_printf("%02x ", insn.bytes[b]);
    120     for (b = insn.nbytes; b < 8; ++b) driver_printf("   ");
    121     driver_printf("\t%.*s", KIT_SLICE_ARG(insn.mnemonic));
    122     if (insn.operands.len) driver_printf(" %.*s", KIT_SLICE_ARG(insn.operands));
    123     if (insn.annotation.len)
    124       driver_printf(" # %.*s", KIT_SLICE_ARG(insn.annotation));
    125     driver_printf("\n");
    126   }
    127   kit_disasm_iter_free(it);
    128 }
    129 
    130 int driver_disas(int argc, char** argv) {
    131   DriverEnv env;
    132   KitContext ctx;
    133   KitDisasmContext dctx;
    134   DisasOpts o;
    135   DriverTargetFeatures tf = {0};
    136   KitTarget* target = NULL;
    137   const uint8_t* data = NULL;
    138   size_t len = 0;
    139   DriverLoad ld = {0};
    140   uint8_t* sbuf = NULL;
    141   size_t sbuf_len = 0;
    142   uint8_t* hexbuf = NULL;
    143   size_t hexbuf_len = 0;
    144   int loaded = 0, npos = 0, rc = 2;
    145   int i;
    146 
    147   if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
    148     driver_help_disas();
    149     return argc < 2 ? 2 : 0;
    150   }
    151 
    152   memset(&o, 0, sizeof o);
    153   o.target = driver_host_target();
    154   driver_env_init(&env);
    155   if (driver_target_features_init(&tf, &env, argc) != 0) {
    156     driver_errf(DISAS_TOOL, "out of memory");
    157     driver_target_features_fini(&tf, &env);
    158     driver_env_fini(&env);
    159     return 1;
    160   }
    161 
    162   for (i = 1; i < argc; ++i) {
    163     const char* a = argv[i];
    164     if (driver_streq(a, "-target")) {
    165       if (i + 1 >= argc) {
    166         driver_errf(DISAS_TOOL, "-target requires an argument");
    167         goto done;
    168       }
    169       if (driver_target_from_triple(argv[++i], &o.target) != 0) {
    170         driver_errf(DISAS_TOOL, "unrecognized target: %s", argv[i]);
    171         goto done;
    172       }
    173       continue;
    174     }
    175     {
    176       int tr = driver_target_features_try_consume(&tf, &env, DISAS_TOOL, argc,
    177                                                   argv, &i);
    178       if (tr < 0) goto done;
    179       if (tr > 0) continue;
    180     }
    181     if (driver_streq(a, "-x")) {
    182       if (i + 1 >= argc) {
    183         driver_errf(DISAS_TOOL, "-x requires a hex string");
    184         goto done;
    185       }
    186       o.hex = argv[++i];
    187       continue;
    188     }
    189     if (driver_streq(a, "--base")) {
    190       if (i + 1 >= argc) {
    191         driver_errf(DISAS_TOOL, "--base requires an address");
    192         goto done;
    193       }
    194       {
    195         const char* v = argv[++i];
    196         uint64_t val = 0;
    197         int base = 10;
    198         if (v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) {
    199           base = 16;
    200           v += 2;
    201         }
    202         if (!*v) {
    203           driver_errf(DISAS_TOOL, "invalid --base address");
    204           goto done;
    205         }
    206         for (; *v; ++v) {
    207           int d;
    208           if (*v >= '0' && *v <= '9')
    209             d = *v - '0';
    210           else if (base == 16 && *v >= 'a' && *v <= 'f')
    211             d = *v - 'a' + 10;
    212           else if (base == 16 && *v >= 'A' && *v <= 'F')
    213             d = *v - 'A' + 10;
    214           else {
    215             driver_errf(DISAS_TOOL, "invalid --base address");
    216             goto done;
    217           }
    218           val = val * (uint64_t)base + (uint64_t)d;
    219         }
    220         o.base = val;
    221       }
    222       continue;
    223     }
    224     if (driver_streq(a, "-")) {
    225       if (npos == 0) o.in = "-";
    226       ++npos;
    227       continue;
    228     }
    229     if (a[0] == '-' && a[1] != '\0') {
    230       driver_errf(DISAS_TOOL, "unknown option: %s", a);
    231       goto done;
    232     }
    233     if (npos == 0)
    234       o.in = a;
    235     else {
    236       driver_errf(DISAS_TOOL, "too many operands: %s", a);
    237       goto done;
    238     }
    239     ++npos;
    240   }
    241 
    242   /* Resolve the byte source: -x wins; else file/stdin. */
    243   if (o.hex) {
    244     if (o.in) {
    245       driver_errf(DISAS_TOOL, "give either -x or a file, not both");
    246       goto done;
    247     }
    248     if (disas_parse_hex(&env, o.hex, &hexbuf, &hexbuf_len) != 0) {
    249       rc = 1;
    250       goto done;
    251     }
    252     data = hexbuf;
    253     len = hexbuf_len;
    254   } else if (o.in && !driver_streq(o.in, "-")) {
    255     KitSlice in;
    256     if (driver_load_bytes(&env.file_io, DISAS_TOOL, o.in, &ld, &in) != 0) {
    257       rc = 1;
    258       goto done;
    259     }
    260     loaded = 1;
    261     data = in.data;
    262     len = in.len;
    263   } else {
    264     if (!driver_read_stdin(&env, &sbuf, &sbuf_len)) {
    265       driver_errf(DISAS_TOOL, "failed to read stdin");
    266       rc = 1;
    267       goto done;
    268     }
    269     data = sbuf;
    270     len = sbuf_len;
    271   }
    272 
    273   ctx = driver_env_to_context(&env);
    274   if (driver_target_new(&ctx, o.target, &tf, DISAS_TOOL, &target) != KIT_OK) {
    275     driver_errf(DISAS_TOOL, "failed to initialize target");
    276     rc = 1;
    277     goto done;
    278   }
    279   memset(&dctx, 0, sizeof dctx);
    280   dctx.target = target;
    281   dctx.context = ctx;
    282   disas_run(&dctx, data, len, o.base);
    283   rc = 0;
    284 
    285 done:
    286   kit_target_free(target);
    287   if (hexbuf) driver_free(&env, hexbuf, hexbuf_len);
    288   if (sbuf) driver_free(&env, sbuf, sbuf_len);
    289   if (loaded) driver_release_bytes(&env.file_io, &ld);
    290   driver_target_features_fini(&tf, &env);
    291   driver_env_fini(&env);
    292   return rc;
    293 }