kit

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

session.c (20137B)


      1 /* KitDebugSession lifecycle, worker handshake, and fault classification.
      2  *
      3  * The session owns a single worker thread that runs the JIT'd entry. The
      4  * REPL thread and worker thread coordinate through two events (resume,
      5  * stop) and one shared KitStopInfo slot. Every fault on the worker
      6  * (trap / SIGSEGV / SIGBUS / SIGILL / SIGFPE / interrupt_signo) drops into
      7  * on_fault here; this TU is also the only place that touches the public
      8  * KitDebugSession entries. */
      9 
     10 #include <string.h>
     11 
     12 #include "dbg/dbg.h"
     13 
     14 /* ---- fault classification ------------------------------------------- */
     15 
     16 static KitStopReason stop_reason_for_step(KitResumeMode mode) {
     17   switch (mode) {
     18     case KIT_RESUME_STEP_INSN:
     19       return KIT_STOP_REASON_STEP_INSN;
     20     case KIT_RESUME_STEP_LINE:
     21       return KIT_STOP_REASON_STEP_LINE;
     22     case KIT_RESUME_NEXT_LINE:
     23       return KIT_STOP_REASON_NEXT_LINE;
     24     case KIT_RESUME_STEP_OUT:
     25       return KIT_STOP_REASON_STEP_OUT;
     26     case KIT_RESUME_CONTINUE:
     27     case KIT_RESUME_ABORT:
     28       break;
     29   }
     30   return KIT_STOP_REASON_UNKNOWN;
     31 }
     32 
     33 static KitStatus on_fault(void* session_v, int signo, KitUnwindFrame* regs) {
     34   KitDebugSession* s = (KitDebugSession*)session_v;
     35   uint64_t bp_addr;
     36   u32 idx;
     37   DbgBp* bp;
     38   /* When a user bp hits its max_hits limit we surface the stop with a valid
     39    * bp_id and defer clearing the patch until the post-park path below, so the
     40    * id is still resolvable while the driver inspects the stop. 0 = no clear. */
     41   u32 auto_clear_user_id = 0;
     42 
     43   if (!s) return KIT_INVALID;
     44 
     45   /* Snapshot the regs into our stop slot up-front. */
     46   memcpy(&s->stop.regs, regs, sizeof(*regs));
     47   s->stop.signal = signo;
     48   s->stop.exit_code = 0;
     49   s->stop.bp_id = 0;
     50   s->stop.reason = KIT_STOP_REASON_UNKNOWN;
     51 
     52   /* Interrupt — host requested via thread_interrupt. */
     53   if (s->os->interrupt_signo != 0 && signo == s->os->interrupt_signo) {
     54     s->stop.kind = KIT_STOP_INTERRUPT;
     55     s->stop.reason = KIT_STOP_REASON_INTERRUPT;
     56     goto park;
     57   }
     58 
     59   /* Breakpoint? The arch layer owns trap-PC normalization (e.g. x86 INT3
     60    * reports the PC after the trap byte). Keep non-breakpoint signals at the
     61    * raw PC if no patch is found. */
     62   bp_addr = regs->pc;
     63   if (s->arch_dbg && s->arch_dbg->breakpoint_addr_from_fault_pc)
     64     bp_addr = s->arch_dbg->breakpoint_addr_from_fault_pc(regs->pc);
     65   idx = dbg_bp_lookup_index(s, bp_addr);
     66   if (idx) {
     67     bp = dbg_bp_at_index(s, idx);
     68     s->stop.regs.pc = bp_addr;
     69 
     70     /* Displaced-step sentinel: complete the step and either resume
     71      * silently (auto_continue path inside dbg_step_resume) or surface
     72      * a generic stop for STEP_INSN. */
     73     if (bp && bp->internal && s->displaced.return_pc == bp_addr) {
     74       dbg_displaced_finalize(s);
     75       /* Sync the on-stack regs with the corrected PC so the OS layer
     76        * writes it back into ucontext on return. */
     77       regs->pc = s->stop.regs.pc;
     78       if (s->pending_step_pending) {
     79         /* CONTINUE-over-bp: do not park; just resume. */
     80         s->pending_step_pending = 0;
     81         return KIT_OK;
     82       }
     83       s->stop.kind = KIT_STOP_BREAKPOINT;
     84       s->stop.bp_id = 0;
     85       s->stop.reason = stop_reason_for_step(s->pending_mode);
     86       goto park;
     87     }
     88 
     89     /* Plain internal bp (e.g. fallback one-shot at PC+4): treat like
     90      * a step completion. */
     91     if (bp && bp->internal) {
     92       /* Clear it so it doesn't linger across resumes. */
     93       dbg_bp_clear(s, bp->user_id);
     94       if (s->pending_step_pending) {
     95         s->pending_step_pending = 0;
     96         return KIT_OK;
     97       }
     98       s->stop.kind = KIT_STOP_BREAKPOINT;
     99       s->stop.bp_id = 0;
    100       s->stop.reason = stop_reason_for_step(s->pending_mode);
    101       goto park;
    102     }
    103 
    104     /* User-visible bp. Apply skip_count / condition / max_hits. */
    105     if (bp) {
    106       bp->hit_count++;
    107       if (bp->hit_count <= bp->skip_count) {
    108         /* Silent skip: re-step over the patch so the original insn
    109          * runs and execution continues without notifying the REPL.
    110          * Implemented by arming a displaced step then deferring the
    111          * "do not park" decision via pending_step_pending. */
    112         s->pending_step_pending = 1;
    113         if (dbg_step_resume(s, KIT_RESUME_CONTINUE) != KIT_OK) {
    114           /* Couldn't arm a step; surface the stop after all. */
    115           s->pending_step_pending = 0;
    116           s->stop.kind = KIT_STOP_BREAKPOINT;
    117           s->stop.bp_id = bp->user_id;
    118           s->stop.reason = KIT_STOP_REASON_USER_BREAKPOINT;
    119           goto park;
    120         }
    121         /* Apply any PC override the prepare-step path set. */
    122         if (s->pending_has_pc) {
    123           regs->pc = s->pending_pc_override;
    124           s->pending_has_pc = 0;
    125         }
    126         return KIT_OK;
    127       }
    128       if (bp->condition && bp->condition(bp->condition_user, regs) == 0) {
    129         /* Condition rejected: same silent-resume path. */
    130         s->pending_step_pending = 1;
    131         if (dbg_step_resume(s, KIT_RESUME_CONTINUE) != KIT_OK) {
    132           s->pending_step_pending = 0;
    133           s->stop.kind = KIT_STOP_BREAKPOINT;
    134           s->stop.bp_id = bp->user_id;
    135           s->stop.reason = KIT_STOP_REASON_USER_BREAKPOINT;
    136           goto park;
    137         }
    138         if (s->pending_has_pc) {
    139           regs->pc = s->pending_pc_override;
    140           s->pending_has_pc = 0;
    141         }
    142         return KIT_OK;
    143       }
    144       s->stop.kind = KIT_STOP_BREAKPOINT;
    145       s->stop.bp_id = bp->user_id;
    146       s->stop.reason = KIT_STOP_REASON_USER_BREAKPOINT;
    147       if (bp->max_hits != 0 && bp->hit_count >= bp->max_hits + bp->skip_count) {
    148         /* Reached the hit limit: surface this stop, then auto-clear the
    149          * patch in the post-park path so the bp_id stays valid while the
    150          * driver inspects, and the bp does not fire again afterward. */
    151         auto_clear_user_id = bp->user_id;
    152       }
    153       goto park;
    154     }
    155   }
    156 
    157   /* Not a patched address — pass through as SIGNAL (covers SEGV, BUS,
    158    * ILL, FPE, and any SIGTRAP from a program-emitted trap). */
    159   s->stop.kind = KIT_STOP_SIGNAL;
    160   s->stop.reason = (s->os->trap_signo != 0 && signo == s->os->trap_signo)
    161                        ? KIT_STOP_REASON_TRAP
    162                        : KIT_STOP_REASON_SIGNAL;
    163 
    164 park:
    165   s->state = DBG_STATE_STOPPED;
    166   s->os->event_signal(s->os->user, s->ev_stop);
    167   s->os->event_wait(s->os->user, s->ev_resume);
    168   s->os->event_reset(s->os->user, s->ev_resume);
    169 
    170   /* Post-park auto-clear: a max_hits-limited bp was surfaced above; now that
    171    * the driver has inspected the stop, drop its patch so it fires no more. */
    172   if (auto_clear_user_id != 0) {
    173     dbg_bp_clear(s, auto_clear_user_id);
    174   }
    175 
    176   if (s->pending_mode == KIT_RESUME_ABORT) {
    177     if (s->os->thread_abort) {
    178       s->os->thread_abort(s->os->user);
    179     }
    180   }
    181 
    182   /* Apply pending PC override (set by step_resume) before returning. */
    183   if (s->pending_has_pc) {
    184     regs->pc = s->pending_pc_override;
    185     s->stop.regs.pc = s->pending_pc_override;
    186     s->pending_has_pc = 0;
    187   } else {
    188     /* Allow REPL set_regs to mutate any field. */
    189     memcpy(regs, &s->stop.regs, sizeof(*regs));
    190   }
    191   return KIT_OK;
    192 }
    193 
    194 /* ---- worker thread -------------------------------------------------- */
    195 
    196 static void worker_run_entry(void* arg) {
    197   KitDebugSession* s = (KitDebugSession*)arg;
    198   typedef int (*EntryIntArgv)(int, char**);
    199   typedef uint64_t (*EntryU64_0)(void);
    200   typedef uint64_t (*EntryU64_1)(uint64_t);
    201   typedef uint64_t (*EntryU64_2)(uint64_t, uint64_t);
    202   typedef uint64_t (*EntryU64_3)(uint64_t, uint64_t, uint64_t);
    203   typedef uint64_t (*EntryU64_4)(uint64_t, uint64_t, uint64_t, uint64_t);
    204   typedef uint64_t (*EntryU64_5)(uint64_t, uint64_t, uint64_t, uint64_t,
    205                                  uint64_t);
    206   typedef uint64_t (*EntryU64_6)(uint64_t, uint64_t, uint64_t, uint64_t,
    207                                  uint64_t, uint64_t);
    208   typedef uint64_t (*EntryU64_7)(uint64_t, uint64_t, uint64_t, uint64_t,
    209                                  uint64_t, uint64_t, uint64_t);
    210   typedef uint64_t (*EntryU64_8)(uint64_t, uint64_t, uint64_t, uint64_t,
    211                                  uint64_t, uint64_t, uint64_t, uint64_t);
    212   int ret = 0;
    213   switch (s->entry_kind) {
    214     case KIT_ENTRY_INT_ARGV:
    215       ret = ((EntryIntArgv)s->entry)(s->entry_argc, s->entry_argv);
    216       break;
    217     case KIT_ENTRY_U64:
    218       switch (s->entry_u64_nargs) {
    219         case 0:
    220           s->entry_u64_ret = ((EntryU64_0)s->entry)();
    221           break;
    222         case 1:
    223           s->entry_u64_ret = ((EntryU64_1)s->entry)(s->entry_u64_args[0]);
    224           break;
    225         case 2:
    226           s->entry_u64_ret = ((EntryU64_2)s->entry)(s->entry_u64_args[0],
    227                                                     s->entry_u64_args[1]);
    228           break;
    229         case 3:
    230           s->entry_u64_ret = ((EntryU64_3)s->entry)(
    231               s->entry_u64_args[0], s->entry_u64_args[1], s->entry_u64_args[2]);
    232           break;
    233         case 4:
    234           s->entry_u64_ret = ((EntryU64_4)s->entry)(
    235               s->entry_u64_args[0], s->entry_u64_args[1], s->entry_u64_args[2],
    236               s->entry_u64_args[3]);
    237           break;
    238         case 5:
    239           s->entry_u64_ret = ((EntryU64_5)s->entry)(
    240               s->entry_u64_args[0], s->entry_u64_args[1], s->entry_u64_args[2],
    241               s->entry_u64_args[3], s->entry_u64_args[4]);
    242           break;
    243         case 6:
    244           s->entry_u64_ret = ((EntryU64_6)s->entry)(
    245               s->entry_u64_args[0], s->entry_u64_args[1], s->entry_u64_args[2],
    246               s->entry_u64_args[3], s->entry_u64_args[4], s->entry_u64_args[5]);
    247           break;
    248         case 7:
    249           s->entry_u64_ret = ((EntryU64_7)s->entry)(
    250               s->entry_u64_args[0], s->entry_u64_args[1], s->entry_u64_args[2],
    251               s->entry_u64_args[3], s->entry_u64_args[4], s->entry_u64_args[5],
    252               s->entry_u64_args[6]);
    253           break;
    254         case 8:
    255           s->entry_u64_ret = ((EntryU64_8)s->entry)(
    256               s->entry_u64_args[0], s->entry_u64_args[1], s->entry_u64_args[2],
    257               s->entry_u64_args[3], s->entry_u64_args[4], s->entry_u64_args[5],
    258               s->entry_u64_args[6], s->entry_u64_args[7]);
    259           break;
    260         default:
    261           s->entry_u64_ret = 0;
    262           break;
    263       }
    264       ret = (int)s->entry_u64_ret;
    265       break;
    266   }
    267   memset(&s->stop, 0, sizeof(s->stop));
    268   s->stop.kind = KIT_STOP_EXIT;
    269   s->stop.reason = KIT_STOP_REASON_EXIT;
    270   s->stop.exit_code = ret;
    271 }
    272 
    273 static void worker_main(void* arg) {
    274   KitDebugSession* s = (KitDebugSession*)arg;
    275   for (;;) {
    276     s->os->event_wait(s->os->user, s->ev_resume);
    277     s->os->event_reset(s->os->user, s->ev_resume);
    278     if (s->worker_should_exit) return;
    279     if (s->state == DBG_STATE_RUNNING && s->entry) {
    280       int aborted = 0;
    281       if (s->os->call_with_catch) {
    282         aborted = s->os->call_with_catch(s->os->user, worker_run_entry, s);
    283       } else {
    284         worker_run_entry(s);
    285       }
    286       if (aborted) {
    287         memset(&s->stop, 0, sizeof(s->stop));
    288         s->stop.kind = KIT_STOP_EXIT;
    289         s->stop.reason = KIT_STOP_REASON_EXIT;
    290         s->stop.exit_code = -1;
    291       }
    292       s->state = DBG_STATE_EXITED;
    293       s->entry = NULL;
    294       s->os->event_signal(s->os->user, s->ev_stop);
    295     }
    296   }
    297 }
    298 
    299 /* ---- public entries ------------------------------------------------- */
    300 
    301 KitStatus kit_dbg_session_new(KitJit* jit, const KitDbgHost* host,
    302                               KitDebugSession** out) {
    303   KitDebugSession* s;
    304   Compiler* c;
    305   Heap* heap;
    306   const KitDbgOs* os;
    307   const ArchImpl* arch;
    308   KitDbgSignalOps ops;
    309   KitStatus st;
    310 
    311   if (out) *out = NULL;
    312   if (!jit || !host || !host->os || !out) return KIT_INVALID;
    313   c = kit_jit_compiler(jit);
    314   if (!c || !c->ctx) return KIT_INVALID;
    315   os = host->os;
    316   if (!os->thread_start || !os->thread_join || !os->event_new ||
    317       !os->event_wait || !os->event_signal || !os->event_reset ||
    318       !os->event_free || !os->signals_install || !os->signals_uninstall ||
    319       !os->code_write_begin || !os->code_write_end || !os->guarded_copy) {
    320     return KIT_INVALID;
    321   }
    322   arch = arch_lookup(kit_jit_image_arch(jit));
    323   if (!arch || !arch->dbg || !arch->dbg->breakpoint_patch) {
    324     return KIT_UNSUPPORTED;
    325   }
    326 
    327   heap = c->ctx->heap;
    328   s = (KitDebugSession*)heap->alloc(heap, sizeof(*s), _Alignof(KitDebugSession));
    329   if (!s) return KIT_NOMEM;
    330   memset(s, 0, sizeof(*s));
    331   s->jit = jit;
    332   s->c = c;
    333   s->heap = heap;
    334   s->os = os;
    335   /* Borrow execmem from the JIT image; displaced-step scratch is the only
    336    * consumer.  May be NULL if the JIT was constructed without one, in
    337    * which case STEP_INSN paths will surface KIT_UNSUPPORTED. */
    338   s->execmem = kit_jit_image_execmem(jit);
    339   s->arch_impl = arch;
    340   s->arch_dbg = arch->dbg;
    341   s->state = DBG_STATE_IDLE;
    342   arena_init(&s->values, heap, DBG_SCRATCH_PAGE_SIZE);
    343 
    344   st = os->event_new(os->user, &s->ev_resume);
    345   if (st != KIT_OK) {
    346     heap->free(heap, s, sizeof(*s));
    347     return st;
    348   }
    349   st = os->event_new(os->user, &s->ev_stop);
    350   if (st != KIT_OK) {
    351     os->event_free(os->user, s->ev_resume);
    352     heap->free(heap, s, sizeof(*s));
    353     return st;
    354   }
    355 
    356   dbg_bp_init(s);
    357 
    358   ops.on_fault = on_fault;
    359   st = os->signals_install(os->user, &ops, s);
    360   if (st != KIT_OK) {
    361     os->event_free(os->user, s->ev_resume);
    362     os->event_free(os->user, s->ev_stop);
    363     heap->free(heap, s, sizeof(*s));
    364     return st;
    365   }
    366 
    367   st = os->thread_start(os->user, worker_main, s, &s->worker);
    368   if (st != KIT_OK) {
    369     os->signals_uninstall(os->user);
    370     os->event_free(os->user, s->ev_resume);
    371     os->event_free(os->user, s->ev_stop);
    372     heap->free(heap, s, sizeof(*s));
    373     return st;
    374   }
    375   s->worker_alive = 1;
    376   *out = s;
    377   return KIT_OK;
    378 }
    379 
    380 KitStatus kit_dbg_session_attach_dwarf(KitDebugSession* s, KitDebugInfo* dw) {
    381   if (!s) return KIT_INVALID;
    382   s->dwarf = dw;
    383   return KIT_OK;
    384 }
    385 
    386 KitStatus dbg_session_signal_resume(KitDebugSession* s) {
    387   if (!s) return KIT_INVALID;
    388   s->state = DBG_STATE_RUNNING;
    389   return s->os->event_signal(s->os->user, s->ev_resume);
    390 }
    391 
    392 KitStatus dbg_session_wait_stop(KitDebugSession* s) {
    393   KitStatus st;
    394   if (!s) return KIT_INVALID;
    395   st = s->os->event_wait(s->os->user, s->ev_stop);
    396   if (st != KIT_OK) return st;
    397   return s->os->event_reset(s->os->user, s->ev_stop);
    398 }
    399 
    400 void kit_dbg_session_free(KitDebugSession* s) {
    401   if (!s) return;
    402   /* If the worker is parked inside the signal handler (STOPPED), there is
    403    * no clean way to unwind it without re-running the user's program to
    404    * completion: the kernel will restart it at the trap PC and re-trap.
    405    * The session is only torn down at process exit, so leak the worker and
    406    * let the OS reap it. We skip event/signal/heap teardown for the same
    407    * reason — the worker may still touch them before _exit. */
    408   if (s->worker_alive && s->state == DBG_STATE_STOPPED) return;
    409 
    410   s->worker_should_exit = 1;
    411   if (s->worker_alive) {
    412     s->os->event_signal(s->os->user, s->ev_resume);
    413     s->os->thread_join(s->os->user, s->worker);
    414     s->worker_alive = 0;
    415   }
    416   s->os->signals_uninstall(s->os->user);
    417   dbg_displaced_fini(s);
    418   dbg_bp_fini(s);
    419   arena_fini(&s->values);
    420   if (s->ev_resume) s->os->event_free(s->os->user, s->ev_resume);
    421   if (s->ev_stop) s->os->event_free(s->os->user, s->ev_stop);
    422   s->heap->free(s->heap, s, sizeof(*s));
    423 }
    424 
    425 KitStatus kit_dbg_session_call(KitDebugSession* s, void* entry, KitEntryKind kind,
    426                                int argc, char** argv, KitStopInfo* stop_out) {
    427   if (!s || !entry) return KIT_INVALID;
    428   if (s->state == DBG_STATE_RUNNING || s->state == DBG_STATE_STOPPED)
    429     return KIT_INVALID;
    430   /* New run: invalidate the previous stop's symbolic-value storage. */
    431   arena_reset(&s->values);
    432   s->entry = entry;
    433   s->entry_kind = kind;
    434   s->entry_argc = argc;
    435   s->entry_argv = argv;
    436   s->state = DBG_STATE_RUNNING;
    437   s->pending_mode = KIT_RESUME_CONTINUE;
    438   s->pending_has_pc = 0;
    439   s->pending_step_pending = 0;
    440   s->os->event_reset(s->os->user, s->ev_stop);
    441   s->os->event_signal(s->os->user, s->ev_resume);
    442   s->os->event_wait(s->os->user, s->ev_stop);
    443   s->os->event_reset(s->os->user, s->ev_stop);
    444   if (stop_out) *stop_out = s->stop;
    445   return KIT_OK;
    446 }
    447 
    448 KitStatus kit_dbg_session_call_u64(KitDebugSession* s, void* entry,
    449                                    const uint64_t* args, uint32_t nargs,
    450                                    uint64_t* ret_out, KitStopInfo* stop_out) {
    451   uint32_t i;
    452   KitStatus st;
    453   if (!s || !entry || nargs > 8u) return KIT_INVALID;
    454   if (s->state == DBG_STATE_RUNNING || s->state == DBG_STATE_STOPPED)
    455     return KIT_INVALID;
    456   for (i = 0; i < nargs; ++i) s->entry_u64_args[i] = args ? args[i] : 0;
    457   for (; i < 8u; ++i) s->entry_u64_args[i] = 0;
    458   s->entry_u64_nargs = nargs;
    459   s->entry_u64_ret = 0;
    460   st = kit_dbg_session_call(s, entry, KIT_ENTRY_U64, 0, NULL, stop_out);
    461   if (st == KIT_OK && ret_out) *ret_out = s->entry_u64_ret;
    462   return st;
    463 }
    464 
    465 KitStatus kit_dbg_session_resume(KitDebugSession* s, KitResumeMode mode,
    466                                  KitStopInfo* stop_out) {
    467   if (!s) return KIT_INVALID;
    468   if (s->state == DBG_STATE_EXITED) return KIT_INVALID;
    469   if (s->state != DBG_STATE_STOPPED) return KIT_INVALID;
    470 
    471   /* Leaving this stop: invalidate the symbolic-value storage tied to it. */
    472   arena_reset(&s->values);
    473   s->pending_mode = mode;
    474   s->pending_has_pc = 0;
    475   s->pending_step_pending = 0;
    476   s->pending_done = 0;
    477 
    478   /* For CONTINUE-over-bp we use displaced step to skip the patched insn
    479    * and rely on the on_fault handler's pending_step_pending fast-path to
    480    * not surface that step's trap to the REPL. */
    481   if (mode == KIT_RESUME_CONTINUE &&
    482       dbg_bp_lookup_index(s, s->stop.regs.pc) != 0) {
    483     s->pending_step_pending = 1;
    484     if (dbg_step_resume(s, KIT_RESUME_STEP_INSN) != KIT_OK) {
    485       s->pending_step_pending = 0;
    486       return KIT_ERR;
    487     }
    488   } else if (mode != KIT_RESUME_CONTINUE) {
    489     KitStatus st = dbg_step_resume(s, mode);
    490     if (st != KIT_OK) return st;
    491   }
    492 
    493   if (!s->pending_done) {
    494     s->state = DBG_STATE_RUNNING;
    495     s->os->event_signal(s->os->user, s->ev_resume);
    496     s->os->event_wait(s->os->user, s->ev_stop);
    497     s->os->event_reset(s->os->user, s->ev_stop);
    498   }
    499   s->pending_done = 0;
    500   if (stop_out) *stop_out = s->stop;
    501   return KIT_OK;
    502 }
    503 
    504 KitStatus kit_dbg_session_interrupt(KitDebugSession* s) {
    505   if (!s) return KIT_INVALID;
    506   if (s->state != DBG_STATE_RUNNING) return KIT_INVALID;
    507   if (!s->os->thread_interrupt) return KIT_UNSUPPORTED;
    508   return s->os->thread_interrupt(s->os->user, s->worker);
    509 }
    510 
    511 KitStatus kit_dbg_session_read_mem(KitDebugSession* s, uint64_t addr, void* dst,
    512                                    size_t n) {
    513   if (!s) return KIT_INVALID;
    514   if (s->state != DBG_STATE_STOPPED && s->state != DBG_STATE_EXITED)
    515     return KIT_INVALID;
    516   return dbg_mem_read(s, addr, dst, n);
    517 }
    518 
    519 KitStatus kit_dbg_session_write_mem(KitDebugSession* s, uint64_t addr,
    520                                     const void* src, size_t n) {
    521   if (!s) return KIT_INVALID;
    522   if (s->state != DBG_STATE_STOPPED && s->state != DBG_STATE_EXITED)
    523     return KIT_INVALID;
    524   return dbg_mem_write(s, addr, src, n);
    525 }
    526 
    527 KitStatus kit_dbg_session_get_regs(KitDebugSession* s, KitUnwindFrame* out) {
    528   if (!s || !out) return KIT_INVALID;
    529   if (s->state != DBG_STATE_STOPPED) return KIT_INVALID;
    530   *out = s->stop.regs;
    531   return KIT_OK;
    532 }
    533 
    534 KitStatus kit_dbg_session_set_regs(KitDebugSession* s, const KitUnwindFrame* in) {
    535   if (!s || !in) return KIT_INVALID;
    536   if (s->state != DBG_STATE_STOPPED) return KIT_INVALID;
    537   if (!kit_jit_image_contains(s->jit, in->pc)) return KIT_INVALID;
    538   s->stop.regs = *in;
    539   return KIT_OK;
    540 }
    541 
    542 KitStatus kit_dbg_session_breakpoint_set(KitDebugSession* s, uint64_t addr,
    543                                          uint32_t* bp_id_out) {
    544   if (!s) return KIT_INVALID;
    545   return dbg_bp_set(s, addr, bp_id_out);
    546 }
    547 
    548 KitStatus kit_dbg_session_breakpoint_clear(KitDebugSession* s, uint32_t bp_id) {
    549   if (!s) return KIT_INVALID;
    550   return dbg_bp_clear(s, bp_id);
    551 }
    552 
    553 KitStatus kit_dbg_session_breakpoint_set_spec(KitDebugSession* s,
    554                                               const KitBreakpointSpec* spec,
    555                                               uint32_t* bp_id_out) {
    556   if (!s || !spec) return KIT_INVALID;
    557   return dbg_bp_set_spec(s, spec, bp_id_out);
    558 }