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 }