kit

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

dbg.c (115451B)


      1 #include <kit/arch.h>
      2 #include <kit/compile.h>
      3 #include <kit/core.h>
      4 #include <kit/dbg.h>
      5 #include <kit/disasm.h>
      6 #include <kit/dwarf.h>
      7 #include <kit/jit.h>
      8 #include <kit/link.h>
      9 #include <kit/object.h>
     10 #include <stddef.h>
     11 #include <stdint.h>
     12 #include <string.h>
     13 
     14 #include "backtrace.h"
     15 #include "cflags.h"
     16 #include "driver.h"
     17 #include "hosted.h"
     18 #include "inputs.h"
     19 
     20 /* `kit dbg` — interactive JIT debugger.
     21  *
     22  * Mirrors `kit run` for compile flags and argv shape, but instead of
     23  * calling the entry directly drops into a REPL that drives a
     24  * KitDebugSession. The session (in libkit) owns the worker thread,
     25  * signal handlers, breakpoint patcher, and per-arch single-step /
     26  * displaced-step trampoline. This driver TU only:
     27  *
     28  *   - parses argv and turns the source list into a JIT image,
     29  *   - opens DWARF and a JIT-image view,
     30  *   - manages a driver-local breakpoint table (id, spec text, enabled
     31  *     flag) keyed off session-side handles,
     32  *   - parses LOC strings (file:line, sym[+off], 0xADDR) into addresses
     33  *     via kit_dwarf_line_to_addr / kit_jit_lookup,
     34  *   - reads commands from stdin and dispatches,
     35  *   - renders KitStopInfo into source-level stop messages and the
     36  *     backtrace via kit_dwarf_unwind_step,
     37  *   - decodes `p name` via kit_dwarf_var_at + kit_dwarf_loc_read.
     38  *
     39  * Forwarding Ctrl-C: while a session call is in flight the driver
     40  * installs a SIGINT handler that calls kit_dbg_session_interrupt; on
     41  * return it restores SIG_DFL so Ctrl-C at the REPL prompt terminates
     42  * the program. */
     43 
     44 #define DBG_TOOL "dbg"
     45 #define LINE_CAP 1024
     46 #define WORD_CAP 256
     47 #define HEX_CAP 8 /* upper bound on per-arch trap-byte save */
     48 
     49 /* ============================================================
     50  * argv parsing (mirrors run.c)
     51  * ============================================================ */
     52 
     53 typedef enum { DBG_ENTRY_FILE, DBG_ENTRY_CMD } DbgEntryKind;
     54 typedef struct {
     55   DbgEntryKind kind;
     56   const char* value; /* argv pointer — no per-entry allocation */
     57 } DbgScriptEntry;
     58 
     59 typedef struct DbgOpts {
     60   DriverEnv* env;
     61   KitFrontendRegistry* frontends;
     62   size_t argv_bound;
     63 
     64   int opt_level;
     65   int debug_info;
     66   const char* entry;
     67   const char* sysroot;
     68   int wants_hosted_libc;
     69   KitLanguage default_lang;
     70   int has_default_lang;
     71 
     72   DriverHostedPlan hosted;
     73   DriverCflags cf;
     74   DriverInputs inputs;
     75 
     76   char** prog_argv;
     77   uint32_t prog_argc;
     78 
     79   DbgScriptEntry* script_entries;
     80   uint32_t nscript_entries;
     81   uint32_t script_entries_cap;
     82   int batch_mode;
     83 } DbgOpts;
     84 
     85 void driver_help_dbg(void) {
     86   driver_printf(
     87       "%.*s",
     88       KIT_SLICE_ARG(KIT_SLICE_LIT(
     89           "kit dbg — interactive JIT debugger\n"
     90           "\n"
     91           "USAGE\n"
     92           "  kit dbg [options] [input.{c,s,wat,wasm} ...] [-- prog-arg ...]\n"
     93           "\n"
     94           "DESCRIPTION\n"
     95           "  Uses `kit run`'s preprocessing, hosted-SDK, and argv shape, but\n"
     96           "  instead of calling the entry directly drops into a REPL that\n"
     97           "  drives the JIT session: breakpoints, source-line stepping into or\n"
     98           "  over calls, instruction stepping, finish, backtrace, registers,\n"
     99           "  locals/args, variable read/write, and raw memory examine. -g is\n"
    100           "  forced on so source lines and\n"
    101           "  variable locations are available at runtime.\n"
    102           "  Current debugger support is limited to Darwin/Linux aarch64\n"
    103           "  hosts.\n"
    104           "\n"
    105           "  Anything after `--` is passed to the JITed program as argv.\n"
    106           "  With no input files, dbg starts an empty JIT session; append "
    107           "code\n"
    108           "  with `jit` or evaluate expressions directly from the REPL.\n"
    109           "\n"
    110           "COMPILE OPTIONS\n"
    111           "  -O0 -O1 -O2       Optimization level; -O2 aliases -O1 "
    112           "(default -O0)\n"
    113           "  -g                Emit DWARF (forced on)\n"
    114           "  -e SYMBOL         Entry symbol (default `main`)\n"
    115           "  --sysroot DIR, -isysroot DIR\n"
    116           "                    Hosted SDK/sysroot for headers with -lc\n"
    117           "  -lc, -l c         Enable hosted libc headers/definitions\n"
    118           "  -x LANG           Default REPL language: c, asm, wasm/wat\n"
    119           "  --language LANG   Same as -x\n"
    120           "  -I DIR            Add quoted-include search path\n"
    121           "  -isystem DIR      Add system-include search path\n"
    122           "  -D NAME[=BODY]    Define a macro\n"
    123           "  -U NAME           Undefine a builtin/predefined macro\n"
    124           "\n"
    125           "REPL COMMANDS (also shown by `h` at the prompt)\n"
    126           "  h, help                      show REPL help\n"
    127           "  q, quit                      exit (Ctrl-D also works)\n"
    128           "  r, run                       start fresh execution at entry\n"
    129           "  c, cont                      continue after a stop\n"
    130           "  s, step                      step to next source line (into "
    131           "calls)\n"
    132           "  si, stepi                    single-step one instruction\n"
    133           "  n, next                      step to next source line (over "
    134           "calls)\n"
    135           "  finish                       run until current frame returns\n"
    136           "  jit [LANG|NAME] { ... }      compile and append a language "
    137           "snippet\n"
    138           "  { ... }                      same as jit { ... }\n"
    139           "  edit [LANG|NAME]             edit and append a language snippet\n"
    140           "                               (Ctrl-G edits the current input line "
    141           "in $EDITOR)\n"
    142           "                               Set $EDITOR before starting dbg to\n"
    143           "                               select the editor command.\n"
    144           "  expr EXPR | expr { ... }     compile and call an expression "
    145           "thunk\n"
    146           "  EXPR                         same as expr EXPR\n"
    147           "                               LANG defaults to the selected "
    148           "language\n"
    149           "  jump ADDR                    set PC to ADDR (no resume)\n"
    150           "  bt, backtrace                print stack trace with arguments\n"
    151           "  b LOC                        set breakpoint at LOC:\n"
    152           "                                  0xADDR | sym[+off] | file.c:line\n"
    153           "  ignore N COUNT               skip the next COUNT hits of bp N\n"
    154           "  d N, delete N                delete breakpoint N\n"
    155           "  enable N | disable N         toggle breakpoint N\n"
    156           "  p NAME                       print variable / global\n"
    157           "  set NAME VALUE               write VALUE into NAME\n"
    158           "  x ADDR [count]               examine memory (default 16 bytes)\n"
    159           "  disasm [ADDR] [count], x/i    disassemble at PC or ADDR\n"
    160           "  list FILE:LINE | l FILE:LINE source listing around FILE:LINE\n"
    161           "  info b                       list breakpoints\n"
    162           "  info reg, info registers     dump registers\n"
    163           "  info locals | info args      list locals / args at current PC\n"
    164           "  info functions [PATTERN]     list JIT functions matching PATTERN\n"
    165           "  info variables [PATTERN]     list JIT globals  matching PATTERN\n")));
    166   driver_printf(
    167       "%.*s",
    168       KIT_SLICE_ARG(KIT_SLICE_LIT(
    169           "\n"
    170           "BATCH / SCRIPTING\n"
    171           "  --script FILE     execute debugger commands from FILE "
    172           "(repeatable)\n"
    173           "  --command CMD     execute CMD as if typed at the REPL "
    174           "(repeatable)\n"
    175           "  -c CMD            same as --command\n"
    176           "  --batch           non-interactive: suppress banner, exit after\n"
    177           "                    --script / --command sources drain; exit 1 on\n"
    178           "                    any command error\n"
    179           "  Each script command must fit on one line. Missing, unreadable,\n"
    180           "  overlong, or malformed explicit scripts return status 1 without\n"
    181           "  falling through to an interactive prompt.\n"
    182           "\n"
    183           "SIGNALS\n"
    184           "  Ctrl-C is forwarded into the running session as an interrupt; at\n"
    185           "  the REPL prompt it terminates the program normally.\n"
    186           "\n"
    187           "GETTING HELP\n"
    188           "  -h, --help                   Show this help and exit (this is "
    189           "the\n"
    190           "                               command-line help; once the REPL is\n"
    191           "                               running, type `h` for REPL "
    192           "commands)\n"
    193           "  --version                    Show Kit version and exit\n"
    194           "\n"
    195           "EXAMPLES\n"
    196           "  # Interactive source debugging (supported host above).\n"
    197           "  kit dbg -O0 program.c -- arg1\n"
    198           "  # At the prompt: b main, r, n, p value, bt, q\n"
    199           "\n"
    200           "  # Deterministic batch session.\n"
    201           "  printf '%s\\n' 'b main' 'r' 'bt' 'q' > commands.dbg\n"
    202           "  kit dbg --batch --script commands.dbg program.c\n"
    203           "\n"
    204           "  # Hosted headers on macOS require Kit's printed SDK.\n"
    205           "  SDK=\"$(kit cc -print-sysroot)\"\n"
    206           "  kit dbg -lc --sysroot \"$SDK\" hosted.c\n"
    207           "\n"
    208           "EXIT CODES\n"
    209           "  0   clean exit       1   compile/link/command error       2   "
    210           "bad "
    211           "usage\n")));
    212 }
    213 
    214 static int dbg_alloc_arrays(DbgOpts* o, int argc) {
    215   size_t bound = (size_t)argc;
    216   o->argv_bound = bound;
    217   o->prog_argv = driver_alloc_zeroed(o->env, bound * sizeof(*o->prog_argv));
    218   if (!o->prog_argv) {
    219     driver_errf(DBG_TOOL, "out of memory");
    220     return 1;
    221   }
    222   if (driver_inputs_init(&o->inputs, o->env, DBG_TOOL, argc) != 0) return 1;
    223   o->inputs.frontends = o->frontends;
    224   if (driver_cflags_init(
    225           &o->cf, o->env,
    226           argc + DRIVER_HOSTED_MAX_INCLUDES + DRIVER_HOSTED_MAX_DEFINES) != 0) {
    227     driver_errf(DBG_TOOL, "out of memory");
    228     return 1;
    229   }
    230   return 0;
    231 }
    232 
    233 static int dbg_apply_hosted_profile(DbgOpts* o) {
    234   DriverHostedRequest req;
    235   uint32_t i;
    236   if (!o->wants_hosted_libc) return 0;
    237   {
    238     DriverHostedRequest z = {0};
    239     req = z;
    240   }
    241   req.env = o->env;
    242   req.tool = DBG_TOOL;
    243   req.target = driver_host_target();
    244   req.sysroot = o->sysroot;
    245   req.static_link = 0;
    246   req.link_inputs = 0;
    247   if (driver_hosted_resolve(&req, &o->hosted) != 0) return 1;
    248   for (i = 0; i < o->hosted.nsystem_includes; ++i) {
    249     o->cf.system_include_dirs[o->cf.nsystem_include_dirs++] =
    250         o->hosted.system_includes[i];
    251   }
    252   for (i = 0; i < o->hosted.ndefines; ++i) {
    253     o->cf.defines[o->cf.ndefines++] = o->hosted.defines[i];
    254   }
    255   return 0;
    256 }
    257 
    258 static int dbg_parse_language_name(DbgOpts* o, const char* name,
    259                                    KitLanguage* out) {
    260   KitLanguage lang;
    261   if (!name || !*name || !out) return 0;
    262   lang = kit_frontend_registry_language_for_name(o->frontends, name);
    263   if (lang == KIT_LANG_UNKNOWN) return 0;
    264   *out = lang;
    265   return 1;
    266 }
    267 
    268 static int dbg_set_default_language(DbgOpts* o, const char* name) {
    269   KitLanguage lang = KIT_LANG_AUTO;
    270   if (!dbg_parse_language_name(o, name, &lang)) {
    271     driver_errf(DBG_TOOL, "unsupported language: %.*s",
    272                 KIT_SLICE_ARG(kit_slice_cstr(name)));
    273     return 1;
    274   }
    275   o->default_lang = lang;
    276   o->has_default_lang = 1;
    277   return 0;
    278 }
    279 
    280 static int dbg_script_entry_push(DbgOpts* o, DbgEntryKind kind,
    281                                  const char* value) {
    282   if (o->nscript_entries >= o->script_entries_cap) {
    283     uint32_t nc = o->script_entries_cap ? o->script_entries_cap * 2 : 4;
    284     size_t old_sz = (size_t)o->script_entries_cap * sizeof(DbgScriptEntry);
    285     size_t new_sz = (size_t)nc * sizeof(DbgScriptEntry);
    286     DbgScriptEntry* nb =
    287         o->env->heap->realloc(o->env->heap, o->script_entries, old_sz, new_sz,
    288                               _Alignof(DbgScriptEntry));
    289     if (!nb) {
    290       driver_errf(DBG_TOOL, "out of memory");
    291       return 1;
    292     }
    293     o->script_entries = nb;
    294     o->script_entries_cap = nc;
    295   }
    296   o->script_entries[o->nscript_entries].kind = kind;
    297   o->script_entries[o->nscript_entries].value = value;
    298   o->nscript_entries++;
    299   return 0;
    300 }
    301 
    302 static int dbg_parse(int argc, char** argv, DbgOpts* o) {
    303   int i;
    304   int after_dash_dash = 0;
    305   if (dbg_alloc_arrays(o, argc) != 0) return 1;
    306 
    307   for (i = 1; i < argc; ++i) {
    308     const char* a = argv[i];
    309 
    310     if (after_dash_dash) {
    311       o->prog_argv[o->prog_argc++] = argv[i];
    312       continue;
    313     }
    314     if (driver_streq(a, "--")) {
    315       after_dash_dash = 1;
    316       continue;
    317     }
    318 
    319     {
    320       int r =
    321           driver_cflags_try_consume(&o->cf, o->env, DBG_TOOL, argc, argv, &i);
    322       if (r < 0) return 1;
    323       if (r > 0) continue;
    324     }
    325 
    326     if (driver_streq(a, "-g")) {
    327       o->debug_info = 1;
    328       continue;
    329     }
    330     if (driver_streq(a, "-O0")) {
    331       o->opt_level = 0;
    332       continue;
    333     }
    334     if (driver_streq(a, "-O1")) {
    335       o->opt_level = 1;
    336       continue;
    337     }
    338     if (driver_streq(a, "-O2")) {
    339       o->opt_level = 1;
    340       continue;
    341     }
    342 
    343     if (driver_streq(a, "--sysroot") || driver_streq(a, "-isysroot")) {
    344       if (++i >= argc) {
    345         driver_errf(DBG_TOOL, "%.*s requires an argument",
    346                     KIT_SLICE_ARG(kit_slice_cstr(a)));
    347         return 1;
    348       }
    349       o->sysroot = argv[i];
    350       continue;
    351     }
    352     if (driver_strneq(a, "--sysroot=", 10)) {
    353       o->sysroot = a + 10;
    354       continue;
    355     }
    356     if (driver_streq(a, "-lc")) {
    357       o->wants_hosted_libc = 1;
    358       continue;
    359     }
    360     if (driver_streq(a, "-l")) {
    361       if (++i >= argc) {
    362         driver_errf(DBG_TOOL, "-l requires an argument");
    363         return 1;
    364       }
    365       if (!driver_streq(argv[i], "c")) {
    366         driver_errf(DBG_TOOL, "unsupported hosted library for JIT: -l%.*s",
    367                     KIT_SLICE_ARG(kit_slice_cstr(argv[i])));
    368         return 1;
    369       }
    370       o->wants_hosted_libc = 1;
    371       continue;
    372     }
    373     if (driver_strneq(a, "-l", 2)) {
    374       if (!driver_streq(a + 2, "c")) {
    375         driver_errf(DBG_TOOL, "unsupported hosted library for JIT: %.*s",
    376                     KIT_SLICE_ARG(kit_slice_cstr(a)));
    377         return 1;
    378       }
    379       o->wants_hosted_libc = 1;
    380       continue;
    381     }
    382 
    383     if (driver_streq(a, "-e")) {
    384       if (++i >= argc) {
    385         driver_errf(DBG_TOOL, "-e requires an argument");
    386         return 1;
    387       }
    388       o->entry = argv[i];
    389       continue;
    390     }
    391 
    392     if (driver_streq(a, "-x") || driver_streq(a, "--language") ||
    393         driver_streq(a, "--lang")) {
    394       if (++i >= argc) {
    395         driver_errf(DBG_TOOL, "%.*s requires an argument",
    396                     KIT_SLICE_ARG(kit_slice_cstr(a)));
    397         return 1;
    398       }
    399       if (dbg_set_default_language(o, argv[i]) != 0) return 1;
    400       continue;
    401     }
    402     if (driver_strneq(a, "--language=", 11)) {
    403       if (dbg_set_default_language(o, a + 11) != 0) return 1;
    404       continue;
    405     }
    406     if (driver_strneq(a, "--lang=", 7)) {
    407       if (dbg_set_default_language(o, a + 7) != 0) return 1;
    408       continue;
    409     }
    410 
    411     if (driver_streq(a, "--script")) {
    412       if (++i >= argc) {
    413         driver_errf(DBG_TOOL, "--script requires a FILE argument");
    414         return 1;
    415       }
    416       if (dbg_script_entry_push(o, DBG_ENTRY_FILE, argv[i]) != 0) return 1;
    417       continue;
    418     }
    419     if (driver_streq(a, "--command") || driver_streq(a, "-c")) {
    420       if (++i >= argc) {
    421         driver_errf(DBG_TOOL, "%.*s requires a CMD argument",
    422                     KIT_SLICE_ARG(kit_slice_cstr(a)));
    423         return 1;
    424       }
    425       if (dbg_script_entry_push(o, DBG_ENTRY_CMD, argv[i]) != 0) return 1;
    426       continue;
    427     }
    428     if (driver_streq(a, "--batch")) {
    429       o->batch_mode = 1;
    430       continue;
    431     }
    432 
    433     if (a[0] == '-' && a[1] != '\0') {
    434       driver_errf(DBG_TOOL, "unknown flag: %.*s",
    435                   KIT_SLICE_ARG(kit_slice_cstr(a)));
    436       return 1;
    437     }
    438 
    439     {
    440       int r = driver_inputs_classify(&o->inputs, a);
    441       if (r < 0) return 1;
    442       if (r == 0) {
    443         driver_errf(DBG_TOOL, "input does not have a recognized suffix: %.*s",
    444                     KIT_SLICE_ARG(kit_slice_cstr(a)));
    445         return 1;
    446       }
    447     }
    448   }
    449 
    450   if (dbg_apply_hosted_profile(o) != 0) return 1;
    451   if (!o->entry) o->entry = "main";
    452   if (!o->debug_info) {
    453     /* Without -g there are no source lines or variable locations to
    454      * read at runtime; force it on so `b file:line` and `p name`
    455      * have something to look at. The user can always re-run without
    456      * if they want to inspect optimized code at the asm level. */
    457     o->debug_info = 1;
    458   }
    459   return 0;
    460 }
    461 
    462 static void dbg_options_release(DbgOpts* o) {
    463   size_t bound = o->argv_bound;
    464   driver_hosted_plan_fini(o->env, &o->hosted);
    465   driver_inputs_release(&o->inputs);
    466   driver_cflags_fini(&o->cf, o->env);
    467   driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv));
    468   if (o->script_entries)
    469     driver_free(o->env, o->script_entries,
    470                 (size_t)o->script_entries_cap * sizeof(DbgScriptEntry));
    471   if (o->frontends) kit_frontend_registry_free(o->frontends);
    472 }
    473 
    474 /* Compile every C source through a compiler owned by the caller and JIT-link
    475  * the result. Compiler ownership stays with the caller so DWARF lookups
    476  * during the REPL session can run against the live compiler. */
    477 static int dbg_compile_and_jit(DbgOpts* o, KitCompiler* compiler,
    478                                const KitJitHost* host, KitJit** out_jit) {
    479   KitCCompileOptions copts;
    480   const char* link_entry = driver_inputs_count(&o->inputs) ? o->entry : NULL;
    481   {
    482     KitCCompileOptions z = {0};
    483     copts = z;
    484   }
    485   KitPreprocessOptions pp;
    486   copts.code.opt_level = o->opt_level;
    487   copts.code.debug_info = o->debug_info;
    488   driver_cflags_fill_pp(&o->cf, &pp);
    489   return driver_inputs_compile_and_jit(&o->inputs, compiler, host, &copts, &pp,
    490                                        link_entry, driver_dlsym_resolver, NULL,
    491                                        out_jit);
    492 }
    493 
    494 static void dbg_fill_compile_options(DbgOpts* o, KitCCompileOptions* copts,
    495                                      KitPreprocessOptions* pp) {
    496   {
    497     KitCCompileOptions z = {0};
    498     *copts = z;
    499   }
    500   copts->code.opt_level = o->opt_level;
    501   copts->code.debug_info = o->debug_info;
    502   driver_cflags_fill_pp(&o->cf, pp);
    503 }
    504 
    505 /* ============================================================
    506  * Breakpoint table (driver-side)
    507  * ============================================================
    508  * Each entry pairs a session-side handle (assigned by libkit) with
    509  * driver-side bookkeeping: a stable integer id we expose to the user, the
    510  * spec text the user gave us, and a flag that lets us cheaply re-arm
    511  * temp-disabled breakpoints. */
    512 
    513 typedef enum BpKind {
    514   BP_ADDR,
    515   BP_SYM,
    516   BP_LINE,
    517 } BpKind;
    518 
    519 typedef struct Bp {
    520   int id; /* user-facing handle, 1.. */
    521   int enabled;
    522   BpKind kind;
    523   char* spec; /* heap-owned NUL-terminated copy */
    524   size_t spec_size;
    525   uint64_t addr;
    526   uint64_t skip_count; /* silent skips before the first stop  */
    527   uint64_t max_hits;   /* 0 = unlimited                       */
    528   uint32_t session_id; /* libkit handle; 0 when disarmed */
    529 } Bp;
    530 
    531 typedef struct DbgSource {
    532   char* name;
    533   size_t name_size;
    534   uint8_t* data;
    535   size_t data_size;
    536   size_t len;
    537 } DbgSource;
    538 
    539 /* ============================================================
    540  * Session-scoped state
    541  * ============================================================ */
    542 
    543 typedef struct DbgState {
    544   DriverEnv* env;
    545   KitCompiler* compiler;
    546   KitContext ctx;
    547   KitCCompileOptions copts;
    548   KitPreprocessOptions pp; /* preprocessor settings for REPL compiles */
    549   KitJit* jit;
    550   KitDebugSession* session;
    551   const KitObjFile* view;
    552   KitDebugInfo* dwarf;
    553   KitWriter* fmt_writer; /* lazily-created stdout writer for kit_dbg_value_format */
    554   void* entry_addr;
    555   const char* entry_name;
    556   KitLanguage default_jit_lang;
    557   const char* default_jit_name;
    558   /* Backing storage for default_jit_name (built from the canonical frontend
    559    * extension), kept stable for the session's lifetime. */
    560   char default_jit_name_buf[32];
    561   KitCompileSession** compile_sessions;
    562   uint32_t ncompile_sessions;
    563   uint32_t compile_sessions_cap;
    564   DbgSource* sources;
    565   uint32_t nsources;
    566   uint32_t sources_cap;
    567   int prog_argc;
    568   char** prog_argv;
    569 
    570   Bp* bps;
    571   uint32_t nbps;
    572   uint32_t bps_cap;
    573   int next_bp_id;
    574   uint64_t
    575       expr_counter; /* user-visible $N; advances only on a successful expr */
    576   uint64_t expr_attempt; /* unique thunk-symbol id; advances every attempt */
    577   uint64_t source_counter;
    578 
    579   int has_stop;
    580   KitStopInfo last_stop;
    581   uint64_t jit_counter;
    582   DriverLineHistory line_history;
    583 
    584   DbgScriptEntry* script_entries; /* transferred from DbgOpts */
    585   uint32_t nscript_entries;
    586   int batch_mode;
    587   int error_count;
    588   int script_failed;
    589 } DbgState;
    590 
    591 /* Like driver_errf but increments s->error_count so --batch can propagate
    592  * command failures to the exit code. */
    593 #define dbg_errf(s, ...) \
    594   (driver_errf(DBG_TOOL, __VA_ARGS__), (void)((s)->error_count++))
    595 
    596 #define DBG_LIST_CTX 5 /* lines printed before/after the target */
    597 
    598 /* SIGINT trampoline. The handler in env.c calls our cb with this state;
    599  * we forward into the session. kit_dbg_session_interrupt is documented
    600  * async-signal-safe. */
    601 static void dbg_on_sigint(void* user) {
    602   DbgState* s = (DbgState*)user;
    603   if (s && s->session) kit_dbg_session_interrupt(s->session);
    604 }
    605 
    606 /* PC-space translation between the JIT runtime address space (where
    607  * SIGTRAP fires and where the debugger installs breakpoints) and the
    608  * image-relative vaddr space DWARF was authored in.  Every DWARF call
    609  * that takes a PC consumes an image vaddr, and every DWARF result
    610  * that names a code address is image-relative — translate at the
    611  * boundary.  Fallback is pass-through so out-of-image PCs (e.g.
    612  * stops inside libc on a future multi-input setup) don't return 0
    613  * and silently degrade lookups. */
    614 static uint64_t dbg_pc_rt_to_img(DbgState* s, uint64_t rt) {
    615   uint64_t v = kit_jit_runtime_to_image(s->jit, rt);
    616   return v ? v : rt;
    617 }
    618 static uint64_t dbg_pc_img_to_rt(DbgState* s, uint64_t img) {
    619   uint64_t v = kit_jit_image_to_runtime(s->jit, img);
    620   return v ? v : img;
    621 }
    622 /* ============================================================
    623  * Tiny driver-local string utilities
    624  * ============================================================
    625  * The driver TU may use plain libc <string.h>/<ctype.h> per the project
    626  * rules (no syscalls). We avoid pulling in libc here only because the
    627  * existing driver shims cover everything we need. */
    628 
    629 static int dbg_isspace(int c) {
    630   return c == ' ' || c == '\t' || c == '\r' || c == '\n';
    631 }
    632 static int dbg_isdigit(int c) { return c >= '0' && c <= '9'; }
    633 static int dbg_isxdigit(int c) {
    634   return dbg_isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
    635 }
    636 
    637 static int dbg_xval(int c) {
    638   if (dbg_isdigit(c)) return c - '0';
    639   if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    640   return c - 'A' + 10;
    641 }
    642 
    643 static char* dbg_dup(DriverEnv* env, const char* s, size_t n,
    644                      size_t* size_out) {
    645   char* p = driver_alloc(env, n + 1);
    646   if (!p) return NULL;
    647   driver_memcpy(p, s, n);
    648   p[n] = '\0';
    649   if (size_out) *size_out = n + 1;
    650   return p;
    651 }
    652 
    653 /* Parse a 0x-prefixed hex literal or a decimal literal into *out. Returns
    654  * the number of characters consumed, or 0 on failure. */
    655 static size_t dbg_parse_uint(const char* s, uint64_t* out) {
    656   size_t i = 0;
    657   uint64_t v = 0;
    658   if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
    659     i = 2;
    660     if (!dbg_isxdigit((unsigned char)s[i])) return 0;
    661     for (; dbg_isxdigit((unsigned char)s[i]); ++i) {
    662       v = (v << 4) | (uint64_t)dbg_xval((unsigned char)s[i]);
    663     }
    664   } else {
    665     if (!dbg_isdigit((unsigned char)s[0])) return 0;
    666     for (; dbg_isdigit((unsigned char)s[i]); ++i) {
    667       v = v * 10 + (uint64_t)(s[i] - '0');
    668     }
    669   }
    670   *out = v;
    671   return i;
    672 }
    673 
    674 static size_t dbg_u64_dec(char* dst, size_t cap, uint64_t v);
    675 
    676 /* ============================================================
    677  * Breakpoint table operations
    678  * ============================================================ */
    679 
    680 static Bp* dbg_bp_grow(DbgState* s) {
    681   uint32_t nc;
    682   size_t old_size, new_size;
    683   Bp* nb;
    684   if (s->nbps < s->bps_cap) return &s->bps[s->nbps];
    685 
    686   nc = s->bps_cap ? s->bps_cap * 2 : 8;
    687   old_size = (size_t)s->bps_cap * sizeof(Bp);
    688   new_size = (size_t)nc * sizeof(Bp);
    689   nb = s->env->heap->realloc(s->env->heap, s->bps, old_size, new_size,
    690                              _Alignof(Bp));
    691   if (!nb) {
    692     dbg_errf(s, "out of memory growing breakpoint table");
    693     return NULL;
    694   }
    695   /* Zero the freshly grown tail so future driver_free walks it cleanly. */
    696   {
    697     char* z = (char*)nb + old_size;
    698     size_t n = new_size - old_size;
    699     size_t j;
    700     for (j = 0; j < n; ++j) z[j] = 0;
    701   }
    702   s->bps = nb;
    703   s->bps_cap = nc;
    704   return &s->bps[s->nbps];
    705 }
    706 
    707 static Bp* dbg_bp_find(DbgState* s, int id) {
    708   uint32_t i;
    709   for (i = 0; i < s->nbps; ++i) {
    710     if (s->bps[i].id == id) return &s->bps[i];
    711   }
    712   return NULL;
    713 }
    714 
    715 static void dbg_bp_release(DbgState* s, Bp* b) {
    716   if (b->session_id) {
    717     kit_dbg_session_breakpoint_clear(s->session, b->session_id);
    718     b->session_id = 0;
    719   }
    720   if (b->spec) {
    721     driver_free(s->env, b->spec, b->spec_size);
    722     b->spec = NULL;
    723     b->spec_size = 0;
    724   }
    725 }
    726 
    727 static int dbg_bp_remove(DbgState* s, int id) {
    728   uint32_t i;
    729   for (i = 0; i < s->nbps; ++i) {
    730     if (s->bps[i].id == id) {
    731       dbg_bp_release(s, &s->bps[i]);
    732       /* Shift tail down to keep the array dense; ids stay stable. */
    733       {
    734         uint32_t j;
    735         for (j = i + 1; j < s->nbps; ++j) s->bps[j - 1] = s->bps[j];
    736       }
    737       s->nbps--;
    738       {
    739         Bp z = {0};
    740         s->bps[s->nbps] = z;
    741       }
    742       return 0;
    743     }
    744   }
    745   return 1;
    746 }
    747 
    748 static void dbg_bps_release_all(DbgState* s) {
    749   uint32_t i;
    750   for (i = 0; i < s->nbps; ++i) dbg_bp_release(s, &s->bps[i]);
    751   if (s->bps) {
    752     driver_free(s->env, s->bps, (size_t)s->bps_cap * sizeof(Bp));
    753     s->bps = NULL;
    754     s->bps_cap = 0;
    755     s->nbps = 0;
    756   }
    757 }
    758 
    759 static void dbg_compile_sessions_release(DbgState* s) {
    760   uint32_t i;
    761   for (i = 0; i < s->ncompile_sessions; ++i) {
    762     kit_compile_session_free(s->compile_sessions[i]);
    763     s->compile_sessions[i] = NULL;
    764   }
    765   if (s->compile_sessions) {
    766     driver_free(s->env, s->compile_sessions,
    767                 (size_t)s->compile_sessions_cap *
    768                     sizeof(*s->compile_sessions));
    769     s->compile_sessions = NULL;
    770   }
    771   s->ncompile_sessions = 0;
    772   s->compile_sessions_cap = 0;
    773 }
    774 
    775 static void dbg_sources_release_all(DbgState* s) {
    776   uint32_t i;
    777   for (i = 0; i < s->nsources; ++i) {
    778     DbgSource* src = &s->sources[i];
    779     if (src->name) driver_free(s->env, src->name, src->name_size);
    780     if (src->data) driver_free(s->env, src->data, src->data_size);
    781   }
    782   if (s->sources) {
    783     driver_free(s->env, s->sources,
    784                 (size_t)s->sources_cap * sizeof(*s->sources));
    785     s->sources = NULL;
    786   }
    787   s->nsources = 0;
    788   s->sources_cap = 0;
    789 }
    790 
    791 /* ============================================================
    792  * LOC parser
    793  * ============================================================
    794  * Resolves a user-supplied location specification into a single address
    795  * within the JIT image:
    796  *
    797  *   0xADDR              raw address
    798  *   sym[+0xN | +N]      symbol via kit_jit_lookup, optional offset
    799  *   file.c:LINE         DWARF lookup
    800  *
    801  * Returns 0 on success, 1 on parse / resolution failure (already
    802  * reported via driver_errf). */
    803 
    804 static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out,
    805                            uint64_t* addr_out) {
    806   /* file:line — uniquely identifiable by a colon NOT preceded by + and
    807    * followed by digits. We require the suffix after ':' to be all
    808    * digits so we don't confuse, say, hypothetical `func:42` (which
    809    * isn't a thing in C). */
    810   const char* colon = driver_strchr(spec, ':');
    811   if (colon && dbg_isdigit((unsigned char)colon[1])) {
    812     size_t file_n = (size_t)(colon - spec);
    813     char* file;
    814     uint64_t line64;
    815     size_t used;
    816     size_t file_size;
    817     uint64_t pc;
    818 
    819     if (file_n == 0) {
    820       dbg_errf(s, "empty file in '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec)));
    821       return 1;
    822     }
    823     file = dbg_dup(s->env, spec, file_n, &file_size);
    824     if (!file) {
    825       dbg_errf(s, "out of memory");
    826       return 1;
    827     }
    828     used = dbg_parse_uint(colon + 1, &line64);
    829     if (!used || colon[1 + used] != '\0') {
    830       dbg_errf(s, "expected file.c:LINE, got '%.*s'",
    831                KIT_SLICE_ARG(kit_slice_cstr(spec)));
    832       driver_free(s->env, file, file_size);
    833       return 1;
    834     }
    835     if (!s->dwarf) {
    836       dbg_errf(s, "no DWARF: cannot resolve %.*s",
    837                KIT_SLICE_ARG(kit_slice_cstr(spec)));
    838       driver_free(s->env, file, file_size);
    839       return 1;
    840     }
    841     {
    842       KitStatus rc = kit_dwarf_line_to_addr(s->dwarf, kit_slice_cstr(file),
    843                                             (uint32_t)line64, &pc);
    844       if (rc == KIT_NOT_FOUND) {
    845         dbg_errf(s, "no line %u in %.*s", (uint32_t)line64,
    846                  KIT_SLICE_ARG(kit_slice_cstr(file)));
    847         driver_free(s->env, file, file_size);
    848         return 1;
    849       }
    850       if (rc == KIT_AMBIGUOUS) {
    851         KitDwarfLineMatch cands[8];
    852         uint32_t n = 0;
    853         uint32_t k;
    854         kit_dwarf_line_to_addr_all(s->dwarf, kit_slice_cstr(file),
    855                                    (uint32_t)line64, cands, 8u, &n);
    856         dbg_errf(s, "ambiguous: %.*s:%u matches %u files",
    857                  KIT_SLICE_ARG(kit_slice_cstr(file)), (uint32_t)line64,
    858                  (unsigned)n);
    859         for (k = 0; k < n && k < 8u; ++k) {
    860           dbg_errf(s, "  %.*s (0x%llx)", KIT_SLICE_ARG(cands[k].file),
    861                    (unsigned long long)cands[k].pc);
    862         }
    863         if (n > 8u) dbg_errf(s, "  ... and %u more", n - 8u);
    864         dbg_errf(s, "use a longer path suffix (e.g. b dir/%.*s:%u)",
    865                  KIT_SLICE_ARG(kit_slice_cstr(file)), (uint32_t)line64);
    866         driver_free(s->env, file, file_size);
    867         return 1;
    868       }
    869       if (rc != KIT_OK) {
    870         dbg_errf(s, "no line entry for %.*s",
    871                  KIT_SLICE_ARG(kit_slice_cstr(spec)));
    872         driver_free(s->env, file, file_size);
    873         return 1;
    874       }
    875     }
    876     driver_free(s->env, file, file_size);
    877     *kind_out = BP_LINE;
    878     *addr_out = dbg_pc_img_to_rt(s, pc);
    879     return 0;
    880   }
    881 
    882   /* 0xADDR or decimal address. */
    883   if ((spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X')) ||
    884       dbg_isdigit((unsigned char)spec[0])) {
    885     uint64_t v;
    886     size_t used = dbg_parse_uint(spec, &v);
    887     if (!used || spec[used] != '\0') {
    888       dbg_errf(s, "trailing junk in address '%.*s'",
    889                KIT_SLICE_ARG(kit_slice_cstr(spec)));
    890       return 1;
    891     }
    892     *kind_out = BP_ADDR;
    893     *addr_out = v;
    894     return 0;
    895   }
    896 
    897   /* sym[+off] */
    898   {
    899     const char* plus = driver_strchr(spec, '+');
    900     size_t name_n = plus ? (size_t)(plus - spec) : driver_strlen(spec);
    901     char* name;
    902     size_t name_size;
    903     void* resolved;
    904     uint64_t off = 0;
    905 
    906     if (name_n == 0) {
    907       dbg_errf(s, "empty symbol in '%.*s'",
    908                KIT_SLICE_ARG(kit_slice_cstr(spec)));
    909       return 1;
    910     }
    911     name = dbg_dup(s->env, spec, name_n, &name_size);
    912     if (!name) {
    913       dbg_errf(s, "out of memory");
    914       return 1;
    915     }
    916     resolved = kit_jit_lookup(s->jit, kit_slice_cstr(name));
    917     if (!resolved) {
    918       dbg_errf(s, "symbol not found: %.*s",
    919                KIT_SLICE_ARG(kit_slice_cstr(name)));
    920       driver_free(s->env, name, name_size);
    921       return 1;
    922     }
    923     driver_free(s->env, name, name_size);
    924 
    925     if (plus) {
    926       size_t used = dbg_parse_uint(plus + 1, &off);
    927       if (!used || plus[1 + used] != '\0') {
    928         dbg_errf(s, "bad offset in '%.*s'",
    929                  KIT_SLICE_ARG(kit_slice_cstr(spec)));
    930         return 1;
    931       }
    932     }
    933 
    934     *kind_out = BP_SYM;
    935     /* Object-pointer to integer cast: implementation-defined in
    936      * standard C, defined as the address on every host where a JIT
    937      * runs. */
    938     {
    939       union {
    940         void* p;
    941         uint64_t u;
    942       } cv;
    943       cv.p = resolved;
    944       *addr_out = cv.u + off;
    945     }
    946     return 0;
    947   }
    948 }
    949 
    950 /* ============================================================
    951  * Stop rendering
    952  * ============================================================ */
    953 
    954 static void dbg_print_pc(DbgState* s, uint64_t pc) {
    955   KitSlice sym = KIT_SLICE_NULL;
    956   uint64_t off = 0;
    957   KitSlice file = KIT_SLICE_NULL;
    958   uint32_t line = 0;
    959   uint32_t col = 0;
    960 
    961   driver_printf("0x%llx", (unsigned long long)pc);
    962   if (kit_jit_addr_to_sym(s->jit, pc, &sym, &off) == KIT_OK && sym.s) {
    963     if (off)
    964       driver_printf(" <%.*s+0x%llx>", KIT_SLICE_ARG(sym),
    965                     (unsigned long long)off);
    966     else
    967       driver_printf(" <%.*s>", KIT_SLICE_ARG(sym));
    968   }
    969   if (s->dwarf &&
    970       kit_dwarf_addr_to_line(s->dwarf, dbg_pc_rt_to_img(s, pc), &file, &line,
    971                              &col) == KIT_OK &&
    972       file.s) {
    973     driver_printf(" at %.*s:%u", KIT_SLICE_ARG(file), line);
    974     if (col) driver_printf(":%u", col);
    975   }
    976 }
    977 
    978 static void dbg_print_source_listing(DbgState* s, KitSlice file, uint32_t line,
    979                                      uint64_t pc, int report_errors);
    980 
    981 static KitSlice dbg_step_stop_label(KitStopReason reason) {
    982   switch (reason) {
    983     case KIT_STOP_REASON_STEP_INSN:
    984       return KIT_SLICE_LIT("Single-step complete at ");
    985     case KIT_STOP_REASON_STEP_LINE:
    986       return KIT_SLICE_LIT("Step complete at ");
    987     case KIT_STOP_REASON_NEXT_LINE:
    988       return KIT_SLICE_LIT("Next complete at ");
    989     case KIT_STOP_REASON_STEP_OUT:
    990       return KIT_SLICE_LIT("Finish complete at ");
    991     case KIT_STOP_REASON_UNKNOWN:
    992     case KIT_STOP_REASON_USER_BREAKPOINT:
    993     case KIT_STOP_REASON_SIGNAL:
    994     case KIT_STOP_REASON_TRAP:
    995     case KIT_STOP_REASON_INTERRUPT:
    996     case KIT_STOP_REASON_EXIT:
    997       break;
    998   }
    999   return KIT_SLICE_LIT("Internal breakpoint hit at ");
   1000 }
   1001 
   1002 static void dbg_cmd_bt(DbgState* s);
   1003 
   1004 static void dbg_render_stop(DbgState* s, const KitStopInfo* st) {
   1005   KitSlice file = {0};
   1006   uint32_t line = 0;
   1007   uint32_t col = 0;
   1008   int has_source = 0;
   1009 
   1010   if (st->kind != KIT_STOP_EXIT && s->dwarf &&
   1011       kit_dwarf_addr_to_line(s->dwarf, dbg_pc_rt_to_img(s, st->regs.pc), &file,
   1012                              &line, &col) == KIT_OK &&
   1013       file.s) {
   1014     has_source = 1;
   1015   }
   1016 
   1017   switch (st->kind) {
   1018     case KIT_STOP_BREAKPOINT: {
   1019       Bp* b = NULL;
   1020       uint32_t i;
   1021       for (i = 0; i < s->nbps; ++i) {
   1022         if (s->bps[i].session_id == st->bp_id) {
   1023           b = &s->bps[i];
   1024           break;
   1025         }
   1026       }
   1027       if (b)
   1028         driver_printf("Breakpoint %d (%.*s) hit at ", b->id,
   1029                       KIT_SLICE_ARG(kit_slice_cstr(b->spec)));
   1030       else {
   1031         KitSlice label = dbg_step_stop_label(st->reason);
   1032         driver_printf("%.*s", KIT_SLICE_ARG(label));
   1033       }
   1034       dbg_print_pc(s, st->regs.pc);
   1035       driver_printf("\n");
   1036       break;
   1037     }
   1038     case KIT_STOP_SIGNAL:
   1039       if (st->reason == KIT_STOP_REASON_TRAP)
   1040         driver_printf("Stopped on trap signal %d at ", st->signal);
   1041       else
   1042         driver_printf("Stopped on signal %d at ", st->signal);
   1043       dbg_print_pc(s, st->regs.pc);
   1044       driver_printf("\n");
   1045       /* A fault/trap is a crash, not a planned stop: print the backtrace
   1046        * automatically (the user would type `bt` next anyway). Breakpoints and
   1047        * step completions are separate stop kinds, so this only fires on a real
   1048        * signal or __builtin_trap/assert. */
   1049       if (s->dwarf) dbg_cmd_bt(s);
   1050       break;
   1051     case KIT_STOP_INTERRUPT:
   1052       driver_printf("Interrupted at ");
   1053       dbg_print_pc(s, st->regs.pc);
   1054       driver_printf("\n");
   1055       break;
   1056     case KIT_STOP_EXIT:
   1057       driver_printf("Program exited with code %d\n", st->exit_code);
   1058       break;
   1059   }
   1060   if (has_source)
   1061     dbg_print_source_listing(s, file, line, dbg_pc_rt_to_img(s, st->regs.pc),
   1062                              0);
   1063 }
   1064 
   1065 /* ============================================================
   1066  * Run / continue / step
   1067  * ============================================================
   1068  * Both `r` and `c` flow through the same wrapper: install SIGINT, drive
   1069  * the session (call or resume), restore SIGINT, render the stop. The
   1070  * dbg owns no signal-handling complexity itself — that's the session's
   1071  * job. */
   1072 
   1073 typedef enum DbgRunMode {
   1074   RUN_FRESH,     /* r       — _call from entry         */
   1075   RUN_CONTINUE,  /* c       — _resume(continue)        */
   1076   RUN_STEP_INSN, /* si      — _resume(step_insn)       */
   1077   RUN_STEP_LINE, /* s       — _resume(step_line)       */
   1078   RUN_NEXT_LINE, /* n       — _resume(next_line)       */
   1079   RUN_STEP_OUT,  /* finish  — _resume(step_out)        */
   1080 } DbgRunMode;
   1081 
   1082 static int dbg_drive(DbgState* s, DbgRunMode mode) {
   1083   KitStatus rc;
   1084 
   1085   if (mode == RUN_FRESH && s->has_stop) {
   1086     /* The previous session is dead (entry returned or signal landed).
   1087      * Start a new one. Try to abort it first in case it's parked in a fault. */
   1088     if (s->session) {
   1089       kit_dbg_session_resume(s->session, KIT_RESUME_ABORT, NULL);
   1090     }
   1091     s->has_stop = 0;
   1092   } else if (mode != RUN_FRESH && !s->has_stop) {
   1093     dbg_errf(s, "no program is running; use 'r' to start");
   1094     return 1;
   1095   }
   1096 
   1097   if (mode == RUN_FRESH && !s->entry_addr) {
   1098     if (!s->entry_name || !*s->entry_name) {
   1099       dbg_errf(s, "no entry symbol configured");
   1100       return 1;
   1101     }
   1102     s->entry_addr = kit_jit_lookup(s->jit, kit_slice_cstr(s->entry_name));
   1103     if (!s->entry_addr) {
   1104       dbg_errf(s, "entry symbol not found: %.*s",
   1105                KIT_SLICE_ARG(kit_slice_cstr(s->entry_name)));
   1106       return 1;
   1107     }
   1108   }
   1109 
   1110   if (driver_install_sigint(dbg_on_sigint, s) != 0) {
   1111     dbg_errf(s, "failed to install SIGINT handler");
   1112     return 1;
   1113   }
   1114 
   1115   if (mode == RUN_FRESH) {
   1116     rc = kit_dbg_session_call(s->session, s->entry_addr, KIT_ENTRY_INT_ARGV,
   1117                               s->prog_argc, s->prog_argv, &s->last_stop);
   1118   } else {
   1119     KitResumeMode rm = KIT_RESUME_CONTINUE;
   1120     switch (mode) {
   1121       case RUN_STEP_INSN:
   1122         rm = KIT_RESUME_STEP_INSN;
   1123         break;
   1124       case RUN_STEP_LINE:
   1125         rm = KIT_RESUME_STEP_LINE;
   1126         break;
   1127       case RUN_NEXT_LINE:
   1128         rm = KIT_RESUME_NEXT_LINE;
   1129         break;
   1130       case RUN_STEP_OUT:
   1131         rm = KIT_RESUME_STEP_OUT;
   1132         break;
   1133       case RUN_CONTINUE:
   1134         rm = KIT_RESUME_CONTINUE;
   1135         break;
   1136       case RUN_FRESH:
   1137         break; /* unreachable */
   1138     }
   1139     rc = kit_dbg_session_resume(s->session, rm, &s->last_stop);
   1140   }
   1141 
   1142   driver_restore_sigint();
   1143 
   1144   if (rc != KIT_OK) {
   1145     driver_errf(
   1146         DBG_TOOL,
   1147         "session %.*s failed (st=%d) — "
   1148         "JIT session implementation pending",
   1149         KIT_SLICE_ARG(kit_slice_cstr(mode == RUN_FRESH ? "call" : "resume")),
   1150         (int)rc);
   1151     return 1;
   1152   }
   1153 
   1154   s->has_stop = 1;
   1155   dbg_render_stop(s, &s->last_stop);
   1156   if (s->last_stop.kind == KIT_STOP_EXIT) s->has_stop = 0;
   1157   return 0;
   1158 }
   1159 
   1160 /* Render a materialized debugger value to stdout via the public formatter.
   1161  * The stdout writer shares libc's stdout buffer with driver_printf, so a
   1162  * "name = " prefix and the value text interleave in call order. */
   1163 static void dbg_emit_value(DbgState* s, const KitDebugValue* v, int indent) {
   1164   KitDebugFormatOptions fo = {.indent = (uint32_t)indent};
   1165   if (!s->fmt_writer) s->fmt_writer = driver_stdout_writer(s->env);
   1166   if (s->fmt_writer) kit_dbg_value_format(s->session, v, s->fmt_writer, &fo);
   1167 }
   1168 static char* dbg_take_word(char* line, char** word_out);
   1169 
   1170 /* ============================================================
   1171  * Backtrace
   1172  * ============================================================
   1173  * Renders one line per frame:
   1174  *   #N 0xPC <sym+off> in func (arg1=val1, arg2=val2) at file:line
   1175  * Parameter rendering uses kit_dwarf_param_iter_* against the frame's
   1176  * PC and the unwound register snapshot. Inlined frames are flagged. */
   1177 
   1178 static void dbg_cmd_bt(DbgState* s) {
   1179   KitDebugBacktrace* bt = NULL;
   1180   uint32_t i, n;
   1181 
   1182   if (!s->has_stop) {
   1183     dbg_errf(s, "no program is stopped");
   1184     return;
   1185   }
   1186   if (!s->dwarf) {
   1187     dbg_errf(s, "no DWARF: backtrace unavailable");
   1188     return;
   1189   }
   1190   if (kit_dbg_backtrace_new(s->session, NULL, &bt) != KIT_OK) {
   1191     dbg_errf(s, "backtrace unavailable");
   1192     return;
   1193   }
   1194 
   1195   n = kit_dbg_backtrace_count(bt);
   1196   for (i = 0; i < n; ++i) {
   1197     KitDebugFrame f;
   1198     if (kit_dbg_backtrace_frame(bt, i, &f) != KIT_OK) break;
   1199 
   1200     driver_printf("#%-2d 0x%llx", (int)i, (unsigned long long)f.pc);
   1201     if (f.sym.s) {
   1202       if (f.sym_offset)
   1203         driver_printf(" <%.*s+0x%llx>", KIT_SLICE_ARG(f.sym),
   1204                       (unsigned long long)f.sym_offset);
   1205       else
   1206         driver_printf(" <%.*s>", KIT_SLICE_ARG(f.sym));
   1207     }
   1208 
   1209     /* DWARF subprogram name + argument values for this frame. Args come from
   1210      * the session's symbolic layer against the frame's own register snapshot. */
   1211     if (f.func.s) {
   1212       KitDebugVarIter* it = NULL;
   1213       KitSlice an;
   1214       KitDebugValue av;
   1215       int first = 1;
   1216       driver_printf(" in %.*s%.*s (", KIT_SLICE_ARG(f.func),
   1217                     KIT_SLICE_ARG(f.inlined ? KIT_SLICE_LIT(" [inlined]")
   1218                                             : KIT_SLICE_NULL));
   1219       if (kit_dbg_vars_new(s->session, &f.regs, KIT_DVRM_ARG, &it) == KIT_OK) {
   1220         for (;;) {
   1221           KitIterResult r = kit_dbg_vars_next(it, &an, &av);
   1222           if (r != KIT_ITER_ITEM) break;
   1223           if (!first) driver_printf(", ");
   1224           driver_printf("%.*s=",
   1225                         KIT_SLICE_ARG(an.s ? an : KIT_SLICE_LIT("?")));
   1226           if (av.bytes)
   1227             dbg_emit_value(s, &av, 0);
   1228           else
   1229             driver_printf("?");
   1230           first = 0;
   1231         }
   1232         kit_dbg_vars_free(it);
   1233       }
   1234       driver_printf(")");
   1235     }
   1236 
   1237     if (f.file.s) {
   1238       driver_printf(" at %.*s:%u", KIT_SLICE_ARG(f.file), f.line);
   1239       if (f.col) driver_printf(":%u", f.col);
   1240     } else {
   1241       driver_printf(" [no debug info for this frame]");
   1242     }
   1243     driver_printf("\n");
   1244   }
   1245   kit_dbg_backtrace_free(bt);
   1246 }
   1247 
   1248 
   1249 /* ============================================================
   1250  * `p name`
   1251  * ============================================================ */
   1252 
   1253 static void dbg_cmd_print(DbgState* s, const char* name) {
   1254   KitSlice nm = kit_slice_cstr(name);
   1255   KitDebugValue val;
   1256   KitStatus rc;
   1257 
   1258   if (!s->has_stop) {
   1259     dbg_errf(s, "no program is stopped");
   1260     return;
   1261   }
   1262 
   1263   rc = kit_dbg_var_read(s->session, NULL, nm, &val);
   1264   if (rc == KIT_OK) {
   1265     if (val.type) {
   1266       driver_printf("%.*s = ", KIT_SLICE_ARG(nm));
   1267       dbg_emit_value(s, &val, 0);
   1268       driver_printf("\n");
   1269     } else {
   1270       /* Resolved a JIT global symbol but no DWARF type — show its address. */
   1271       uint64_t addr = 0;
   1272       kit_dbg_value_as_u64(&val, &addr);
   1273       driver_printf("%.*s = 0x%llx (no DWARF type info)\n", KIT_SLICE_ARG(nm),
   1274                     (unsigned long long)addr);
   1275     }
   1276     return;
   1277   }
   1278   if (rc == KIT_NOT_FOUND) {
   1279     dbg_errf(s, "no variable or symbol named '%.*s'", KIT_SLICE_ARG(nm));
   1280     return;
   1281   }
   1282   dbg_errf(s, "could not read %.*s", KIT_SLICE_ARG(nm));
   1283 }
   1284 
   1285 /* ============================================================
   1286  * `set NAME VALUE`
   1287  * ============================================================
   1288  * Writes a 64-bit value into a variable. Routes to write_mem for
   1289  * frame-relative and global locations, set_regs for register-resident
   1290  * variables. v1 supports integer/pointer scalars only — float and
   1291  * aggregate writes are out of scope. */
   1292 
   1293 static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) {
   1294   KitSlice nm = kit_slice_cstr(name);
   1295   KitStatus rc;
   1296 
   1297   if (!s->has_stop) {
   1298     dbg_errf(s, "no program is stopped");
   1299     return;
   1300   }
   1301   rc = kit_dbg_var_write_u64(s->session, NULL, nm, value);
   1302   switch (rc) {
   1303     case KIT_OK:
   1304       /* Refresh the cached stop registers so a register write is visible to
   1305        * subsequent reads. */
   1306       kit_dbg_session_get_regs(s->session, &s->last_stop.regs);
   1307       return;
   1308     case KIT_NOT_FOUND:
   1309       dbg_errf(s, "no variable named '%.*s'", KIT_SLICE_ARG(nm));
   1310       return;
   1311     case KIT_UNSUPPORTED:
   1312       dbg_errf(s, "cannot set '%.*s': location is a DWARF expression",
   1313                KIT_SLICE_ARG(nm));
   1314       return;
   1315     default:
   1316       dbg_errf(s, "memory write failed");
   1317       return;
   1318   }
   1319 }
   1320 
   1321 /* ============================================================
   1322  * `jump ADDR`
   1323  * ============================================================
   1324  * Move PC without resuming. The session validates that the new PC lies
   1325  * within the JIT image. */
   1326 
   1327 static void dbg_cmd_jump(DbgState* s, uint64_t pc) {
   1328   KitUnwindFrame fr;
   1329   if (!s->has_stop) {
   1330     dbg_errf(s, "no program is stopped");
   1331     return;
   1332   }
   1333   fr = s->last_stop.regs;
   1334   fr.pc = pc;
   1335   if (kit_dbg_session_set_regs(s->session, &fr) != 0) {
   1336     dbg_errf(s, "jump failed (pc 0x%llx outside image?)",
   1337              (unsigned long long)pc);
   1338     return;
   1339   }
   1340   s->last_stop.regs = fr;
   1341   driver_printf("PC set to 0x%llx\n", (unsigned long long)pc);
   1342 }
   1343 
   1344 /* ============================================================
   1345  * `info locals` / `info args` / `info reg`
   1346  * ============================================================ */
   1347 
   1348 static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) {
   1349   KitDebugVarIter* it = NULL;
   1350   KitSlice nm;
   1351   KitDebugValue val;
   1352   int printed = 0;
   1353 
   1354   if (!s->has_stop) {
   1355     dbg_errf(s, "no program is stopped");
   1356     return;
   1357   }
   1358   if (!s->dwarf) {
   1359     dbg_errf(s, "no DWARF: %.*s unavailable",
   1360              KIT_SLICE_ARG(kit_slice_cstr(label)));
   1361     return;
   1362   }
   1363 
   1364   if (kit_dbg_vars_new(s->session, NULL, mask, &it) != KIT_OK) {
   1365     driver_printf("No %.*s.\n", KIT_SLICE_ARG(kit_slice_cstr(label)));
   1366     return;
   1367   }
   1368   for (;;) {
   1369     KitIterResult r = kit_dbg_vars_next(it, &nm, &val);
   1370     if (r != KIT_ITER_ITEM) break;
   1371     printed = 1;
   1372     if (!val.bytes) {
   1373       driver_printf("  %.*s = <unreadable>\n", KIT_SLICE_ARG(nm));
   1374       continue;
   1375     }
   1376     driver_printf("  %.*s = ", KIT_SLICE_ARG(nm));
   1377     dbg_emit_value(s, &val, 1);
   1378     driver_printf("\n");
   1379   }
   1380   kit_dbg_vars_free(it);
   1381   if (!printed)
   1382     driver_printf("No %.*s.\n", KIT_SLICE_ARG(kit_slice_cstr(label)));
   1383 }
   1384 
   1385 static void dbg_cmd_info_reg(DbgState* s) {
   1386   KitArchKind arch = driver_host_target().arch;
   1387   uint32_t n = kit_arch_register_count(arch);
   1388   uint32_t i;
   1389 
   1390   if (!s->has_stop) {
   1391     dbg_errf(s, "no program is stopped");
   1392     return;
   1393   }
   1394   if (n == 0) {
   1395     dbg_errf(s, "no register table for this arch");
   1396     return;
   1397   }
   1398   driver_printf("pc     0x%016llx\n", (unsigned long long)s->last_stop.regs.pc);
   1399   driver_printf("cfa    0x%016llx\n",
   1400                 (unsigned long long)s->last_stop.regs.cfa);
   1401   for (i = 0; i < n; ++i) {
   1402     KitArchReg r;
   1403     if (kit_arch_register_at(arch, i, &r) != KIT_OK) continue;
   1404     if (r.dwarf_idx >= 32) continue; /* outside KitUnwindFrame.regs */
   1405     driver_printf("%-6.*s 0x%016llx\n", KIT_SLICE_ARG(r.name),
   1406                   (unsigned long long)s->last_stop.regs.regs[r.dwarf_idx]);
   1407   }
   1408 }
   1409 
   1410 /* ============================================================
   1411  * `info functions [PATTERN]` / `info variables [PATTERN]`
   1412  * ============================================================ */
   1413 
   1414 /* Tiny glob matcher: '*' matches any run, '?' matches any single byte.
   1415  * NULL pattern matches every name. */
   1416 static int dbg_glob(const char* pat, const char* s) {
   1417   if (!pat) return 1;
   1418   while (*pat && *s) {
   1419     if (*pat == '*') {
   1420       if (pat[1] == '\0') return 1;
   1421       while (*s) {
   1422         if (dbg_glob(pat + 1, s)) return 1;
   1423         ++s;
   1424       }
   1425       return dbg_glob(pat + 1, s);
   1426     }
   1427     if (*pat != '?' && *pat != *s) return 0;
   1428     ++pat;
   1429     ++s;
   1430   }
   1431   while (*pat == '*') ++pat;
   1432   return *pat == '\0' && *s == '\0';
   1433 }
   1434 
   1435 static void dbg_cmd_info_syms(DbgState* s, KitSymKind want,
   1436                               const char* pattern) {
   1437   KitJitSymIter* it = NULL;
   1438   KitJitSym sym;
   1439   int printed = 0;
   1440 
   1441   if (kit_jit_sym_iter_new(s->jit, &it) != KIT_OK) {
   1442     dbg_errf(s, "symbol enumeration unavailable");
   1443     return;
   1444   }
   1445   for (;;) {
   1446     KitIterResult r = kit_jit_sym_iter_next(it, &sym);
   1447     if (r != KIT_ITER_ITEM) break;
   1448     if (sym.kind != want) continue;
   1449     if (pattern && !dbg_glob(pattern, sym.name.s)) continue;
   1450     driver_printf("0x%016llx  %.*s\n", (unsigned long long)sym.addr,
   1451                   KIT_SLICE_ARG(sym.name));
   1452     printed = 1;
   1453   }
   1454   kit_jit_sym_iter_free(it);
   1455   if (!printed) driver_printf("(none)\n");
   1456 }
   1457 
   1458 static int dbg_refresh_dwarf(DbgState* s) {
   1459   if (s->dwarf) {
   1460     kit_dwarf_free(s->dwarf);
   1461     s->dwarf = NULL;
   1462   }
   1463   s->view = kit_jit_view(s->jit);
   1464   if (s->view) {
   1465     if (kit_dwarf_open(&s->ctx, s->view, &s->dwarf) != KIT_OK) s->dwarf = NULL;
   1466     if (s->dwarf && s->session) {
   1467       kit_dbg_session_attach_dwarf(s->session, s->dwarf);
   1468     }
   1469   } else if (s->session) {
   1470     kit_dbg_session_attach_dwarf(s->session, NULL);
   1471   }
   1472   return 0;
   1473 }
   1474 
   1475 static int dbg_buf_append(DbgState* s, char** buf, size_t* len, size_t* cap,
   1476                           const char* src, size_t n) {
   1477   if (*len + n + 1u > *cap) {
   1478     size_t nc = *cap ? *cap * 2u : 1024u;
   1479     char* nb;
   1480     while (nc < *len + n + 1u) nc *= 2u;
   1481     nb = (char*)s->env->heap->realloc(s->env->heap, *buf, *cap, nc,
   1482                                       _Alignof(char));
   1483     if (!nb) return 1;
   1484     *buf = nb;
   1485     *cap = nc;
   1486   }
   1487   driver_memcpy(*buf + *len, src, n);
   1488   *len += n;
   1489   (*buf)[*len] = '\0';
   1490   return 0;
   1491 }
   1492 
   1493 static DbgSource* dbg_source_find(DbgState* s, KitSlice name) {
   1494   uint32_t i;
   1495   if (!s || !name.s || !name.len) return NULL;
   1496   for (i = 0; i < s->nsources; ++i) {
   1497     DbgSource* src = &s->sources[i];
   1498     if (src->name && src->name_size == name.len + 1u &&
   1499         memcmp(src->name, name.s, name.len) == 0) {
   1500       return src;
   1501     }
   1502   }
   1503   return NULL;
   1504 }
   1505 
   1506 static DbgSource* dbg_source_grow(DbgState* s) {
   1507   uint32_t nc;
   1508   size_t old_size, new_size;
   1509   DbgSource* ns;
   1510   if (s->nsources < s->sources_cap) return &s->sources[s->nsources];
   1511 
   1512   nc = s->sources_cap ? s->sources_cap * 2 : 8;
   1513   old_size = (size_t)s->sources_cap * sizeof(*s->sources);
   1514   new_size = (size_t)nc * sizeof(*s->sources);
   1515   ns = (DbgSource*)s->env->heap->realloc(s->env->heap, s->sources, old_size,
   1516                                          new_size, _Alignof(DbgSource));
   1517   if (!ns) return NULL;
   1518   {
   1519     char* z = (char*)ns + old_size;
   1520     size_t n = new_size - old_size;
   1521     size_t j;
   1522     for (j = 0; j < n; ++j) z[j] = 0;
   1523   }
   1524   s->sources = ns;
   1525   s->sources_cap = nc;
   1526   return &s->sources[s->nsources];
   1527 }
   1528 
   1529 static int dbg_source_intern_name(DbgState* s, KitSlice name,
   1530                                   const char** out) {
   1531   DbgSource* src;
   1532   size_t name_size;
   1533   char* name_copy;
   1534   if (out) *out = NULL;
   1535   if (!s || !name.s || !name.len) return 1;
   1536 
   1537   src = dbg_source_find(s, name);
   1538   if (src) {
   1539     if (out) *out = src->name;
   1540     return 0;
   1541   }
   1542 
   1543   name_copy = dbg_dup(s->env, name.s, name.len, &name_size);
   1544   if (!name_copy) return 1;
   1545   src = dbg_source_grow(s);
   1546   if (!src) {
   1547     driver_free(s->env, name_copy, name_size);
   1548     return 1;
   1549   }
   1550   src->name = name_copy;
   1551   src->name_size = name_size;
   1552   s->nsources++;
   1553   if (out) *out = src->name;
   1554   return 0;
   1555 }
   1556 
   1557 static int dbg_source_cache_put(DbgState* s, KitSlice name, const char* data,
   1558                                 size_t len) {
   1559   DbgSource* src;
   1560   uint8_t* data_copy;
   1561   size_t data_size = len ? len : 1u;
   1562   if (!s || !name.s || !name.len || !data) return 1;
   1563 
   1564   data_copy = (uint8_t*)driver_alloc(s->env, data_size);
   1565   if (!data_copy) return 1;
   1566   if (len) driver_memcpy(data_copy, data, len);
   1567 
   1568   src = dbg_source_find(s, name);
   1569   if (!src) {
   1570     if (dbg_source_intern_name(s, name, NULL) != 0) {
   1571       driver_free(s->env, data_copy, data_size);
   1572       return 1;
   1573     }
   1574     src = dbg_source_find(s, name);
   1575     if (!src) {
   1576       driver_free(s->env, data_copy, data_size);
   1577       return 1;
   1578     }
   1579   }
   1580   if (src->data) {
   1581     driver_free(s->env, src->data, src->data_size);
   1582   }
   1583 
   1584   src->data = data_copy;
   1585   src->data_size = data_size;
   1586   src->len = len;
   1587   return 0;
   1588 }
   1589 
   1590 static int dbg_brace_delta(const char* p) {
   1591   int d = 0;
   1592   while (*p) {
   1593     if (*p == '{')
   1594       ++d;
   1595     else if (*p == '}')
   1596       --d;
   1597     ++p;
   1598   }
   1599   return d;
   1600 }
   1601 
   1602 /* Caller buffer sizes for the REPL filename builders below. ".<ext>" and
   1603  * "<dbg-jit.<ext>>" with the canonical frontend extension comfortably fit. */
   1604 #define DBG_JIT_SUFFIX_CAP 16
   1605 #define DBG_JIT_NAME_CAP 32
   1606 /* DbgState.default_jit_name_buf must match DBG_JIT_NAME_CAP (it is sized with a
   1607  * literal because it precedes this macro in the file). */
   1608 _Static_assert(sizeof(((DbgState*)0)->default_jit_name_buf) == DBG_JIT_NAME_CAP,
   1609                "default_jit_name_buf must equal DBG_JIT_NAME_CAP");
   1610 
   1611 /* Canonical language name for the JIT REPL (kit_language_name with a "c"
   1612  * fallback so C and any unnamed/out-of-range language render as "c", matching
   1613  * the former hardcoded default). The result is borrowed static storage. */
   1614 static const char* dbg_jit_language_name(KitCompiler* c, KitLanguage lang) {
   1615   const char* name = kit_language_name(c, lang);
   1616   return name ? name : "c";
   1617 }
   1618 
   1619 /* Append NUL-terminated `s` into `buf[cap]` starting at `*pos`, advancing
   1620  * `*pos`. Truncates rather than overflowing; the result is always
   1621  * NUL-terminated when cap > 0. (dbg.c builds strings with driver_* helpers
   1622  * rather than stdio.) */
   1623 static void dbg_str_append(char* buf, size_t cap, size_t* pos, const char* s) {
   1624   size_t n = driver_strlen(s);
   1625   size_t room;
   1626   if (*pos >= cap) return;
   1627   room = cap - 1u - *pos; /* leave space for NUL */
   1628   if (n > room) n = room;
   1629   driver_memcpy(buf + *pos, s, n);
   1630   *pos += n;
   1631   buf[*pos] = '\0';
   1632 }
   1633 
   1634 /* Build the dotted file suffix (e.g. ".toy") for `lang` into `buf`, defaulting
   1635  * to ".c" for C and any unnamed/out-of-range language. The canonical bare
   1636  * extension is the single source of truth (kit_language_default_extension);
   1637  * the leading "." is tool presentation. Returns `buf`. */
   1638 static const char* dbg_jit_language_suffix(KitCompiler* c, KitLanguage lang,
   1639                                            char* buf, size_t cap) {
   1640   const char* ext = kit_language_default_extension(c, lang);
   1641   size_t pos = 0;
   1642   if (lang == KIT_LANG_C || !ext) ext = "c";
   1643   if (cap > 0) buf[0] = '\0';
   1644   dbg_str_append(buf, cap, &pos, ".");
   1645   dbg_str_append(buf, cap, &pos, ext);
   1646   return buf;
   1647 }
   1648 
   1649 /* Build the default synthesized REPL source name (e.g. "<dbg-jit.toy>") for
   1650  * `lang` into `buf`, derived from the canonical suffix. Returns `buf`. */
   1651 static const char* dbg_jit_default_name(KitCompiler* c, KitLanguage lang,
   1652                                         char* buf, size_t cap) {
   1653   char suffix[DBG_JIT_SUFFIX_CAP];
   1654   size_t pos = 0;
   1655   dbg_jit_language_suffix(c, lang, suffix, sizeof suffix);
   1656   if (cap > 0) buf[0] = '\0';
   1657   dbg_str_append(buf, cap, &pos, "<dbg-jit");
   1658   dbg_str_append(buf, cap, &pos, suffix);
   1659   dbg_str_append(buf, cap, &pos, ">");
   1660   return buf;
   1661 }
   1662 
   1663 static int dbg_jit_uses_default_name(KitCompiler* c, KitLanguage lang,
   1664                                      const char* input_name) {
   1665   char def[DBG_JIT_NAME_CAP];
   1666   return !input_name || driver_streq(input_name, dbg_jit_default_name(
   1667                                                      c, lang, def, sizeof def));
   1668 }
   1669 
   1670 static int dbg_make_repl_source_name(KitCompiler* c, KitLanguage lang,
   1671                                      uint64_t id, char* out, size_t cap) {
   1672   const char* prefix = "<dbg-jit-";
   1673   char suffix_buf[DBG_JIT_SUFFIX_CAP];
   1674   const char* suffix =
   1675       dbg_jit_language_suffix(c, lang, suffix_buf, sizeof suffix_buf);
   1676   char num[32];
   1677   size_t prefix_len = driver_strlen(prefix);
   1678   size_t suffix_len = driver_strlen(suffix);
   1679   size_t num_len = dbg_u64_dec(num, sizeof(num), id);
   1680   size_t need;
   1681   if (!num_len) return 1;
   1682   need = prefix_len + num_len + suffix_len + 2u;
   1683   if (need > cap) return 1;
   1684   driver_memcpy(out, prefix, prefix_len);
   1685   driver_memcpy(out + prefix_len, num, num_len);
   1686   driver_memcpy(out + prefix_len + num_len, suffix, suffix_len);
   1687   out[prefix_len + num_len + suffix_len] = '>';
   1688   out[prefix_len + num_len + suffix_len + 1u] = '\0';
   1689   return 0;
   1690 }
   1691 
   1692 /* Resolve a `jit`/`:language` tag to a language and a display source name.
   1693  *
   1694  * `tag` is either a frontend `-x` spelling (c/toy/asm/s/wasm/wat) or, for the
   1695  * `jit` command, a `name.ext` filename. Empty/NULL selects the session default.
   1696  * `name_buf`/`name_cap` back the canonical synthesized name for the resolved
   1697  * cases. On return `*name_out` points to a string the caller must keep alive
   1698  * (intern) UNLESS it equals `s->default_jit_name`, which DbgState already owns.
   1699  * The storage cases:
   1700  *   - resolved by name: `*name_out` is the canonical "<dbg-jit.EXT>" written
   1701  *     into `name_buf` (single source of truth via dbg_jit_default_name);
   1702  *   - resolved by path (the `name.ext` fallback): `*name_out` is the raw `tag`,
   1703  *     so the resolver is never handed a language name as a path. */
   1704 static KitLanguage dbg_jit_language_for_tag(DbgState* s, const char* tag,
   1705                                             char* name_buf, size_t name_cap,
   1706                                             const char** name_out) {
   1707   KitLanguage lang;
   1708   if (!tag || !*tag) {
   1709     if (name_out) *name_out = s->default_jit_name;
   1710     return s->default_jit_lang;
   1711   }
   1712   lang = kit_language_for_name(s->compiler, tag);
   1713   if (lang != KIT_LANG_UNKNOWN) {
   1714     dbg_jit_default_name(s->compiler, lang, name_buf, name_cap);
   1715     if (name_out) *name_out = name_buf;
   1716     return lang;
   1717   }
   1718   /* Unknown name: treat `tag` as a `name.ext` filename and resolve by
   1719    * extension (kit_language_for_name does not consult extensions). */
   1720   if (name_out) *name_out = tag;
   1721   return kit_language_for_path(s->compiler, tag);
   1722 }
   1723 
   1724 static KitStatus dbg_compile_sessions_reserve(DbgState* s, KitLanguage lang) {
   1725   KitCompileSession** nb;
   1726   uint32_t want;
   1727   uint32_t ncap;
   1728   uint32_t i;
   1729   size_t old_sz;
   1730   size_t new_sz;
   1731 
   1732   if (!s || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
   1733     return KIT_INVALID;
   1734   if (lang == UINT32_MAX) return KIT_INVALID;
   1735   want = lang + 1u;
   1736   if (want <= s->compile_sessions_cap) return KIT_OK;
   1737   ncap = s->compile_sessions_cap ? s->compile_sessions_cap : 8u;
   1738   while (ncap < want) {
   1739     if (ncap > UINT32_MAX / 2u) {
   1740       ncap = want;
   1741       break;
   1742     }
   1743     ncap *= 2u;
   1744   }
   1745   if ((size_t)ncap > SIZE_MAX / sizeof(*s->compile_sessions))
   1746     return KIT_NOMEM;
   1747   old_sz = (size_t)s->compile_sessions_cap * sizeof(*s->compile_sessions);
   1748   new_sz = (size_t)ncap * sizeof(*s->compile_sessions);
   1749   nb = (KitCompileSession**)s->env->heap->realloc(
   1750       s->env->heap, s->compile_sessions, old_sz, new_sz,
   1751       _Alignof(KitCompileSession*));
   1752   if (!nb) return KIT_NOMEM;
   1753   for (i = s->compile_sessions_cap; i < ncap; ++i) nb[i] = NULL;
   1754   s->compile_sessions = nb;
   1755   s->compile_sessions_cap = ncap;
   1756   return KIT_OK;
   1757 }
   1758 
   1759 static KitStatus dbg_compile_session_for(DbgState* s, KitLanguage lang,
   1760                                          KitCompileSession** out) {
   1761   KitCompileSessionOptions sopts;
   1762   KitStatus st;
   1763 
   1764   if (!s || !out || lang == KIT_LANG_UNKNOWN || lang == KIT_LANG_AUTO)
   1765     return KIT_INVALID;
   1766   *out = NULL;
   1767   st = dbg_compile_sessions_reserve(s, lang);
   1768   if (st != KIT_OK) return st;
   1769   if (s->compile_sessions[lang]) {
   1770     *out = s->compile_sessions[lang];
   1771     return KIT_OK;
   1772   }
   1773   {
   1774     KitCompileSessionOptions z = {0};
   1775     sopts = z;
   1776   }
   1777   sopts.lang = lang;
   1778   sopts.compile.code = s->copts.code;
   1779   sopts.compile.diagnostics = s->copts.diagnostics;
   1780   sopts.compile.preprocess = s->pp;
   1781   st = kit_compile_session_new(s->compiler, &sopts, &s->compile_sessions[lang]);
   1782   if (st != KIT_OK) return st;
   1783   if (s->ncompile_sessions < lang + 1u) s->ncompile_sessions = lang + 1u;
   1784   *out = s->compile_sessions[lang];
   1785   return KIT_OK;
   1786 }
   1787 
   1788 static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang,
   1789                                      const char* input_name, const char* src,
   1790                                      size_t len,
   1791                                      KitFrontendInputKind input_kind,
   1792                                      const char* repl_entry_name) {
   1793   KitSourceInput sin;
   1794   KitCompileSession* session = NULL;
   1795   KitObjBuilder* ob = NULL;
   1796   KitStatus st;
   1797   char generated_name[96];
   1798   char default_name[DBG_JIT_NAME_CAP];
   1799   const char* effective_name =
   1800       input_name ? input_name
   1801                  : dbg_jit_default_name(s->compiler, lang, default_name,
   1802                                         sizeof default_name);
   1803   uint64_t source_id = s->source_counter + 1u;
   1804   int generated_source_name = 0;
   1805 
   1806   s->jit_counter++;
   1807   if (input_kind == KIT_FRONTEND_INPUT_REPL_TOPLEVEL &&
   1808       dbg_jit_uses_default_name(s->compiler, lang, input_name)) {
   1809     if (dbg_make_repl_source_name(s->compiler, lang, source_id, generated_name,
   1810                                   sizeof(generated_name)) != 0) {
   1811       dbg_errf(s, "repl source name overflow");
   1812       return 1;
   1813     }
   1814     if (dbg_source_intern_name(s, kit_slice_cstr(generated_name),
   1815                                &effective_name) != 0) {
   1816       dbg_errf(s, "out of memory naming repl source");
   1817       return 1;
   1818     }
   1819     generated_source_name = 1;
   1820   }
   1821   memset(&sin, 0, sizeof(sin));
   1822   sin.name = kit_slice_cstr(effective_name);
   1823   sin.bytes.data = (const uint8_t*)src;
   1824   sin.bytes.len = len;
   1825   sin.lang = lang;
   1826   sin.input_kind = input_kind;
   1827   sin.repl_entry_name = kit_slice_cstr(repl_entry_name);
   1828   st = dbg_compile_session_for(s, lang, &session);
   1829   /* Stage the compile: on success the frontend's durable declarations are left
   1830    * pending so we only commit them once the object has actually been published
   1831    * into the JIT image. A failed compile is already rolled back internally. */
   1832   if (st == KIT_OK) st = kit_compile_session_stage(session, &sin, &ob);
   1833   if (st != KIT_OK || !ob) {
   1834     if (ob) kit_obj_builder_free(ob);
   1835     dbg_errf(s, "jit compile failed");
   1836     return 1;
   1837   }
   1838   {
   1839     KitLinkSessionOptions lopts = {0};
   1840     KitLinkSession* link = NULL;
   1841     KitJitPublishOptions popts = {0};
   1842     KitJitPublishResult pres;
   1843     lopts.output_kind = KIT_LINK_OUTPUT_RELOCATABLE;
   1844     st = kit_link_session_new(s->compiler, &lopts, &link);
   1845     if (st == KIT_OK) st = kit_link_session_add_obj(link, ob);
   1846     popts.kind = KIT_JIT_PUBLISH_APPEND_OBJECTS;
   1847     popts.link = link;
   1848     if (st == KIT_OK) st = kit_jit_publish(s->jit, &popts, &pres);
   1849     kit_link_session_free(link);
   1850   }
   1851   if (st != KIT_OK) {
   1852     /* Publish rejected the object (e.g. a duplicate global). Roll back the
   1853      * staged declarations so the frontend never advertises a symbol the JIT
   1854      * image does not have. */
   1855     kit_compile_session_abort(session);
   1856     dbg_errf(s, "jit append failed");
   1857     return 1;
   1858   }
   1859   /* Published: make the staged declarations durable. */
   1860   kit_compile_session_commit(session);
   1861   if (generated_source_name) s->source_counter = source_id;
   1862   {
   1863     /* Cache the verbatim toplevel text only for frontends that re-read
   1864      * earlier toplevel source on later compiles (toy today), per the
   1865      * frontend's static capability rather than a hard-coded language. */
   1866     KitFrontendCaps caps = {0};
   1867     int cache_source = kit_frontend_caps(s->compiler, lang, &caps) == KIT_OK &&
   1868                        caps.cache_repl_toplevel_source;
   1869     if (cache_source && input_kind == KIT_FRONTEND_INPUT_REPL_TOPLEVEL &&
   1870         dbg_source_cache_put(s, sin.name, src, len) != 0) {
   1871       dbg_errf(s, "out of memory caching source for list");
   1872     }
   1873   }
   1874   dbg_refresh_dwarf(s);
   1875   return 0;
   1876 }
   1877 
   1878 static int dbg_jit_compile_append(DbgState* s, KitLanguage lang,
   1879                                   const char* input_name, const char* src,
   1880                                   size_t len) {
   1881   return dbg_jit_compile_append_ex(s, lang, input_name, src, len,
   1882                                    KIT_FRONTEND_INPUT_REPL_TOPLEVEL, NULL);
   1883 }
   1884 
   1885 static int dbg_parse_jit_lang_arg(DbgState* s, const char* rest,
   1886                                   KitLanguage* lang_out,
   1887                                   const char** input_name_out,
   1888                                   const char** after_out) {
   1889   const char* p = rest;
   1890   const char* input_name = NULL;
   1891   KitLanguage lang;
   1892 
   1893   while (*p && dbg_isspace((unsigned char)*p)) ++p;
   1894   if (*p && *p != '{') {
   1895     const char* tag = p;
   1896     size_t tag_n;
   1897     char tag_buf[64];
   1898     char name_buf[DBG_JIT_NAME_CAP];
   1899     while (*p && !dbg_isspace((unsigned char)*p) && *p != '{') ++p;
   1900     tag_n = (size_t)(p - tag);
   1901     if (tag_n == 0 || tag_n >= sizeof(tag_buf)) {
   1902       dbg_errf(s, "language/name is too long");
   1903       return 1;
   1904     }
   1905     driver_memcpy(tag_buf, tag, tag_n);
   1906     tag_buf[tag_n] = '\0';
   1907     lang = dbg_jit_language_for_tag(s, tag_buf, name_buf, sizeof name_buf,
   1908                                     &input_name);
   1909     /* `input_name` points into stack storage (name_buf or tag_buf) here, never
   1910      * the session default, so it must be interned to outlive this frame. */
   1911     if (dbg_source_intern_name(s, kit_slice_cstr(input_name), &input_name) !=
   1912         0) {
   1913       dbg_errf(s, "out of memory naming repl source");
   1914       return 1;
   1915     }
   1916     while (*p && dbg_isspace((unsigned char)*p)) ++p;
   1917   } else {
   1918     lang = dbg_jit_language_for_tag(s, NULL, NULL, 0, &input_name);
   1919   }
   1920 
   1921   *lang_out = lang;
   1922   *input_name_out = input_name;
   1923   *after_out = p;
   1924   return 0;
   1925 }
   1926 
   1927 static void dbg_cmd_jit(DbgState* s, const char* rest) {
   1928   char* src = NULL;
   1929   size_t len = 0, cap = 0;
   1930   const char* p;
   1931   const char* input_name;
   1932   KitLanguage lang;
   1933   int depth;
   1934 
   1935   if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return;
   1936   if (*p != '{') {
   1937     dbg_errf(s, "usage: jit [c|asm|name.ext] { ... }");
   1938     return;
   1939   }
   1940   ++p;
   1941   depth = 1 + dbg_brace_delta(p);
   1942   {
   1943     const char* end = p + driver_strlen(p);
   1944     if (depth <= 0) {
   1945       while (end > p && end[-1] != '}') --end;
   1946       if (end > p) --end;
   1947     }
   1948     if (dbg_buf_append(s, &src, &len, &cap, p, (size_t)(end - p)) != 0)
   1949       goto oom;
   1950     if (dbg_buf_append(s, &src, &len, &cap, "\n", 1) != 0) goto oom;
   1951   }
   1952   while (depth > 0) {
   1953     char line[LINE_CAP];
   1954     int n;
   1955     driver_printf("     > ");
   1956     driver_flush_stdout();
   1957     n = driver_read_line(line, sizeof(line));
   1958     if (n <= 0) {
   1959       dbg_errf(s, "unterminated jit block");
   1960       goto out;
   1961     }
   1962     depth += dbg_brace_delta(line);
   1963     if (depth <= 0) {
   1964       char* close = line;
   1965       while (*close && *close != '}') ++close;
   1966       if (dbg_buf_append(s, &src, &len, &cap, line, (size_t)(close - line)) !=
   1967           0)
   1968         goto oom;
   1969       if (dbg_buf_append(s, &src, &len, &cap, "\n", 1) != 0) goto oom;
   1970       break;
   1971     }
   1972     if (dbg_buf_append(s, &src, &len, &cap, line, driver_strlen(line)) != 0)
   1973       goto oom;
   1974     if (dbg_buf_append(s, &src, &len, &cap, "\n", 1) != 0) goto oom;
   1975   }
   1976 
   1977   (void)dbg_jit_compile_append(s, lang, input_name, src, len);
   1978   goto out;
   1979 
   1980 oom:
   1981   dbg_errf(s, "out of memory");
   1982 out:
   1983   if (src) driver_free(s->env, src, cap);
   1984 }
   1985 
   1986 static void dbg_cmd_edit(DbgState* s, const char* rest) {
   1987   KitLanguage lang;
   1988   const char* input_name;
   1989   const char* p;
   1990   uint8_t* src = NULL;
   1991   size_t len = 0;
   1992   char suffix[DBG_JIT_SUFFIX_CAP];
   1993 
   1994   if (dbg_parse_jit_lang_arg(s, rest, &lang, &input_name, &p) != 0) return;
   1995   if (*p) {
   1996     dbg_errf(s, "usage: edit [c|asm|name.ext]");
   1997     return;
   1998   }
   1999   if (!driver_edit_temp(
   2000           s->env,
   2001           dbg_jit_language_suffix(s->compiler, lang, suffix, sizeof suffix),
   2002           NULL, 0, &src, &len)) {
   2003     dbg_errf(s, "editor failed");
   2004     return;
   2005   }
   2006   if (len == 0) {
   2007     dbg_errf(s, "empty editor buffer; nothing appended");
   2008     goto out;
   2009   }
   2010   (void)dbg_jit_compile_append(s, lang, input_name, (const char*)src, len);
   2011 
   2012 out:
   2013   driver_free(s->env, src, len);
   2014 }
   2015 
   2016 static void dbg_cmd_language(DbgState* s, const char* rest) {
   2017   char tmp[WORD_CAP];
   2018   const char* p = rest;
   2019   size_t n = 0;
   2020   KitLanguage lang;
   2021 
   2022   while (*p && dbg_isspace((unsigned char)*p)) ++p;
   2023   while (p[n] && !dbg_isspace((unsigned char)p[n])) ++n;
   2024   if (n == 0) {
   2025     const KitPreprocessOptions* pp = &s->pp;
   2026     KitCompileSession* cached =
   2027         s->default_jit_lang < s->ncompile_sessions
   2028             ? s->compile_sessions[s->default_jit_lang]
   2029             : NULL;
   2030     driver_printf("Language: %.*s\n",
   2031                   KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name(
   2032                       s->compiler, s->default_jit_lang))));
   2033     driver_printf(
   2034         "Language options: input=%.*s opt=-O%d debug=%.*s includes=%u "
   2035         "system-includes=%u defines=%u undefines=%u session=%.*s\n",
   2036         KIT_SLICE_ARG(kit_slice_cstr(s->default_jit_name)),
   2037         s->copts.code.opt_level,
   2038         KIT_SLICE_ARG(kit_slice_cstr(s->copts.code.debug_info ? "on" : "off")),
   2039         (unsigned)pp->ninclude_dirs, (unsigned)pp->nsystem_include_dirs,
   2040         (unsigned)pp->ndefines, (unsigned)pp->nundefines,
   2041         KIT_SLICE_ARG(kit_slice_cstr(cached ? "cached" : "not-created")));
   2042     return;
   2043   }
   2044   if (n >= sizeof(tmp)) {
   2045     dbg_errf(s, "usage: :language c|asm|wat|wasm");
   2046     return;
   2047   }
   2048   driver_memcpy(tmp, p, n);
   2049   tmp[n] = '\0';
   2050   /* `:language` only needs the resolved language; the display name is
   2051    * recomputed below from dbg_jit_default_name, so no name buffer is requested.
   2052    */
   2053   lang = dbg_jit_language_for_tag(s, tmp, NULL, 0, NULL);
   2054   if (lang == KIT_LANG_UNKNOWN) {
   2055     dbg_errf(s, "unsupported language: %.*s",
   2056              KIT_SLICE_ARG(kit_slice_cstr(tmp)));
   2057     return;
   2058   }
   2059   s->default_jit_lang = lang;
   2060   s->default_jit_name =
   2061       dbg_jit_default_name(s->compiler, lang, s->default_jit_name_buf,
   2062                            sizeof s->default_jit_name_buf);
   2063   driver_printf(
   2064       "Language: %.*s\n",
   2065       KIT_SLICE_ARG(kit_slice_cstr(dbg_jit_language_name(s->compiler, lang))));
   2066 }
   2067 
   2068 static size_t dbg_u64_dec(char* dst, size_t cap, uint64_t v) {
   2069   char tmp[32];
   2070   size_t n = 0;
   2071   size_t i;
   2072   if (!dst || cap == 0) return 0;
   2073   if (v == 0) {
   2074     if (cap < 2u) return 0;
   2075     dst[0] = '0';
   2076     dst[1] = '\0';
   2077     return 1;
   2078   }
   2079   while (v && n < sizeof(tmp)) {
   2080     tmp[n++] = (char)('0' + (v % 10u));
   2081     v /= 10u;
   2082   }
   2083   if (n + 1u > cap) return 0;
   2084   for (i = 0; i < n; ++i) dst[i] = tmp[n - 1u - i];
   2085   dst[n] = '\0';
   2086   return n;
   2087 }
   2088 
   2089 static int dbg_call_u64_entry(DbgState* s, void* entry, const uint64_t* args,
   2090                               uint32_t nargs, uint64_t* ret_out) {
   2091   uint64_t ret = 0;
   2092   KitStopInfo stop;
   2093 
   2094   if (driver_install_sigint(dbg_on_sigint, s) != 0) {
   2095     dbg_errf(s, "failed to install SIGINT handler");
   2096     return 1;
   2097   }
   2098   if (kit_dbg_session_call_u64(s->session, entry, args, nargs, &ret, &stop) !=
   2099       KIT_OK) {
   2100     driver_restore_sigint();
   2101     dbg_errf(s, "call failed (debuggee must be idle or exited)");
   2102     return 1;
   2103   }
   2104   driver_restore_sigint();
   2105   s->last_stop = stop;
   2106   s->has_stop = (stop.kind != KIT_STOP_EXIT);
   2107   if (ret_out) *ret_out = ret;
   2108   if (stop.kind != KIT_STOP_EXIT) {
   2109     s->has_stop = 1;
   2110     dbg_render_stop(s, &stop);
   2111     return 2;
   2112   }
   2113   return 0;
   2114 }
   2115 
   2116 static void dbg_cmd_expr(DbgState* s, const char* expr) {
   2117   char* body = NULL;
   2118   size_t body_len = 0, body_cap = 0;
   2119   char num[32];
   2120   char name[64];
   2121   size_t num_len;
   2122   size_t prefix_len;
   2123   void* entry;
   2124   uint64_t ret = 0;
   2125   uint64_t attempt;
   2126   uint64_t id;
   2127   int is_block = 0;
   2128 
   2129   while (*expr && dbg_isspace((unsigned char)*expr)) ++expr;
   2130   if (!*expr) {
   2131     dbg_errf(s, "usage: expr EXPR | expr { STATEMENTS }");
   2132     return;
   2133   }
   2134 
   2135   if (*expr == '{') {
   2136     const char* p = expr + 1;
   2137     int depth = 1 + dbg_brace_delta(p);
   2138     const char* end = p + driver_strlen(p);
   2139     is_block = 1;
   2140     if (depth <= 0) {
   2141       while (end > p && end[-1] != '}') --end;
   2142       if (end > p) --end;
   2143     }
   2144     if (dbg_buf_append(s, &body, &body_len, &body_cap, p, (size_t)(end - p)) !=
   2145         0)
   2146       goto oom;
   2147     if (dbg_buf_append(s, &body, &body_len, &body_cap, "\n", 1) != 0) goto oom;
   2148     while (depth > 0) {
   2149       char line[LINE_CAP];
   2150       int n;
   2151       driver_printf("expr > ");
   2152       driver_flush_stdout();
   2153       n = driver_read_line(line, sizeof(line));
   2154       if (n <= 0) {
   2155         dbg_errf(s, "unterminated expr block");
   2156         goto out;
   2157       }
   2158       depth += dbg_brace_delta(line);
   2159       if (depth <= 0) {
   2160         char* close = line;
   2161         while (*close && *close != '}') ++close;
   2162         if (dbg_buf_append(s, &body, &body_len, &body_cap, line,
   2163                            (size_t)(close - line)) != 0)
   2164           goto oom;
   2165         if (dbg_buf_append(s, &body, &body_len, &body_cap, "\n", 1) != 0)
   2166           goto oom;
   2167         break;
   2168       }
   2169       if (dbg_buf_append(s, &body, &body_len, &body_cap, line,
   2170                          driver_strlen(line)) != 0)
   2171         goto oom;
   2172       if (dbg_buf_append(s, &body, &body_len, &body_cap, "\n", 1) != 0)
   2173         goto oom;
   2174     }
   2175   }
   2176 
   2177   /* The thunk symbol name uses a monotonic per-attempt counter so each
   2178    * compiled thunk gets a unique linkage name even across failed attempts.
   2179    * The user-visible result number ($N) is a separate counter advanced only
   2180    * after a full success (compile + publish + call), so a failed snippet does
   2181    * not consume a result number. */
   2182   attempt = ++s->expr_attempt;
   2183   num_len = dbg_u64_dec(num, sizeof(num), attempt);
   2184   if (!num_len) {
   2185     dbg_errf(s, "expression counter overflow");
   2186     goto out;
   2187   }
   2188   prefix_len = driver_strlen("__kit_dbg_expr_");
   2189   if (prefix_len + num_len + 1u > sizeof(name)) {
   2190     dbg_errf(s, "expression symbol too long");
   2191     goto out;
   2192   }
   2193   driver_memcpy(name, "__kit_dbg_expr_", prefix_len);
   2194   driver_memcpy(name + prefix_len, num, num_len + 1u);
   2195 
   2196   {
   2197     const char* src = is_block ? body : expr;
   2198     size_t len = is_block ? body_len : driver_strlen(expr);
   2199     KitFrontendInputKind kind =
   2200         is_block ? KIT_FRONTEND_INPUT_REPL_BLOCK : KIT_FRONTEND_INPUT_REPL_EXPR;
   2201     /* s->default_jit_name mirrors dbg_jit_default_name(s->default_jit_lang),
   2202      * kept in sync wherever default_jit_lang is set. */
   2203     if (dbg_jit_compile_append_ex(s, s->default_jit_lang, s->default_jit_name,
   2204                                   src, len, kind, name) != 0) {
   2205       goto out;
   2206     }
   2207   }
   2208 
   2209   entry = kit_jit_lookup(s->jit, kit_slice_cstr(name));
   2210   if (!entry) {
   2211     dbg_errf(s, "expression thunk not found: %.*s",
   2212              KIT_SLICE_ARG(kit_slice_cstr(name)));
   2213     goto out;
   2214   }
   2215   if (dbg_call_u64_entry(s, entry, NULL, 0, &ret) == 0) {
   2216     id = ++s->expr_counter;
   2217     driver_printf("$%llu = %llu (0x%llx)\n", (unsigned long long)id,
   2218                   (unsigned long long)ret, (unsigned long long)ret);
   2219   }
   2220   goto out;
   2221 
   2222 oom:
   2223   dbg_errf(s, "out of memory");
   2224 out:
   2225   if (body) driver_free(s->env, body, body_cap);
   2226 }
   2227 
   2228 /* ============================================================
   2229  * `x ADDR [count]`
   2230  * ============================================================
   2231  * Examine memory: reads `count` bytes (default 16) at `addr` from the
   2232  * worker's address space and prints them as 16-byte rows. */
   2233 
   2234 static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) {
   2235   uint8_t buf[256];
   2236   size_t remaining = count;
   2237 
   2238   if (!s->has_stop) {
   2239     dbg_errf(s, "no program is stopped");
   2240     return;
   2241   }
   2242 
   2243   while (remaining) {
   2244     size_t chunk = remaining > sizeof(buf) ? sizeof(buf) : remaining;
   2245     size_t i;
   2246     if (kit_dbg_session_read_mem(s->session, addr, buf, chunk) != KIT_OK) {
   2247       dbg_errf(s, "read failed at 0x%llx", (unsigned long long)addr);
   2248       return;
   2249     }
   2250     for (i = 0; i < chunk; i += 16) {
   2251       size_t j;
   2252       size_t row = chunk - i < 16 ? chunk - i : 16;
   2253       driver_printf("0x%llx:", (unsigned long long)(addr + i));
   2254       for (j = 0; j < row; ++j) driver_printf(" %02x", buf[i + j]);
   2255       driver_printf("\n");
   2256     }
   2257     addr += chunk;
   2258     remaining -= chunk;
   2259   }
   2260 }
   2261 
   2262 /* ============================================================
   2263  * `disasm [ADDR] [count]`, `x/i [ADDR] [count]`
   2264  * ============================================================
   2265  * Decode a small instruction window from the stopped program. Without an
   2266  * address, starts at the stopped PC; count is in instructions. */
   2267 
   2268 static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) {
   2269   KitDisasmIter* it = NULL;
   2270   size_t shown = 0;
   2271 
   2272   if (!s->has_stop) {
   2273     dbg_errf(s, "no program is stopped");
   2274     return;
   2275   }
   2276   if (count == 0) count = 8;
   2277   if (count > 32) {
   2278     dbg_errf(s, "disasm count too large");
   2279     return;
   2280   }
   2281   if (kit_dbg_disasm_new(s->session, addr, (uint32_t)count, &it) != KIT_OK) {
   2282     dbg_errf(s, "read failed at 0x%llx", (unsigned long long)addr);
   2283     return;
   2284   }
   2285   while (shown < count) {
   2286     KitInsn insn;
   2287     KitIterResult r = kit_disasm_iter_next(it, &insn);
   2288     if (r == KIT_ITER_END) break;
   2289     if (r != KIT_ITER_ITEM) {
   2290       dbg_errf(s, "disassembly failed");
   2291       break;
   2292     }
   2293     driver_printf("0x%llx: %-8.*s", (unsigned long long)insn.vaddr,
   2294                   KIT_SLICE_ARG(insn.mnemonic));
   2295     if (insn.operands.len) driver_printf(" %.*s", KIT_SLICE_ARG(insn.operands));
   2296     if (insn.annotation.len)
   2297       driver_printf(" %.*s", KIT_SLICE_ARG(insn.annotation));
   2298     driver_printf("\n");
   2299     shown++;
   2300   }
   2301   kit_disasm_iter_free(it);
   2302 }
   2303 
   2304 /* ============================================================
   2305  * Source listing
   2306  * ============================================================
   2307  * Print a context window of source lines centered on `file:line`.
   2308  *
   2309  * Reads REPL snippets from the debugger's in-memory source cache and normal
   2310  * files from disk via env.file_io.  When neither is available (e.g. the DWARF
   2311  * came from a `.o` / `.a` whose source isn't available here), command-mode
   2312  * reports the DWARF line number alone and source stops silently keep the
   2313  * already-rendered location line. */
   2314 
   2315 static void dbg_print_source_bytes(const uint8_t* data, size_t size,
   2316                                    uint32_t line, int report_errors) {
   2317   static const uint8_t empty[1] = {0};
   2318   uint32_t target = line;
   2319   uint32_t lo = target > DBG_LIST_CTX ? target - DBG_LIST_CTX : 1;
   2320   uint32_t hi = target + DBG_LIST_CTX;
   2321   uint32_t cur = 1;
   2322   const uint8_t* p;
   2323   const uint8_t* end;
   2324   const uint8_t* line_start;
   2325 
   2326   if (!data && size != 0) return;
   2327   if (!data) data = empty;
   2328   p = data;
   2329   end = data + size;
   2330   line_start = p;
   2331 
   2332   while (p <= end) {
   2333     int eol = (p == end) || (*p == '\n');
   2334     if (eol) {
   2335       if (p == end && p == line_start && end > data && end[-1] == '\n') break;
   2336       if (cur >= lo && cur <= hi) {
   2337         size_t len = (size_t)(p - line_start);
   2338         driver_printf(
   2339             "%6u%.*s %.*s\n", cur,
   2340             KIT_SLICE_ARG(kit_slice_cstr(cur == target ? " >" : "  ")),
   2341             (int)len, (const char*)line_start);
   2342       }
   2343       ++cur;
   2344       if (p == end) break;
   2345       line_start = p + 1;
   2346     }
   2347     ++p;
   2348   }
   2349   if (report_errors && cur <= target)
   2350     driver_errf(DBG_TOOL, "file has only %u lines; %u requested", cur - 1u,
   2351                 target);
   2352 }
   2353 
   2354 static void dbg_print_source_listing(DbgState* s, KitSlice file, uint32_t line,
   2355                                      uint64_t pc, int report_errors) {
   2356   char path[1024];
   2357   KitFileData fd;
   2358   const KitFileIO* io;
   2359   DbgSource* cached;
   2360 
   2361   if (!file.s || file.len == 0) {
   2362     if (report_errors) {
   2363       dbg_errf(s, "bad file in '%.*s'", KIT_SLICE_ARG(file));
   2364     }
   2365     return;
   2366   }
   2367 
   2368   cached = dbg_source_find(s, file);
   2369   if (cached && cached->data) {
   2370     dbg_print_source_bytes(cached->data, cached->len, line, report_errors);
   2371     return;
   2372   }
   2373 
   2374   if (file.len >= sizeof(path)) {
   2375     if (report_errors) {
   2376       dbg_errf(s, "bad file in '%.*s'", KIT_SLICE_ARG(file));
   2377     }
   2378     return;
   2379   }
   2380   driver_memcpy(path, file.s, file.len);
   2381   path[file.len] = '\0';
   2382 
   2383   /* Try to read the file via file_io.  On miss, fall back to the
   2384    * DWARF-only summary line per doc/DBG.md §10. */
   2385   io = s->env && s->env->file_io.read_all ? &s->env->file_io : NULL;
   2386   if (!io || io->read_all(io->user, path, &fd) != KIT_OK) {
   2387     if (report_errors) {
   2388       driver_printf("%.*s:%u  [source not available; pc=0x%llx]\n",
   2389                     KIT_SLICE_ARG(kit_slice_cstr(path)), line,
   2390                     (unsigned long long)pc);
   2391     }
   2392     return;
   2393   }
   2394 
   2395   dbg_print_source_bytes(fd.data, fd.size, line, report_errors);
   2396 
   2397   if (io->release) io->release(io->user, &fd);
   2398 }
   2399 
   2400 static void dbg_cmd_list(DbgState* s, const char* spec) {
   2401   const char* colon;
   2402   size_t flen;
   2403   char path[1024];
   2404   uint64_t line_u;
   2405   size_t used;
   2406   uint64_t pc;
   2407   KitSlice file;
   2408 
   2409   if (!s->dwarf) {
   2410     dbg_errf(s, "no DWARF: cannot resolve %.*s",
   2411              KIT_SLICE_ARG(kit_slice_cstr(spec)));
   2412     return;
   2413   }
   2414 
   2415   colon = driver_strchr(spec, ':');
   2416   if (!colon || !dbg_isdigit((unsigned char)colon[1])) {
   2417     dbg_errf(s, "usage: list file:line");
   2418     return;
   2419   }
   2420   flen = (size_t)(colon - spec);
   2421   if (flen == 0 || flen >= sizeof(path)) {
   2422     dbg_errf(s, "bad file in '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(spec)));
   2423     return;
   2424   }
   2425   driver_memcpy(path, spec, flen);
   2426   path[flen] = '\0';
   2427   used = dbg_parse_uint(colon + 1, &line_u);
   2428   if (!used || colon[1 + used] != '\0') {
   2429     dbg_errf(s, "usage: list file:line");
   2430     return;
   2431   }
   2432 
   2433   /* Validate via DWARF first. */
   2434   {
   2435     KitStatus st = kit_dwarf_line_to_addr(s->dwarf, kit_slice_cstr(path),
   2436                                           (uint32_t)line_u, &pc);
   2437     if (st == KIT_NOT_FOUND) {
   2438       dbg_errf(s, "no line %u in %.*s", (uint32_t)line_u,
   2439                KIT_SLICE_ARG(kit_slice_cstr(path)));
   2440       return;
   2441     }
   2442     if (st == KIT_AMBIGUOUS) {
   2443       dbg_errf(s,
   2444                "ambiguous: %.*s:%u matches multiple files; "
   2445                "use a longer path suffix",
   2446                KIT_SLICE_ARG(kit_slice_cstr(path)), (uint32_t)line_u);
   2447       return;
   2448     }
   2449     if (st != KIT_OK) {
   2450       dbg_errf(s, "no line entry for %.*s",
   2451                KIT_SLICE_ARG(kit_slice_cstr(spec)));
   2452       return;
   2453     }
   2454   }
   2455 
   2456   file = kit_slice_cstr(path);
   2457   dbg_print_source_listing(s, file, (uint32_t)line_u, pc, 1);
   2458 }
   2459 
   2460 /* ============================================================
   2461  * `info b`, `b LOC`, `d N`, `enable N` / `disable N`, `ignore N COUNT`
   2462  * ============================================================ */
   2463 
   2464 /* Arm a Bp through the appropriate session entry. The plain breakpoint_set
   2465  * is used when no skip/cap is in effect; otherwise the spec form. The
   2466  * driver does not yet wire conditions, but the spec callback slot is left
   2467  * NULL so this remains forward-compatible. */
   2468 static int dbg_bp_arm(DbgState* s, Bp* b) {
   2469   KitStatus st;
   2470   if (b->skip_count == 0 && b->max_hits == 0) {
   2471     st = kit_dbg_session_breakpoint_set(s->session, b->addr, &b->session_id);
   2472   } else {
   2473     KitBreakpointSpec spec;
   2474     KitBreakpointSpec z = {0};
   2475     spec = z;
   2476     spec.addr = b->addr;
   2477     spec.skip_count = b->skip_count;
   2478     spec.max_hits = b->max_hits;
   2479     st = kit_dbg_session_breakpoint_set_spec(s->session, &spec, &b->session_id);
   2480   }
   2481   return st == KIT_OK ? 0 : 1;
   2482 }
   2483 
   2484 static void dbg_cmd_break(DbgState* s, const char* spec) {
   2485   BpKind kind;
   2486   uint64_t addr;
   2487   Bp* b;
   2488 
   2489   if (!*spec) {
   2490     dbg_errf(s, "usage: b <0xADDR | sym[+off] | file.c:line>");
   2491     return;
   2492   }
   2493   if (dbg_resolve_loc(s, spec, &kind, &addr) != 0) return;
   2494 
   2495   b = dbg_bp_grow(s);
   2496   if (!b) return;
   2497 
   2498   {
   2499     Bp z = {0};
   2500     *b = z;
   2501   }
   2502   b->id = ++s->next_bp_id;
   2503   b->enabled = 1;
   2504   b->kind = kind;
   2505   b->addr = addr;
   2506   b->spec = dbg_dup(s->env, spec, driver_strlen(spec), &b->spec_size);
   2507   if (!b->spec) {
   2508     dbg_errf(s, "out of memory");
   2509     return;
   2510   }
   2511 
   2512   if (dbg_bp_arm(s, b) != 0) {
   2513     dbg_errf(s, "failed to arm breakpoint at 0x%llx", (unsigned long long)addr);
   2514     b->session_id = 0;
   2515     b->enabled = 0;
   2516   }
   2517 
   2518   s->nbps++;
   2519   driver_printf(
   2520       "Breakpoint %d at 0x%llx (%.*s)%.*s\n", b->id, (unsigned long long)addr,
   2521       KIT_SLICE_ARG(kit_slice_cstr(spec)),
   2522       KIT_SLICE_ARG(kit_slice_cstr(b->session_id ? "" : " [disarmed]")));
   2523 }
   2524 
   2525 static void dbg_cmd_info_b(DbgState* s) {
   2526   uint32_t i;
   2527   if (s->nbps == 0) {
   2528     driver_printf("No breakpoints.\n");
   2529     return;
   2530   }
   2531   driver_printf("Num  Enb  Address              Skip   Max    Spec\n");
   2532   for (i = 0; i < s->nbps; ++i) {
   2533     Bp* b = &s->bps[i];
   2534     driver_printf(
   2535         "%-4d %-4s 0x%-18llx %-6llu %-6llu %.*s%.*s\n", b->id,
   2536         b->enabled ? "y" : "n", (unsigned long long)b->addr,
   2537         (unsigned long long)b->skip_count, (unsigned long long)b->max_hits,
   2538         KIT_SLICE_ARG(kit_slice_cstr(b->spec)),
   2539         KIT_SLICE_ARG(kit_slice_cstr(b->session_id ? "" : " [disarmed]")));
   2540   }
   2541 }
   2542 
   2543 static void dbg_cmd_ignore(DbgState* s, int id, uint64_t count) {
   2544   Bp* b = dbg_bp_find(s, id);
   2545   if (!b) {
   2546     dbg_errf(s, "no breakpoint %d", id);
   2547     return;
   2548   }
   2549   if (b->session_id) {
   2550     kit_dbg_session_breakpoint_clear(s->session, b->session_id);
   2551     b->session_id = 0;
   2552   }
   2553   b->skip_count = count;
   2554   if (b->enabled) {
   2555     if (dbg_bp_arm(s, b) != 0) {
   2556       dbg_errf(s, "failed to re-arm breakpoint %d", id);
   2557       return;
   2558     }
   2559   }
   2560   driver_printf("Breakpoint %d will skip the next %llu hits\n", id,
   2561                 (unsigned long long)count);
   2562 }
   2563 
   2564 static void dbg_cmd_delete(DbgState* s, int id) {
   2565   if (dbg_bp_remove(s, id) != 0) {
   2566     dbg_errf(s, "no breakpoint %d", id);
   2567   }
   2568 }
   2569 
   2570 static void dbg_cmd_set_enabled(DbgState* s, int id, int enable) {
   2571   Bp* b = dbg_bp_find(s, id);
   2572   if (!b) {
   2573     dbg_errf(s, "no breakpoint %d", id);
   2574     return;
   2575   }
   2576   if (enable && !b->enabled) {
   2577     if (dbg_bp_arm(s, b) != 0) {
   2578       dbg_errf(s, "failed to re-arm breakpoint %d", id);
   2579       return;
   2580     }
   2581     b->enabled = 1;
   2582   } else if (!enable && b->enabled) {
   2583     if (b->session_id) {
   2584       kit_dbg_session_breakpoint_clear(s->session, b->session_id);
   2585       b->session_id = 0;
   2586     }
   2587     b->enabled = 0;
   2588   }
   2589 }
   2590 
   2591 /* ============================================================
   2592  * Help
   2593  * ============================================================ */
   2594 
   2595 static void dbg_cmd_help(void) {
   2596   driver_printf(
   2597       "%.*s",
   2598       KIT_SLICE_ARG(KIT_SLICE_LIT(
   2599           "Commands (abbrev. shown):\n"
   2600           "  h, help                     show this help\n"
   2601           "  q, quit                     exit (Ctrl-D also works)\n"
   2602           "  :language c|asm|wasm/wat\n"
   2603           "                              select language for jit/expr input\n"
   2604           "  r, run                      start fresh execution at entry\n"
   2605           "  c, cont                     continue after a stop\n"
   2606           "  s, step                     step to next source line (into "
   2607           "calls)\n"
   2608           "  si, stepi                   single-step one instruction\n"
   2609           "  n, next                     step to next source line (over "
   2610           "calls)\n"
   2611           "  finish                      run until current frame returns\n"
   2612           "  jit [LANG|NAME] { ... }     compile and append a language "
   2613           "snippet\n"
   2614           "  { ... }                     same as jit { ... }\n"
   2615           "  edit [LANG|NAME], e [...]   edit and append a language snippet\n"
   2616           "  Ctrl-G                      edit the current input line in "
   2617           "$EDITOR\n"
   2618           "  expr EXPR | expr { ... }    compile and call an expression thunk\n"
   2619           "  EXPR                        same as expr EXPR\n"
   2620           "                              LANG defaults to the selected "
   2621           "language\n"
   2622           "  jump ADDR                   set PC to ADDR (no resume)\n"
   2623           "  bt, backtrace               print stack trace with arguments\n"
   2624           "  b LOC                       set breakpoint at LOC:\n"
   2625           "                                 0xADDR | sym[+off] | file.c:line\n"
   2626           "  ignore N COUNT              skip the next COUNT hits of bp N\n"
   2627           "  d N, delete N               delete breakpoint N\n"
   2628           "  enable N | disable N        toggle breakpoint N\n"
   2629           "  p NAME                      print variable / global\n"
   2630           "  set NAME VALUE              write VALUE into NAME\n"
   2631           "  x ADDR [count]              examine memory (count bytes, default "
   2632           "16)\n"
   2633           "  disasm [ADDR] [count], x/i  disassemble at PC or ADDR\n"
   2634           "  list FILE:LINE, l FILE:LINE source listing around FILE:LINE\n"
   2635           "  info b                      list breakpoints\n"
   2636           "  info reg, info registers    dump registers\n"
   2637           "  info locals                 list locals at current PC\n"
   2638           "  info args                   list args at current PC\n"
   2639           "  info functions [PATTERN]    list JIT functions matching PATTERN\n"
   2640           "  info variables [PATTERN]    list JIT globals  matching "
   2641           "PATTERN\n")));
   2642 }
   2643 
   2644 static KitLanguage dbg_default_language_from_inputs(KitCompiler* c,
   2645                                                     const DbgOpts* o) {
   2646   if (o->has_default_lang) return o->default_lang;
   2647   if (o->inputs.nsources) return kit_language_for_path(c, o->inputs.sources[0]);
   2648   if (o->inputs.nsource_memory) return o->inputs.source_memory[0].lang;
   2649   return KIT_LANG_C;
   2650 }
   2651 
   2652 /* ============================================================
   2653  * Tokenizer / dispatch
   2654  * ============================================================ */
   2655 
   2656 /* Trim leading whitespace and split off one whitespace-delimited word.
   2657  * Returns the start of the next region (i.e. the rest of the line, with
   2658  * its own leading whitespace skipped). The input buffer is mutated:
   2659  * the byte after the first word is replaced with a NUL. */
   2660 static char* dbg_take_word(char* line, char** word_out) {
   2661   char* p = line;
   2662   char* w;
   2663   while (*p && dbg_isspace((unsigned char)*p)) ++p;
   2664   if (!*p) {
   2665     *word_out = p;
   2666     return p;
   2667   }
   2668   w = p;
   2669   while (*p && !dbg_isspace((unsigned char)*p)) ++p;
   2670   if (*p) {
   2671     *p = '\0';
   2672     ++p;
   2673   }
   2674   while (*p && dbg_isspace((unsigned char)*p)) ++p;
   2675   *word_out = w;
   2676   return p;
   2677 }
   2678 
   2679 static int dbg_dispatch(DbgState* s, char* line) {
   2680   char raw[LINE_CAP];
   2681   size_t raw_len = driver_strlen(line);
   2682   char* cmd;
   2683   char* rest;
   2684 
   2685   if (raw_len >= sizeof(raw)) raw_len = sizeof(raw) - 1u;
   2686   driver_memcpy(raw, line, raw_len);
   2687   raw[raw_len] = '\0';
   2688 
   2689   rest = dbg_take_word(line, &cmd);
   2690 
   2691   if (!*cmd) return 0; /* blank line */
   2692 
   2693   if (driver_streq(cmd, "h") || driver_streq(cmd, "help")) {
   2694     dbg_cmd_help();
   2695     return 0;
   2696   }
   2697   if (driver_streq(cmd, ":language") || driver_streq(cmd, ":lang") ||
   2698       driver_streq(cmd, "language")) {
   2699     dbg_cmd_language(s, rest);
   2700     return 0;
   2701   }
   2702   if (driver_streq(cmd, "q") || driver_streq(cmd, "quit") ||
   2703       driver_streq(cmd, "exit")) {
   2704     return 1; /* signal "exit REPL" */
   2705   }
   2706   if (driver_streq(cmd, "r") || driver_streq(cmd, "run")) {
   2707     dbg_drive(s, RUN_FRESH);
   2708     return 0;
   2709   }
   2710   if (driver_streq(cmd, "abort")) {
   2711     if (s->has_stop && s->session) {
   2712       kit_dbg_session_resume(s->session, KIT_RESUME_ABORT, NULL);
   2713       s->has_stop = 0;
   2714       dbg_errf(s, "execution aborted");
   2715     } else {
   2716       dbg_errf(s, "nothing to abort");
   2717     }
   2718     return 0;
   2719   }
   2720   if (driver_streq(cmd, "c") || driver_streq(cmd, "cont") ||
   2721       driver_streq(cmd, "continue")) {
   2722     dbg_drive(s, RUN_CONTINUE);
   2723     return 0;
   2724   }
   2725   if (driver_streq(cmd, "s") || driver_streq(cmd, "step")) {
   2726     dbg_drive(s, RUN_STEP_LINE);
   2727     return 0;
   2728   }
   2729   if (driver_streq(cmd, "si") || driver_streq(cmd, "stepi")) {
   2730     dbg_drive(s, RUN_STEP_INSN);
   2731     return 0;
   2732   }
   2733   if (driver_streq(cmd, "n") || driver_streq(cmd, "next")) {
   2734     dbg_drive(s, RUN_NEXT_LINE);
   2735     return 0;
   2736   }
   2737   if (driver_streq(cmd, "finish")) {
   2738     dbg_drive(s, RUN_STEP_OUT);
   2739     return 0;
   2740   }
   2741   if (driver_streq(cmd, "jit")) {
   2742     dbg_cmd_jit(s, rest);
   2743     return 0;
   2744   }
   2745   if (driver_streq(cmd, "edit") || driver_streq(cmd, "e")) {
   2746     dbg_cmd_edit(s, rest);
   2747     return 0;
   2748   }
   2749   if (driver_streq(cmd, "expr")) {
   2750     if (!s->session) {
   2751       dbg_errf(s, "no JIT session");
   2752       return 0;
   2753     }
   2754     dbg_cmd_expr(s, rest);
   2755     return 0;
   2756   }
   2757   if (driver_streq(cmd, "bt") || driver_streq(cmd, "backtrace") ||
   2758       driver_streq(cmd, "where")) {
   2759     dbg_cmd_bt(s);
   2760     return 0;
   2761   }
   2762   if (driver_streq(cmd, "b") || driver_streq(cmd, "break")) {
   2763     char* loc;
   2764     dbg_take_word(rest, &loc);
   2765     dbg_cmd_break(s, loc);
   2766     return 0;
   2767   }
   2768   if (driver_streq(cmd, "info")) {
   2769     char* what;
   2770     rest = dbg_take_word(rest, &what);
   2771     if (driver_streq(what, "b") || driver_streq(what, "break") ||
   2772         driver_streq(what, "breakpoints")) {
   2773       dbg_cmd_info_b(s);
   2774     } else if (driver_streq(what, "reg") || driver_streq(what, "registers")) {
   2775       dbg_cmd_info_reg(s);
   2776     } else if (driver_streq(what, "locals")) {
   2777       dbg_cmd_info_vars(s, 1u << KIT_DVR_LOCAL, "locals");
   2778     } else if (driver_streq(what, "args")) {
   2779       dbg_cmd_info_vars(s, 1u << KIT_DVR_ARG, "args");
   2780     } else if (driver_streq(what, "functions") || driver_streq(what, "func")) {
   2781       char* pat;
   2782       dbg_take_word(rest, &pat);
   2783       dbg_cmd_info_syms(s, KIT_SK_FUNC, *pat ? pat : NULL);
   2784     } else if (driver_streq(what, "variables") || driver_streq(what, "var")) {
   2785       char* pat;
   2786       dbg_take_word(rest, &pat);
   2787       dbg_cmd_info_syms(s, KIT_SK_OBJ, *pat ? pat : NULL);
   2788     } else {
   2789       dbg_errf(s, "unknown 'info' subcommand: %.*s",
   2790                KIT_SLICE_ARG(kit_slice_cstr(what)));
   2791     }
   2792     return 0;
   2793   }
   2794   if (driver_streq(cmd, "set")) {
   2795     char* name;
   2796     char* val_s;
   2797     uint64_t v;
   2798     size_t used;
   2799     rest = dbg_take_word(rest, &name);
   2800     if (!*name) {
   2801       dbg_errf(s, "usage: set NAME VALUE");
   2802       return 0;
   2803     }
   2804     /* Accept an optional `=` between name and value: `set x = 5`. */
   2805     if (driver_streq(name, "=")) {
   2806       dbg_errf(s, "usage: set NAME VALUE");
   2807       return 0;
   2808     }
   2809     rest = dbg_take_word(rest, &val_s);
   2810     if (driver_streq(val_s, "=")) {
   2811       rest = dbg_take_word(rest, &val_s);
   2812     }
   2813     if (!*val_s) {
   2814       dbg_errf(s, "usage: set NAME VALUE");
   2815       return 0;
   2816     }
   2817     used = dbg_parse_uint(val_s, &v);
   2818     if (!used || val_s[used] != '\0') {
   2819       dbg_errf(s, "expected integer value, got '%.*s'",
   2820                KIT_SLICE_ARG(kit_slice_cstr(val_s)));
   2821       return 0;
   2822     }
   2823     dbg_cmd_set(s, name, v);
   2824     return 0;
   2825   }
   2826   if (driver_streq(cmd, "jump") || driver_streq(cmd, "j")) {
   2827     char* addr_s;
   2828     uint64_t addr;
   2829     size_t used;
   2830     dbg_take_word(rest, &addr_s);
   2831     if (!*addr_s) {
   2832       dbg_errf(s, "usage: jump ADDR");
   2833       return 0;
   2834     }
   2835     used = dbg_parse_uint(addr_s, &addr);
   2836     if (!used || addr_s[used] != '\0') {
   2837       dbg_errf(s, "bad address '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(addr_s)));
   2838       return 0;
   2839     }
   2840     dbg_cmd_jump(s, addr);
   2841     return 0;
   2842   }
   2843   if (driver_streq(cmd, "ignore")) {
   2844     char* id_s;
   2845     char* cnt_s;
   2846     uint64_t id;
   2847     uint64_t cnt;
   2848     size_t used;
   2849     rest = dbg_take_word(rest, &id_s);
   2850     dbg_take_word(rest, &cnt_s);
   2851     if (!*id_s || !*cnt_s) {
   2852       dbg_errf(s, "usage: ignore N COUNT");
   2853       return 0;
   2854     }
   2855     used = dbg_parse_uint(id_s, &id);
   2856     if (!used || id_s[used] != '\0') {
   2857       dbg_errf(s, "expected breakpoint id");
   2858       return 0;
   2859     }
   2860     used = dbg_parse_uint(cnt_s, &cnt);
   2861     if (!used || cnt_s[used] != '\0') {
   2862       dbg_errf(s, "expected hit count");
   2863       return 0;
   2864     }
   2865     dbg_cmd_ignore(s, (int)id, cnt);
   2866     return 0;
   2867   }
   2868   if (driver_streq(cmd, "d") || driver_streq(cmd, "delete")) {
   2869     char* arg;
   2870     uint64_t id;
   2871     size_t used;
   2872     dbg_take_word(rest, &arg);
   2873     if (!*arg) {
   2874       dbg_errf(s, "usage: d <id>");
   2875       return 0;
   2876     }
   2877     used = dbg_parse_uint(arg, &id);
   2878     if (!used || arg[used] != '\0') {
   2879       dbg_errf(s, "expected breakpoint id");
   2880       return 0;
   2881     }
   2882     dbg_cmd_delete(s, (int)id);
   2883     return 0;
   2884   }
   2885   if (driver_streq(cmd, "enable") || driver_streq(cmd, "disable")) {
   2886     char* arg;
   2887     uint64_t id;
   2888     size_t used;
   2889     dbg_take_word(rest, &arg);
   2890     used = dbg_parse_uint(arg, &id);
   2891     if (!used || arg[used] != '\0') {
   2892       dbg_errf(s, "expected breakpoint id");
   2893       return 0;
   2894     }
   2895     dbg_cmd_set_enabled(s, (int)id, driver_streq(cmd, "enable"));
   2896     return 0;
   2897   }
   2898   if (driver_streq(cmd, "p") || driver_streq(cmd, "print")) {
   2899     char* name;
   2900     dbg_take_word(rest, &name);
   2901     if (!*name) {
   2902       dbg_errf(s, "usage: p <name>");
   2903       return 0;
   2904     }
   2905     dbg_cmd_print(s, name);
   2906     return 0;
   2907   }
   2908   if (driver_streq(cmd, "list") || driver_streq(cmd, "l")) {
   2909     char* loc;
   2910     dbg_take_word(rest, &loc);
   2911     if (!*loc) {
   2912       dbg_errf(s, "usage: list file:line");
   2913       return 0;
   2914     }
   2915     dbg_cmd_list(s, loc);
   2916     return 0;
   2917   }
   2918   if (driver_streq(cmd, "disasm") || driver_streq(cmd, "x/i")) {
   2919     char* addr_s;
   2920     char* count_s;
   2921     uint64_t addr = s->has_stop ? s->last_stop.regs.pc : 0;
   2922     uint64_t count = 8;
   2923     size_t used;
   2924     rest = dbg_take_word(rest, &addr_s);
   2925     if (*addr_s) {
   2926       used = dbg_parse_uint(addr_s, &addr);
   2927       if (!used || addr_s[used] != '\0') {
   2928         dbg_errf(s, "bad address '%.*s'",
   2929                  KIT_SLICE_ARG(kit_slice_cstr(addr_s)));
   2930         return 0;
   2931       }
   2932       dbg_take_word(rest, &count_s);
   2933       if (*count_s) {
   2934         used = dbg_parse_uint(count_s, &count);
   2935         if (!used || count_s[used] != '\0') {
   2936           dbg_errf(s, "bad count '%.*s'",
   2937                    KIT_SLICE_ARG(kit_slice_cstr(count_s)));
   2938           return 0;
   2939         }
   2940       }
   2941     }
   2942     dbg_cmd_disasm(s, addr, (size_t)count);
   2943     return 0;
   2944   }
   2945   if (driver_streq(cmd, "x") || driver_streq(cmd, "examine")) {
   2946     char* addr_s;
   2947     char* count_s;
   2948     uint64_t addr;
   2949     uint64_t count = 16;
   2950     size_t used;
   2951     rest = dbg_take_word(rest, &addr_s);
   2952     if (!*addr_s) {
   2953       dbg_errf(s, "usage: x <addr> [count]");
   2954       return 0;
   2955     }
   2956     used = dbg_parse_uint(addr_s, &addr);
   2957     if (!used || addr_s[used] != '\0') {
   2958       dbg_errf(s, "bad address '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(addr_s)));
   2959       return 0;
   2960     }
   2961     dbg_take_word(rest, &count_s);
   2962     if (*count_s) {
   2963       used = dbg_parse_uint(count_s, &count);
   2964       if (!used || count_s[used] != '\0') {
   2965         dbg_errf(s, "bad count '%.*s'", KIT_SLICE_ARG(kit_slice_cstr(count_s)));
   2966         return 0;
   2967       }
   2968     }
   2969     dbg_cmd_examine(s, addr, (size_t)count);
   2970     return 0;
   2971   }
   2972 
   2973   if (cmd[0] == '{') {
   2974     dbg_cmd_jit(s, raw);
   2975     return 0;
   2976   }
   2977 
   2978   if (s->session) {
   2979     dbg_cmd_expr(s, raw);
   2980   } else {
   2981     dbg_errf(s, "unknown command: %.*s (try 'h')",
   2982              KIT_SLICE_ARG(kit_slice_cstr(cmd)));
   2983   }
   2984   return 0;
   2985 }
   2986 
   2987 /* ============================================================
   2988  * Completion
   2989  * ============================================================ */
   2990 
   2991 static int dbg_slice_has_prefix(KitSlice s, const char* prefix,
   2992                                 size_t prefix_len) {
   2993   return s.len >= prefix_len && memcmp(s.s, prefix, prefix_len) == 0;
   2994 }
   2995 
   2996 static int dbg_cstr_has_prefix(const char* s, const char* prefix,
   2997                                size_t prefix_len) {
   2998   return driver_strlen(s) >= prefix_len && memcmp(s, prefix, prefix_len) == 0;
   2999 }
   3000 
   3001 static int dbg_completion_seen(DriverLineCompletionList* out, const char* text,
   3002                                size_t len) {
   3003   uint32_t i;
   3004   for (i = 0; i < out->count; ++i) {
   3005     if (driver_strlen(out->items[i].text) == len &&
   3006         memcmp(out->items[i].text, text, len) == 0)
   3007       return 1;
   3008   }
   3009   return 0;
   3010 }
   3011 
   3012 static void dbg_completion_add_cstr(DriverLineCompletionList* out,
   3013                                     const char* prefix, size_t prefix_len,
   3014                                     const char* text) {
   3015   size_t len = driver_strlen(text);
   3016   if (!dbg_cstr_has_prefix(text, prefix, prefix_len)) return;
   3017   if (dbg_completion_seen(out, text, len)) return;
   3018   (void)driver_line_completion_add(out, text, len);
   3019 }
   3020 
   3021 static void dbg_completion_add_slice(DriverLineCompletionList* out,
   3022                                      const char* prefix, size_t prefix_len,
   3023                                      KitSlice text) {
   3024   if (!text.s || !dbg_slice_has_prefix(text, prefix, prefix_len)) return;
   3025   if (dbg_completion_seen(out, text.s, text.len)) return;
   3026   (void)driver_line_completion_add(out, text.s, text.len);
   3027 }
   3028 
   3029 static void dbg_completion_add_u64(DriverLineCompletionList* out,
   3030                                    const char* prefix, size_t prefix_len,
   3031                                    uint64_t v) {
   3032   char buf[32];
   3033   size_t n = 0;
   3034   char rev[32];
   3035   size_t r = 0;
   3036   if (v == 0) {
   3037     rev[r++] = '0';
   3038   } else {
   3039     while (v && r < sizeof(rev)) {
   3040       rev[r++] = (char)('0' + (v % 10u));
   3041       v /= 10u;
   3042     }
   3043   }
   3044   while (r > 0 && n + 1u < sizeof(buf)) buf[n++] = rev[--r];
   3045   buf[n] = '\0';
   3046   dbg_completion_add_cstr(out, prefix, prefix_len, buf);
   3047 }
   3048 
   3049 static void dbg_word_bounds(const char* line, size_t cursor, size_t* start,
   3050                             size_t* end) {
   3051   size_t len = driver_strlen(line);
   3052   size_t s = cursor < len ? cursor : len;
   3053   size_t e = s;
   3054   while (s > 0 && !dbg_isspace((unsigned char)line[s - 1])) --s;
   3055   while (e < len && !dbg_isspace((unsigned char)line[e])) ++e;
   3056   *start = s;
   3057   *end = e;
   3058 }
   3059 
   3060 static size_t dbg_read_word_at(const char* line, size_t start, char* out,
   3061                                size_t cap) {
   3062   size_t i = start;
   3063   size_t n = 0;
   3064   while (line[i] && dbg_isspace((unsigned char)line[i])) ++i;
   3065   while (line[i] && !dbg_isspace((unsigned char)line[i])) {
   3066     if (n + 1u < cap) out[n++] = line[i];
   3067     ++i;
   3068   }
   3069   if (cap) out[n] = '\0';
   3070   return n;
   3071 }
   3072 
   3073 static void dbg_complete_commands(DriverLineCompletionList* out,
   3074                                   const char* prefix, size_t prefix_len) {
   3075   static const char* const cmds[] = {
   3076       "help",   "quit",     "exit",      "run",     "cont",  "continue",
   3077       "step",   "stepi",    "next",      "finish",  "jit",   "edit",
   3078       "expr",   "bt",       "backtrace", "where",   "break", "info",
   3079       "ignore", "delete",   "enable",    "disable", "print", "set",
   3080       "jump",   "list",     "disasm",    "x",       "x/i",   ":language",
   3081       ":lang",  "language", "abort",     "h",       "q",     "r",
   3082       "c",      "s",        "si",        "n",       "b",     "d",
   3083       "p",      "l",        "e",
   3084   };
   3085   size_t i;
   3086   for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); ++i)
   3087     dbg_completion_add_cstr(out, prefix, prefix_len, cmds[i]);
   3088 }
   3089 
   3090 static void dbg_complete_info(DriverLineCompletionList* out, const char* prefix,
   3091                               size_t prefix_len) {
   3092   static const char* const words[] = {
   3093       "b",    "break",     "breakpoints", "reg",       "registers", "locals",
   3094       "args", "functions", "func",        "variables", "var",
   3095   };
   3096   size_t i;
   3097   for (i = 0; i < sizeof(words) / sizeof(words[0]); ++i)
   3098     dbg_completion_add_cstr(out, prefix, prefix_len, words[i]);
   3099 }
   3100 
   3101 static void dbg_complete_languages(DriverLineCompletionList* out,
   3102                                    const char* prefix, size_t prefix_len) {
   3103   static const char* const langs[] = {"c", "asm", "wasm", "wat"};
   3104   size_t i;
   3105   for (i = 0; i < sizeof(langs) / sizeof(langs[0]); ++i)
   3106     dbg_completion_add_cstr(out, prefix, prefix_len, langs[i]);
   3107 }
   3108 
   3109 static void dbg_complete_symbols(DbgState* s, DriverLineCompletionList* out,
   3110                                  const char* prefix, size_t prefix_len,
   3111                                  int funcs, int objects) {
   3112   KitJitSymIter* it = NULL;
   3113   KitJitSym sym;
   3114   if (!s->jit || kit_jit_sym_iter_new(s->jit, &it) != KIT_OK) return;
   3115   for (;;) {
   3116     KitIterResult r = kit_jit_sym_iter_next(it, &sym);
   3117     if (r != KIT_ITER_ITEM) break;
   3118     if ((sym.kind == KIT_SK_FUNC && funcs) ||
   3119         (sym.kind == KIT_SK_OBJ && objects))
   3120       dbg_completion_add_slice(out, prefix, prefix_len, sym.name);
   3121   }
   3122   kit_jit_sym_iter_free(it);
   3123 }
   3124 
   3125 static void dbg_complete_locals(DbgState* s, DriverLineCompletionList* out,
   3126                                 const char* prefix, size_t prefix_len) {
   3127   KitDwarfVarIter* it = NULL;
   3128   KitDwarfVar v;
   3129   if (!s->has_stop || !s->dwarf) return;
   3130   if (kit_dwarf_vars_at_new(s->dwarf, dbg_pc_rt_to_img(s, s->last_stop.regs.pc),
   3131                             KIT_DVRM_LOCAL | KIT_DVRM_ARG, &it) != KIT_OK)
   3132     return;
   3133   for (;;) {
   3134     KitIterResult r = kit_dwarf_vars_at_next(it, &v);
   3135     if (r != KIT_ITER_ITEM) break;
   3136     dbg_completion_add_slice(out, prefix, prefix_len, v.name);
   3137   }
   3138   kit_dwarf_vars_at_free(it);
   3139 }
   3140 
   3141 static void dbg_complete_files(DbgState* s, DriverLineCompletionList* out,
   3142                                const char* prefix, size_t prefix_len) {
   3143   KitDwarfCuIter* cui = NULL;
   3144   KitDwarfCu cu;
   3145   if (!s->dwarf) return;
   3146   if (kit_dwarf_cu_iter_new(s->dwarf, &cui) != KIT_OK) return;
   3147   for (;;) {
   3148     KitDwarfLineIter* li = NULL;
   3149     KitDwarfLineRow row;
   3150     KitIterResult cr = kit_dwarf_cu_iter_next(cui, &cu);
   3151     if (cr != KIT_ITER_ITEM) break;
   3152     if (kit_dwarf_line_iter_new(s->dwarf, cu.offset, &li) != KIT_OK) continue;
   3153     for (;;) {
   3154       KitSlice file = KIT_SLICE_NULL;
   3155       KitIterResult lr = kit_dwarf_line_iter_next(li, &row);
   3156       if (lr != KIT_ITER_ITEM) break;
   3157       if (kit_dwarf_line_file(s->dwarf, cu.offset, row.file_index, &file) ==
   3158           KIT_OK)
   3159         dbg_completion_add_slice(out, prefix, prefix_len, file);
   3160     }
   3161     kit_dwarf_line_iter_free(li);
   3162   }
   3163   kit_dwarf_cu_iter_free(cui);
   3164 }
   3165 
   3166 static void dbg_complete_breakpoint_ids(DbgState* s,
   3167                                         DriverLineCompletionList* out,
   3168                                         const char* prefix, size_t prefix_len) {
   3169   uint32_t i;
   3170   for (i = 0; i < s->nbps; ++i)
   3171     dbg_completion_add_u64(out, prefix, prefix_len, (uint64_t)s->bps[i].id);
   3172 }
   3173 
   3174 static void dbg_complete(void* user, const char* line, size_t cursor,
   3175                          DriverLineCompletionList* out) {
   3176   DbgState* s = (DbgState*)user;
   3177   char cmd[WORD_CAP];
   3178   char arg1[WORD_CAP];
   3179   size_t start, end;
   3180   size_t prefix_len;
   3181   const char* prefix;
   3182 
   3183   dbg_word_bounds(line, cursor, &start, &end);
   3184   out->replace_start = start;
   3185   out->replace_end = end;
   3186   prefix = line + start;
   3187   prefix_len = cursor > start ? cursor - start : 0;
   3188 
   3189   dbg_read_word_at(line, 0, cmd, sizeof(cmd));
   3190   if (start == 0 || cmd[0] == '\0') {
   3191     dbg_complete_commands(out, prefix, prefix_len);
   3192     return;
   3193   }
   3194 
   3195   if (driver_streq(cmd, "info")) {
   3196     dbg_read_word_at(line, start == 0 ? 0 : driver_strlen(cmd), arg1,
   3197                      sizeof(arg1));
   3198     if (arg1[0] == '\0' || start <= driver_strlen(cmd) + 1u) {
   3199       dbg_complete_info(out, prefix, prefix_len);
   3200       return;
   3201     }
   3202   }
   3203 
   3204   if (driver_streq(cmd, ":language") || driver_streq(cmd, ":lang") ||
   3205       driver_streq(cmd, "language")) {
   3206     dbg_complete_languages(out, prefix, prefix_len);
   3207     return;
   3208   }
   3209   if (driver_streq(cmd, "jit") || driver_streq(cmd, "edit") ||
   3210       driver_streq(cmd, "e")) {
   3211     dbg_complete_languages(out, prefix, prefix_len);
   3212     dbg_complete_symbols(s, out, prefix, prefix_len, 1, 0);
   3213     return;
   3214   }
   3215   if (driver_streq(cmd, "b") || driver_streq(cmd, "break")) {
   3216     dbg_complete_symbols(s, out, prefix, prefix_len, 1, 0);
   3217     dbg_complete_files(s, out, prefix, prefix_len);
   3218     return;
   3219   }
   3220   if (driver_streq(cmd, "list") || driver_streq(cmd, "l")) {
   3221     dbg_complete_files(s, out, prefix, prefix_len);
   3222     return;
   3223   }
   3224   if (driver_streq(cmd, "p") || driver_streq(cmd, "print") ||
   3225       driver_streq(cmd, "set")) {
   3226     dbg_complete_locals(s, out, prefix, prefix_len);
   3227     dbg_complete_symbols(s, out, prefix, prefix_len, 0, 1);
   3228     return;
   3229   }
   3230   if (driver_streq(cmd, "d") || driver_streq(cmd, "delete") ||
   3231       driver_streq(cmd, "enable") || driver_streq(cmd, "disable") ||
   3232       driver_streq(cmd, "ignore")) {
   3233     dbg_complete_breakpoint_ids(s, out, prefix, prefix_len);
   3234     return;
   3235   }
   3236 
   3237   dbg_complete_locals(s, out, prefix, prefix_len);
   3238   dbg_complete_symbols(s, out, prefix, prefix_len, 1, 1);
   3239 }
   3240 
   3241 /* ============================================================
   3242  * Script source helpers
   3243  * ============================================================ */
   3244 
   3245 typedef enum DbgScriptResult {
   3246   DBG_SCRIPT_CONTINUE = 0,
   3247   DBG_SCRIPT_QUIT,
   3248   DBG_SCRIPT_ERROR,
   3249 } DbgScriptResult;
   3250 
   3251 /* Execute a single command string as if typed at the REPL. */
   3252 static DbgScriptResult dbg_run_script_cmd(DbgState* s, const char* cmd) {
   3253   char line[LINE_CAP];
   3254   size_t n = driver_strlen(cmd);
   3255   int errors = s->error_count;
   3256   int quit;
   3257   if (n >= LINE_CAP) {
   3258     dbg_errf(s, "script command exceeds %u bytes", (unsigned)(LINE_CAP - 1u));
   3259     return DBG_SCRIPT_ERROR;
   3260   }
   3261   driver_memcpy(line, cmd, n);
   3262   line[n] = '\0';
   3263   quit = dbg_dispatch(s, line);
   3264   if (quit) return DBG_SCRIPT_QUIT;
   3265   return s->error_count != errors ? DBG_SCRIPT_ERROR : DBG_SCRIPT_CONTINUE;
   3266 }
   3267 
   3268 /* Execute debugger commands from a file, one per line. */
   3269 static DbgScriptResult dbg_run_script_file(DbgState* s, const char* path) {
   3270   DriverLoad load = {0};
   3271   KitSlice bytes;
   3272   const char* p;
   3273   const char* end;
   3274   char line[LINE_CAP];
   3275   uint64_t line_no = 0;
   3276   if (driver_load_bytes(&s->env->file_io, DBG_TOOL, path, &load, &bytes) != 0) {
   3277     ++s->error_count; /* driver_load_bytes already emitted the diagnostic */
   3278     return DBG_SCRIPT_ERROR;
   3279   }
   3280   p = (const char*)bytes.data;
   3281   end = p + bytes.len;
   3282   while (p < end) {
   3283     const char* nl = p;
   3284     size_t len;
   3285     int errors = s->error_count;
   3286     int quit;
   3287     ++line_no;
   3288     while (nl < end && *nl != '\n') nl++;
   3289     len = (size_t)(nl - p);
   3290     /* Strip trailing carriage return. */
   3291     if (len > 0 && p[len - 1] == '\r') len--;
   3292     if (len >= LINE_CAP) {
   3293       dbg_errf(s, "%s:%llu: script line exceeds %u bytes", path,
   3294                (unsigned long long)line_no, (unsigned)(LINE_CAP - 1u));
   3295       driver_release_bytes(&s->env->file_io, &load);
   3296       return DBG_SCRIPT_ERROR;
   3297     }
   3298     driver_memcpy(line, p, len);
   3299     line[len] = '\0';
   3300     p = (nl < end) ? nl + 1 : end;
   3301     quit = dbg_dispatch(s, line);
   3302     if (quit) {
   3303       driver_release_bytes(&s->env->file_io, &load);
   3304       return DBG_SCRIPT_QUIT;
   3305     }
   3306     if (s->error_count != errors) {
   3307       driver_release_bytes(&s->env->file_io, &load);
   3308       return DBG_SCRIPT_ERROR;
   3309     }
   3310   }
   3311   driver_release_bytes(&s->env->file_io, &load);
   3312   return DBG_SCRIPT_CONTINUE;
   3313 }
   3314 
   3315 /* ============================================================
   3316  * REPL
   3317  * ============================================================ */
   3318 
   3319 static void dbg_repl(DbgState* s) {
   3320   char line[LINE_CAP];
   3321   uint32_t i;
   3322 
   3323   if (!s->batch_mode) driver_printf("kit dbg — 'h' for help, 'q' to quit\n");
   3324 
   3325   /* Phase 1: drain --script / --command sources in order. */
   3326   for (i = 0; i < s->nscript_entries; i++) {
   3327     DbgScriptEntry* e = &s->script_entries[i];
   3328     DbgScriptResult result =
   3329         (e->kind == DBG_ENTRY_FILE) ? dbg_run_script_file(s, e->value)
   3330                                    : dbg_run_script_cmd(s, e->value);
   3331     if (result == DBG_SCRIPT_QUIT) return;
   3332     if (result == DBG_SCRIPT_ERROR) {
   3333       s->script_failed = 1;
   3334       return;
   3335     }
   3336   }
   3337 
   3338   /* Phase 2: in --batch mode, exit without falling through to stdin. */
   3339   if (s->batch_mode) return;
   3340 
   3341   /* Phase 3: interactive loop. */
   3342   for (;;) {
   3343     int n;
   3344     char edit_suffix[DBG_JIT_SUFFIX_CAP];
   3345     /* Ctrl-G edits the typed line in $EDITOR; tag the temp file with the
   3346      * current language's extension so editors highlight it. Recomputed each
   3347      * iteration since `:language` can change the default mid-session. */
   3348     n = driver_read_line_edit(
   3349         s->env, "(kit) ", line, sizeof(line), &s->line_history, dbg_complete, s,
   3350         dbg_jit_language_suffix(s->compiler, s->default_jit_lang, edit_suffix,
   3351                                 sizeof edit_suffix));
   3352     if (n == 0) { /* EOF — Ctrl-D */
   3353       driver_printf("\n");
   3354       return;
   3355     }
   3356     if (n == -1) {
   3357       dbg_errf(s, "stdin read error");
   3358       return;
   3359     }
   3360     if (n == -2) { /* SIGINT during prompt */
   3361       driver_printf("\n");
   3362       continue;
   3363     }
   3364     if (dbg_dispatch(s, line)) return;
   3365   }
   3366 }
   3367 
   3368 /* ============================================================
   3369  * Top-level entry
   3370  * ============================================================ */
   3371 
   3372 static int dbg_init_frontends(DbgOpts* o, const KitContext* ctx,
   3373                               const KitDriverExtension* ext) {
   3374   if (kit_frontend_registry_new(ctx, &o->frontends) != KIT_OK ||
   3375       kit_frontend_registry_add_builtin(o->frontends) != KIT_OK ||
   3376       (ext && ext->register_frontends &&
   3377        ext->register_frontends(o->frontends) != KIT_OK)) {
   3378     driver_errf(DBG_TOOL, "failed to initialize frontend registry");
   3379     return 1;
   3380   }
   3381   return 0;
   3382 }
   3383 
   3384 int driver_dbg_ex(int argc, char** argv, const KitDriverExtension* ext) {
   3385   DriverEnv env;
   3386   DbgOpts o = {0};
   3387   KitCompiler* compiler = NULL;
   3388   KitTarget* target = NULL;
   3389   KitJit* jit = NULL;
   3390   DbgState st = {0};
   3391   KitContext ctx;
   3392   KitJitHost jhost;
   3393   KitDbgHost dhost;
   3394   int rc;
   3395 
   3396   if (driver_argv_wants_help(argc, argv, 1)) {
   3397     driver_help_dbg();
   3398     return 0;
   3399   }
   3400 
   3401   driver_env_init(&env);
   3402   o.env = &env;
   3403   ctx = driver_env_to_context(&env);
   3404 
   3405   if (dbg_init_frontends(&o, &ctx, ext) != 0) {
   3406     dbg_options_release(&o);
   3407     driver_env_fini(&env);
   3408     return 1;
   3409   }
   3410 
   3411   if (dbg_parse(argc, argv, &o) != 0) {
   3412     dbg_options_release(&o);
   3413     driver_env_fini(&env);
   3414     return 2;
   3415   }
   3416 
   3417   jhost = driver_env_to_jit_host(&env);
   3418   dhost = driver_env_to_dbg_host(&env);
   3419   {
   3420     KitTargetOptions topts;
   3421     memset(&topts, 0, sizeof topts);
   3422     topts.spec = driver_host_target();
   3423     if (kit_target_new(&ctx, &topts, &target) != KIT_OK) {
   3424       driver_errf(DBG_TOOL, "failed to initialize compiler");
   3425       kit_target_free(target);
   3426       dbg_options_release(&o);
   3427       driver_env_fini(&env);
   3428       return 1;
   3429     }
   3430     {
   3431       KitCompilerOptions copts;
   3432       memset(&copts, 0, sizeof copts);
   3433       copts.frontends = o.frontends;
   3434       if (kit_compiler_new_ex(target, &ctx, &copts, &compiler) != KIT_OK) {
   3435         driver_errf(DBG_TOOL, "failed to initialize compiler");
   3436         kit_target_free(target);
   3437         dbg_options_release(&o);
   3438         driver_env_fini(&env);
   3439         return 1;
   3440       }
   3441       driver_diag_set_compiler(compiler);
   3442     }
   3443   }
   3444 
   3445   rc = dbg_compile_and_jit(&o, compiler, &jhost, &jit);
   3446   if (rc != 0) {
   3447     driver_compiler_free(compiler);
   3448     kit_target_free(target);
   3449     dbg_options_release(&o);
   3450     driver_env_fini(&env);
   3451     return rc;
   3452   }
   3453 
   3454   st.env = &env;
   3455   st.compiler = compiler;
   3456   st.ctx = ctx;
   3457   dbg_fill_compile_options(&o, &st.copts, &st.pp);
   3458   st.jit = jit;
   3459   st.default_jit_lang = dbg_default_language_from_inputs(compiler, &o);
   3460   st.default_jit_name = dbg_jit_default_name(st.compiler, st.default_jit_lang,
   3461                                              st.default_jit_name_buf,
   3462                                              sizeof st.default_jit_name_buf);
   3463   st.prog_argc = (int)o.prog_argc;
   3464   st.prog_argv = o.prog_argv;
   3465   st.entry_name = o.entry;
   3466   st.script_entries = o.script_entries;
   3467   st.nscript_entries = o.nscript_entries;
   3468   st.batch_mode = o.batch_mode;
   3469 
   3470   if (driver_inputs_count(&o.inputs) != 0) {
   3471     st.entry_addr = kit_jit_lookup(jit, kit_slice_cstr(o.entry));
   3472     if (!st.entry_addr) {
   3473       driver_errf(DBG_TOOL, "entry symbol not found: %.*s",
   3474                   KIT_SLICE_ARG(kit_slice_cstr(o.entry)));
   3475       kit_jit_free(jit);
   3476       driver_compiler_free(compiler);
   3477       kit_target_free(target);
   3478       dbg_options_release(&o);
   3479       driver_env_fini(&env);
   3480       return 1;
   3481     }
   3482   }
   3483 
   3484   if (kit_dbg_session_new(jit, &dhost, &st.session) != KIT_OK) {
   3485     driver_errf(DBG_TOOL,
   3486                 "JIT session not yet implemented in libkit — "
   3487                 "REPL will start in degraded mode (commands will surface "
   3488                 "'session implementation pending' until the lib lands)");
   3489     st.session = NULL;
   3490     /* Keep going so the surrounding driver path is exercised. */
   3491   }
   3492 
   3493   st.view = kit_jit_view(jit);
   3494   if (st.view) {
   3495     if (kit_dwarf_open(&ctx, st.view, &st.dwarf) != KIT_OK) st.dwarf = NULL;
   3496     if (st.dwarf && st.session) {
   3497       kit_dbg_session_attach_dwarf(st.session, st.dwarf);
   3498     }
   3499   }
   3500 
   3501   dbg_repl(&st);
   3502 
   3503   driver_line_history_fini(&env, &st.line_history);
   3504   dbg_bps_release_all(&st);
   3505   dbg_compile_sessions_release(&st);
   3506   dbg_sources_release_all(&st);
   3507   if (st.fmt_writer) st.fmt_writer->close(st.fmt_writer);
   3508   if (st.dwarf) kit_dwarf_free(st.dwarf);
   3509   if (st.session) kit_dbg_session_free(st.session);
   3510   kit_jit_free(jit);
   3511   driver_compiler_free(compiler);
   3512   kit_target_free(target);
   3513   dbg_options_release(&o);
   3514   driver_env_fini(&env);
   3515   return (st.script_failed || (st.batch_mode && st.error_count > 0)) ? 1 : 0;
   3516 }
   3517 
   3518 int driver_dbg(int argc, char** argv) { return driver_dbg_ex(argc, argv, NULL); }