kit

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

disas.c (8989B)


      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. Recognized audited\n"
     43           "                   spellings include aarch64, x86_64, and riscv64;\n"
     44           "                   a canonical triple with one of those arches is\n"
     45           "                   also accepted. Default: host architecture.\n"
     46           "  -x \"HEX...\"      disassemble these hex bytes (spaces allowed)\n"
     47           "  --base ADDR      address of the first byte (default 0)\n"
     48           "  -h, --help       show this help\n"
     49           "\n"
     50           "INPUT AND LIMITATIONS\n"
     51           "  FILE is raw code, not an object file; use kit objdump -d for\n"
     52           "  objects. Use -- before a leading-dash file name. A trailing\n"
     53           "  partial fixed-width instruction is\n"
     54           "  rendered as `(truncated)` and currently still returns success.\n"
     55           "\n"
     56           "EXAMPLES\n"
     57           "  kit disas -target x86_64 -x 'b8 2a000000 c3'\n"
     58           "  kit disas -target aarch64 --base 0x1000 -x 'c0035fd6'\n"
     59           "  kit disas -target riscv64 code.bin\n"
     60           "  bytes=$(kit mc -target aarch64 -p 'ret')\n"
     61           "  kit disas -target aarch64 -x \"$bytes\"\n"
     62           "\n"
     63           "EXIT CODES\n"
     64           "  0   success           1   error                2   bad usage\n")));
     65 }
     66 
     67 static int disas_is_hex(int c) {
     68   return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
     69          (c >= 'A' && c <= 'F');
     70 }
     71 
     72 /* Decode a whitespace-tolerant hex string into freshly-allocated bytes.
     73  * Returns 0 on success (caller frees via driver_free(env, *out, *outlen)). */
     74 static int disas_parse_hex(DriverEnv* env, const char* s, uint8_t** out,
     75                            size_t* outlen) {
     76   size_t ndig = 0, n, bi = 0;
     77   const char* p;
     78   uint8_t* buf;
     79   int hi = -1;
     80   for (p = s; *p; ++p) {
     81     if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') continue;
     82     if (!disas_is_hex((unsigned char)*p)) {
     83       driver_errf(DISAS_TOOL, "invalid hex digit: '%c'", *p);
     84       return 1;
     85     }
     86     ++ndig;
     87   }
     88   if (ndig == 0) {
     89     driver_errf(DISAS_TOOL, "no hex bytes given");
     90     return 1;
     91   }
     92   if (ndig & 1) {
     93     driver_errf(DISAS_TOOL, "hex string has an odd number of digits");
     94     return 1;
     95   }
     96   n = ndig / 2;
     97   buf = (uint8_t*)driver_alloc(env, n);
     98   if (!buf) {
     99     driver_errf(DISAS_TOOL, "out of memory");
    100     return 1;
    101   }
    102   for (p = s; *p; ++p) {
    103     int v;
    104     if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') continue;
    105     v = driver_hex_nibble((char)(unsigned char)*p);
    106     if (hi < 0)
    107       hi = v;
    108     else {
    109       buf[bi++] = (uint8_t)((hi << 4) | v);
    110       hi = -1;
    111     }
    112   }
    113   *out = buf;
    114   *outlen = n;
    115   return 0;
    116 }
    117 
    118 static void disas_run(const KitDisasmContext* dctx, const uint8_t* data,
    119                       size_t len, uint64_t base) {
    120   KitDisasmIter* it = NULL;
    121   KitInsn insn;
    122   if (kit_disasm_iter_new(dctx, data, len, base, NULL, &it) != KIT_OK) {
    123     driver_errf(DISAS_TOOL, "no disassembler for the selected target");
    124     return;
    125   }
    126   while (kit_disasm_iter_next(it, &insn) == KIT_ITER_ITEM) {
    127     uint32_t b;
    128     driver_printf("%8llx:\t", (unsigned long long)insn.vaddr);
    129     for (b = 0; b < insn.nbytes; ++b) driver_printf("%02x ", insn.bytes[b]);
    130     for (b = insn.nbytes; b < 8; ++b) driver_printf("   ");
    131     driver_printf("\t%.*s", KIT_SLICE_ARG(insn.mnemonic));
    132     if (insn.operands.len) driver_printf(" %.*s", KIT_SLICE_ARG(insn.operands));
    133     if (insn.annotation.len)
    134       driver_printf(" # %.*s", KIT_SLICE_ARG(insn.annotation));
    135     driver_printf("\n");
    136   }
    137   kit_disasm_iter_free(it);
    138 }
    139 
    140 int driver_disas(int argc, char** argv) {
    141   DriverEnv env;
    142   KitContext ctx;
    143   KitDisasmContext dctx;
    144   DisasOpts o;
    145   DriverTargetFeatures tf = {0};
    146   KitTarget* target = NULL;
    147   const uint8_t* data = NULL;
    148   size_t len = 0;
    149   DriverLoad ld = {0};
    150   uint8_t* sbuf = NULL;
    151   size_t sbuf_len = 0;
    152   uint8_t* hexbuf = NULL;
    153   size_t hexbuf_len = 0;
    154   int loaded = 0, npos = 0, rc = 2, options = 1;
    155   int i;
    156 
    157   if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
    158     driver_help_disas();
    159     return argc < 2 ? 2 : 0;
    160   }
    161 
    162   memset(&o, 0, sizeof o);
    163   o.target = driver_host_target();
    164   driver_env_init(&env);
    165   if (driver_target_features_init(&tf, &env, argc) != 0) {
    166     driver_errf(DISAS_TOOL, "out of memory");
    167     driver_target_features_fini(&tf, &env);
    168     driver_env_fini(&env);
    169     return 1;
    170   }
    171 
    172   for (i = 1; i < argc; ++i) {
    173     const char* a = argv[i];
    174     if (options && driver_streq(a, "--")) {
    175       options = 0;
    176       continue;
    177     }
    178     if (!options) {
    179       if (npos == 0)
    180         o.in = a;
    181       else {
    182         driver_errf(DISAS_TOOL, "too many operands: %s", a);
    183         goto done;
    184       }
    185       ++npos;
    186       continue;
    187     }
    188     if (driver_streq(a, "-target")) {
    189       if (i + 1 >= argc) {
    190         driver_errf(DISAS_TOOL, "-target requires an argument");
    191         goto done;
    192       }
    193       if (driver_target_from_triple(argv[++i], &o.target) != 0) {
    194         driver_err_unknown_target(DISAS_TOOL, argv[i]);
    195         goto done;
    196       }
    197       continue;
    198     }
    199     {
    200       int tr = driver_target_features_try_consume(&tf, &env, DISAS_TOOL, argc,
    201                                                   argv, &i);
    202       if (tr < 0) goto done;
    203       if (tr > 0) continue;
    204     }
    205     if (driver_streq(a, "-x")) {
    206       if (i + 1 >= argc) {
    207         driver_errf(DISAS_TOOL, "-x requires a hex string");
    208         goto done;
    209       }
    210       o.hex = argv[++i];
    211       continue;
    212     }
    213     if (driver_streq(a, "--base")) {
    214       if (i + 1 >= argc) {
    215         driver_errf(DISAS_TOOL, "--base requires an address");
    216         goto done;
    217       }
    218       if (driver_parse_u64(argv[++i], &o.base) != 0) {
    219         driver_errf(DISAS_TOOL, "invalid --base address");
    220         goto done;
    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 }