kit

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

emu.c (10532B)


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