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 }