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:
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);
+}