kit

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

session.c (19160B)


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