kit

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

commit 0cca95ea4a0d690b13f196cfadd82f6a34de859b
parent 4027978edd85e8d34153b0677c111c7cb0b89781
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 11 Jun 2026 22:13:07 -0700

feat(dbg): promote the symbolic debugger engine into public kit/dbg.h

Lift the symbolic debugger engine out of the driver and into the public API,
so driver/cmd/dbg.c becomes a thin REPL over it (it roughly halves).

New public surface in kit/dbg.h:
- kit_dbg_session_* (renamed from the kit_jit_session_* control surface;
  KitJitSession -> KitDebugSession to reflect the broadened scope)
- kit_dbg_backtrace_* - FP-chain unwind with spilled-return recovery
- kit_dbg_value_* / kit_dbg_var_read/write / kit_dbg_vars_* - type-aware
  value formatting, deref/field/index, and locals/args enumeration over DWARF
- kit_dbg_resolve / kit_dbg_symbolize_pc / kit_dbg_disasm_new

src/dbg/ gains symbolic.c and refactors session/bp/step/mem/displaced onto
the new surface; config_stubs and the strbuf helper are extended to match.
Docs (DBG/DESIGN/DRIVER/INTERFACES, plan/DEBUG) updated.

Tier 4 of the driver -> libkit promotion; developed in parallel with the
build/target/object/dwarf promotion in the preceding commit.

Diffstat:
Mdoc/DBG.md | 14+++++++-------
Mdoc/DESIGN.md | 4++--
Mdoc/DRIVER.md | 2+-
Mdoc/INTERFACES.md | 2+-
Mdoc/plan/DEBUG.md | 8++++----
Mdriver/cmd/dbg.c | 626+++++++++++++++----------------------------------------------------------------
Mdriver/env.h | 2+-
Mdriver/lib/backtrace.c | 90++++++++++++++++++++++++++++++++++++++-----------------------------------------
Minclude/kit/core.h | 2+-
Minclude/kit/dbg.h | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/api/config_stubs.c | 191++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/dbg/bp.c | 26+++++++++++++-------------
Msrc/dbg/dbg.h | 52+++++++++++++++++++++++++++++-----------------------
Msrc/dbg/displaced.c | 10+++++-----
Msrc/dbg/mem.c | 4++--
Msrc/dbg/session.c | 56+++++++++++++++++++++++++++++++-------------------------
Msrc/dbg/step.c | 32++++++++++++++++----------------
Asrc/dbg/symbolic.c | 937+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18 files changed, 1553 insertions(+), 688 deletions(-)

diff --git a/doc/DBG.md b/doc/DBG.md @@ -16,7 +16,7 @@ the line/CFI/variable tables the source-level features consume. driver/cmd/dbg.c REPL: command parsing, stop rendering, DWARF queries, driver-local breakpoint table, SIGINT forwarding driver/env/* KitDbgOs host adapter (threads, signals, W^X, fault copy) - │ (public API: kit_jit_session_*, KitDbgOs vtable) + │ (public API: kit_dbg_session_*, KitDbgOs vtable) ▼ src/dbg/ library-side session session.c worker thread, event handshake, fault classification @@ -32,7 +32,7 @@ src/arch/{aa64,x64,rv64}/dbg.c trap encoding, insn decode, displaced shims The session never calls `pthread_*`, `sigaction`, `mprotect`, or an icache flush directly. Every host primitive funnels through one vtable, `KitDbgOs` -(`include/kit/dbg.h`), supplied at `kit_jit_session_new` through a +(`include/kit/dbg.h`), supplied at `kit_dbg_session_new` through a `KitDbgHost`. Everything architecture-specific — the trap byte sequence, instruction decoding, the displaced-step shim — reaches the session through `ArchImpl.dbg` (an `ArchDbgOps`, `src/arch/arch.h`). The session itself is pure @@ -97,7 +97,7 @@ handler. `KitStopReason`): - **Interrupt** — the signal equals the host's `interrupt_signo` (delivered by - `kit_jit_session_interrupt` via `pthread_kill`). Reported as + `kit_dbg_session_interrupt` via `pthread_kill`). Reported as `KIT_STOP_INTERRUPT`. - **Breakpoint** — the faulting PC, normalized by the arch hook `breakpoint_addr_from_fault_pc` (x86 INT3 reports the PC *after* the trap byte; @@ -124,7 +124,7 @@ async-signal context inside `on_fault`. ### Teardown while parked -`kit_jit_session_free` deliberately leaks the worker when it is torn down in the +`kit_dbg_session_free` deliberately leaks the worker when it is torn down in the STOPPED state. A worker parked inside the signal handler cannot be cleanly unwound without re-running the program to completion, and the session is only freed at process exit, so the OS reaps the thread and the event/signal/heap @@ -132,7 +132,7 @@ teardown is skipped. This keeps quitting from a stopped prompt immediate. ## Resume-mode state machine -`kit_jit_session_resume` takes a `KitResumeMode` and produces the next stop. +`kit_dbg_session_resume` takes a `KitResumeMode` and produces the next stop. Plain `CONTINUE` simply unblocks the parked handler — *unless* the current PC sits on a breakpoint patch, in which case the original instruction must execute before control continues. Everything more elaborate is built in `src/dbg/step.c` on two @@ -264,7 +264,7 @@ require STOPPED (there is no live register snapshot once the program has exited) The driver TU mirrors `kit run` for compile flags and argv shape (with `-g` forced on), turns the input list into a JIT image, opens a DWARF view over it -(`kit_jit_view` → `kit_dwarf_open` → `kit_jit_session_attach_dwarf`), then +(`kit_jit_view` → `kit_dwarf_open` → `kit_dbg_session_attach_dwarf`), then reads commands from stdin and dispatches them. Inputs follow the shared `DriverInputs` shape used by `kit run` — C sources, objects, archives, and stdin — so a session can mix pipeline-compiled `-g` sources with prebuilt `.o`/`.a`. With @@ -286,7 +286,7 @@ Responsibilities that stay in the driver: - **DWARF queries** — `bt` via `kit_dwarf_unwind_step`; `p name` / `set` via `kit_dwarf_var_at` + `kit_dwarf_loc_read`; locals/args enumeration; `list`. - **SIGINT forwarding** — while a session call is in flight the driver installs a - SIGINT handler that calls `kit_jit_session_interrupt`; at the prompt it + SIGINT handler that calls `kit_dbg_session_interrupt`; at the prompt it restores default behavior so Ctrl-C terminates. ### Runtime-vs-image PC translation diff --git a/doc/DESIGN.md b/doc/DESIGN.md @@ -36,7 +36,7 @@ machinery to JIT, debug, and emulate what it produces. Capabilities: metal. The implementation obeys the same constraints it imposes on its output. - **No global state.** There are no mutable globals or hidden singletons. All state hangs off an explicit context — a `KitCompiler` or a subsystem handle - (`KitObjBuilder`, `KitLinkSession`, `KitJit`, `KitJitSession`, + (`KitObjBuilder`, `KitLinkSession`, `KitJit`, `KitDebugSession`, `KitEmu`, frontend state) — so the library is reentrant and embeddable. - **The host supplies all side effects.** libkit itself touches no OS. The host injects every side effect through vtables: heap, diagnostics, file I/O, @@ -162,7 +162,7 @@ and final emission, for any enabled object format. ``` source/object inputs -> compile/link to a JIT LinkImage -> kit_link_jit (KitExecMem from KitJitHost maps + protects pages) - -> KitJit / KitJitSession + -> KitJit / KitDebugSession -> run (invoke entry) | dbg (breakpoints, stepping, regs/mem via KitDbgHost) ``` diff --git a/doc/DRIVER.md b/doc/DRIVER.md @@ -105,7 +105,7 @@ tool reaches into compiler internals. | `disas` | Disassemble a raw, headerless byte buffer (file/stdin/inline `-x` hex) for a `-target` arch. | | `mc` | Assemble one instruction and show its encoding (llvm-mc style); lists any relocations. | | `run` | JIT-compile inputs and call the entry symbol in-process. | -| `dbg` | Interactive JIT debugger (REPL over a `KitJitSession`). | +| `dbg` | Interactive JIT debugger (REPL over a `KitDebugSession`). | | `emu` | Run a guest user-mode ELF (aarch64/riscv64) via per-block JIT translation. | | `cas` / `pkg` | Content-addressed store and signed `.kpkg` distribution. | diff --git a/doc/INTERFACES.md b/doc/INTERFACES.md @@ -95,7 +95,7 @@ it uses. | `link.h` | Linker: byte/object/archive/DSO inputs, linker-script model, emit or JIT. | `KitLinkSession`, `KitLinkScript` | driver, jit | | `jit.h` | JIT image: mapped pages, symbol resolution, publish/append/replace, object view. | `KitJit` | runtime, dbg | | `interp.h` | Threaded-bytecode interpreter over the optimizer IR; host-identity and emu/guest configurations. | `KitInterpProgram` | `run --no-jit`, emu | -| `dbg.h` | In-process JIT execution control: breakpoints, stepping, regs/mem, signal host. | `KitJitSession` | debuggers | +| `dbg.h` | In-process JIT execution: the `kit_dbg_session_*` control substrate (breakpoints, stepping, regs/mem, signal host) plus a `kit_dbg_*` symbolic layer (backtraces, typed/navigable values + formatting, scope enumeration, location resolution, disasm-at-PC) composed over the session's DWARF + JIT image. | `KitDebugSession`, `KitDebugValue`, `KitDebugFrame` | debuggers | | `dwarf.h` | DWARF5 consumer: PC<->line, type/var/subprogram queries, structural iterators. | `KitDebugInfo`, `KitDwarfType` | debuggers, dumpers | | `disasm.h` | Disassembly of byte ranges and objects, with symbol/reloc annotation. | `KitDisasmIter` | objdump, dbg | | `emu.h` | User-mode guest-ELF emulator (per-block JIT). | `KitEmu` | emu tool | diff --git a/doc/plan/DEBUG.md b/doc/plan/DEBUG.md @@ -3,7 +3,7 @@ This roadmap consolidates the remaining work across the interactive JIT debugger (`kit dbg`), the DWARF producer/consumer, and the not-yet-built sampling profiler (`kit prof`). Designs live one level up: -[../DBG.md](../DBG.md) covers the `KitJitSession` architecture, the +[../DBG.md](../DBG.md) covers the `KitDebugSession` architecture, the `KitDbgOs` host vtable, software breakpoints, and displaced single-step; [../DWARF.md](../DWARF.md) covers the producer pipeline and the `kit_dwarf_*` consumer surface. This document is forward-looking: it states @@ -95,7 +95,7 @@ still lack focused unit coverage. Following red-green TDD (see - `test/dbg/source_step`: scripted `n` / `step` / `finish`, asserting the reported source line at each stop. - Make session teardown explicit enough to test stopping while the worker is - parked. Note: `kit_jit_session_free` deliberately leaks a worker parked + parked. Note: `kit_dbg_session_free` deliberately leaks a worker parked inside the signal handler (no async-safe unwind), so tests must account for this rather than expect a clean join. @@ -193,8 +193,8 @@ Public API (`include/kit.h`): because it extracts only PC and FP on the hot path. - Declare `KitProfBuf` (fixed-capacity sample ring: `pcs[PROF_MAX_DEPTH]` per sample, `count`/`cap`/`dropped`) and `KitProfWriter` (post-run symbolication - callback vtable), plus `kit_jit_session_prof_attach(session, buf)` (before - `session_call`) and `kit_jit_session_prof_collect(session, buf, writer)`. + callback vtable), plus `kit_dbg_session_prof_attach(session, buf)` (before + `session_call`) and `kit_dbg_session_prof_collect(session, buf, writer)`. Library (`src/dbg/prof.c`, freestanding C11): diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c @@ -20,7 +20,7 @@ * * Mirrors `kit run` for compile flags and argv shape, but instead of * calling the entry directly drops into a REPL that drives a - * KitJitSession. The session (in libkit) owns the worker thread, + * KitDebugSession. The session (in libkit) owns the worker thread, * signal handlers, breakpoint patcher, and per-arch single-step / * displaced-step trampoline. This driver TU only: * @@ -36,7 +36,7 @@ * - decodes `p name` via kit_dwarf_var_at + kit_dwarf_loc_read. * * Forwarding Ctrl-C: while a session call is in flight the driver - * installs a SIGINT handler that calls kit_jit_session_interrupt; on + * installs a SIGINT handler that calls kit_dbg_session_interrupt; on * return it restores SIG_DFL so Ctrl-C at the REPL prompt terminates * the program. */ @@ -447,9 +447,10 @@ typedef struct DbgState { KitCCompileOptions copts; KitPreprocessOptions pp; /* preprocessor settings for REPL compiles */ KitJit* jit; - KitJitSession* session; + KitDebugSession* session; const KitObjFile* view; KitDebugInfo* dwarf; + KitWriter* fmt_writer; /* lazily-created stdout writer for kit_dbg_value_format */ void* entry_addr; const char* entry_name; KitLanguage default_jit_lang; @@ -492,11 +493,11 @@ typedef struct DbgState { #define DBG_LIST_CTX 5 /* lines printed before/after the target */ /* SIGINT trampoline. The handler in env.c calls our cb with this state; - * we forward into the session. kit_jit_session_interrupt is documented + * we forward into the session. kit_dbg_session_interrupt is documented * async-signal-safe. */ static void dbg_on_sigint(void* user) { DbgState* s = (DbgState*)user; - if (s && s->session) kit_jit_session_interrupt(s->session); + if (s && s->session) kit_dbg_session_interrupt(s->session); } /* PC-space translation between the JIT runtime address space (where @@ -515,31 +516,6 @@ static uint64_t dbg_pc_img_to_rt(DbgState* s, uint64_t img) { uint64_t v = kit_jit_image_to_runtime(s->jit, img); return v ? v : img; } - -/* Build a frame view in image-PC space for DWARF queries that key off - * frame->pc (subprogram_at, param_iter_new, vars_at_new, unwind_step). - * The register snapshot and CFA stay in their original (runtime) form - * because dw_eval_expr / loc_read interpret them as live host values. */ -static KitUnwindFrame dbg_frame_for_dwarf(DbgState* s, - const KitUnwindFrame* rt) { - KitUnwindFrame out = *rt; - out.pc = dbg_pc_rt_to_img(s, rt->pc); - return out; -} - -/* Translate any image-vaddr fields stored on a KitDwarfVarLoc back - * to runtime addresses before the loc is handed to a memory accessor - * (session_read_mem / session_write_mem operate in runtime space). - * Only DLOC_GLOBAL carries an absolute address straight from - * .debug_info; DLOC_REG / DLOC_FRAME_OFS / DLOC_EXPR derive their - * effective address from live register state or are evaluated against - * the frame, both of which are already in runtime space. */ -static void dbg_translate_loc(DbgState* s, KitDwarfVarLoc* loc) { - if (!loc) return; - if (loc->kind == KIT_DLOC_GLOBAL) - loc->v.global = dbg_pc_img_to_rt(s, loc->v.global); -} - /* ============================================================ * Tiny driver-local string utilities * ============================================================ @@ -635,7 +611,7 @@ static Bp* dbg_bp_find(DbgState* s, int id) { static void dbg_bp_release(DbgState* s, Bp* b) { if (b->session_id) { - kit_jit_session_breakpoint_clear(s->session, b->session_id); + kit_dbg_session_breakpoint_clear(s->session, b->session_id); b->session_id = 0; } if (b->spec) { @@ -913,8 +889,6 @@ static KitSlice dbg_step_stop_label(KitStopReason reason) { } static void dbg_cmd_bt(DbgState* s); -static KitStatus dbg_dwarf_read_mem(void* user, uint64_t addr, void* dst, - size_t n); static void dbg_render_stop(DbgState* s, const KitStopInfo* st) { KitSlice file = {0}; @@ -1001,7 +975,7 @@ static int dbg_drive(DbgState* s, DbgRunMode mode) { /* The previous session is dead (entry returned or signal landed). * Start a new one. Try to abort it first in case it's parked in a fault. */ if (s->session) { - kit_jit_session_resume(s->session, KIT_RESUME_ABORT, NULL); + kit_dbg_session_resume(s->session, KIT_RESUME_ABORT, NULL); } s->has_stop = 0; } else if (mode != RUN_FRESH && !s->has_stop) { @@ -1028,7 +1002,7 @@ static int dbg_drive(DbgState* s, DbgRunMode mode) { } if (mode == RUN_FRESH) { - rc = kit_jit_session_call(s->session, s->entry_addr, KIT_ENTRY_INT_ARGV, + rc = kit_dbg_session_call(s->session, s->entry_addr, KIT_ENTRY_INT_ARGV, s->prog_argc, s->prog_argv, &s->last_stop); } else { KitResumeMode rm = KIT_RESUME_CONTINUE; @@ -1051,7 +1025,7 @@ static int dbg_drive(DbgState* s, DbgRunMode mode) { case RUN_FRESH: break; /* unreachable */ } - rc = kit_jit_session_resume(s->session, rm, &s->last_stop); + rc = kit_dbg_session_resume(s->session, rm, &s->last_stop); } driver_restore_sigint(); @@ -1072,15 +1046,14 @@ static int dbg_drive(DbgState* s, DbgRunMode mode) { return 0; } -/* Forward declarations: backtrace renders parameter values via the - * type-aware printer defined below. */ -static void dbg_print_value(DbgState*, const KitDwarfType*, const uint8_t*, - size_t got, int depth); -static int dbg_read_value(DbgState*, const KitDwarfVarLoc*, - const KitUnwindFrame*, uint8_t* stack_buf, - size_t stack_cap, uint8_t** buf_out, - size_t* alloc_out, size_t* got_out); -static void dbg_release_value_buf(DbgState*, uint8_t* buf, size_t alloc); +/* Render a materialized debugger value to stdout via the public formatter. + * The stdout writer shares libc's stdout buffer with driver_printf, so a + * "name = " prefix and the value text interleave in call order. */ +static void dbg_emit_value(DbgState* s, const KitDebugValue* v, int indent) { + KitDebugFormatOptions fo = {.indent = (uint32_t)indent}; + if (!s->fmt_writer) s->fmt_writer = driver_stdout_writer(s->env); + if (s->fmt_writer) kit_dbg_value_format(s->session, v, s->fmt_writer, &fo); +} static char* dbg_take_word(char* line, char** word_out); /* ============================================================ @@ -1092,8 +1065,8 @@ static char* dbg_take_word(char* line, char** word_out); * PC and the unwound register snapshot. Inlined frames are flagged. */ static void dbg_cmd_bt(DbgState* s) { - KitUnwindFrame frame; - int level = 0; + KitDebugBacktrace* bt = NULL; + uint32_t i, n; if (!s->has_stop) { dbg_errf(s, "no program is stopped"); @@ -1103,407 +1076,99 @@ static void dbg_cmd_bt(DbgState* s) { dbg_errf(s, "no DWARF: backtrace unavailable"); return; } + if (kit_dbg_backtrace_new(s->session, NULL, &bt) != KIT_OK) { + dbg_errf(s, "backtrace unavailable"); + return; + } - frame = s->last_stop.regs; - for (;;) { - KitDwarfSubprogram sp; - KitUnwindFrame img_frame; - int have_sp; - - driver_printf("#%-2d ", level); - driver_printf("0x%llx", (unsigned long long)frame.pc); - - { - KitSlice sym = KIT_SLICE_NULL; - uint64_t off = 0; - if (kit_jit_addr_to_sym(s->jit, frame.pc, &sym, &off) == KIT_OK && - sym.s) { - if (off) - driver_printf(" <%.*s+0x%llx>", KIT_SLICE_ARG(sym), - (unsigned long long)off); - else - driver_printf(" <%.*s>", KIT_SLICE_ARG(sym)); - } + n = kit_dbg_backtrace_count(bt); + for (i = 0; i < n; ++i) { + KitDebugFrame f; + if (kit_dbg_backtrace_frame(bt, i, &f) != KIT_OK) break; + + driver_printf("#%-2d 0x%llx", (int)i, (unsigned long long)f.pc); + if (f.sym.s) { + if (f.sym_offset) + driver_printf(" <%.*s+0x%llx>", KIT_SLICE_ARG(f.sym), + (unsigned long long)f.sym_offset); + else + driver_printf(" <%.*s>", KIT_SLICE_ARG(f.sym)); } - img_frame = dbg_frame_for_dwarf(s, &frame); - have_sp = (kit_dwarf_subprogram_at(s->dwarf, img_frame.pc, &sp) == KIT_OK); - if (have_sp && sp.name.s) { - KitDwarfParamIter* it = NULL; - KitDwarfVar p; + /* DWARF subprogram name + argument values for this frame. Args come from + * the session's symbolic layer against the frame's own register snapshot. */ + if (f.func.s) { + KitDebugVarIter* it = NULL; + KitSlice an; + KitDebugValue av; int first = 1; - driver_printf(" in %.*s%.*s (", KIT_SLICE_ARG(sp.name), - KIT_SLICE_ARG(sp.inlined ? KIT_SLICE_LIT(" [inlined]") - : KIT_SLICE_NULL)); - if (kit_dwarf_param_iter_new(s->dwarf, img_frame.pc, &it) == KIT_OK) { + driver_printf(" in %.*s%.*s (", KIT_SLICE_ARG(f.func), + KIT_SLICE_ARG(f.inlined ? KIT_SLICE_LIT(" [inlined]") + : KIT_SLICE_NULL)); + if (kit_dbg_vars_new(s->session, &f.regs, KIT_DVRM_ARG, &it) == KIT_OK) { for (;;) { - KitIterResult r; - uint8_t stack_buf[64]; - uint8_t* buf; - size_t alloc; - size_t got; - r = kit_dwarf_param_iter_next(it, &p); + KitIterResult r = kit_dbg_vars_next(it, &an, &av); if (r != KIT_ITER_ITEM) break; if (!first) driver_printf(", "); driver_printf("%.*s=", - KIT_SLICE_ARG(p.name.s ? p.name : KIT_SLICE_LIT("?"))); - dbg_translate_loc(s, &p.loc); - if (dbg_read_value(s, &p.loc, &frame, stack_buf, sizeof(stack_buf), - &buf, &alloc, &got) == 0) { - dbg_print_value(s, p.loc.type, buf, got, 0); - dbg_release_value_buf(s, buf, alloc); - } else { + KIT_SLICE_ARG(an.s ? an : KIT_SLICE_LIT("?"))); + if (av.bytes) + dbg_emit_value(s, &av, 0); + else driver_printf("?"); - } first = 0; } - kit_dwarf_param_iter_free(it); + kit_dbg_vars_free(it); } driver_printf(")"); } - { - KitSlice file = KIT_SLICE_NULL; - uint32_t line = 0; - uint32_t col = 0; - KitStatus rc = - kit_dwarf_addr_to_line(s->dwarf, img_frame.pc, &file, &line, &col); - if (rc == KIT_OK && file.s) { - driver_printf(" at %.*s:%u", KIT_SLICE_ARG(file), line); - if (col) driver_printf(":%u", col); - } else if (rc == KIT_NOT_FOUND) { - driver_printf(" [no debug info for this frame]"); - } + if (f.file.s) { + driver_printf(" at %.*s:%u", KIT_SLICE_ARG(f.file), f.line); + if (f.col) driver_printf(":%u", f.col); + } else { + driver_printf(" [no debug info for this frame]"); } driver_printf("\n"); - - /* Advance to the caller by following the frame-pointer chain. kit keeps a - * frame pointer on every backend with a uniform record (fp[0] = caller fp, - * fp[1] = saved return address), so a memory-reading FP walk is reliable - * where kit_dwarf_unwind_step is not — the CFI stepper takes no memory - * provider and so cannot recover a return address spilled to the stack - * (the common case), terminating after the leaf frame. Reads go through the - * session; pc/fp/cfa stay runtime addresses, translated per DWARF query. */ - { - KitArchKind arch = driver_host_target().arch; - int fpreg = driver_bt_fp_dwarf_reg(arch); - int ptr = driver_bt_ptr_size(arch); - uint64_t ra = 0, next_fp = 0; - if (fpreg < 0) break; - if (!driver_bt_fp_step(arch, dbg_dwarf_read_mem, s->session, - frame.regs[fpreg], &ra, &next_fp)) - break; /* bottom of stack */ - /* Stop at the kit-image boundary: past `main` the chain runs into the - * session/JIT trampoline and host libc, which carry no symbols and whose - * depth is host-dependent. */ - if (kit_jit_runtime_to_image(s->jit, ra) == 0) break; - frame.pc = ra; - frame.regs[fpreg] = next_fp; - /* Caller CFA in kit's layout: the address just above the saved pair. */ - frame.cfa = next_fp + 2u * (uint64_t)ptr; - } - if (++level > 256) { - dbg_errf(s, "backtrace truncated at 256 frames"); - break; - } - } -} - -/* ============================================================ - * Type-aware value printer - * ============================================================ - * Walks a KitDwarfType and pretty-prints its byte representation. Used - * by `p`, `info locals`, `info args`, and the backtrace-arg renderer. - * Self-recursive for aggregates (struct, union, array). When `type` is - * NULL (no DWARF type info recovered) the printer falls back to LE-as-u64 - * for small reads and a hex dump for the rest. The function emits the - * value only — callers print any leading "name = " and the trailing - * newline. */ - -static uint64_t dbg_load_le_u(const uint8_t* buf, size_t n) { - uint64_t v = 0; - size_t i; - for (i = 0; i < n && i < 8; ++i) v |= ((uint64_t)buf[i]) << (8 * i); - return v; -} - -static int64_t dbg_load_le_s(const uint8_t* buf, size_t n) { - uint64_t v = dbg_load_le_u(buf, n); - if (n > 0 && n < 8) { - uint64_t sign = (uint64_t)1 << (8 * n - 1); - if (v & sign) v |= ~((sign << 1) - 1); } - return (int64_t)v; -} - -static void dbg_indent(int n) { - int i; - for (i = 0; i < n; ++i) driver_printf(" "); + kit_dbg_backtrace_free(bt); } -static void dbg_print_value(DbgState* s, const KitDwarfType* type, - const uint8_t* buf, size_t got, int depth) { - KitDwarfTypeInfo ti; - - if (!type) { - if (got == 0) { - driver_printf("<empty>"); - return; - } - if (got <= 8) { - uint64_t v = dbg_load_le_u(buf, got); - driver_printf("0x%llx (%llu)", (unsigned long long)v, - (unsigned long long)v); - return; - } - { - size_t i; - driver_printf("{"); - for (i = 0; i < got; ++i) driver_printf(" %02x", buf[i]); - driver_printf(" }"); - return; - } - } - - ti = kit_dwarf_type_info(type); - switch (ti.kind) { - case KIT_DT_VOID: - driver_printf("void"); - return; - case KIT_DT_SINT: - case KIT_DT_CHAR: - driver_printf("%lld", (long long)dbg_load_le_s(buf, got)); - return; - case KIT_DT_UINT: - case KIT_DT_BOOL: - driver_printf("%llu", (unsigned long long)dbg_load_le_u(buf, got)); - return; - case KIT_DT_PTR: - driver_printf("0x%llx", (unsigned long long)dbg_load_le_u(buf, got)); - return; - case KIT_DT_FLOAT: - if (got == 4) { - union { - uint32_t u; - float f; - } cv; - cv.u = (uint32_t)dbg_load_le_u(buf, 4); - driver_printf("%g", (double)cv.f); - } else if (got == 8) { - union { - uint64_t u; - double d; - } cv; - cv.u = dbg_load_le_u(buf, 8); - driver_printf("%g", cv.d); - } else { - size_t i; - driver_printf("<float-%zu", got); - for (i = 0; i < got; ++i) driver_printf(" %02x", buf[i]); - driver_printf(">"); - } - return; - case KIT_DT_ENUM: { - int64_t v = dbg_load_le_s(buf, got); - KitDwarfEnumIter* it = NULL; - KitDwarfEnumVal ev; - KitSlice match = KIT_SLICE_NULL; - if (kit_dwarf_enum_iter_new(s->dwarf, type, &it) == KIT_OK) { - for (;;) { - KitIterResult r = kit_dwarf_enum_iter_next(it, &ev); - if (r != KIT_ITER_ITEM) break; - if (ev.value == v) { - match = ev.name; - break; - } - } - kit_dwarf_enum_iter_free(it); - } - if (match.s) - driver_printf("%.*s (%lld)", KIT_SLICE_ARG(match), (long long)v); - else - driver_printf("%lld", (long long)v); - return; - } - case KIT_DT_TYPEDEF: - dbg_print_value(s, ti.inner, buf, got, depth); - return; - case KIT_DT_ARRAY: { - uint32_t n = ti.element_count; - size_t esz = 0; - uint32_t i; - if (ti.inner) { - KitDwarfTypeInfo ein = kit_dwarf_type_info(ti.inner); - esz = ein.byte_size; - } - if (esz == 0 || n == 0 || (size_t)n * esz > got) { - size_t k; - driver_printf("{"); - for (k = 0; k < got; ++k) driver_printf(" %02x", buf[k]); - driver_printf(" }"); - return; - } - driver_printf("{\n"); - for (i = 0; i < n; ++i) { - dbg_indent(depth + 1); - driver_printf("[%u] = ", i); - dbg_print_value(s, ti.inner, buf + (size_t)i * esz, esz, depth + 1); - driver_printf(",\n"); - } - dbg_indent(depth); - driver_printf("}"); - return; - } - case KIT_DT_STRUCT: - case KIT_DT_UNION: { - KitDwarfFieldIter* it = NULL; - KitDwarfField f; - driver_printf("{\n"); - if (kit_dwarf_field_iter_new(s->dwarf, type, &it) == KIT_OK) { - for (;;) { - KitIterResult r = kit_dwarf_field_iter_next(it, &f); - if (r != KIT_ITER_ITEM) break; - size_t fsz = 0; - dbg_indent(depth + 1); - driver_printf( - ".%.*s = ", - KIT_SLICE_ARG(f.name.len ? f.name : KIT_SLICE_LIT("<anon>"))); - if (f.bit_size) { - /* Bitfield: read up to 8 bytes spanning the storage - * unit at byte_offset, shift, mask. */ - size_t off = f.byte_offset; - size_t take = (off + 8 <= got) ? 8 : (off < got ? got - off : 0); - uint64_t raw = take ? dbg_load_le_u(buf + off, take) : 0; - uint64_t mask = (f.bit_size >= 64) - ? (uint64_t)-1 - : (((uint64_t)1 << f.bit_size) - 1); - uint64_t v = (raw >> f.bit_offset) & mask; - driver_printf("%llu", (unsigned long long)v); - } else { - if (f.type) { - KitDwarfTypeInfo fti = kit_dwarf_type_info(f.type); - fsz = fti.byte_size; - } - if (f.type && fsz > 0 && (size_t)f.byte_offset + fsz <= got) { - dbg_print_value(s, f.type, buf + f.byte_offset, fsz, depth + 1); - } else { - driver_printf("<truncated>"); - } - } - driver_printf(",\n"); - } - kit_dwarf_field_iter_free(it); - } - dbg_indent(depth); - driver_printf("}"); - return; - } - case KIT_DT_FUNC: - driver_printf("<function@0x%llx>", - (unsigned long long)dbg_load_le_u(buf, got)); - return; - } - driver_printf("<?>"); -} - -/* KitDwarfReadMemFn adapter: forwards a DWARF-driven memory read into - * the JIT session's address space. The user pointer carries the - * KitJitSession. */ -static KitStatus dbg_dwarf_read_mem(void* user, uint64_t addr, void* dst, - size_t n) { - KitJitSession* sess = (KitJitSession*)user; - if (!sess) return KIT_INVALID; - return kit_jit_session_read_mem(sess, addr, dst, n); -} - -/* Read a variable's bytes into a heap or stack buffer sized for its DIE - * type. On success returns 0 and sets *buf_out (which may point at - * stack_buf or at a heap allocation) plus *got_out. The caller frees - * *buf_out via dbg_release_value_buf when done. */ -static int dbg_read_value(DbgState* s, const KitDwarfVarLoc* loc, - const KitUnwindFrame* frame, uint8_t* stack_buf, - size_t stack_cap, uint8_t** buf_out, - size_t* alloc_out, size_t* got_out) { - uint8_t* buf = stack_buf; - size_t alloc = 0; - size_t cap = stack_cap; - size_t got = 0; - - if (loc->byte_size > cap) { - alloc = loc->byte_size; - buf = driver_alloc(s->env, alloc); - if (!buf) return 1; - cap = alloc; - } - if (kit_dwarf_loc_read(s->dwarf, loc, frame, dbg_dwarf_read_mem, s->session, - buf, cap, &got) != KIT_OK) { - if (alloc) driver_free(s->env, buf, alloc); - return 1; - } - *buf_out = buf; - *alloc_out = alloc; - *got_out = got; - return 0; -} - -static void dbg_release_value_buf(DbgState* s, uint8_t* buf, size_t alloc) { - if (alloc) driver_free(s->env, buf, alloc); -} /* ============================================================ * `p name` * ============================================================ */ static void dbg_cmd_print(DbgState* s, const char* name) { - KitDwarfVarLoc loc; - uint8_t stack_buf[64]; - uint8_t* buf; - size_t alloc; - size_t got; + KitSlice nm = kit_slice_cstr(name); + KitDebugValue val; + KitStatus rc; if (!s->has_stop) { dbg_errf(s, "no program is stopped"); return; } - { - KitStatus rc = - s->dwarf ? kit_dwarf_var_at(s->dwarf, - dbg_pc_rt_to_img(s, s->last_stop.regs.pc), - kit_slice_cstr(name), &loc) - : KIT_NOT_FOUND; - if (rc == KIT_OK) { - dbg_translate_loc(s, &loc); - if (dbg_read_value(s, &loc, &s->last_stop.regs, stack_buf, - sizeof(stack_buf), &buf, &alloc, &got) != 0) { - dbg_errf(s, "could not read %.*s", KIT_SLICE_ARG(kit_slice_cstr(name))); - return; - } - driver_printf("%.*s = ", KIT_SLICE_ARG(kit_slice_cstr(name))); - dbg_print_value(s, loc.type, buf, got, 0); + rc = kit_dbg_var_read(s->session, NULL, nm, &val); + if (rc == KIT_OK) { + if (val.type) { + driver_printf("%.*s = ", KIT_SLICE_ARG(nm)); + dbg_emit_value(s, &val, 0); driver_printf("\n"); - dbg_release_value_buf(s, buf, alloc); - return; - } - - /* DWARF didn't know about it — try a global symbol. */ - { - void* p = kit_jit_lookup(s->jit, kit_slice_cstr(name)); - if (p) { - union { - void* p; - uint64_t u; - } cv; - cv.p = p; - driver_printf("%.*s = 0x%llx (no DWARF type info)\n", - KIT_SLICE_ARG(kit_slice_cstr(name)), - (unsigned long long)cv.u); - return; - } + } else { + /* Resolved a JIT global symbol but no DWARF type — show its address. */ + uint64_t addr = 0; + kit_dbg_value_as_u64(&val, &addr); + driver_printf("%.*s = 0x%llx (no DWARF type info)\n", KIT_SLICE_ARG(nm), + (unsigned long long)addr); } - dbg_errf(s, "no variable or symbol named '%.*s'", - KIT_SLICE_ARG(kit_slice_cstr(name))); + return; } + if (rc == KIT_NOT_FOUND) { + dbg_errf(s, "no variable or symbol named '%.*s'", KIT_SLICE_ARG(nm)); + return; + } + dbg_errf(s, "could not read %.*s", KIT_SLICE_ARG(nm)); } /* ============================================================ @@ -1515,64 +1180,29 @@ static void dbg_cmd_print(DbgState* s, const char* name) { * aggregate writes are out of scope. */ static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) { - KitDwarfVarLoc loc; - uint8_t buf[8]; - size_t sz; - size_t i; + KitSlice nm = kit_slice_cstr(name); + KitStatus rc; if (!s->has_stop) { dbg_errf(s, "no program is stopped"); return; } - { - KitStatus rc = - s->dwarf ? kit_dwarf_var_at(s->dwarf, - dbg_pc_rt_to_img(s, s->last_stop.regs.pc), - kit_slice_cstr(name), &loc) - : KIT_NOT_FOUND; - if (rc != KIT_OK) { - dbg_errf(s, "no variable named '%.*s'", - KIT_SLICE_ARG(kit_slice_cstr(name))); + rc = kit_dbg_var_write_u64(s->session, NULL, nm, value); + switch (rc) { + case KIT_OK: + /* Refresh the cached stop registers so a register write is visible to + * subsequent reads. */ + kit_dbg_session_get_regs(s->session, &s->last_stop.regs); return; - } - } - dbg_translate_loc(s, &loc); - - sz = (loc.byte_size == 0 || loc.byte_size > 8) ? 8 : loc.byte_size; - for (i = 0; i < sz; ++i) buf[i] = (uint8_t)(value >> (8 * i)); - - switch (loc.kind) { - case KIT_DLOC_FRAME_OFS: { - uint64_t addr = - s->last_stop.regs.cfa + (uint64_t)(int64_t)loc.v.frame_ofs; - if (kit_jit_session_write_mem(s->session, addr, buf, sz) != KIT_OK) { - dbg_errf(s, "memory write failed"); - } + case KIT_NOT_FOUND: + dbg_errf(s, "no variable named '%.*s'", KIT_SLICE_ARG(nm)); return; - } - case KIT_DLOC_GLOBAL: - if (kit_jit_session_write_mem(s->session, loc.v.global, buf, sz) != - KIT_OK) { - dbg_errf(s, "memory write failed"); - } - return; - case KIT_DLOC_REG: { - KitUnwindFrame fr = s->last_stop.regs; - if (loc.v.reg >= 32) { - dbg_errf(s, "register %u outside the snapshot range", loc.v.reg); - return; - } - fr.regs[loc.v.reg] = value; - if (kit_jit_session_set_regs(s->session, &fr) != KIT_OK) { - dbg_errf(s, "register write failed"); - return; - } - s->last_stop.regs = fr; - return; - } - case KIT_DLOC_EXPR: + case KIT_UNSUPPORTED: dbg_errf(s, "cannot set '%.*s': location is a DWARF expression", - KIT_SLICE_ARG(kit_slice_cstr(name))); + KIT_SLICE_ARG(nm)); + return; + default: + dbg_errf(s, "memory write failed"); return; } } @@ -1591,7 +1221,7 @@ static void dbg_cmd_jump(DbgState* s, uint64_t pc) { } fr = s->last_stop.regs; fr.pc = pc; - if (kit_jit_session_set_regs(s->session, &fr) != 0) { + if (kit_dbg_session_set_regs(s->session, &fr) != 0) { dbg_errf(s, "jump failed (pc 0x%llx outside image?)", (unsigned long long)pc); return; @@ -1605,8 +1235,9 @@ static void dbg_cmd_jump(DbgState* s, uint64_t pc) { * ============================================================ */ static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) { - KitDwarfVarIter* it = NULL; - KitDwarfVar v; + KitDebugVarIter* it = NULL; + KitSlice nm; + KitDebugValue val; int printed = 0; if (!s->has_stop) { @@ -1619,31 +1250,23 @@ static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) { return; } - if (kit_dwarf_vars_at_new(s->dwarf, dbg_pc_rt_to_img(s, s->last_stop.regs.pc), - mask, &it) != KIT_OK) { + if (kit_dbg_vars_new(s->session, NULL, mask, &it) != KIT_OK) { driver_printf("No %.*s.\n", KIT_SLICE_ARG(kit_slice_cstr(label))); return; } for (;;) { - KitIterResult r = kit_dwarf_vars_at_next(it, &v); + KitIterResult r = kit_dbg_vars_next(it, &nm, &val); if (r != KIT_ITER_ITEM) break; - uint8_t stack_buf[64]; - uint8_t* buf; - size_t alloc; - size_t got; printed = 1; - dbg_translate_loc(s, &v.loc); - if (dbg_read_value(s, &v.loc, &s->last_stop.regs, stack_buf, - sizeof(stack_buf), &buf, &alloc, &got) != 0) { - driver_printf(" %.*s = <unreadable>\n", KIT_SLICE_ARG(v.name)); + if (!val.bytes) { + driver_printf(" %.*s = <unreadable>\n", KIT_SLICE_ARG(nm)); continue; } - driver_printf(" %.*s = ", KIT_SLICE_ARG(v.name)); - dbg_print_value(s, v.loc.type, buf, got, 1); + driver_printf(" %.*s = ", KIT_SLICE_ARG(nm)); + dbg_emit_value(s, &val, 1); driver_printf("\n"); - dbg_release_value_buf(s, buf, alloc); } - kit_dwarf_vars_at_free(it); + kit_dbg_vars_free(it); if (!printed) driver_printf("No %.*s.\n", KIT_SLICE_ARG(kit_slice_cstr(label))); } @@ -1730,10 +1353,10 @@ static int dbg_refresh_dwarf(DbgState* s) { if (s->view) { if (kit_dwarf_open(&s->ctx, s->view, &s->dwarf) != KIT_OK) s->dwarf = NULL; if (s->dwarf && s->session) { - kit_jit_session_attach_dwarf(s->session, s->dwarf); + kit_dbg_session_attach_dwarf(s->session, s->dwarf); } } else if (s->session) { - kit_jit_session_attach_dwarf(s->session, NULL); + kit_dbg_session_attach_dwarf(s->session, NULL); } return 0; } @@ -2320,7 +1943,7 @@ static int dbg_call_u64_entry(DbgState* s, void* entry, const uint64_t* args, dbg_errf(s, "failed to install SIGINT handler"); return 1; } - if (kit_jit_session_call_u64(s->session, entry, args, nargs, &ret, &stop) != + if (kit_dbg_session_call_u64(s->session, entry, args, nargs, &ret, &stop) != KIT_OK) { driver_restore_sigint(); dbg_errf(s, "call failed (debuggee must be idle or exited)"); @@ -2468,7 +2091,7 @@ static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) { while (remaining) { size_t chunk = remaining > sizeof(buf) ? sizeof(buf) : remaining; size_t i; - if (kit_jit_session_read_mem(s->session, addr, buf, chunk) != KIT_OK) { + if (kit_dbg_session_read_mem(s->session, addr, buf, chunk) != KIT_OK) { dbg_errf(s, "read failed at 0x%llx", (unsigned long long)addr); return; } @@ -2491,10 +2114,7 @@ static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) { * address, starts at the stopped PC; count is in instructions. */ static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) { - uint8_t buf[512]; - KitDisasmContext dctx; KitDisasmIter* it = NULL; - size_t byte_count; size_t shown = 0; if (!s->has_stop) { @@ -2506,21 +2126,10 @@ static void dbg_cmd_disasm(DbgState* s, uint64_t addr, size_t count) { dbg_errf(s, "disasm count too large"); return; } - byte_count = count * 16u; - if (byte_count > sizeof(buf)) byte_count = sizeof(buf); - if (kit_jit_session_read_mem(s->session, addr, buf, byte_count) != KIT_OK) { + if (kit_dbg_disasm_new(s->session, addr, (uint32_t)count, &it) != KIT_OK) { dbg_errf(s, "read failed at 0x%llx", (unsigned long long)addr); return; } - - memset(&dctx, 0, sizeof(dctx)); - dctx.target = kit_compiler_target(s->compiler); - dctx.context = s->ctx; - if (kit_disasm_iter_new(&dctx, buf, byte_count, addr, s->view, &it) != - KIT_OK) { - dbg_errf(s, "disassembler unavailable"); - return; - } while (shown < count) { KitInsn insn; KitIterResult r = kit_disasm_iter_next(it, &insn); @@ -2707,7 +2316,7 @@ static void dbg_cmd_list(DbgState* s, const char* spec) { static int dbg_bp_arm(DbgState* s, Bp* b) { KitStatus st; if (b->skip_count == 0 && b->max_hits == 0) { - st = kit_jit_session_breakpoint_set(s->session, b->addr, &b->session_id); + st = kit_dbg_session_breakpoint_set(s->session, b->addr, &b->session_id); } else { KitBreakpointSpec spec; KitBreakpointSpec z = {0}; @@ -2715,7 +2324,7 @@ static int dbg_bp_arm(DbgState* s, Bp* b) { spec.addr = b->addr; spec.skip_count = b->skip_count; spec.max_hits = b->max_hits; - st = kit_jit_session_breakpoint_set_spec(s->session, &spec, &b->session_id); + st = kit_dbg_session_breakpoint_set_spec(s->session, &spec, &b->session_id); } return st == KIT_OK ? 0 : 1; } @@ -2786,7 +2395,7 @@ static void dbg_cmd_ignore(DbgState* s, int id, uint64_t count) { return; } if (b->session_id) { - kit_jit_session_breakpoint_clear(s->session, b->session_id); + kit_dbg_session_breakpoint_clear(s->session, b->session_id); b->session_id = 0; } b->skip_count = count; @@ -2820,7 +2429,7 @@ static void dbg_cmd_set_enabled(DbgState* s, int id, int enable) { b->enabled = 1; } else if (!enable && b->enabled) { if (b->session_id) { - kit_jit_session_breakpoint_clear(s->session, b->session_id); + kit_dbg_session_breakpoint_clear(s->session, b->session_id); b->session_id = 0; } b->enabled = 0; @@ -2946,7 +2555,7 @@ static int dbg_dispatch(DbgState* s, char* line) { } if (driver_streq(cmd, "abort")) { if (s->has_stop && s->session) { - kit_jit_session_resume(s->session, KIT_RESUME_ABORT, NULL); + kit_dbg_session_resume(s->session, KIT_RESUME_ABORT, NULL); s->has_stop = 0; dbg_errf(s, "execution aborted"); } else { @@ -3650,7 +3259,7 @@ int driver_dbg(int argc, char** argv) { } } - if (kit_jit_session_new(jit, &dhost, &st.session) != KIT_OK) { + if (kit_dbg_session_new(jit, &dhost, &st.session) != KIT_OK) { driver_errf(DBG_TOOL, "JIT session not yet implemented in libkit — " "REPL will start in degraded mode (commands will surface " @@ -3663,7 +3272,7 @@ int driver_dbg(int argc, char** argv) { if (st.view) { if (kit_dwarf_open(&ctx, st.view, &st.dwarf) != KIT_OK) st.dwarf = NULL; if (st.dwarf && st.session) { - kit_jit_session_attach_dwarf(st.session, st.dwarf); + kit_dbg_session_attach_dwarf(st.session, st.dwarf); } } @@ -3673,8 +3282,9 @@ int driver_dbg(int argc, char** argv) { dbg_bps_release_all(&st); dbg_compile_sessions_release(&st); dbg_sources_release_all(&st); + if (st.fmt_writer) st.fmt_writer->close(st.fmt_writer); if (st.dwarf) kit_dwarf_free(st.dwarf); - if (st.session) kit_jit_session_free(st.session); + if (st.session) kit_dbg_session_free(st.session); kit_jit_free(jit); driver_compiler_free(compiler); kit_target_free(target); diff --git a/driver/env.h b/driver/env.h @@ -352,7 +352,7 @@ void driver_flush_stdout(void); /* Install / restore a SIGINT handler. While installed, SIGINT runs `cb(user)` * synchronously (so `cb` must be async-signal safe). Used by `dbg` to - * forward Ctrl-C into kit_jit_session_interrupt while the worker is + * forward Ctrl-C into kit_dbg_session_interrupt while the worker is * running, and to restore SIG_DFL while sitting at the REPL prompt so * Ctrl-C terminates the program normally. Returns 0 on success. */ int driver_install_sigint(void (*cb)(void*), void* user); diff --git a/driver/lib/backtrace.c b/driver/lib/backtrace.c @@ -1,5 +1,6 @@ #include "backtrace.h" +#include <kit/dbg.h> #include <stdarg.h> #include <stdio.h> @@ -72,66 +73,61 @@ static void bt_appendf(char* buf, size_t cap, size_t* len, const char* fmt, if (*len >= cap) *len = cap - 1; /* clamp to keep the NUL terminator */ } -/* Render one frame line: "#N 0xPC [<sym+off>] [at file:line[:col]]". */ +/* Render one frame line: "#N 0xPC [<sym+off>] [at file:line[:col]]". + * + * Symbolization (jit symbol + DWARF func/line) comes from the public + * kit_dbg_symbolize_pc; this function only owns the line formatting. For a PC + * that doesn't translate into the kit image (libc/dyld trampolines) the frame + * carries in_image=false and prints bare, rather than mis-attributing it to the + * nearest symbol with a giant offset. */ static void bt_render_frame(const DriverBtCtx* ctx, int level, uint64_t pc) { char line[1024]; size_t len = 0; - uint64_t img_pc = pc; - /* Whether symbol/DWARF lookups are meaningful for this PC. With a JIT image, - * an address that doesn't translate to an image vaddr is outside the kit - * image (libc/dyld trampolines) — print it bare rather than mis-attributing - * it to the nearest symbol with a giant offset. Without a JIT, treat the PCs - * as already image-relative. */ - int in_image = 1; + KitSlice name = KIT_SLICE_NULL; + uint64_t off = 0; + KitSlice file = KIT_SLICE_NULL; + uint32_t srcline = 0, col = 0; + int have = 0; line[0] = '\0'; bt_appendf(line, sizeof line, &len, "#%-2d 0x%llx", level, (unsigned long long)pc); if (ctx->jit) { - uint64_t v = kit_jit_runtime_to_image(ctx->jit, pc); - if (v) - img_pc = v; - else - in_image = 0; - } - - if (in_image) { - KitSlice sym = KIT_SLICE_NULL; - uint64_t off = 0; - int have_name = 0; - if (ctx->jit && kit_jit_addr_to_sym(ctx->jit, pc, &sym, &off) == KIT_OK && - sym.s) { - have_name = 1; - } else if (ctx->dwarf) { - KitSlice fn; - uint64_t lo = 0, hi = 0; - if (kit_dwarf_func_at(ctx->dwarf, img_pc, &fn, &lo, &hi) == KIT_OK && - fn.s) { - sym = fn; - off = (img_pc >= lo) ? (img_pc - lo) : 0; - have_name = 1; - } + KitDebugFrame f; + if (kit_dbg_symbolize_pc(ctx->jit, ctx->dwarf, pc, &f) == KIT_OK && + f.in_image) { + name = f.sym.s ? f.sym : f.func; + off = f.sym.s ? f.sym_offset : f.func_offset; + file = f.file; + srcline = f.line; + col = f.col; + have = 1; } - if (have_name) { - if (off) - bt_appendf(line, sizeof line, &len, " <%.*s+0x%llx>", (int)sym.len, - sym.s, (unsigned long long)off); - else - bt_appendf(line, sizeof line, &len, " <%.*s>", (int)sym.len, sym.s); + } else if (ctx->dwarf) { + /* No JIT image: treat the PC as already image-relative and consult DWARF + * directly. */ + KitSlice fn; + uint64_t lo = 0, hi = 0; + if (kit_dwarf_func_at(ctx->dwarf, pc, &fn, &lo, &hi) == KIT_OK && fn.s) { + name = fn; + off = (pc >= lo) ? (pc - lo) : 0; } + (void)kit_dwarf_addr_to_line(ctx->dwarf, pc, &file, &srcline, &col); + have = 1; } - if (in_image && ctx->dwarf) { - KitSlice file = KIT_SLICE_NULL; - uint32_t srcline = 0, col = 0; - if (kit_dwarf_addr_to_line(ctx->dwarf, img_pc, &file, &srcline, &col) == - KIT_OK && - file.s) { - bt_appendf(line, sizeof line, &len, " at %.*s:%u", (int)file.len, file.s, - srcline); - if (col) bt_appendf(line, sizeof line, &len, ":%u", col); - } + if (have && name.s) { + if (off) + bt_appendf(line, sizeof line, &len, " <%.*s+0x%llx>", (int)name.len, + name.s, (unsigned long long)off); + else + bt_appendf(line, sizeof line, &len, " <%.*s>", (int)name.len, name.s); + } + if (have && file.s) { + bt_appendf(line, sizeof line, &len, " at %.*s:%u", (int)file.len, file.s, + srcline); + if (col) bt_appendf(line, sizeof line, &len, ":%u", col); } ctx->emit(ctx->emit_user, line); diff --git a/include/kit/core.h b/include/kit/core.h @@ -37,7 +37,7 @@ typedef struct KitObjBuilder KitObjBuilder; typedef struct KitObjFile KitObjFile; typedef struct KitLinkSession KitLinkSession; typedef struct KitJit KitJit; -typedef struct KitJitSession KitJitSession; +typedef struct KitDebugSession KitDebugSession; typedef struct KitDebugInfo KitDebugInfo; typedef struct KitEmu KitEmu; diff --git a/include/kit/dbg.h b/include/kit/dbg.h @@ -2,14 +2,24 @@ #define KIT_DBG_H #include <kit/arch.h> +#include <kit/disasm.h> +#include <kit/dwarf.h> #include <kit/jit.h> /* * Controlled in-process JIT execution. * - * A session owns the stop/resume state for one JIT image. Hosts provide the - * OS hooks through KitDbgHost when they need breakpointing, stepping, or + * A KitDebugSession owns the stop/resume state for one JIT image. Hosts provide + * the OS hooks through KitDbgHost when they need breakpointing, stepping, or * signal-aware memory/register access. + * + * Two altitudes share the session object: + * - The control substrate (kit_dbg_session_*, below) moves bytes and raw + * registers: run / step / break / read / write. + * - The symbolic layer (kit_dbg_*, further below) composes that substrate + * with the session's attached DWARF and JIT image into the operations an + * interactive debugger performs: backtraces, typed values, scope + * enumeration, location resolution, and disassembly at the stopped PC. */ typedef struct KitDbgSignalOps { @@ -102,36 +112,175 @@ typedef struct KitBreakpointSpec { void* condition_user; } KitBreakpointSpec; -KIT_API KitStatus kit_jit_session_new(KitJit*, const KitDbgHost*, - KitJitSession** out); -KIT_API void kit_jit_session_free(KitJitSession*); -KIT_API KitStatus kit_jit_session_attach_dwarf(KitJitSession*, KitDebugInfo*); +KIT_API KitStatus kit_dbg_session_new(KitJit*, const KitDbgHost*, + KitDebugSession** out); +KIT_API void kit_dbg_session_free(KitDebugSession*); +KIT_API KitStatus kit_dbg_session_attach_dwarf(KitDebugSession*, KitDebugInfo*); -KIT_API KitStatus kit_jit_session_call(KitJitSession*, void* entry, +KIT_API KitStatus kit_dbg_session_call(KitDebugSession*, void* entry, KitEntryKind, int argc, char** argv, KitStopInfo* stop_out); -KIT_API KitStatus kit_jit_session_call_u64(KitJitSession*, void* entry, +KIT_API KitStatus kit_dbg_session_call_u64(KitDebugSession*, void* entry, const uint64_t* args, uint32_t nargs, uint64_t* ret_out, KitStopInfo* stop_out); -KIT_API KitStatus kit_jit_session_resume(KitJitSession*, KitResumeMode, +KIT_API KitStatus kit_dbg_session_resume(KitDebugSession*, KitResumeMode, KitStopInfo* stop_out); -KIT_API KitStatus kit_jit_session_interrupt(KitJitSession*); +KIT_API KitStatus kit_dbg_session_interrupt(KitDebugSession*); -KIT_API KitStatus kit_jit_session_read_mem(KitJitSession*, uint64_t addr, +KIT_API KitStatus kit_dbg_session_read_mem(KitDebugSession*, uint64_t addr, void* dst, size_t n); -KIT_API KitStatus kit_jit_session_write_mem(KitJitSession*, uint64_t addr, +KIT_API KitStatus kit_dbg_session_write_mem(KitDebugSession*, uint64_t addr, const void* src, size_t n); -KIT_API KitStatus kit_jit_session_get_regs(KitJitSession*, KitUnwindFrame* out); -KIT_API KitStatus kit_jit_session_set_regs(KitJitSession*, +KIT_API KitStatus kit_dbg_session_get_regs(KitDebugSession*, KitUnwindFrame* out); +KIT_API KitStatus kit_dbg_session_set_regs(KitDebugSession*, const KitUnwindFrame*); -KIT_API KitStatus kit_jit_session_breakpoint_set(KitJitSession*, uint64_t addr, +KIT_API KitStatus kit_dbg_session_breakpoint_set(KitDebugSession*, uint64_t addr, uint32_t* bp_id_out); -KIT_API KitStatus kit_jit_session_breakpoint_clear(KitJitSession*, +KIT_API KitStatus kit_dbg_session_breakpoint_clear(KitDebugSession*, uint32_t bp_id); -KIT_API KitStatus kit_jit_session_breakpoint_set_spec(KitJitSession*, +KIT_API KitStatus kit_dbg_session_breakpoint_set_spec(KitDebugSession*, const KitBreakpointSpec*, uint32_t* bp_id_out); +/* =========================================================================== + * Symbolic layer — interactive inspection over a stopped KitDebugSession. + * + * Address-space convention: callers always speak RUNTIME addresses (the + * addresses a KitStopInfo reports). These entry points translate to + * image-relative form for DWARF queries internally and return runtime + * addresses. The session is the memory provider, so no KitDwarfReadMemFn + * plumbing is required of the caller. + * + * Value lifetime (the "stop epoch"): bytes materialized into a KitDebugValue + * are owned by the session and stay valid until the next call/resume on that + * session. KitSlice names and source paths in results borrow from the attached + * DWARF / JIT image and live as long as those do. Backtrace and variable + * iterators are explicit cursors the caller frees. + * ========================================================================= */ + +/* ---- Frame description ------------------------------------------------- */ +typedef struct KitDebugFrame { + KitUnwindFrame regs; /* this frame's registers (runtime); zero for a bare PC */ + uint64_t pc; + uint64_t cfa; + KitSlice sym; /* JIT image symbol covering pc ("" if none) */ + uint64_t sym_offset; + KitSlice func; /* DWARF subprogram name ("" if no debug info) */ + uint64_t func_offset; + bool inlined; + KitSlice file; /* source path ("" if no line info) */ + uint32_t line; + uint32_t col; + bool in_image; /* false once the chain leaves the kit JIT image */ +} KitDebugFrame; + +/* Describe a single PC symbolically from an image + (optional) DWARF, with no + * session. Fills sym/func/file/line and in_image; leaves regs/cfa zero. `dwarf` + * may be NULL (symbol + image-boundary info only). Serves crash reporters that + * captured a PC list out-of-band, where no live session exists. */ +KIT_API KitStatus kit_dbg_symbolize_pc(KitJit*, KitDebugInfo* /*nullable*/, + uint64_t pc, KitDebugFrame* out); + +/* ---- Backtrace -------------------------------------------------------- */ +/* Frame-pointer-chain unwind of the stopped session. kit keeps a frame pointer + * on every backend (uniform fp[0]=caller fp, fp[1]=saved return address), so + * the walk is reliable where kit_dwarf_unwind_step is not (the CFI stepper + * takes no memory provider and cannot recover a spilled return address). Reads + * go through the session; the walk stops at the JIT-image boundary. `top` + * selects the starting frame (NULL = the current stop's registers). */ +typedef struct KitDebugBacktrace KitDebugBacktrace; +KIT_API KitStatus kit_dbg_backtrace_new(KitDebugSession*, + const KitUnwindFrame* top /*nullable*/, + KitDebugBacktrace** out); +KIT_API uint32_t kit_dbg_backtrace_count(const KitDebugBacktrace*); +KIT_API KitStatus kit_dbg_backtrace_frame(const KitDebugBacktrace*, + uint32_t level, KitDebugFrame* out); +KIT_API void kit_dbg_backtrace_free(KitDebugBacktrace*); + +/* ---- Typed values ----------------------------------------------------- */ +typedef struct KitDebugValue { + const KitDwarfType* type; /* NULL when no DWARF type was recovered */ + const uint8_t* bytes; /* materialized contents; session-owned, stop epoch */ + size_t len; + bool has_address; /* true when the value has a live memory location */ + uint64_t address; /* runtime lvalue address when has_address */ +} KitDebugValue; + +/* Read a variable visible at `frame` (NULL = current top) by name. Tries DWARF + * locals/args/globals first; on a miss, resolves a JIT global symbol (then the + * value's type is NULL and bytes hold the symbol's address). */ +KIT_API KitStatus kit_dbg_var_read(KitDebugSession*, + const KitUnwindFrame* frame /*nullable*/, + KitSlice name, KitDebugValue* out); +/* Materialize a typed region at a runtime address (e.g. after a deref). */ +KIT_API KitStatus kit_dbg_value_at(KitDebugSession*, uint64_t addr, + const KitDwarfType*, KitDebugValue* out); + +/* Navigation. field/index reslice already-materialized bytes (no memory read); + * deref reads the pointee through the session. */ +KIT_API KitStatus kit_dbg_value_field(KitDebugSession*, const KitDebugValue*, + KitSlice field, KitDebugValue* out); +KIT_API KitStatus kit_dbg_value_index(KitDebugSession*, const KitDebugValue*, + uint64_t index, KitDebugValue* out); +KIT_API KitStatus kit_dbg_value_deref(KitDebugSession*, const KitDebugValue*, + KitDebugValue* out); + +/* Scalar extraction (honors the value's DWARF type for sign/float; falls back + * to a little-endian load when type is NULL). For structured access, walk + * `type` via kit/dwarf.h alongside these; for a ready-made rendering use + * kit_dbg_value_format below. */ +KIT_API KitStatus kit_dbg_value_as_u64(const KitDebugValue*, uint64_t* out); +KIT_API KitStatus kit_dbg_value_as_i64(const KitDebugValue*, int64_t* out); +KIT_API KitStatus kit_dbg_value_as_f64(const KitDebugValue*, double* out); + +/* Pretty-print a value to `out`, recursing through aggregates (one field/element + * per line, indented), resolving enum names, and formatting floats. Emits the + * value text only — no leading name, no trailing newline. `opt` may be NULL for + * defaults (base indent 0). */ +typedef struct KitDebugFormatOptions { + uint32_t indent; /* base indent level for nested aggregates */ +} KitDebugFormatOptions; +KIT_API KitStatus kit_dbg_value_format(KitDebugSession*, const KitDebugValue*, + KitWriter* out, + const KitDebugFormatOptions* /*nullable*/); + +/* Write a scalar into a variable by name (frame NULL = current top). Routes to + * register / frame / global storage; rejects DWARF-expression locations. */ +KIT_API KitStatus kit_dbg_var_write_u64(KitDebugSession*, + const KitUnwindFrame* frame /*nullable*/, + KitSlice name, uint64_t value); + +/* ---- Scope enumeration ------------------------------------------------ */ +/* Iterate variables in scope at `frame` (NULL = current top). `roles` is a + * KitDwarfVarRoleMask bitset. Each step yields the name and the materialized + * value (stop-epoch lifetime). */ +typedef struct KitDebugVarIter KitDebugVarIter; +KIT_API KitStatus kit_dbg_vars_new(KitDebugSession*, + const KitUnwindFrame* frame /*nullable*/, + uint32_t roles, KitDebugVarIter** out); +KIT_API KitIterResult kit_dbg_vars_next(KitDebugVarIter*, KitSlice* name_out, + KitDebugValue* value_out); +KIT_API void kit_dbg_vars_free(KitDebugVarIter*); + +/* ---- Location resolution + breakpoints -------------------------------- */ +/* Resolve a gdb-style spec to a runtime address: "0xADDR" / "NAME[+off]" / + * "file.c:LINE". On an ambiguous file:line, returns KIT_AMBIGUOUS and fills up + * to `cap` candidate image addresses into `cands`, with the total in *ncand + * (both may be NULL when the caller does not want candidates). */ +KIT_API KitStatus kit_dbg_resolve(KitDebugSession*, KitSlice spec, + uint64_t* addr_out, KitDwarfLineMatch* cands, + uint32_t cap, uint32_t* ncand); +/* Resolve `spec` and set a breakpoint there (convenience over kit_dbg_resolve + + * kit_dbg_session_breakpoint_set). */ +KIT_API KitStatus kit_dbg_break(KitDebugSession*, KitSlice spec, + uint32_t* bp_id_out); + +/* ---- Disassemble at the stopped PC ------------------------------------ */ +/* Read `count` instructions starting at runtime `addr` from the stopped image + * and return a standard disassembly iterator over them. */ +KIT_API KitStatus kit_dbg_disasm_new(KitDebugSession*, uint64_t addr, + uint32_t count, KitDisasmIter** out); + #endif diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c @@ -639,23 +639,23 @@ void kit_jit_sym_iter_free(KitJitSymIter* it) { (void)it; } #endif #if !KIT_DBG_ENABLED -KitStatus kit_jit_session_new(KitJit* jit, const KitDbgHost* host, - KitJitSession** out) { +KitStatus kit_dbg_session_new(KitJit* jit, const KitDbgHost* host, + KitDebugSession** out) { (void)jit; (void)host; if (out) *out = NULL; return KIT_UNSUPPORTED; } -void kit_jit_session_free(KitJitSession* s) { (void)s; } +void kit_dbg_session_free(KitDebugSession* s) { (void)s; } -KitStatus kit_jit_session_attach_dwarf(KitJitSession* s, KitDebugInfo* dwarf) { +KitStatus kit_dbg_session_attach_dwarf(KitDebugSession* s, KitDebugInfo* dwarf) { (void)s; (void)dwarf; return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_call(KitJitSession* s, void* entry, KitEntryKind kind, +KitStatus kit_dbg_session_call(KitDebugSession* s, void* entry, KitEntryKind kind, int argc, char** argv, KitStopInfo* stop_out) { (void)s; (void)entry; @@ -666,7 +666,7 @@ KitStatus kit_jit_session_call(KitJitSession* s, void* entry, KitEntryKind kind, return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_call_u64(KitJitSession* s, void* entry, +KitStatus kit_dbg_session_call_u64(KitDebugSession* s, void* entry, const uint64_t* args, uint32_t nargs, uint64_t* ret_out, KitStopInfo* stop_out) { (void)s; @@ -678,7 +678,7 @@ KitStatus kit_jit_session_call_u64(KitJitSession* s, void* entry, return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_resume(KitJitSession* s, KitResumeMode mode, +KitStatus kit_dbg_session_resume(KitDebugSession* s, KitResumeMode mode, KitStopInfo* stop_out) { (void)s; (void)mode; @@ -686,12 +686,12 @@ KitStatus kit_jit_session_resume(KitJitSession* s, KitResumeMode mode, return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_interrupt(KitJitSession* s) { +KitStatus kit_dbg_session_interrupt(KitDebugSession* s) { (void)s; return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_read_mem(KitJitSession* s, uint64_t addr, void* dst, +KitStatus kit_dbg_session_read_mem(KitDebugSession* s, uint64_t addr, void* dst, size_t n) { (void)s; (void)addr; @@ -700,7 +700,7 @@ KitStatus kit_jit_session_read_mem(KitJitSession* s, uint64_t addr, void* dst, return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_write_mem(KitJitSession* s, uint64_t addr, +KitStatus kit_dbg_session_write_mem(KitDebugSession* s, uint64_t addr, const void* src, size_t n) { (void)s; (void)addr; @@ -709,19 +709,19 @@ KitStatus kit_jit_session_write_mem(KitJitSession* s, uint64_t addr, return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_get_regs(KitJitSession* s, KitUnwindFrame* out) { +KitStatus kit_dbg_session_get_regs(KitDebugSession* s, KitUnwindFrame* out) { (void)s; (void)out; return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_set_regs(KitJitSession* s, const KitUnwindFrame* in) { +KitStatus kit_dbg_session_set_regs(KitDebugSession* s, const KitUnwindFrame* in) { (void)s; (void)in; return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_breakpoint_set(KitJitSession* s, uint64_t addr, +KitStatus kit_dbg_session_breakpoint_set(KitDebugSession* s, uint64_t addr, uint32_t* bp_id_out) { (void)s; (void)addr; @@ -729,13 +729,13 @@ KitStatus kit_jit_session_breakpoint_set(KitJitSession* s, uint64_t addr, return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_breakpoint_clear(KitJitSession* s, uint32_t bp_id) { +KitStatus kit_dbg_session_breakpoint_clear(KitDebugSession* s, uint32_t bp_id) { (void)s; (void)bp_id; return KIT_UNSUPPORTED; } -KitStatus kit_jit_session_breakpoint_set_spec(KitJitSession* s, +KitStatus kit_dbg_session_breakpoint_set_spec(KitDebugSession* s, const KitBreakpointSpec* spec, uint32_t* bp_id_out) { (void)s; @@ -743,6 +743,167 @@ KitStatus kit_jit_session_breakpoint_set_spec(KitJitSession* s, (void)bp_id_out; return KIT_UNSUPPORTED; } + +/* Symbolic inspection layer (symbolic.c). */ +KitStatus kit_dbg_symbolize_pc(KitJit* jit, KitDebugInfo* dwarf, uint64_t pc, + KitDebugFrame* out) { + (void)jit; + (void)dwarf; + (void)pc; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_backtrace_new(KitDebugSession* s, const KitUnwindFrame* top, + KitDebugBacktrace** out) { + (void)s; + (void)top; + if (out) *out = NULL; + return KIT_UNSUPPORTED; +} + +uint32_t kit_dbg_backtrace_count(const KitDebugBacktrace* bt) { + (void)bt; + return 0; +} + +KitStatus kit_dbg_backtrace_frame(const KitDebugBacktrace* bt, uint32_t level, + KitDebugFrame* out) { + (void)bt; + (void)level; + (void)out; + return KIT_UNSUPPORTED; +} + +void kit_dbg_backtrace_free(KitDebugBacktrace* bt) { (void)bt; } + +KitStatus kit_dbg_var_read(KitDebugSession* s, const KitUnwindFrame* frame, + KitSlice name, KitDebugValue* out) { + (void)s; + (void)frame; + (void)name; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_at(KitDebugSession* s, uint64_t addr, + const KitDwarfType* type, KitDebugValue* out) { + (void)s; + (void)addr; + (void)type; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_field(KitDebugSession* s, const KitDebugValue* v, + KitSlice field, KitDebugValue* out) { + (void)s; + (void)v; + (void)field; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_index(KitDebugSession* s, const KitDebugValue* v, + uint64_t index, KitDebugValue* out) { + (void)s; + (void)v; + (void)index; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_deref(KitDebugSession* s, const KitDebugValue* v, + KitDebugValue* out) { + (void)s; + (void)v; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_as_u64(const KitDebugValue* v, uint64_t* out) { + (void)v; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_as_i64(const KitDebugValue* v, int64_t* out) { + (void)v; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_as_f64(const KitDebugValue* v, double* out) { + (void)v; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_value_format(KitDebugSession* s, const KitDebugValue* v, + KitWriter* out, + const KitDebugFormatOptions* opt) { + (void)s; + (void)v; + (void)out; + (void)opt; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_var_write_u64(KitDebugSession* s, const KitUnwindFrame* frame, + KitSlice name, uint64_t value) { + (void)s; + (void)frame; + (void)name; + (void)value; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_vars_new(KitDebugSession* s, const KitUnwindFrame* frame, + uint32_t roles, KitDebugVarIter** out) { + (void)s; + (void)frame; + (void)roles; + if (out) *out = NULL; + return KIT_UNSUPPORTED; +} + +KitIterResult kit_dbg_vars_next(KitDebugVarIter* it, KitSlice* name_out, + KitDebugValue* value_out) { + (void)it; + (void)name_out; + (void)value_out; + return KIT_ITER_ERROR; +} + +void kit_dbg_vars_free(KitDebugVarIter* it) { (void)it; } + +KitStatus kit_dbg_resolve(KitDebugSession* s, KitSlice spec, uint64_t* addr_out, + KitDwarfLineMatch* cands, uint32_t cap, + uint32_t* ncand) { + (void)s; + (void)spec; + (void)addr_out; + (void)cands; + (void)cap; + (void)ncand; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_break(KitDebugSession* s, KitSlice spec, uint32_t* bp_id_out) { + (void)s; + (void)spec; + (void)bp_id_out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_dbg_disasm_new(KitDebugSession* s, uint64_t addr, uint32_t count, + KitDisasmIter** out) { + (void)s; + (void)addr; + (void)count; + if (out) *out = NULL; + return KIT_UNSUPPORTED; +} #endif #if !KIT_EMU_ENABLED diff --git a/src/dbg/bp.c b/src/dbg/bp.c @@ -18,7 +18,7 @@ static u32 bp_find_slot(DbgBpTable* t, uint64_t addr) { return 0; } -static u32 bp_alloc_slot(KitJitSession* s) { +static u32 bp_alloc_slot(KitDebugSession* s) { DbgBpTable* t = &s->bps; u32 i; for (i = 0; i < t->cap; ++i) { @@ -42,13 +42,13 @@ static u32 bp_alloc_slot(KitJitSession* s) { } } -void dbg_bp_init(KitJitSession* s) { +void dbg_bp_init(KitDebugSession* s) { memset(&s->bps, 0, sizeof(s->bps)); s->bps.next_user_id = 1; s->bps.next_internal_id = DBG_BP_ID_INTERNAL_BASE; } -void dbg_bp_fini(KitJitSession* s) { +void dbg_bp_fini(KitDebugSession* s) { DbgBpTable* t = &s->bps; u32 i; if (t->slots) { @@ -75,7 +75,7 @@ void dbg_bp_fini(KitJitSession* s) { } } -static KitStatus bp_install_patch(KitJitSession* s, DbgBp* b) { +static KitStatus bp_install_patch(KitDebugSession* s, DbgBp* b) { void* write_addr = NULL; uint8_t patch[ARCH_DBG_MAX_TRAP_BYTES]; u32 patch_len = 0; @@ -99,7 +99,7 @@ static KitStatus bp_install_patch(KitJitSession* s, DbgBp* b) { return KIT_OK; } -static void bp_remove_patch(KitJitSession* s, DbgBp* b) { +static void bp_remove_patch(KitDebugSession* s, DbgBp* b) { void* write_addr = NULL; if (!b->enabled || !b->saved_len) return; if (s->os->code_write_begin(s->os->user, (void*)(uintptr_t)b->addr, @@ -114,7 +114,7 @@ static void bp_remove_patch(KitJitSession* s, DbgBp* b) { b->enabled = 0; } -static KitStatus bp_set_common(KitJitSession* s, const KitBreakpointSpec* spec, +static KitStatus bp_set_common(KitDebugSession* s, const KitBreakpointSpec* spec, int internal, u32* id_out) { uint64_t addr = spec->addr; u32 slot; @@ -162,27 +162,27 @@ static KitStatus bp_set_common(KitJitSession* s, const KitBreakpointSpec* spec, return KIT_OK; } -KitStatus dbg_bp_set(KitJitSession* s, uint64_t addr, u32* id_out) { +KitStatus dbg_bp_set(KitDebugSession* s, uint64_t addr, u32* id_out) { KitBreakpointSpec spec; memset(&spec, 0, sizeof(spec)); spec.addr = addr; return bp_set_common(s, &spec, 0, id_out); } -KitStatus dbg_bp_set_spec(KitJitSession* s, const KitBreakpointSpec* spec, +KitStatus dbg_bp_set_spec(KitDebugSession* s, const KitBreakpointSpec* spec, u32* id_out) { if (!spec) return KIT_INVALID; return bp_set_common(s, spec, 0, id_out); } -KitStatus dbg_bp_set_internal(KitJitSession* s, uint64_t addr, u32* id_out) { +KitStatus dbg_bp_set_internal(KitDebugSession* s, uint64_t addr, u32* id_out) { KitBreakpointSpec spec; memset(&spec, 0, sizeof(spec)); spec.addr = addr; return bp_set_common(s, &spec, 1, id_out); } -KitStatus dbg_bp_clear(KitJitSession* s, u32 id) { +KitStatus dbg_bp_clear(KitDebugSession* s, u32 id) { u32 i; if (id == 0) return KIT_OK; for (i = 0; i < s->bps.cap; ++i) { @@ -200,16 +200,16 @@ KitStatus dbg_bp_clear(KitJitSession* s, u32 id) { return KIT_OK; /* silent on unknown id, per contract */ } -u32 dbg_bp_lookup_index(KitJitSession* s, uint64_t addr) { +u32 dbg_bp_lookup_index(KitDebugSession* s, uint64_t addr) { return bp_find_slot(&s->bps, addr); } -DbgBp* dbg_bp_at_index(KitJitSession* s, u32 idx) { +DbgBp* dbg_bp_at_index(KitDebugSession* s, u32 idx) { if (idx == 0 || idx > s->bps.cap) return NULL; return &s->bps.slots[idx - 1]; } -void dbg_bp_unpatch_read(KitJitSession* s, uint64_t addr, void* buf, size_t n) { +void dbg_bp_unpatch_read(KitDebugSession* s, uint64_t addr, void* buf, size_t n) { u32 i; u8* out = (u8*)buf; uint64_t end = addr + n; diff --git a/src/dbg/dbg.h b/src/dbg/dbg.h @@ -1,7 +1,7 @@ #ifndef KIT_DBG_INTERNAL_H #define KIT_DBG_INTERNAL_H -/* Internal contracts for src/dbg/. The public KitJitSession entries are +/* Internal contracts for src/dbg/. The public KitDebugSession entries are * defined in session.c on top of these primitives; bp.c, step.c, mem.c, and * displaced.c own the target-independent session machinery. Per-arch debug * behavior is reached through ArchImpl.dbg. */ @@ -11,6 +11,7 @@ #include <kit/jit.h> #include "arch/arch.h" +#include "core/arena.h" #include "core/core.h" #define DBG_SCRATCH_PAGE_SIZE 4096u @@ -54,34 +55,34 @@ typedef struct DbgBpTable { u32 next_internal_id; /* monotonic, starts at DBG_BP_ID_INTERNAL_BASE */ } DbgBpTable; -struct KitJitSession; /* fwd */ +struct KitDebugSession; /* fwd */ -void dbg_bp_init(struct KitJitSession*); -void dbg_bp_fini(struct KitJitSession*); +void dbg_bp_init(struct KitDebugSession*); +void dbg_bp_fini(struct KitDebugSession*); /* set/clear with the user-facing handle space. The internal variants are * used by step.c for one-shot temporaries. */ -KitStatus dbg_bp_set(struct KitJitSession*, uint64_t addr, u32* id_out); -KitStatus dbg_bp_set_spec(struct KitJitSession*, const KitBreakpointSpec*, +KitStatus dbg_bp_set(struct KitDebugSession*, uint64_t addr, u32* id_out); +KitStatus dbg_bp_set_spec(struct KitDebugSession*, const KitBreakpointSpec*, u32* id_out); -KitStatus dbg_bp_set_internal(struct KitJitSession*, uint64_t addr, +KitStatus dbg_bp_set_internal(struct KitDebugSession*, uint64_t addr, u32* id_out); -KitStatus dbg_bp_clear(struct KitJitSession*, u32 id); +KitStatus dbg_bp_clear(struct KitDebugSession*, u32 id); /* Lookup at a PC. Returns the slot index + 1 (so 0 means "not patched"); * the caller uses dbg_bp_at_index to fetch the entry. */ -u32 dbg_bp_lookup_index(struct KitJitSession*, uint64_t addr); -DbgBp* dbg_bp_at_index(struct KitJitSession*, u32 idx); +u32 dbg_bp_lookup_index(struct KitDebugSession*, uint64_t addr); +DbgBp* dbg_bp_at_index(struct KitDebugSession*, u32 idx); /* Memory-read fixup: if [addr, addr+n) overlaps any patched bp, write the * original bytes back into `buf` at the right offsets. */ -void dbg_bp_unpatch_read(struct KitJitSession*, uint64_t addr, void* buf, +void dbg_bp_unpatch_read(struct KitDebugSession*, uint64_t addr, void* buf, size_t n); /* ---- memory --------------------------------------------------------- */ -KitStatus dbg_mem_read(struct KitJitSession*, uint64_t addr, void* dst, +KitStatus dbg_mem_read(struct KitDebugSession*, uint64_t addr, void* dst, size_t n); -KitStatus dbg_mem_write(struct KitJitSession*, uint64_t addr, const void* src, +KitStatus dbg_mem_write(struct KitDebugSession*, uint64_t addr, const void* src, size_t n); /* ---- displaced step ------------------------------------------------- */ @@ -99,24 +100,24 @@ typedef struct DbgDisplaced { uint32_t internal_bp; /* id of the one-shot bp at return_pc */ } DbgDisplaced; -KitStatus dbg_displaced_init(struct KitJitSession*); -void dbg_displaced_fini(struct KitJitSession*); +KitStatus dbg_displaced_init(struct KitDebugSession*); +void dbg_displaced_fini(struct KitDebugSession*); /* Prepare an out-of-line single-step at `insn_pc`. Sets *new_pc to the * scratch entry the worker should branch to; arms an internal bp on the * shim's trap sentinel. Returns KIT_OK on success, KIT_UNSUPPORTED if the * insn family is not supported. */ -KitStatus dbg_displaced_prepare(struct KitJitSession*, uint64_t insn_pc, +KitStatus dbg_displaced_prepare(struct KitDebugSession*, uint64_t insn_pc, uint64_t* new_pc); /* After the shim trap fires, finalize: clear the internal bp, restore the * user-visible PC to the decoded fallthrough PC (or leave a branch target * captured by the shim alone). */ -void dbg_displaced_finalize(struct KitJitSession*); -KitStatus dbg_arch_decode_insn(struct KitJitSession*, uint64_t pc, +void dbg_displaced_finalize(struct KitDebugSession*); +KitStatus dbg_arch_decode_insn(struct KitDebugSession*, uint64_t pc, ArchDbgInsn* out); /* ---- step state machine --------------------------------------------- */ -KitStatus dbg_step_resume(struct KitJitSession*, KitResumeMode mode); +KitStatus dbg_step_resume(struct KitDebugSession*, KitResumeMode mode); /* ---- session state -------------------------------------------------- */ typedef enum DbgSessionState { @@ -126,7 +127,7 @@ typedef enum DbgSessionState { DBG_STATE_EXITED = 3, /* worker entry returned */ } DbgSessionState; -struct KitJitSession { +struct KitDebugSession { KitJit* jit; Compiler* c; Heap* heap; @@ -175,15 +176,20 @@ struct KitJitSession { /* optional DWARF binding (caller-owned; needed for source-level steps) */ KitDebugInfo* dwarf; + /* Stop-epoch scratch for the symbolic layer (symbolic.c): KitDebugValue byte + * storage plus backtrace/iterator allocations. Reset on every call/resume so + * materialized values stay valid exactly until the next one. */ + Arena values; + /* set by dbg_step_resume when it has already driven the worker through - * its own signal/wait cycles; tells kit_jit_session_resume not to + * its own signal/wait cycles; tells kit_dbg_session_resume not to * issue another resume. */ u8 pending_done; u8 pad2[3]; }; /* internal helpers shared between session.c and step.c */ -KitStatus dbg_session_wait_stop(struct KitJitSession*); -KitStatus dbg_session_signal_resume(struct KitJitSession*); +KitStatus dbg_session_wait_stop(struct KitDebugSession*); +KitStatus dbg_session_signal_resume(struct KitDebugSession*); #endif diff --git a/src/dbg/displaced.c b/src/dbg/displaced.c @@ -12,7 +12,7 @@ #include "dbg/dbg.h" -KitStatus dbg_displaced_init(KitJitSession* s) { +KitStatus dbg_displaced_init(KitDebugSession* s) { const KitExecMem* mem; KitStatus st; if (s->displaced.valid) return KIT_OK; @@ -33,14 +33,14 @@ KitStatus dbg_displaced_init(KitJitSession* s) { return KIT_OK; } -void dbg_displaced_fini(KitJitSession* s) { +void dbg_displaced_fini(KitDebugSession* s) { const KitExecMem* mem = s->execmem; if (!s->displaced.valid) return; if (mem && mem->release) mem->release(mem->user, &s->displaced.region); memset(&s->displaced, 0, sizeof(s->displaced)); } -KitStatus dbg_displaced_prepare(KitJitSession* s, uint64_t insn_pc, +KitStatus dbg_displaced_prepare(KitDebugSession* s, uint64_t insn_pc, uint64_t* new_pc) { ArchDbgInsn insn; u32 brk_off = 0; @@ -98,7 +98,7 @@ KitStatus dbg_displaced_prepare(KitJitSession* s, uint64_t insn_pc, return KIT_OK; } -void dbg_displaced_finalize(KitJitSession* s) { +void dbg_displaced_finalize(KitDebugSession* s) { if (s->displaced.internal_bp != 0) { dbg_bp_clear(s, s->displaced.internal_bp); s->displaced.internal_bp = 0; @@ -114,7 +114,7 @@ void dbg_displaced_finalize(KitJitSession* s) { s->displaced.fallthrough_pc = 0; } -KitStatus dbg_arch_decode_insn(KitJitSession* s, uint64_t pc, +KitStatus dbg_arch_decode_insn(KitDebugSession* s, uint64_t pc, ArchDbgInsn* out) { uint8_t buf[ARCH_DBG_MAX_INSN_BYTES]; KitStatus st; diff --git a/src/dbg/mem.c b/src/dbg/mem.c @@ -7,7 +7,7 @@ #include "dbg/dbg.h" -KitStatus dbg_mem_read(KitJitSession* s, uint64_t addr, void* dst, size_t n) { +KitStatus dbg_mem_read(KitDebugSession* s, uint64_t addr, void* dst, size_t n) { KitStatus st; if (!s || !dst || n == 0) return KIT_INVALID; st = s->os->guarded_copy(s->os->user, dst, (const void*)(uintptr_t)addr, n); @@ -16,7 +16,7 @@ KitStatus dbg_mem_read(KitJitSession* s, uint64_t addr, void* dst, size_t n) { return KIT_OK; } -KitStatus dbg_mem_write(KitJitSession* s, uint64_t addr, const void* src, +KitStatus dbg_mem_write(KitDebugSession* s, uint64_t addr, const void* src, size_t n) { if (!s || !src || n == 0) return KIT_INVALID; return s->os->guarded_copy(s->os->user, (void*)(uintptr_t)addr, src, n); diff --git a/src/dbg/session.c b/src/dbg/session.c @@ -1,11 +1,11 @@ -/* KitJitSession lifecycle, worker handshake, and fault classification. +/* KitDebugSession lifecycle, worker handshake, and fault classification. * * The session owns a single worker thread that runs the JIT'd entry. The * REPL thread and worker thread coordinate through two events (resume, * stop) and one shared KitStopInfo slot. Every fault on the worker * (trap / SIGSEGV / SIGBUS / SIGILL / SIGFPE / interrupt_signo) drops into * on_fault here; this TU is also the only place that touches the public - * KitJitSession entries. */ + * KitDebugSession entries. */ #include <string.h> @@ -31,7 +31,7 @@ static KitStopReason stop_reason_for_step(KitResumeMode mode) { } static KitStatus on_fault(void* session_v, int signo, KitUnwindFrame* regs) { - KitJitSession* s = (KitJitSession*)session_v; + KitDebugSession* s = (KitDebugSession*)session_v; uint64_t bp_addr; u32 idx; DbgBp* bp; @@ -194,7 +194,7 @@ park: /* ---- worker thread -------------------------------------------------- */ static void worker_run_entry(void* arg) { - KitJitSession* s = (KitJitSession*)arg; + KitDebugSession* s = (KitDebugSession*)arg; typedef int (*EntryIntArgv)(int, char**); typedef uint64_t (*EntryU64_0)(void); typedef uint64_t (*EntryU64_1)(uint64_t); @@ -271,7 +271,7 @@ static void worker_run_entry(void* arg) { } static void worker_main(void* arg) { - KitJitSession* s = (KitJitSession*)arg; + KitDebugSession* s = (KitDebugSession*)arg; for (;;) { s->os->event_wait(s->os->user, s->ev_resume); s->os->event_reset(s->os->user, s->ev_resume); @@ -298,9 +298,9 @@ static void worker_main(void* arg) { /* ---- public entries ------------------------------------------------- */ -KitStatus kit_jit_session_new(KitJit* jit, const KitDbgHost* host, - KitJitSession** out) { - KitJitSession* s; +KitStatus kit_dbg_session_new(KitJit* jit, const KitDbgHost* host, + KitDebugSession** out) { + KitDebugSession* s; Compiler* c; Heap* heap; const KitDbgOs* os; @@ -325,7 +325,7 @@ KitStatus kit_jit_session_new(KitJit* jit, const KitDbgHost* host, } heap = c->ctx->heap; - s = (KitJitSession*)heap->alloc(heap, sizeof(*s), _Alignof(KitJitSession)); + s = (KitDebugSession*)heap->alloc(heap, sizeof(*s), _Alignof(KitDebugSession)); if (!s) return KIT_NOMEM; memset(s, 0, sizeof(*s)); s->jit = jit; @@ -339,6 +339,7 @@ KitStatus kit_jit_session_new(KitJit* jit, const KitDbgHost* host, s->arch_impl = arch; s->arch_dbg = arch->dbg; s->state = DBG_STATE_IDLE; + arena_init(&s->values, heap, DBG_SCRATCH_PAGE_SIZE); st = os->event_new(os->user, &s->ev_resume); if (st != KIT_OK) { @@ -376,19 +377,19 @@ KitStatus kit_jit_session_new(KitJit* jit, const KitDbgHost* host, return KIT_OK; } -KitStatus kit_jit_session_attach_dwarf(KitJitSession* s, KitDebugInfo* dw) { +KitStatus kit_dbg_session_attach_dwarf(KitDebugSession* s, KitDebugInfo* dw) { if (!s) return KIT_INVALID; s->dwarf = dw; return KIT_OK; } -KitStatus dbg_session_signal_resume(KitJitSession* s) { +KitStatus dbg_session_signal_resume(KitDebugSession* s) { if (!s) return KIT_INVALID; s->state = DBG_STATE_RUNNING; return s->os->event_signal(s->os->user, s->ev_resume); } -KitStatus dbg_session_wait_stop(KitJitSession* s) { +KitStatus dbg_session_wait_stop(KitDebugSession* s) { KitStatus st; if (!s) return KIT_INVALID; st = s->os->event_wait(s->os->user, s->ev_stop); @@ -396,7 +397,7 @@ KitStatus dbg_session_wait_stop(KitJitSession* s) { return s->os->event_reset(s->os->user, s->ev_stop); } -void kit_jit_session_free(KitJitSession* s) { +void kit_dbg_session_free(KitDebugSession* s) { if (!s) return; /* If the worker is parked inside the signal handler (STOPPED), there is * no clean way to unwind it without re-running the user's program to @@ -415,16 +416,19 @@ void kit_jit_session_free(KitJitSession* s) { s->os->signals_uninstall(s->os->user); dbg_displaced_fini(s); dbg_bp_fini(s); + arena_fini(&s->values); if (s->ev_resume) s->os->event_free(s->os->user, s->ev_resume); if (s->ev_stop) s->os->event_free(s->os->user, s->ev_stop); s->heap->free(s->heap, s, sizeof(*s)); } -KitStatus kit_jit_session_call(KitJitSession* s, void* entry, KitEntryKind kind, +KitStatus kit_dbg_session_call(KitDebugSession* s, void* entry, KitEntryKind kind, int argc, char** argv, KitStopInfo* stop_out) { if (!s || !entry) return KIT_INVALID; if (s->state == DBG_STATE_RUNNING || s->state == DBG_STATE_STOPPED) return KIT_INVALID; + /* New run: invalidate the previous stop's symbolic-value storage. */ + arena_reset(&s->values); s->entry = entry; s->entry_kind = kind; s->entry_argc = argc; @@ -441,7 +445,7 @@ KitStatus kit_jit_session_call(KitJitSession* s, void* entry, KitEntryKind kind, return KIT_OK; } -KitStatus kit_jit_session_call_u64(KitJitSession* s, void* entry, +KitStatus kit_dbg_session_call_u64(KitDebugSession* s, void* entry, const uint64_t* args, uint32_t nargs, uint64_t* ret_out, KitStopInfo* stop_out) { uint32_t i; @@ -453,17 +457,19 @@ KitStatus kit_jit_session_call_u64(KitJitSession* s, void* entry, for (; i < 8u; ++i) s->entry_u64_args[i] = 0; s->entry_u64_nargs = nargs; s->entry_u64_ret = 0; - st = kit_jit_session_call(s, entry, KIT_ENTRY_U64, 0, NULL, stop_out); + st = kit_dbg_session_call(s, entry, KIT_ENTRY_U64, 0, NULL, stop_out); if (st == KIT_OK && ret_out) *ret_out = s->entry_u64_ret; return st; } -KitStatus kit_jit_session_resume(KitJitSession* s, KitResumeMode mode, +KitStatus kit_dbg_session_resume(KitDebugSession* s, KitResumeMode mode, KitStopInfo* stop_out) { if (!s) return KIT_INVALID; if (s->state == DBG_STATE_EXITED) return KIT_INVALID; if (s->state != DBG_STATE_STOPPED) return KIT_INVALID; + /* Leaving this stop: invalidate the symbolic-value storage tied to it. */ + arena_reset(&s->values); s->pending_mode = mode; s->pending_has_pc = 0; s->pending_step_pending = 0; @@ -495,14 +501,14 @@ KitStatus kit_jit_session_resume(KitJitSession* s, KitResumeMode mode, return KIT_OK; } -KitStatus kit_jit_session_interrupt(KitJitSession* s) { +KitStatus kit_dbg_session_interrupt(KitDebugSession* s) { if (!s) return KIT_INVALID; if (s->state != DBG_STATE_RUNNING) return KIT_INVALID; if (!s->os->thread_interrupt) return KIT_UNSUPPORTED; return s->os->thread_interrupt(s->os->user, s->worker); } -KitStatus kit_jit_session_read_mem(KitJitSession* s, uint64_t addr, void* dst, +KitStatus kit_dbg_session_read_mem(KitDebugSession* s, uint64_t addr, void* dst, size_t n) { if (!s) return KIT_INVALID; if (s->state != DBG_STATE_STOPPED && s->state != DBG_STATE_EXITED) @@ -510,7 +516,7 @@ KitStatus kit_jit_session_read_mem(KitJitSession* s, uint64_t addr, void* dst, return dbg_mem_read(s, addr, dst, n); } -KitStatus kit_jit_session_write_mem(KitJitSession* s, uint64_t addr, +KitStatus kit_dbg_session_write_mem(KitDebugSession* s, uint64_t addr, const void* src, size_t n) { if (!s) return KIT_INVALID; if (s->state != DBG_STATE_STOPPED && s->state != DBG_STATE_EXITED) @@ -518,14 +524,14 @@ KitStatus kit_jit_session_write_mem(KitJitSession* s, uint64_t addr, return dbg_mem_write(s, addr, src, n); } -KitStatus kit_jit_session_get_regs(KitJitSession* s, KitUnwindFrame* out) { +KitStatus kit_dbg_session_get_regs(KitDebugSession* s, KitUnwindFrame* out) { if (!s || !out) return KIT_INVALID; if (s->state != DBG_STATE_STOPPED) return KIT_INVALID; *out = s->stop.regs; return KIT_OK; } -KitStatus kit_jit_session_set_regs(KitJitSession* s, const KitUnwindFrame* in) { +KitStatus kit_dbg_session_set_regs(KitDebugSession* s, const KitUnwindFrame* in) { if (!s || !in) return KIT_INVALID; if (s->state != DBG_STATE_STOPPED) return KIT_INVALID; if (!kit_jit_image_contains(s->jit, in->pc)) return KIT_INVALID; @@ -533,18 +539,18 @@ KitStatus kit_jit_session_set_regs(KitJitSession* s, const KitUnwindFrame* in) { return KIT_OK; } -KitStatus kit_jit_session_breakpoint_set(KitJitSession* s, uint64_t addr, +KitStatus kit_dbg_session_breakpoint_set(KitDebugSession* s, uint64_t addr, uint32_t* bp_id_out) { if (!s) return KIT_INVALID; return dbg_bp_set(s, addr, bp_id_out); } -KitStatus kit_jit_session_breakpoint_clear(KitJitSession* s, uint32_t bp_id) { +KitStatus kit_dbg_session_breakpoint_clear(KitDebugSession* s, uint32_t bp_id) { if (!s) return KIT_INVALID; return dbg_bp_clear(s, bp_id); } -KitStatus kit_jit_session_breakpoint_set_spec(KitJitSession* s, +KitStatus kit_dbg_session_breakpoint_set_spec(KitDebugSession* s, const KitBreakpointSpec* spec, uint32_t* bp_id_out) { if (!s || !spec) return KIT_INVALID; diff --git a/src/dbg/step.c b/src/dbg/step.c @@ -12,19 +12,19 @@ #define DBG_STEP_LINE_INSN_CAP 1024u -static KitStatus run_step_line_loop(KitJitSession* s); +static KitStatus run_step_line_loop(KitDebugSession* s); /* DWARF line/CFI tables are authored in image-relative vaddrs (kit's * debug emitter writes them, the JIT view applies relocs against final * image vaddrs). Stop PCs and the values dropped onto the stack by * BL/RET, on the other hand, live in runtime address space. Every * DWARF call from the session translates at the boundary. */ -static uint64_t step_rt_to_img(KitJitSession* s, uint64_t pc) { +static uint64_t step_rt_to_img(KitDebugSession* s, uint64_t pc) { uint64_t v = kit_jit_runtime_to_image(s->jit, pc); return v ? v : pc; } -static KitStatus prepare_step_insn(KitJitSession* s) { +static KitStatus prepare_step_insn(KitDebugSession* s) { uint64_t pc = s->stop.regs.pc; uint64_t scratch_entry = 0; KitStatus st = dbg_displaced_prepare(s, pc, &scratch_entry); @@ -36,7 +36,7 @@ static KitStatus prepare_step_insn(KitJitSession* s) { /* Drive a single displaced-step cycle synchronously. Returns KIT_OK on * success; the session is parked again at the post-step PC. */ -static KitStatus do_one_displaced(KitJitSession* s) { +static KitStatus do_one_displaced(KitDebugSession* s) { KitStatus st = prepare_step_insn(s); if (st != KIT_OK) return st; st = dbg_session_signal_resume(s); @@ -44,7 +44,7 @@ static KitStatus do_one_displaced(KitJitSession* s) { return dbg_session_wait_stop(s); } -static int stop_is_internal_completion(const KitJitSession* s) { +static int stop_is_internal_completion(const KitDebugSession* s) { return s->stop.kind == KIT_STOP_BREAKPOINT && s->stop.bp_id == 0; } @@ -54,7 +54,7 @@ static int stop_is_internal_completion(const KitJitSession* s) { * later resurface as a spurious bp_id==0 stop, so clear it here on any return * path that is not its own completion. Mirrors the displaced-step sentinel * cleanup. */ -static KitStatus run_to_internal_bp(KitJitSession* s, uint64_t target) { +static KitStatus run_to_internal_bp(KitDebugSession* s, uint64_t target) { u32 bp_id = 0; KitStatus st = dbg_bp_set_internal(s, target, &bp_id); if (st != KIT_OK) return st; @@ -74,7 +74,7 @@ static KitStatus run_to_internal_bp(KitJitSession* s, uint64_t target) { return KIT_OK; } -static KitStatus direct_call_target(KitJitSession* s, uint64_t* target) { +static KitStatus direct_call_target(KitDebugSession* s, uint64_t* target) { ArchDbgInsn insn; if (!s->arch_dbg || !s->arch_dbg->direct_call_target) return KIT_NOT_FOUND; if (dbg_arch_decode_insn(s, s->stop.regs.pc, &insn) != KIT_OK) @@ -82,7 +82,7 @@ static KitStatus direct_call_target(KitJitSession* s, uint64_t* target) { return s->arch_dbg->direct_call_target(&insn, target); } -static KitStatus direct_jump_target(KitJitSession* s, uint64_t* target) { +static KitStatus direct_jump_target(KitDebugSession* s, uint64_t* target) { ArchDbgInsn insn; if (!s->arch_dbg || !s->arch_dbg->direct_jump_target) return KIT_NOT_FOUND; if (dbg_arch_decode_insn(s, s->stop.regs.pc, &insn) != KIT_OK) @@ -90,7 +90,7 @@ static KitStatus direct_jump_target(KitJitSession* s, uint64_t* target) { return s->arch_dbg->direct_jump_target(&insn, target); } -static KitStatus dwarf_line_for(KitJitSession* s, uint64_t pc, KitSlice* file, +static KitStatus dwarf_line_for(KitDebugSession* s, uint64_t pc, KitSlice* file, uint32_t* line) { uint32_t col = 0; *file = KIT_SLICE_NULL; @@ -99,7 +99,7 @@ static KitStatus dwarf_line_for(KitJitSession* s, uint64_t pc, KitSlice* file, &col); } -static KitStatus dwarf_sub_for(KitJitSession* s, uint64_t pc, +static KitStatus dwarf_sub_for(KitDebugSession* s, uint64_t pc, KitDwarfSubprogram* out) { memset(out, 0, sizeof(*out)); return kit_dwarf_subprogram_at(s->dwarf, step_rt_to_img(s, pc), out); @@ -117,7 +117,7 @@ static int line_changed(KitSlice base_file, uint32_t base_line, return 0; } -static KitStatus try_step_into_direct_call(KitJitSession* s, int* did) { +static KitStatus try_step_into_direct_call(KitDebugSession* s, int* did) { uint64_t target = 0; KitStatus st; *did = 0; @@ -133,7 +133,7 @@ static KitStatus try_step_into_direct_call(KitJitSession* s, int* did) { return KIT_OK; } -static KitStatus try_follow_direct_jump(KitJitSession* s, int* did) { +static KitStatus try_follow_direct_jump(KitDebugSession* s, int* did) { uint64_t target = 0; KitStatus st; *did = 0; @@ -144,7 +144,7 @@ static KitStatus try_follow_direct_jump(KitJitSession* s, int* did) { return run_to_internal_bp(s, target); } -static KitStatus run_step_line_loop(KitJitSession* s) { +static KitStatus run_step_line_loop(KitDebugSession* s) { KitSlice base_file = KIT_SLICE_NULL; uint32_t base_line = 0; KitDwarfSubprogram base_sub; @@ -196,7 +196,7 @@ static KitStatus run_step_line_loop(KitJitSession* s) { return KIT_OK; } -static KitStatus run_step_out(KitJitSession* s) { +static KitStatus run_step_out(KitDebugSession* s) { KitUnwindFrame frame; KitStatus st; frame = s->stop.regs; @@ -216,7 +216,7 @@ static KitStatus run_step_out(KitJitSession* s) { return run_to_internal_bp(s, frame.pc); } -static KitStatus run_next_line(KitJitSession* s) { +static KitStatus run_next_line(KitDebugSession* s) { ArchDbgInsn insn; if (!s->arch_dbg || !s->arch_dbg->is_call || @@ -248,7 +248,7 @@ static KitStatus run_next_line(KitJitSession* s) { } } -KitStatus dbg_step_resume(KitJitSession* s, KitResumeMode mode) { +KitStatus dbg_step_resume(KitDebugSession* s, KitResumeMode mode) { switch (mode) { case KIT_RESUME_ABORT: return KIT_OK; diff --git a/src/dbg/symbolic.c b/src/dbg/symbolic.c @@ -0,0 +1,937 @@ +/* Symbolic inspection layer over a stopped KitDebugSession. + * + * The control substrate (session.c/bp.c/step.c/mem.c) moves bytes and raw + * registers. This TU composes that substrate with the session's attached DWARF + * (s->dwarf) and JIT image (s->jit) into the operations an interactive debugger + * performs: PC symbolization, frame-pointer backtraces, typed variable values + * with navigation, scope enumeration, location resolution, and disassembly at + * the stopped PC. + * + * Two pieces of glue every caller would otherwise repeat live here, once: + * - address-space translation: DWARF queries key off image vaddrs while the + * session reads/writes runtime addresses (sym_pc_rt_to_img / _img_to_rt / + * sym_translate_loc); + * - the memory provider: kit_dwarf_loc_read wants a KitDwarfReadMemFn, which + * here just forwards into the session (sym_read_mem). + * + * KitDebugValue byte storage comes from the session's stop-epoch arena + * (s->values), reset by session.c on every call/resume. Backtraces and variable + * iterators are heap-allocated cursors with explicit frees; the KitSlice names + * they expose borrow from the DWARF / JIT image. */ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "dbg/dbg.h" + +/* ---- address-space + memory glue ------------------------------------- */ + +static uint64_t sym_pc_rt_to_img(KitDebugSession* s, uint64_t rt) { + uint64_t v = kit_jit_runtime_to_image(s->jit, rt); + return v ? v : rt; +} + +static uint64_t sym_pc_img_to_rt(KitDebugSession* s, uint64_t img) { + uint64_t v = kit_jit_image_to_runtime(s->jit, img); + return v ? v : img; +} + +/* Only DLOC_GLOBAL carries an absolute address straight from .debug_info; the + * other kinds derive their effective address from live (runtime) register + * state or are evaluated against the frame. */ +static void sym_translate_loc(KitDebugSession* s, KitDwarfVarLoc* loc) { + if (loc && loc->kind == KIT_DLOC_GLOBAL) + loc->v.global = sym_pc_img_to_rt(s, loc->v.global); +} + +static KitStatus sym_read_mem(void* user, uint64_t addr, void* dst, size_t n) { + return kit_dbg_session_read_mem((KitDebugSession*)user, addr, dst, n); +} + +/* ---- little-endian scalar loads -------------------------------------- */ + +static uint64_t sym_load_u(const uint8_t* b, size_t n) { + uint64_t v = 0; + size_t i; + for (i = 0; i < n && i < 8; ++i) v |= ((uint64_t)b[i]) << (8 * i); + return v; +} + +static int64_t sym_load_s(const uint8_t* b, size_t n) { + uint64_t v = sym_load_u(b, n); + if (n > 0 && n < 8) { + uint64_t sign = (uint64_t)1 << (8 * n - 1); + if (v & sign) v |= ~((sign << 1) - 1); + } + return (int64_t)v; +} + +/* ---- per-arch frame-pointer walk knobs -------------------------------- */ + +static int sym_fp_reg(KitArchKind a) { + switch (a) { + case KIT_ARCH_ARM_64: + return 29; + case KIT_ARCH_X86_64: + return 6; + case KIT_ARCH_RV32: + case KIT_ARCH_RV64: + return 8; + default: + return -1; + } +} + +static int sym_ptr_size(KitArchKind a) { + switch (a) { + case KIT_ARCH_ARM_64: + case KIT_ARCH_X86_64: + case KIT_ARCH_RV64: + return 8; + case KIT_ARCH_RV32: + return 4; + default: + return 0; + } +} + +/* Advance `fr` to its caller via the uniform fp record (fp[0]=caller fp, + * fp[1]=saved return address). Returns 1 on a valid in-image caller, 0 at the + * chain terminator / image boundary / garbage. */ +static int sym_unwind_next(KitDebugSession* s, KitUnwindFrame* fr, + int fpreg, int ptr) { + uint64_t fp, ra = 0, nfp = 0, align; + if (fpreg < 0 || ptr <= 0) return 0; + fp = fr->regs[fpreg]; + if (fp == 0) return 0; + align = (uint64_t)ptr - 1u; + if (fp & align) return 0; + if (kit_dbg_session_read_mem(s, fp + (uint64_t)ptr, &ra, (size_t)ptr) != KIT_OK) + return 0; + if (kit_dbg_session_read_mem(s, fp, &nfp, (size_t)ptr) != KIT_OK) return 0; + if (ra == 0) return 0; + if (nfp <= fp) return 0; + if (nfp & align) return 0; + if (kit_jit_runtime_to_image(s->jit, ra) == 0) return 0; /* left the image */ + fr->pc = ra; + fr->regs[fpreg] = nfp; + fr->cfa = nfp + 2u * (uint64_t)ptr; /* just above the saved pair */ + return 1; +} + +/* ---- single-PC symbolization ----------------------------------------- */ + +static void sym_describe(KitJit* jit, KitDebugInfo* dwarf, uint64_t pc, + KitDebugFrame* f) { + /* Image vaddr for DWARF queries; fall back to the runtime pc when the image + * translation yields 0 (it overloads 0 as "not in image", which also covers + * a symbol sitting exactly at the image base). in_image is reported for the + * caller's benefit (e.g. a crash reporter suppressing foreign frames) but is + * NOT used to gate symbolization: kit_jit_addr_to_sym already returns nothing + * for an address its table does not cover. */ + uint64_t v = jit ? kit_jit_runtime_to_image(jit, pc) : 0; + uint64_t img = v ? v : pc; + f->pc = pc; + f->in_image = jit ? (v != 0) : true; + + if (jit) { + KitSlice sym = KIT_SLICE_NULL; + uint64_t off = 0; + if (kit_jit_addr_to_sym(jit, pc, &sym, &off) == KIT_OK && sym.s) { + f->sym = sym; + f->sym_offset = off; + } + } + if (dwarf) { + KitDwarfSubprogram sp; + KitSlice file = KIT_SLICE_NULL; + uint32_t line = 0, col = 0; + if (kit_dwarf_subprogram_at(dwarf, img, &sp) == KIT_OK && sp.name.s) { + f->func = sp.name; + f->inlined = sp.inlined; + f->func_offset = (img >= sp.low_pc) ? (img - sp.low_pc) : 0; + } + if (kit_dwarf_addr_to_line(dwarf, img, &file, &line, &col) == KIT_OK && + file.s) { + f->file = file; + f->line = line; + f->col = col; + } + } +} + +KitStatus kit_dbg_symbolize_pc(KitJit* jit, KitDebugInfo* dwarf, uint64_t pc, + KitDebugFrame* out) { + if (!jit || !out) return KIT_INVALID; + memset(out, 0, sizeof(*out)); + sym_describe(jit, dwarf, pc, out); + return KIT_OK; +} + +/* ---- backtrace -------------------------------------------------------- */ + +struct KitDebugBacktrace { + Heap* heap; + KitDebugFrame* frames; + uint32_t count; +}; + +#define SYM_BT_MAX 256u + +KitStatus kit_dbg_backtrace_new(KitDebugSession* s, const KitUnwindFrame* top, + KitDebugBacktrace** out) { + KitArchKind arch; + int fpreg, ptr; + KitUnwindFrame walk, start; + KitDebugBacktrace* bt; + uint32_t n, i; + + if (!s || !out) return KIT_INVALID; + *out = NULL; + arch = kit_jit_image_arch(s->jit); + fpreg = sym_fp_reg(arch); + ptr = sym_ptr_size(arch); + start = top ? *top : s->stop.regs; + + /* Pass 1: count the chain (bounded). */ + n = 1; + walk = start; + while (n < SYM_BT_MAX && sym_unwind_next(s, &walk, fpreg, ptr)) ++n; + + bt = (KitDebugBacktrace*)s->heap->alloc(s->heap, sizeof(*bt), _Alignof(KitDebugBacktrace)); + if (!bt) return KIT_NOMEM; + bt->heap = s->heap; + bt->count = n; + bt->frames = (KitDebugFrame*)s->heap->alloc( + s->heap, (size_t)n * sizeof(KitDebugFrame), _Alignof(KitDebugFrame)); + if (!bt->frames) { + s->heap->free(s->heap, bt, sizeof(*bt)); + return KIT_NOMEM; + } + + /* Pass 2: fill. */ + walk = start; + for (i = 0; i < n; ++i) { + KitDebugFrame* f = &bt->frames[i]; + memset(f, 0, sizeof(*f)); + f->regs = walk; + f->cfa = walk.cfa; + sym_describe(s->jit, s->dwarf, walk.pc, f); + if (i + 1 < n) sym_unwind_next(s, &walk, fpreg, ptr); + } + *out = bt; + return KIT_OK; +} + +uint32_t kit_dbg_backtrace_count(const KitDebugBacktrace* bt) { + return bt ? bt->count : 0; +} + +KitStatus kit_dbg_backtrace_frame(const KitDebugBacktrace* bt, uint32_t level, + KitDebugFrame* out) { + if (!bt || !out || level >= bt->count) return KIT_INVALID; + *out = bt->frames[level]; + return KIT_OK; +} + +void kit_dbg_backtrace_free(KitDebugBacktrace* bt) { + if (!bt) return; + bt->heap->free(bt->heap, bt->frames, (size_t)bt->count * sizeof(KitDebugFrame)); + bt->heap->free(bt->heap, bt, sizeof(*bt)); +} + +/* ---- typed values ----------------------------------------------------- */ + +/* Resolve through DW_TAG_typedef chains to the underlying type. */ +static const KitDwarfType* sym_strip_typedef(const KitDwarfType* t) { + while (t) { + KitDwarfTypeInfo ti = kit_dwarf_type_info(t); + if (ti.kind != KIT_DT_TYPEDEF || !ti.inner) break; + t = ti.inner; + } + return t; +} + +static void* sym_value_buf(KitDebugSession* s, size_t sz) { + return arena_alloc(&s->values, sz ? sz : 1, 16); +} + +/* Materialize a translated loc against `fr` (runtime regs/cfa) into the + * stop-epoch arena. */ +static KitStatus sym_materialize(KitDebugSession* s, const KitDwarfVarLoc* loc, + const KitUnwindFrame* fr, KitDebugValue* out) { + size_t cap = loc->byte_size ? loc->byte_size : 8; + uint8_t* buf = (uint8_t*)sym_value_buf(s, cap); + size_t got = 0; + if (!buf) return KIT_NOMEM; + if (kit_dwarf_loc_read(s->dwarf, loc, fr, sym_read_mem, s, buf, cap, &got) != + KIT_OK) + return KIT_ERR; + out->type = loc->type; + out->bytes = buf; + out->len = got; + out->has_address = false; + out->address = 0; + if (loc->kind == KIT_DLOC_GLOBAL) { + out->has_address = true; + out->address = loc->v.global; + } else if (loc->kind == KIT_DLOC_FRAME_OFS) { + out->has_address = true; + out->address = fr->cfa + (uint64_t)(int64_t)loc->v.frame_ofs; + } + return KIT_OK; +} + +KitStatus kit_dbg_var_read(KitDebugSession* s, const KitUnwindFrame* frame, + KitSlice name, KitDebugValue* out) { + KitUnwindFrame fr; + if (!s || !out) return KIT_INVALID; + memset(out, 0, sizeof(*out)); + fr = frame ? *frame : s->stop.regs; + + if (s->dwarf) { + KitDwarfVarLoc loc; + if (kit_dwarf_var_at(s->dwarf, sym_pc_rt_to_img(s, fr.pc), name, &loc) == + KIT_OK) { + sym_translate_loc(s, &loc); + return sym_materialize(s, &loc, &fr, out); + } + } + + /* DWARF didn't know it — fall back to a JIT global symbol. The value bytes + * hold the symbol's runtime address; type stays NULL. */ + { + void* p = kit_jit_lookup(s->jit, name); + if (p) { + union { + void* p; + uint64_t u; + } cv; + uint8_t* buf = (uint8_t*)sym_value_buf(s, sizeof(uint64_t)); + if (!buf) return KIT_NOMEM; + cv.p = p; + memcpy(buf, &cv.u, sizeof(cv.u)); + out->type = NULL; + out->bytes = buf; + out->len = sizeof(uint64_t); + out->has_address = false; + out->address = 0; + return KIT_OK; + } + } + return KIT_NOT_FOUND; +} + +KitStatus kit_dbg_value_at(KitDebugSession* s, uint64_t addr, + const KitDwarfType* type, KitDebugValue* out) { + KitDwarfTypeInfo ti; + size_t sz = 8; + uint8_t* buf; + if (!s || !out) return KIT_INVALID; + memset(out, 0, sizeof(*out)); + if (type) { + ti = kit_dwarf_type_info(type); + if (ti.byte_size) sz = ti.byte_size; + } + buf = (uint8_t*)sym_value_buf(s, sz); + if (!buf) return KIT_NOMEM; + if (kit_dbg_session_read_mem(s, addr, buf, sz) != KIT_OK) return KIT_ERR; + out->type = type; + out->bytes = buf; + out->len = sz; + out->has_address = true; + out->address = addr; + return KIT_OK; +} + +KitStatus kit_dbg_value_field(KitDebugSession* s, const KitDebugValue* v, + KitSlice field, KitDebugValue* out) { + const KitDwarfType* t; + KitDwarfTypeInfo ti; + KitDwarfFieldIter* it = NULL; + KitDwarfField f; + if (!s || !v || !out) return KIT_INVALID; + t = sym_strip_typedef(v->type); + if (!t) return KIT_INVALID; + ti = kit_dwarf_type_info(t); + if (ti.kind != KIT_DT_STRUCT && ti.kind != KIT_DT_UNION) return KIT_INVALID; + if (kit_dwarf_field_iter_new(s->dwarf, t, &it) != KIT_OK) return KIT_ERR; + for (;;) { + KitIterResult r = kit_dwarf_field_iter_next(it, &f); + if (r != KIT_ITER_ITEM) break; + if (f.name.len == field.len && f.name.len && + memcmp(f.name.s, field.s, field.len) == 0) { + size_t fsz = 0; + kit_dwarf_field_iter_free(it); + if (f.bit_size) return KIT_UNSUPPORTED; /* bitfields: not sub-sliceable */ + if (f.type) { + KitDwarfTypeInfo fti = kit_dwarf_type_info(f.type); + fsz = fti.byte_size; + } + if (!fsz || (size_t)f.byte_offset + fsz > v->len) return KIT_ERR; + memset(out, 0, sizeof(*out)); + out->type = f.type; + out->bytes = v->bytes + f.byte_offset; + out->len = fsz; + out->has_address = v->has_address; + out->address = v->has_address ? v->address + f.byte_offset : 0; + return KIT_OK; + } + } + kit_dwarf_field_iter_free(it); + return KIT_NOT_FOUND; +} + +KitStatus kit_dbg_value_index(KitDebugSession* s, const KitDebugValue* v, + uint64_t index, KitDebugValue* out) { + const KitDwarfType* t; + KitDwarfTypeInfo ti, ei; + size_t esz; + if (!s || !v || !out) return KIT_INVALID; + t = sym_strip_typedef(v->type); + if (!t) return KIT_INVALID; + ti = kit_dwarf_type_info(t); + if ((ti.kind != KIT_DT_ARRAY && ti.kind != KIT_DT_PTR) || !ti.inner) + return KIT_INVALID; + ei = kit_dwarf_type_info(ti.inner); + esz = ei.byte_size ? ei.byte_size : 1; + if (ti.kind == KIT_DT_ARRAY) { + size_t off = (size_t)index * esz; + if (off + esz > v->len) return KIT_INVALID; + memset(out, 0, sizeof(*out)); + out->type = ti.inner; + out->bytes = v->bytes + off; + out->len = esz; + out->has_address = v->has_address; + out->address = v->has_address ? v->address + off : 0; + return KIT_OK; + } + /* pointer: read element[index] through the session. */ + { + uint64_t base = sym_load_u(v->bytes, v->len < 8 ? v->len : 8); + return kit_dbg_value_at(s, base + index * esz, ti.inner, out); + } +} + +KitStatus kit_dbg_value_deref(KitDebugSession* s, const KitDebugValue* v, + KitDebugValue* out) { + const KitDwarfType* t; + KitDwarfTypeInfo ti; + uint64_t ptr; + if (!s || !v || !out) return KIT_INVALID; + t = sym_strip_typedef(v->type); + if (!t) return KIT_INVALID; + ti = kit_dwarf_type_info(t); + if (ti.kind != KIT_DT_PTR) return KIT_INVALID; + ptr = sym_load_u(v->bytes, v->len < 8 ? v->len : 8); + return kit_dbg_value_at(s, ptr, ti.inner, out); +} + +KitStatus kit_dbg_value_as_u64(const KitDebugValue* v, uint64_t* out) { + if (!v || !out || !v->bytes) return KIT_INVALID; + *out = sym_load_u(v->bytes, v->len); + return KIT_OK; +} + +KitStatus kit_dbg_value_as_i64(const KitDebugValue* v, int64_t* out) { + if (!v || !out || !v->bytes) return KIT_INVALID; + *out = sym_load_s(v->bytes, v->len); + return KIT_OK; +} + +KitStatus kit_dbg_value_as_f64(const KitDebugValue* v, double* out) { + if (!v || !out || !v->bytes) return KIT_INVALID; + if (v->len == 4) { + union { + uint32_t u; + float f; + } cv; + cv.u = (uint32_t)sym_load_u(v->bytes, 4); + *out = (double)cv.f; + return KIT_OK; + } + if (v->len == 8) { + union { + uint64_t u; + double d; + } cv; + cv.u = sym_load_u(v->bytes, 8); + *out = cv.d; + return KIT_OK; + } + return KIT_UNSUPPORTED; +} + +/* ---- value formatting ------------------------------------------------- */ + +typedef struct SymFmt { + KitWriter* w; + KitDebugSession* s; + KitStatus st; +} SymFmt; + +static void fw(SymFmt* f, const void* p, size_t n) { + if (f->st == KIT_OK) f->st = kit_writer_write(f->w, p, n); +} +static void fw_cstr(SymFmt* f, const char* s) { fw(f, s, strlen(s)); } +static void fw_slice(SymFmt* f, KitSlice s) { + if (s.s) fw(f, s.s, s.len); +} +static void fw_fmt(SymFmt* f, const char* fmt, ...) { + char b[64]; + va_list ap; + int n; + va_start(ap, fmt); + n = vsnprintf(b, sizeof(b), fmt, ap); + va_end(ap); + if (n < 0) return; + if ((size_t)n >= sizeof(b)) n = (int)sizeof(b) - 1; + fw(f, b, (size_t)n); +} +static void fw_dec_u(SymFmt* f, uint64_t v) { + fw_fmt(f, "%llu", (unsigned long long)v); +} +static void fw_dec_i(SymFmt* f, int64_t v) { + fw_fmt(f, "%lld", (long long)v); +} +static void fw_hex(SymFmt* f, uint64_t v) { + fw_fmt(f, "0x%llx", (unsigned long long)v); +} +static void fw_f64(SymFmt* f, double v) { fw_fmt(f, "%g", v); } +static void fw_byte(SymFmt* f, uint8_t b) { + static const char H[] = "0123456789abcdef"; + char two[3]; + two[0] = ' '; + two[1] = H[b >> 4]; + two[2] = H[b & 0xf]; + fw(f, two, 3); +} +static void fw_indent(SymFmt* f, int n) { + int i; + for (i = 0; i < n; ++i) fw(f, " ", 2); +} + +/* Recursively render `buf[0..got)` as a value of `type`. Mirrors the driver's + * historical dbg_print_value byte-for-byte for the non-float kinds (the dbg + * golden tests pin them); floats render via snprintf "%g". */ +static void sym_fmt(SymFmt* f, const KitDwarfType* type, const uint8_t* buf, + size_t got, int depth) { + KitDwarfTypeInfo ti; + + if (!type) { + if (got == 0) { + fw_cstr(f, "<empty>"); + return; + } + if (got <= 8) { + uint64_t v = sym_load_u(buf, got); + fw_hex(f, v); + fw_cstr(f, " ("); + fw_dec_u(f, v); + fw_cstr(f, ")"); + return; + } + { + size_t i; + fw_cstr(f, "{"); + for (i = 0; i < got; ++i) fw_byte(f, buf[i]); + fw_cstr(f, " }"); + return; + } + } + + ti = kit_dwarf_type_info(type); + switch (ti.kind) { + case KIT_DT_VOID: + fw_cstr(f, "void"); + return; + case KIT_DT_SINT: + case KIT_DT_CHAR: + fw_dec_i(f, sym_load_s(buf, got)); + return; + case KIT_DT_UINT: + case KIT_DT_BOOL: + fw_dec_u(f, sym_load_u(buf, got)); + return; + case KIT_DT_PTR: + fw_hex(f, sym_load_u(buf, got)); + return; + case KIT_DT_FLOAT: + if (got == 4) { + union { + uint32_t u; + float f; + } cv; + cv.u = (uint32_t)sym_load_u(buf, 4); + fw_f64(f, (double)cv.f); + } else if (got == 8) { + union { + uint64_t u; + double d; + } cv; + cv.u = sym_load_u(buf, 8); + fw_f64(f, cv.d); + } else { + size_t i; + fw_cstr(f, "<float-"); + fw_dec_u(f, got); + for (i = 0; i < got; ++i) fw_byte(f, buf[i]); + fw_cstr(f, ">"); + } + return; + case KIT_DT_ENUM: { + int64_t v = sym_load_s(buf, got); + KitDwarfEnumIter* it = NULL; + KitDwarfEnumVal ev; + KitSlice match = KIT_SLICE_NULL; + if (kit_dwarf_enum_iter_new(f->s->dwarf, type, &it) == KIT_OK) { + for (;;) { + KitIterResult r = kit_dwarf_enum_iter_next(it, &ev); + if (r != KIT_ITER_ITEM) break; + if (ev.value == v) { + match = ev.name; + break; + } + } + kit_dwarf_enum_iter_free(it); + } + if (match.s) { + fw_slice(f, match); + fw_cstr(f, " ("); + fw_dec_i(f, v); + fw_cstr(f, ")"); + } else { + fw_dec_i(f, v); + } + return; + } + case KIT_DT_TYPEDEF: + sym_fmt(f, ti.inner, buf, got, depth); + return; + case KIT_DT_ARRAY: { + uint32_t n = ti.element_count; + size_t esz = 0; + uint32_t i; + if (ti.inner) { + KitDwarfTypeInfo ein = kit_dwarf_type_info(ti.inner); + esz = ein.byte_size; + } + if (esz == 0 || n == 0 || (size_t)n * esz > got) { + size_t k; + fw_cstr(f, "{"); + for (k = 0; k < got; ++k) fw_byte(f, buf[k]); + fw_cstr(f, " }"); + return; + } + fw_cstr(f, "{\n"); + for (i = 0; i < n; ++i) { + fw_indent(f, depth + 1); + fw_cstr(f, "["); + fw_dec_u(f, i); + fw_cstr(f, "] = "); + sym_fmt(f, ti.inner, buf + (size_t)i * esz, esz, depth + 1); + fw_cstr(f, ",\n"); + } + fw_indent(f, depth); + fw_cstr(f, "}"); + return; + } + case KIT_DT_STRUCT: + case KIT_DT_UNION: { + KitDwarfFieldIter* it = NULL; + KitDwarfField fld; + fw_cstr(f, "{\n"); + if (kit_dwarf_field_iter_new(f->s->dwarf, type, &it) == KIT_OK) { + for (;;) { + KitIterResult r = kit_dwarf_field_iter_next(it, &fld); + size_t fsz = 0; + if (r != KIT_ITER_ITEM) break; + fw_indent(f, depth + 1); + fw_cstr(f, "."); + fw_slice(f, fld.name.len ? fld.name : KIT_SLICE_LIT("<anon>")); + fw_cstr(f, " = "); + if (fld.bit_size) { + size_t off = fld.byte_offset; + size_t take = (off + 8 <= got) ? 8 : (off < got ? got - off : 0); + uint64_t raw = take ? sym_load_u(buf + off, take) : 0; + uint64_t mask = (fld.bit_size >= 64) + ? (uint64_t)-1 + : (((uint64_t)1 << fld.bit_size) - 1); + uint64_t v = (raw >> fld.bit_offset) & mask; + fw_dec_u(f, v); + } else { + if (fld.type) { + KitDwarfTypeInfo fti = kit_dwarf_type_info(fld.type); + fsz = fti.byte_size; + } + if (fld.type && fsz > 0 && (size_t)fld.byte_offset + fsz <= got) { + sym_fmt(f, fld.type, buf + fld.byte_offset, fsz, depth + 1); + } else { + fw_cstr(f, "<truncated>"); + } + } + fw_cstr(f, ",\n"); + } + kit_dwarf_field_iter_free(it); + } + fw_indent(f, depth); + fw_cstr(f, "}"); + return; + } + case KIT_DT_FUNC: + fw_cstr(f, "<function@"); + fw_hex(f, sym_load_u(buf, got)); + fw_cstr(f, ">"); + return; + } + fw_cstr(f, "<?>"); +} + +KitStatus kit_dbg_value_format(KitDebugSession* s, const KitDebugValue* v, + KitWriter* out, + const KitDebugFormatOptions* opt) { + SymFmt f; + if (!s || !v || !out) return KIT_INVALID; + f.w = out; + f.s = s; + f.st = KIT_OK; + sym_fmt(&f, v->type, v->bytes, v->len, opt ? (int)opt->indent : 0); + return f.st; +} + +KitStatus kit_dbg_var_write_u64(KitDebugSession* s, const KitUnwindFrame* frame, + KitSlice name, uint64_t value) { + KitUnwindFrame fr; + KitDwarfVarLoc loc; + uint8_t buf[8]; + size_t sz, i; + if (!s) return KIT_INVALID; + if (!s->dwarf) return KIT_NOT_FOUND; + fr = frame ? *frame : s->stop.regs; + if (kit_dwarf_var_at(s->dwarf, sym_pc_rt_to_img(s, fr.pc), name, &loc) != + KIT_OK) + return KIT_NOT_FOUND; + sym_translate_loc(s, &loc); + sz = (loc.byte_size == 0 || loc.byte_size > 8) ? 8 : loc.byte_size; + for (i = 0; i < sz; ++i) buf[i] = (uint8_t)(value >> (8 * i)); + + switch (loc.kind) { + case KIT_DLOC_FRAME_OFS: + return kit_dbg_session_write_mem( + s, fr.cfa + (uint64_t)(int64_t)loc.v.frame_ofs, buf, sz); + case KIT_DLOC_GLOBAL: + return kit_dbg_session_write_mem(s, loc.v.global, buf, sz); + case KIT_DLOC_REG: { + KitUnwindFrame w = s->stop.regs; + if (loc.v.reg >= 32) return KIT_INVALID; + w.regs[loc.v.reg] = value; + return kit_dbg_session_set_regs(s, &w); + } + case KIT_DLOC_EXPR: + return KIT_UNSUPPORTED; + } + return KIT_UNSUPPORTED; +} + +/* ---- scope enumeration ------------------------------------------------ */ + +struct KitDebugVarIter { + KitDebugSession* s; + KitUnwindFrame frame; + KitDwarfVarIter* inner; +}; + +KitStatus kit_dbg_vars_new(KitDebugSession* s, const KitUnwindFrame* frame, + uint32_t roles, KitDebugVarIter** out) { + KitDebugVarIter* it; + KitDwarfVarIter* inner = NULL; + KitStatus st; + if (!s || !out) return KIT_INVALID; + *out = NULL; + if (!s->dwarf) return KIT_NOT_FOUND; + it = (KitDebugVarIter*)s->heap->alloc(s->heap, sizeof(*it), + _Alignof(KitDebugVarIter)); + if (!it) return KIT_NOMEM; + it->s = s; + it->frame = frame ? *frame : s->stop.regs; + st = kit_dwarf_vars_at_new(s->dwarf, sym_pc_rt_to_img(s, it->frame.pc), roles, + &inner); + if (st != KIT_OK) { + s->heap->free(s->heap, it, sizeof(*it)); + return st; + } + it->inner = inner; + *out = it; + return KIT_OK; +} + +KitIterResult kit_dbg_vars_next(KitDebugVarIter* it, KitSlice* name_out, + KitDebugValue* value_out) { + KitDwarfVar v; + KitIterResult r; + if (!it || !value_out) return KIT_ITER_END; + r = kit_dwarf_vars_at_next(it->inner, &v); + if (r != KIT_ITER_ITEM) return r; + if (name_out) *name_out = v.name; + memset(value_out, 0, sizeof(*value_out)); + value_out->type = v.loc.type; + sym_translate_loc(it->s, &v.loc); + /* On a read failure leave bytes NULL: the caller renders "<unreadable>" but + * the enumeration continues. */ + (void)sym_materialize(it->s, &v.loc, &it->frame, value_out); + return KIT_ITER_ITEM; +} + +void kit_dbg_vars_free(KitDebugVarIter* it) { + if (!it) return; + kit_dwarf_vars_at_free(it->inner); + it->s->heap->free(it->s->heap, it, sizeof(*it)); +} + +/* ---- location resolution + breakpoints -------------------------------- */ + +static int sym_is_digit(int c) { return c >= '0' && c <= '9'; } + +/* Parse a decimal or 0x-hex integer from [s, s+n); returns bytes consumed (0 on + * no digits) and writes the value. */ +static size_t sym_parse_u64(const char* s, size_t n, uint64_t* out) { + uint64_t v = 0; + size_t i = 0; + int any = 0; + if (n >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + i = 2; + for (; i < n; ++i) { + int c = s[i], d; + if (sym_is_digit(c)) + d = c - '0'; + else if (c >= 'a' && c <= 'f') + d = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + d = c - 'A' + 10; + else + break; + v = (v << 4) | (uint64_t)d; + any = 1; + } + } else { + for (; i < n; ++i) { + if (!sym_is_digit(s[i])) break; + v = v * 10u + (uint64_t)(s[i] - '0'); + any = 1; + } + } + if (!any) return 0; + *out = v; + return i; +} + +KitStatus kit_dbg_resolve(KitDebugSession* s, KitSlice spec, uint64_t* addr_out, + KitDwarfLineMatch* cands, uint32_t cap, + uint32_t* ncand) { + const char* p; + size_t n, i; + if (!s || !addr_out || !spec.s || spec.len == 0) return KIT_INVALID; + if (ncand) *ncand = 0; + p = spec.s; + n = spec.len; + + /* file:line — a ':' immediately followed by a digit. */ + for (i = 0; i < n; ++i) { + if (p[i] == ':' && i + 1 < n && sym_is_digit((unsigned char)p[i + 1])) { + KitSlice file = {.s = p, .len = i}; + uint64_t line = 0; + uint64_t pc = 0; + KitStatus rc; + size_t used = sym_parse_u64(p + i + 1, n - i - 1, &line); + if (i == 0 || used != n - i - 1) return KIT_INVALID; + if (!s->dwarf) return KIT_NOT_FOUND; + rc = kit_dwarf_line_to_addr(s->dwarf, file, (uint32_t)line, &pc); + if (rc == KIT_AMBIGUOUS) { + if (cands && cap) + kit_dwarf_line_to_addr_all(s->dwarf, file, (uint32_t)line, cands, cap, + ncand); + else if (ncand) + kit_dwarf_line_to_addr_all(s->dwarf, file, (uint32_t)line, NULL, 0, + ncand); + return KIT_AMBIGUOUS; + } + if (rc != KIT_OK) return rc; + *addr_out = sym_pc_img_to_rt(s, pc); + return KIT_OK; + } + } + + /* 0xADDR or decimal address. */ + if ((n >= 1 && sym_is_digit((unsigned char)p[0]))) { + uint64_t v = 0; + size_t used = sym_parse_u64(p, n, &v); + if (!used || used != n) return KIT_INVALID; + *addr_out = v; + return KIT_OK; + } + + /* NAME[+off] */ + { + size_t name_n = n; + uint64_t off = 0; + void* resolved; + KitSlice name; + for (i = 0; i < n; ++i) { + if (p[i] == '+') { + name_n = i; + break; + } + } + if (name_n == 0) return KIT_INVALID; + if (name_n < n) { + size_t used = sym_parse_u64(p + name_n + 1, n - name_n - 1, &off); + if (!used || used != n - name_n - 1) return KIT_INVALID; + } + name.s = p; + name.len = name_n; + resolved = kit_jit_lookup(s->jit, name); + if (!resolved) return KIT_NOT_FOUND; + { + union { + void* p; + uint64_t u; + } cv; + cv.p = resolved; + *addr_out = cv.u + off; + } + return KIT_OK; + } +} + +KitStatus kit_dbg_break(KitDebugSession* s, KitSlice spec, + uint32_t* bp_id_out) { + uint64_t addr = 0; + KitStatus rc = kit_dbg_resolve(s, spec, &addr, NULL, 0, NULL); + if (rc != KIT_OK) return rc; + return kit_dbg_session_breakpoint_set(s, addr, bp_id_out); +} + +/* ---- disassemble at the stopped PC ------------------------------------ */ + +KitStatus kit_dbg_disasm_new(KitDebugSession* s, uint64_t addr, uint32_t count, + KitDisasmIter** out) { + KitDisasmContext* dctx; + uint8_t* buf; + size_t bytec; + if (!s || !out || count == 0) return KIT_INVALID; + *out = NULL; + if (count > SYM_BT_MAX) count = SYM_BT_MAX; + bytec = (size_t)count * 16u; /* generous upper bound per instruction */ + buf = (uint8_t*)arena_alloc(&s->values, bytec, 16); + if (!buf) return KIT_NOMEM; + if (kit_dbg_session_read_mem(s, addr, buf, bytec) != KIT_OK) return KIT_ERR; + /* The disasm iterator's compiler keeps a pointer into this KitDisasmContext, + * so it must outlive the iterator: park it in the stop-epoch arena (freed at + * the next resume, after the caller has freed the iterator). */ + dctx = arena_znew(&s->values, KitDisasmContext); + if (!dctx) return KIT_NOMEM; + dctx->target = kit_compiler_target(s->c); + dctx->context = *s->c->ctx; + return kit_disasm_iter_new(dctx, buf, bytec, addr, kit_jit_view(s->jit), out); +}