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 }