kit

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

emu.c (10562B)


      1 #include <kit/core.h>
      2 #include <kit/emu.h>
      3 #include <kit/object.h>
      4 #include <stddef.h>
      5 #include <stdint.h>
      6 #include <string.h>
      7 
      8 #include "driver.h"
      9 
     10 /* `kit emu` — run a guest user-mode executable on the host via libkit's
     11  * per-basic-block JIT translator.
     12  *
     13  * Argv shape mirrors `kit run`: a single positional input (the guest
     14  * executable path) followed by `--` and the guest argv. Flags configure the
     15  * translator (optimize level), tracing (PC / instruction / block), and
     16  * the guest arch (auto-detected when -arch is absent).
     17  *
     18  * The freestanding emu core takes guest bytes; this driver handles the
     19  * path -> bytes step and the argv/envp marshalling. The driver returns
     20  * the guest's exit code on a clean exit, or 1 on internal failure. */
     21 
     22 #define EMU_TOOL "emu"
     23 
     24 typedef struct EmuOptions {
     25   DriverEnv* env;
     26   size_t argv_bound;
     27 
     28   int opt_level;
     29   int interp; /* -interp: run translated blocks through the IR interpreter */
     30   KitEmuTraceFlags trace;
     31   KitArchKind guest_arch;
     32   int guest_arch_set;
     33   KitTargetSpec guest_target;
     34   int guest_target_set;
     35 
     36   const char* guest_path; /* positional input (required) */
     37 
     38   /* Guest argv collected after `--`. argv[0] defaults to guest_path
     39    * when the user supplied no `--` segment. The trailing NULL is
     40    * added at marshalling time and is not counted in nguest_argv. */
     41   const char** guest_argv;
     42   uint32_t nguest_argv;
     43 } EmuOptions;
     44 
     45 static void emu_usage(void) {
     46   driver_errf(EMU_TOOL, "%.*s",
     47               KIT_SLICE_ARG(KIT_SLICE_LIT(
     48                   "usage: kit emu [options] guest-exe [-- guest-arg...]\n"
     49                   "       kit emu --help    for full option reference")));
     50 }
     51 
     52 void driver_help_emu(void) {
     53   driver_printf(
     54       "%.*s",
     55       KIT_SLICE_ARG(KIT_SLICE_LIT(
     56           "kit emu — run a guest user-mode executable on the host\n"
     57           "\n"
     58           "USAGE\n"
     59           "  kit emu [options] guest-exe [-- guest-arg...]\n"
     60           "\n"
     61           "DESCRIPTION\n"
     62           "  Loads a static guest user-mode executable and runs it on the host "
     63           "via\n"
     64           "  the per-basic-block JIT translator. The host code generated by "
     65           "the\n"
     66           "  translator runs natively on the host arch.\n"
     67           "\n"
     68           "  The driver returns the guest's exit code on a clean exit, or 1 "
     69           "on\n"
     70           "  internal failure. Argv shape mirrors `kit run`: anything after\n"
     71           "  `--` is forwarded as the guest argv. With no `--`, argv[0] "
     72           "defaults\n"
     73           "  to the guest executable path.\n"
     74           "\n"
     75           "OPTIONS\n"
     76           "  -O0 -O1 -O2       Translator optimization level; -O2 aliases "
     77           "-O1 (default -O0)\n"
     78           "  -interp           Run translated blocks through the IR "
     79           "interpreter\n"
     80           "                    instead of JITing them (forces -O1)\n"
     81           "  -arch ARCH        Force guest arch: aarch64 (alias arm64) or\n"
     82           "                    riscv64 (alias rv64). When omitted the arch is\n"
     83           "                    auto-detected from the executable.\n"
     84           "  -tracepc          Trace each translated PC\n"
     85           "  -traceinsn        Trace each guest instruction\n"
     86           "  -traceblock       Trace each translated basic block\n"
     87           "  -h, --help        Show this help and exit\n"
     88           "\n"
     89           "EXAMPLES\n"
     90           "  kit emu hello\n"
     91           "  kit emu -arch riscv64 hello -- foo bar\n"
     92           "  kit emu -O2 -tracepc prog\n"
     93           "\n"
     94           "EXIT CODES\n"
     95           "  Returns the guest's exit code on clean exit, or 1 on internal\n"
     96           "  failure. 2 on bad command-line usage.\n")));
     97 }
     98 
     99 static int emu_alloc_arrays(EmuOptions* o, int argc) {
    100   size_t bound = (size_t)argc;
    101   o->argv_bound = bound;
    102   /* +1 to leave room for the guest_path default at index 0. */
    103   o->guest_argv =
    104       driver_alloc_zeroed(o->env, (bound + 1) * sizeof(*o->guest_argv));
    105   if (!o->guest_argv) {
    106     driver_errf(EMU_TOOL, "out of memory");
    107     return 1;
    108   }
    109   return 0;
    110 }
    111 
    112 static int emu_record_arch(EmuOptions* o, const char* val) {
    113   KitArchKind arch;
    114   /* Recognize arch names through the shared driver_arch_from_name table, then
    115    * restrict to the supported guest set so an unsupported (but otherwise
    116    * valid) arch still reports the same "unsupported -arch value" error. */
    117   if (driver_arch_from_name(val, &arch, NULL) == 0 &&
    118       (arch == KIT_ARCH_ARM_64 || arch == KIT_ARCH_RV64)) {
    119     o->guest_arch = arch;
    120     o->guest_arch_set = 1;
    121     return 0;
    122   }
    123   driver_errf(EMU_TOOL, "unsupported -arch value: %.*s",
    124               KIT_SLICE_ARG(kit_slice_cstr(val)));
    125   return 1;
    126 }
    127 
    128 static int emu_parse(int argc, char** argv, EmuOptions* o) {
    129   int i;
    130   int after_dash_dash = 0;
    131   if (emu_alloc_arrays(o, argc) != 0) return 1;
    132 
    133   for (i = 1; i < argc; ++i) {
    134     const char* a = argv[i];
    135 
    136     if (after_dash_dash) {
    137       o->guest_argv[o->nguest_argv++] = argv[i];
    138       continue;
    139     }
    140     if (driver_streq(a, "--")) {
    141       after_dash_dash = 1;
    142       continue;
    143     }
    144 
    145     if (driver_streq(a, "-O0")) {
    146       o->opt_level = 0;
    147       continue;
    148     }
    149     if (driver_streq(a, "-O1")) {
    150       o->opt_level = 1;
    151       continue;
    152     }
    153     if (driver_streq(a, "-O2")) {
    154       o->opt_level = 1;
    155       continue;
    156     }
    157 
    158     if (driver_streq(a, "-tracepc")) {
    159       o->trace |= KIT_EMU_TRACE_PC;
    160       continue;
    161     }
    162     if (driver_streq(a, "-traceinsn")) {
    163       o->trace |= KIT_EMU_TRACE_INSN;
    164       continue;
    165     }
    166     if (driver_streq(a, "-traceblock")) {
    167       o->trace |= KIT_EMU_TRACE_BLOCK;
    168       continue;
    169     }
    170 
    171     if (driver_streq(a, "-interp")) {
    172       o->interp = 1;
    173       continue;
    174     }
    175 
    176     if (driver_streq(a, "-arch")) {
    177       if (++i >= argc) {
    178         driver_errf(EMU_TOOL, "-arch requires an argument");
    179         return 1;
    180       }
    181       if (emu_record_arch(o, argv[i]) != 0) return 1;
    182       continue;
    183     }
    184 
    185     if (a[0] == '-' && a[1] != '\0') {
    186       driver_errf(EMU_TOOL, "unknown flag: %.*s",
    187                   KIT_SLICE_ARG(kit_slice_cstr(a)));
    188       return 1;
    189     }
    190 
    191     if (o->guest_path) {
    192       driver_errf(EMU_TOOL, "multiple guest executable inputs: %.*s, %.*s",
    193                   KIT_SLICE_ARG(kit_slice_cstr(o->guest_path)),
    194                   KIT_SLICE_ARG(kit_slice_cstr(a)));
    195       return 1;
    196     }
    197     o->guest_path = a;
    198   }
    199 
    200   if (!o->guest_path) {
    201     driver_errf(EMU_TOOL, "missing guest executable input");
    202     emu_usage();
    203     return 1;
    204   }
    205   return 0;
    206 }
    207 
    208 static void emu_options_release(EmuOptions* o) {
    209   size_t bound = o->argv_bound;
    210   if (o->guest_argv) {
    211     driver_free(o->env, o->guest_argv, (bound + 1) * sizeof(*o->guest_argv));
    212   }
    213 }
    214 
    215 static const char* emu_arch_name(KitArchKind a) {
    216   switch (a) {
    217     case KIT_ARCH_ARM_64:
    218       return "aarch64";
    219     case KIT_ARCH_RV64:
    220       return "riscv64";
    221     case KIT_ARCH_X86_64:
    222       return "x86_64";
    223     case KIT_ARCH_X86_32:
    224       return "x86";
    225     case KIT_ARCH_ARM_32:
    226       return "arm";
    227     case KIT_ARCH_RV32:
    228       return "riscv32";
    229     case KIT_ARCH_WASM:
    230       return "wasm";
    231   }
    232   return "?";
    233 }
    234 
    235 static int emu_resolve_target(EmuOptions* o, const KitSlice* input) {
    236   KitTargetSpec detected;
    237   if (kit_detect_target(input->data, input->len, &detected) != KIT_OK) {
    238     driver_errf(EMU_TOOL, "could not detect target from %.*s",
    239                 KIT_SLICE_ARG(kit_slice_cstr(o->guest_path)));
    240     return 1;
    241   }
    242   if (o->guest_arch_set) detected.arch = o->guest_arch;
    243   o->guest_target = detected;
    244   o->guest_target_set = 1;
    245   return 0;
    246 }
    247 
    248 /* Build a NULL-terminated argv for the guest. argv[0] defaults to the
    249  * guest executable path if the user supplied no `--` segment, matching Unix
    250  * convention. The returned array points into the caller-owned argv; the
    251  * trailing NULL slot lives in the EmuOptions back-store. */
    252 static void emu_finalize_argv(EmuOptions* o, const char*** out_argv) {
    253   if (o->nguest_argv == 0) {
    254     o->guest_argv[0] = o->guest_path;
    255     o->guest_argv[1] = 0;
    256   } else {
    257     o->guest_argv[o->nguest_argv] = 0;
    258   }
    259   *out_argv = (const char**)o->guest_argv;
    260 }
    261 
    262 int driver_emu(int argc, char** argv) {
    263   DriverEnv env;
    264   EmuOptions eo = {0};
    265   KitContext ctx;
    266   KitTarget* target = NULL;
    267   KitCompiler* compiler = NULL;
    268   DriverLoad guest_lf = {0};
    269   KitSlice guest_in;
    270   KitEmuOptions opts;
    271   KitJitHost jhost;
    272   const char** guest_argv;
    273   int exit_code = 0;
    274   int rc = 1;
    275 
    276   if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
    277     driver_help_emu();
    278     return 0;
    279   }
    280 
    281   driver_env_init(&env);
    282   eo.env = &env;
    283 
    284   if (emu_parse(argc, argv, &eo) != 0) {
    285     emu_options_release(&eo);
    286     driver_env_fini(&env);
    287     return 2;
    288   }
    289 
    290   ctx = driver_env_to_context(&env);
    291   if (!ctx.file_io || !ctx.file_io->read_all) {
    292     driver_errf(EMU_TOOL, "host file I/O unavailable");
    293     goto out;
    294   }
    295 
    296   if (driver_load_bytes(ctx.file_io, EMU_TOOL, eo.guest_path, &guest_lf,
    297                         &guest_in) != 0) {
    298     goto out;
    299   }
    300 
    301   if (emu_resolve_target(&eo, &guest_in) != 0) goto out;
    302 
    303   /* The emu's host-side compiler runs at the host's native target —
    304    * the JIT image holds host code, while the guest target is resolved
    305    * from the executable and optional driver flags. */
    306   {
    307     KitTargetOptions topts;
    308     memset(&topts, 0, sizeof topts);
    309     topts.spec = driver_host_target();
    310     if (kit_target_new(&ctx, &topts, &target) != KIT_OK ||
    311         driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
    312       driver_errf(EMU_TOOL, "failed to initialize compiler");
    313       goto out;
    314     }
    315   }
    316 
    317   emu_finalize_argv(&eo, &guest_argv);
    318   jhost = driver_env_to_jit_host(&env);
    319 
    320   {
    321     KitEmuOptions z = {0};
    322     opts = z;
    323   }
    324   opts.guest_name = kit_slice_cstr(eo.guest_path);
    325   opts.guest_bytes = guest_in;
    326   opts.guest_target = eo.guest_target;
    327   opts.has_guest_target = eo.guest_target_set != 0;
    328   opts.jit_host = &jhost;
    329   opts.optimize = eo.opt_level;
    330   opts.mode = eo.interp ? KIT_EMU_MODE_INTERP : KIT_EMU_MODE_JIT;
    331   opts.trace = eo.trace;
    332   opts.argv = (const char* const*)guest_argv;
    333   opts.envp = 0;
    334 
    335   if (kit_emu_run(compiler, &opts, &exit_code) != KIT_OK) {
    336     driver_errf(
    337         EMU_TOOL, "emulation of %.*s (%.*s) failed",
    338         KIT_SLICE_ARG(kit_slice_cstr(eo.guest_path)),
    339         KIT_SLICE_ARG(kit_slice_cstr(emu_arch_name(eo.guest_target.arch))));
    340     goto out;
    341   }
    342 
    343   rc = exit_code;
    344 
    345 out:
    346   if (compiler) driver_compiler_free(compiler);
    347   kit_target_free(target);
    348   if (guest_lf.loaded && ctx.file_io)
    349     driver_release_bytes(ctx.file_io, &guest_lf);
    350   emu_options_release(&eo);
    351   driver_env_fini(&env);
    352   return rc;
    353 }