kit

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

commit 1b5a5963a73fae9898dc2301f2cafd0a5a91d70f
parent 0463b6ab0c2e8e7f51c0aabca3a7730b2b3d2be7
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 11 May 2026 10:45:18 -0700

dbg: implement cfree_jit_view + image/runtime PC translation

Unblocks every DWARF-dependent REPL feature (b file:line, bt, p NAME,
info locals/args, source-level resume modes) by surfacing the JIT's
.debug_* sections through cfree_jit_view and translating PCs at every
DWARF call boundary.

LinkImage captures the input ObjBuilders at the tail of link_resolve so
they outlive link_free; cfree_jit_view lazily builds a private CfreeObjFile
that copies debug-section bytes and applies relocations against final
image vaddrs.  cfree_jit_runtime_to_image / cfree_jit_image_to_runtime
expose the segment-table translation the driver and session-side step
loops need.  Single-input v1; multi-input degrades to NULL pending
cross-CU offset adjustment.

Diffstat:
Mdriver/dbg.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Minclude/cfree.h | 19+++++++++++++++++++
Msrc/api/pipeline.c | 34++++++++++++++++++++++++++++++++++
Msrc/dbg/step.c | 21+++++++++++++++++++--
Msrc/link/link.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/link/link_internal.h | 24++++++++++++++++++++++++
Msrc/link/link_jit.c | 227++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/link/link_layout.c | 7+++++++
8 files changed, 464 insertions(+), 14 deletions(-)

diff --git a/driver/dbg.c b/driver/dbg.c @@ -349,6 +349,47 @@ static void dbg_on_sigint(void* user) { if (s && s->session) cfree_jit_session_interrupt(s->session); } +/* PC-space translation between the JIT runtime address space (where + * SIGTRAP fires and where the debugger installs breakpoints) and the + * image-relative vaddr space DWARF was authored in. Every DWARF call + * that takes a PC consumes an image vaddr, and every DWARF result + * that names a code address is image-relative — translate at the + * boundary. Fallback is pass-through so out-of-image PCs (e.g. + * stops inside libc on a future multi-input setup) don't return 0 + * and silently degrade lookups. */ +static uint64_t dbg_pc_rt_to_img(DbgState* s, uint64_t rt) { + uint64_t v = cfree_jit_runtime_to_image(s->jit, rt); + return v ? v : rt; +} +static uint64_t dbg_pc_img_to_rt(DbgState* s, uint64_t img) { + uint64_t v = cfree_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 CfreeUnwindFrame dbg_frame_for_dwarf(DbgState* s, + const CfreeUnwindFrame* rt) { + CfreeUnwindFrame out = *rt; + out.pc = dbg_pc_rt_to_img(s, rt->pc); + return out; +} + +/* Translate any image-vaddr fields stored on a CfreeDwarfVarLoc 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, CfreeDwarfVarLoc* loc) { + if (!loc) return; + if (loc->kind == CFREE_DLOC_GLOBAL) + loc->v.global = dbg_pc_img_to_rt(s, loc->v.global); +} + /* ============================================================ * Tiny driver-local string utilities * ============================================================ @@ -539,7 +580,7 @@ static int dbg_resolve_loc(DbgState* s, const char* spec, BpKind* kind_out, } driver_free(s->env, file, file_size); *kind_out = BP_LINE; - *addr_out = pc; + *addr_out = dbg_pc_img_to_rt(s, pc); return 0; } @@ -626,7 +667,9 @@ static void dbg_print_pc(DbgState* s, uint64_t pc) { driver_printf(" <%s>", sym); } if (s->dwarf && - cfree_dwarf_addr_to_line(s->dwarf, pc, &file, &line, &col) == 0 && file) { + cfree_dwarf_addr_to_line(s->dwarf, dbg_pc_rt_to_img(s, pc), &file, &line, + &col) == 0 && + file) { driver_printf(" at %s:%u", file, line); if (col) driver_printf(":%u", col); } @@ -778,6 +821,7 @@ static void dbg_cmd_bt(DbgState* s) { frame = s->last_stop.regs; for (;;) { CfreeDwarfSubprogram sp; + CfreeUnwindFrame img_frame; int have_sp; int step; @@ -795,13 +839,14 @@ static void dbg_cmd_bt(DbgState* s) { } } - have_sp = (cfree_dwarf_subprogram_at(s->dwarf, frame.pc, &sp) == 0); + img_frame = dbg_frame_for_dwarf(s, &frame); + have_sp = (cfree_dwarf_subprogram_at(s->dwarf, img_frame.pc, &sp) == 0); if (have_sp && sp.name) { CfreeDwarfParamIter* it; CfreeDwarfVar p; int first = 1; driver_printf(" in %s%s (", sp.name, sp.inlined ? " [inlined]" : ""); - it = cfree_dwarf_param_iter_new(s->dwarf, frame.pc); + it = cfree_dwarf_param_iter_new(s->dwarf, img_frame.pc); if (it) { while (cfree_dwarf_param_iter_next(it, &p)) { uint8_t stack_buf[64]; @@ -810,6 +855,7 @@ static void dbg_cmd_bt(DbgState* s) { size_t got; if (!first) driver_printf(", "); driver_printf("%s=", p.name ? p.name : "?"); + 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); @@ -828,8 +874,8 @@ static void dbg_cmd_bt(DbgState* s) { const char* file = NULL; uint32_t line = 0; uint32_t col = 0; - if (cfree_dwarf_addr_to_line(s->dwarf, frame.pc, &file, &line, &col) == - 0 && + if (cfree_dwarf_addr_to_line(s->dwarf, img_frame.pc, &file, &line, + &col) == 0 && file) { driver_printf(" at %s:%u", file, line); if (col) driver_printf(":%u", col); @@ -837,12 +883,19 @@ static void dbg_cmd_bt(DbgState* s) { } driver_printf("\n"); - step = cfree_dwarf_unwind_step(s->dwarf, &frame); + /* unwind_step keys off frame.pc as an image vaddr (FDE lookup) and + * writes the caller's return PC — read straight from registers or + * the stack — which is already a runtime address. Feed the image + * frame in, then copy back to `frame` so the next iteration's + * lookups translate from the new runtime PC. */ + step = cfree_dwarf_unwind_step(s->dwarf, &img_frame); if (step == 1) break; /* bottom of stack */ if (step != 0) { driver_errf(DBG_TOOL, "unwind step failed"); break; } + frame.cfa = img_frame.cfa; + frame.pc = img_frame.pc; /* runtime PC from the stack */ if (++level > 256) { driver_errf(DBG_TOOL, "backtrace truncated at 256 frames"); break; @@ -1092,7 +1145,9 @@ static void dbg_cmd_print(DbgState* s, const char* name) { } if (s->dwarf && - cfree_dwarf_var_at(s->dwarf, s->last_stop.regs.pc, name, &loc) == 0) { + cfree_dwarf_var_at(s->dwarf, dbg_pc_rt_to_img(s, s->last_stop.regs.pc), + name, &loc) == 0) { + dbg_translate_loc(s, &loc); if (dbg_read_value(s, &loc, &s->last_stop.regs, stack_buf, sizeof(stack_buf), &buf, &alloc, &got) != 0) { driver_errf(DBG_TOOL, "could not read %s", name); @@ -1141,10 +1196,12 @@ static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) { return; } if (!s->dwarf || - cfree_dwarf_var_at(s->dwarf, s->last_stop.regs.pc, name, &loc) != 0) { + cfree_dwarf_var_at(s->dwarf, dbg_pc_rt_to_img(s, s->last_stop.regs.pc), + name, &loc) != 0) { driver_errf(DBG_TOOL, "no variable named '%s'", name); 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)); @@ -1226,7 +1283,8 @@ static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) { return; } - it = cfree_dwarf_vars_at_new(s->dwarf, s->last_stop.regs.pc, mask); + it = cfree_dwarf_vars_at_new(s->dwarf, + dbg_pc_rt_to_img(s, s->last_stop.regs.pc), mask); if (!it) { driver_printf("No %s.\n", label); return; @@ -1237,6 +1295,7 @@ static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) { 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", v.name); diff --git a/include/cfree.h b/include/cfree.h @@ -433,6 +433,25 @@ const CfreeObjFile* cfree_jit_view(CfreeJit*); int cfree_jit_addr_to_sym(CfreeJit*, uint64_t addr, const char** name_out, uint64_t* off_out); +/* PC-space translation between the JIT's runtime address space (where + * executable code actually lives) and the image-relative vaddr space + * (the coordinate system the linked image — and any DWARF emitted at + * compile time — was authored in). + * + * The DWARF consumer (cfree_dwarf_addr_to_line, cfree_dwarf_line_to_addr, + * cfree_dwarf_unwind_step, etc.) operates entirely in image-relative + * vaddrs; the debugger, host signal handlers, and breakpoint installer + * work in runtime addresses. Callers translate at every boundary. + * + * Both functions return 0 if the input is not contained in any mapped + * segment. Identity maps for the JIT's iplt / abs-symbol cases are out + * of scope here — those addresses don't participate in source-level + * stepping. + * + * Stable for the JIT's lifetime; constant-time over jit segment count. */ +uint64_t cfree_jit_runtime_to_image(CfreeJit*, uint64_t runtime_pc); +uint64_t cfree_jit_image_to_runtime(CfreeJit*, uint64_t image_vaddr); + /* Enumerate every globally visible symbol in the resolved JIT image. * Drives `info functions` / `info variables` and tab completion in dbg. * `name` is interned and valid until cfree_jit_free; CfreeSymKind is the diff --git a/src/api/pipeline.c b/src/api/pipeline.c @@ -807,6 +807,40 @@ CfreeObjFile* cfree_obj_open(const CfreeEnv* env, return f; } +/* Internal: allocate an empty CfreeObjFile with a freshly initialized + * private Compiler and an empty ObjBuilder. The caller populates the + * builder via the normal obj_section/obj_write/obj_reloc/obj_finalize + * surface and the returned CfreeObjFile is closed via cfree_obj_close + * (which compiler_fini's the private compiler and obj_free's the + * builder), exactly like a file produced by cfree_obj_open. + * + * Used by cfree_jit_view to surface the JIT's debug sections to the + * DWARF consumer without re-emitting the image as an on-disk ELF/Mach-O + * blob. Format (ELF / Mach-O / etc.) is the caller's choice — DWARF + * lookup is name-based, so ELF is the natural pick. */ +CfreeObjFile* cfree_objfile_empty_new(const CfreeEnv* env, CfreeTarget target, + CfreeObjFmt fmt) { + Heap* h; + CfreeObjFile* f; + if (!env || !env->heap) return NULL; + h = (Heap*)env->heap; + f = (CfreeObjFile*)h->alloc(h, sizeof(*f), _Alignof(CfreeObjFile)); + if (!f) return NULL; + compiler_init(&f->compiler, target, env); + if (setjmp(f->compiler.panic)) { + compiler_run_cleanups(&f->compiler); + compiler_fini(&f->compiler); + h->free(h, f, sizeof(*f)); + return NULL; + } + f->fmt = fmt; + f->sec_data_cache = NULL; + f->sec_data_size = NULL; + f->sec_data_n = 0; + f->ob = obj_new(&f->compiler); + return f; +} + void cfree_obj_close(CfreeObjFile* f) { Heap* h; if (!f) return; diff --git a/src/dbg/step.c b/src/dbg/step.c @@ -13,6 +13,16 @@ #define DBG_AA64_BL_MASK 0xFC000000u #define DBG_AA64_BL_OP 0x94000000u +/* DWARF line/CFI tables are authored in image-relative vaddrs (cfree'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(CfreeJitSession* s, uint64_t pc) { + uint64_t v = cfree_jit_runtime_to_image(s->jit, pc); + return v ? v : pc; +} + static int prepare_step_insn(CfreeJitSession* s) { uint64_t pc = s->stop.regs.pc; uint64_t scratch_entry = 0; @@ -44,13 +54,14 @@ static int dwarf_line_for(CfreeJitSession* s, uint64_t pc, const char** file, uint32_t col = 0; *file = NULL; *line = 0; - return cfree_dwarf_addr_to_line(s->dwarf, pc, file, line, &col); + return cfree_dwarf_addr_to_line(s->dwarf, step_rt_to_img(s, pc), file, line, + &col); } static int dwarf_sub_for(CfreeJitSession* s, uint64_t pc, CfreeDwarfSubprogram* out) { memset(out, 0, sizeof(*out)); - return cfree_dwarf_subprogram_at(s->dwarf, pc, out); + return cfree_dwarf_subprogram_at(s->dwarf, step_rt_to_img(s, pc), out); } static int line_changed(const char* base_file, uint32_t base_line, @@ -125,7 +136,11 @@ static int run_step_out(CfreeJitSession* s) { CfreeUnwindFrame frame; u32 bp_id = 0; frame = s->stop.regs; + frame.pc = step_rt_to_img(s, frame.pc); /* CFI lookup is in image space */ if (cfree_dwarf_unwind_step(s->dwarf, &frame) != 0) return 1; + /* On success unwind_step writes frame.pc from the saved return-address + * register / stack slot — already a runtime PC, no inverse translation + * needed before the internal bp install. */ if (frame.pc == 0) return 1; if (dbg_bp_set_internal(s, frame.pc, &bp_id) != 0) return 1; if (dbg_session_signal_resume(s) != 0) return 1; @@ -150,10 +165,12 @@ static int run_next_line(CfreeJitSession* s) { { CfreeUnwindFrame frame = s->stop.regs; u32 bp_id = 0; + frame.pc = step_rt_to_img(s, frame.pc); if (cfree_dwarf_unwind_step(s->dwarf, &frame) != 0 || frame.pc == 0) { /* Fall back to stepping into the call. */ return run_step_line_loop(s); } + /* frame.pc is now a runtime return-address (from the stack). */ if (dbg_bp_set_internal(s, frame.pc, &bp_id) != 0) { return run_step_line_loop(s); } diff --git a/src/link/link.c b/src/link/link.c @@ -346,6 +346,62 @@ void link_set_interp_path(Linker* l, const char* path) { l->interp_path = (path && path[0]) ? pool_intern_cstr(l->c->global, path) : 0; } +/* ---- debug-input capture ---- + * + * Called once at the tail of link_resolve. For each LinkInput, record + * its ObjBuilder on the LinkImage so the JIT debug view (cfree_jit_view) + * can read .debug_* sections after the Linker is freed. Two ownership + * regimes: + * + * LINK_INPUT_OBJ_BYTES: linker owns; transfer to the image and null + * the LinkInput's obj so linker_release doesn't double-free. + * LINK_INPUT_OBJ: caller owns; borrow the pointer (do not free + * at image teardown). Caller is responsible for keeping the + * builder alive at least as long as the JIT. + * + * DSO / archive-only inputs carry no source-level debug info worth + * surfacing through cfree_jit_view, so their slot stays NULL. */ +void link_capture_debug_inputs(Linker* l, LinkImage* img) { + u32 n; + u32 i; + Heap* h; + if (!l || !img) return; + n = LinkInputs_count(&l->inputs); + img->dbg_objs_n = n; + if (n == 0) { + img->dbg_objs = NULL; + img->dbg_objs_owned = NULL; + return; + } + h = img->heap; + img->dbg_objs = (ObjBuilder**)h->alloc(h, sizeof(*img->dbg_objs) * n, + _Alignof(ObjBuilder*)); + img->dbg_objs_owned = (u8*)h->alloc(h, sizeof(*img->dbg_objs_owned) * n, 1u); + if (!img->dbg_objs || !img->dbg_objs_owned) + compiler_panic(img->c, no_loc(), + "link_capture_debug_inputs: oom on dbg arrays"); + memset(img->dbg_objs, 0, sizeof(*img->dbg_objs) * n); + memset(img->dbg_objs_owned, 0, sizeof(*img->dbg_objs_owned) * n); + for (i = 0; i < n; ++i) { + LinkInput* in = LinkInputs_at(&l->inputs, i); + if (!in || !in->obj) continue; + switch (in->kind) { + case LINK_INPUT_OBJ_BYTES: + img->dbg_objs[i] = in->obj; + img->dbg_objs_owned[i] = 1u; + in->obj = NULL; /* transfer: linker_release must not free it */ + break; + case LINK_INPUT_OBJ: + img->dbg_objs[i] = in->obj; + img->dbg_objs_owned[i] = 0u; /* borrowed; caller still owns */ + break; + default: + /* DSO / TBD: skip — no user-level debug sections we expose. */ + break; + } + } +} + /* ---- LinkImage accessors ---- */ const LinkSymbol* link_symbol(LinkImage* img, LinkSymId id) { @@ -428,6 +484,17 @@ static void link_image_release(LinkImage* img) { img->heap->free(img->heap, img->input_maps, sizeof(*img->input_maps) * img->ninput_maps); } + if (img->dbg_objs) { + for (i = 0; i < img->dbg_objs_n; ++i) { + if (img->dbg_objs[i] && img->dbg_objs_owned && img->dbg_objs_owned[i]) + obj_free(img->dbg_objs[i]); + } + img->heap->free(img->heap, img->dbg_objs, + sizeof(*img->dbg_objs) * img->dbg_objs_n); + if (img->dbg_objs_owned) + img->heap->free(img->heap, img->dbg_objs_owned, + sizeof(*img->dbg_objs_owned) * img->dbg_objs_n); + } symhash_fini(&img->globals); if (img->dyn) link_dyn_state_free(img); img->heap->free(img->heap, img, sizeof(*img)); diff --git a/src/link/link_internal.h b/src/link/link_internal.h @@ -112,6 +112,14 @@ struct Linker { /* Defined in link_layout.c. */ void link_ingest_archives(struct Linker*); +/* Defined in link.c. Walks the Linker's inputs and records each input's + * ObjBuilder on the LinkImage so the JIT debug view can reach its + * .debug_* sections after link_free runs. LINK_INPUT_OBJ_BYTES + * builders are moved (the LinkInput's obj pointer is nulled so + * linker_release skips them); LINK_INPUT_OBJ builders are borrowed + * (the caller still owns). DSO / TBD inputs are skipped. */ +void link_capture_debug_inputs(struct Linker*, LinkImage*); + /* Defined in link_dyn.c. Phase 4: synthesize .interp/.dynsym/.dynstr/ * .gnu.hash/.rela.dyn/.rela.plt/.plt/.got.plt/.dynamic when the link * is producing a PIE / ET_DYN exe. No-op when there are zero imports @@ -294,6 +302,22 @@ struct LinkImage { InputMap* input_maps; /* one per input; indexed by input_id-1 */ u32 ninput_maps; + /* Debug-capture state for the JIT path. Populated by + * link_capture_debug_inputs at the tail of link_resolve so the input + * ObjBuilders (which carry .debug_* sections + their per-section + * relocations — neither consumed nor mutated by layout) survive the + * Linker's teardown and become reachable from cfree_jit_view. + * + * Parallel to input_maps: dbg_objs[i] is the ObjBuilder for input + * (i+1), or NULL when no debug info is present / the input kind isn't + * relevant (DSO/TBD). dbg_objs_owned[i] is 1 when the image must + * obj_free the builder at link_image_free (transferred from + * LINK_INPUT_OBJ_BYTES), 0 when borrowed (LINK_INPUT_OBJ — caller + * still owns). */ + ObjBuilder** dbg_objs; + u8* dbg_objs_owned; + u32 dbg_objs_n; + /* Dynamic-link state (Phase 4). NULL when emit_pie was not set on * the Linker — i.e., the static-exe / JIT path. Owned by the image. */ LinkDynState* dyn; diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -12,11 +12,18 @@ #include <string.h> #include "core/bytes.h" +#include "core/buf.h" #include "core/heap.h" #include "core/pool.h" #include "core/util.h" #include "link/link.h" #include "link/link_internal.h" +#include "obj/obj.h" + +/* Defined in src/api/pipeline.c — allocates an empty CfreeObjFile with + * a private Compiler and an empty ObjBuilder for caller population. */ +CfreeObjFile* cfree_objfile_empty_new(const CfreeEnv* env, CfreeTarget target, + CfreeObjFmt fmt); static SrcLoc no_loc(void) { SrcLoc l = {0, 0, 0}; @@ -48,6 +55,14 @@ struct CfreeJit { LinkImage* image; CfreeExecMemRegion* segs; /* one per image->nsegments */ u32 nsegs; + /* DWARF view, lazily constructed on first cfree_jit_view call. Built + * over a private Compiler so its string pools and the new ObjBuilder + * are owned end-to-end by the view; freed in cfree_jit_free. NULL + * means "not yet built"; view_built distinguishes "tried and gave up" + * (multi-input v1, etc.) from "untried". */ + CfreeObjFile* view; + u8 view_built; + u8 pad[7]; }; /* AArch64 ELF ABI: TP points 16 bytes before the TLS image; TLSLE @@ -269,6 +284,8 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) { jit->image = img; jit->segs = segs; jit->nsegs = img->nsegments; + jit->view = NULL; + jit->view_built = 0u; /* Take ownership of the image: undefer it from the compiler so a * future panic doesn't reap something we still hold. */ @@ -300,6 +317,13 @@ void cfree_jit_free(CfreeJit* jit) { if (!jit) return; heap = (Heap*)jit->c->env->heap; mem = jit->c->env->execmem; + /* The debug view (if built) is closed first — it owns a private + * Compiler whose pools must be released before the image's + * referenced builders are freed in link_image_free. */ + if (jit->view) { + cfree_obj_close(jit->view); + jit->view = NULL; + } if (jit->segs && mem && mem->release) { for (i = 0; i < jit->nsegs; ++i) { if (jit->segs[i].size) mem->release(mem->user, &jit->segs[i]); @@ -334,9 +358,171 @@ void* cfree_jit_lookup(CfreeJit* jit, const char* name) { /* ---- inspector entries ---- */ +/* True if `name` (NUL-terminated) is a debug section the DWARF consumer + * (src/dwarf/dwarf_open.c) might read. Everything else is skipped. */ +static int jit_view_is_debug_name(const char* name) { + if (!name) return 0; + if (name[0] == '.' && name[1] == 'd' && name[2] == 'e' && name[3] == 'b' && + name[4] == 'u' && name[5] == 'g' && name[6] == '_') + return 1; /* .debug_* */ + /* .eh_frame is consulted by cfree_dwarf for CFI unwinding when + * available; cfree itself doesn't currently emit it, but inputs read + * from external .o files may carry it. */ + if (name[0] == '.' && name[1] == 'e' && name[2] == 'h' && name[3] == '_' && + name[4] == 'f' && name[5] == 'r' && name[6] == 'a' && name[7] == 'm' && + name[8] == 'e' && name[9] == '\0') + return 1; + return 0; +} + +/* True if input `ii` carries any debug section that's worth surfacing. + * Cheap walk over the input's section table. */ +static int jit_view_input_has_debug(CfreeJit* jit, u32 ii) { + ObjBuilder* ob; + u32 nsec, k; + if (ii >= jit->image->dbg_objs_n) return 0; + ob = jit->image->dbg_objs[ii]; + if (!ob) return 0; + nsec = obj_section_count(ob); + for (k = 0; k < nsec; ++k) { + const Section* s = obj_section_get(ob, (ObjSecId)(k + 1)); + const char* nm; + if (!s || !s->name) continue; + nm = pool_str(jit->c->global, s->name, NULL); + if (jit_view_is_debug_name(nm)) return 1; + } + return 0; +} + +/* Resolve an input-local ObjSymId to the final image vaddr of the + * defining LinkSymbol, going through the per-input InputMap and the + * image's LinkSyms table. Returns 0 if the symbol is undefined or the + * mapping is missing — debug relocations against unresolved symbols + * collapse to zero, which is the DWARF "absent" convention. */ +static u64 jit_view_sym_vaddr(CfreeJit* jit, u32 ii, ObjSymId obj_sym) { + const InputMap* m; + LinkSymId lid; + const LinkSymbol* s; + if (obj_sym == OBJ_SYM_NONE) return 0; + if (ii >= jit->image->ninput_maps) return 0; + m = &jit->image->input_maps[ii]; + if (!m->sym || obj_sym >= m->nsym) return 0; + lid = m->sym[obj_sym]; + if (lid == LINK_SYM_NONE || lid > LinkSyms_count(&jit->image->syms)) return 0; + s = LinkSyms_at(&jit->image->syms, lid - 1); + if (!s || !s->defined) return 0; + return s->vaddr; /* image-relative — what DWARF was emitted in */ +} + +/* Copy one debug section from input `ii` into `view_ob` with its + * relocations applied against final image vaddrs. Relocations in + * .debug_* are almost universally R_ABS{32,64} against a code symbol; + * link_reloc_apply ignores its P argument for those kinds, so we pass + * P=0. */ +static void jit_view_copy_debug_section(CfreeJit* jit, u32 ii, + ObjSecId in_sec_id, + ObjBuilder* view_ob) { + ObjBuilder* in_ob = jit->image->dbg_objs[ii]; + const Section* in_sec = obj_section_get(in_ob, in_sec_id); + Heap* h; + u32 nbytes, k, total_relocs; + const char* nm; + Sym view_name; + ObjSecId out_id; + u8* bytes; + if (!in_sec) return; + nbytes = in_sec->bytes.total; + if (nbytes == 0) return; + h = (Heap*)jit->c->env->heap; + + nm = pool_str(jit->c->global, in_sec->name, NULL); + if (!nm) return; + view_name = pool_intern_cstr(obj_compiler(view_ob)->global, nm); + out_id = obj_section_ex(view_ob, view_name, SEC_DEBUG, SSEM_PROGBITS, + in_sec->flags, in_sec->align ? in_sec->align : 1u, + in_sec->entsize, 0, 0); + + bytes = (u8*)h->alloc(h, nbytes, 1); + if (!bytes) return; + buf_flatten(&in_sec->bytes, bytes); + + /* Apply this section's relocations in place. obj_reloc_at returns + * all relocations across the input; filter by section_id. */ + total_relocs = obj_reloc_total(in_ob); + for (k = 0; k < total_relocs; ++k) { + const Reloc* r = obj_reloc_at(in_ob, k); + u64 S; + if (!r || r->section_id != in_sec_id) continue; + if (r->offset >= nbytes) continue; /* malformed; skip */ + S = jit_view_sym_vaddr(jit, ii, r->sym); + /* P is unused by ABS kinds; PC-relative debug-section relocs are + * not produced by cfree's debug emitter, but if some external .o + * carried one against a debug section, P=0 would give a + * non-meaningful offset — acceptable for a viewer that only reads + * absolute address fields. */ + link_reloc_apply(jit->c, (RelocKind)r->kind, bytes + r->offset, S, + r->addend, 0); + } + + obj_write(view_ob, out_id, bytes, nbytes); + h->free(h, bytes, nbytes); +} + +/* Build the view on first call. Returns NULL if no input carries + * debug info, or if more than one does (v1 doesn't concatenate cross- + * CU references; see doc/DBG.md §12). */ +static CfreeObjFile* jit_view_build(CfreeJit* jit) { + u32 i, dbg_input_ii = UINT32_MAX, n_with_debug = 0; + CfreeObjFile* view; + ObjBuilder* view_ob; + u32 nsec, k; + + if (!jit->image || jit->image->dbg_objs_n == 0) return NULL; + + for (i = 0; i < jit->image->dbg_objs_n; ++i) { + if (jit_view_input_has_debug(jit, i)) { + dbg_input_ii = i; + ++n_with_debug; + } + } + if (n_with_debug == 0) return NULL; + if (n_with_debug > 1) { + /* v1 limitation: cross-CU offset adjustment for concatenated + * .debug_abbrev / .debug_str / .debug_str_offsets isn't wired up + * yet. Single-TU dbg sessions are the supported shape. */ + return NULL; + } + + view = + cfree_objfile_empty_new(jit->c->env, jit->c->target, jit->c->target.obj); + if (!view) return NULL; + view_ob = cfree_obj_builder(view); + if (!view_ob) { + cfree_obj_close(view); + return NULL; + } + + nsec = obj_section_count(jit->image->dbg_objs[dbg_input_ii]); + for (k = 0; k < nsec; ++k) { + const Section* s = obj_section_get(jit->image->dbg_objs[dbg_input_ii], + (ObjSecId)(k + 1)); + const char* nm; + if (!s || !s->name) continue; + nm = pool_str(jit->c->global, s->name, NULL); + if (!jit_view_is_debug_name(nm)) continue; + jit_view_copy_debug_section(jit, dbg_input_ii, (ObjSecId)(k + 1), view_ob); + } + + obj_finalize(view_ob); + return view; +} + const CfreeObjFile* cfree_jit_view(CfreeJit* jit) { - (void)jit; - return NULL; + if (!jit) return NULL; + if (jit->view_built) return jit->view; + jit->view = jit_view_build(jit); + jit->view_built = 1u; + return jit->view; } /* True for symbol kinds the user-facing JIT inspector surfaces. Mapping @@ -448,6 +634,43 @@ int cfree_jit_image_contains(CfreeJit* jit, uint64_t runtime_addr) { return 0; } +/* runtime <-> image vaddr translation. The pair is symmetric with + * vaddr_to_runtime above; exposed here so dwarf consumers and the + * driver can cross the boundary at every DWARF call. Walks the + * segment table (at most a handful of entries) so callers can do this + * per stop without measurable cost. */ +uint64_t cfree_jit_runtime_to_image(CfreeJit* jit, uint64_t runtime_pc) { + u32 i; + uintptr_t a; + if (!jit || !jit->segs) return 0; + a = (uintptr_t)runtime_pc; + for (i = 0; i < jit->nsegs; ++i) { + uintptr_t lo = (uintptr_t)jit->segs[i].runtime; + uintptr_t hi = lo + (uintptr_t)jit->segs[i].size; + if (a >= lo && a < hi) { + const LinkSegment* s = &jit->image->segments[i]; + return s->vaddr + (uint64_t)(a - lo); + } + } + /* One-past-end: lets a return-address that sits exactly at a + * segment's end-boundary still round-trip. */ + for (i = 0; i < jit->nsegs; ++i) { + uintptr_t hi = (uintptr_t)jit->segs[i].runtime + (uintptr_t)jit->segs[i].size; + if (a == hi) { + const LinkSegment* s = &jit->image->segments[i]; + return s->vaddr + s->mem_size; + } + } + return 0; +} + +uint64_t cfree_jit_image_to_runtime(CfreeJit* jit, uint64_t image_vaddr) { + uintptr_t rt; + if (!jit || !jit->segs) return 0; + rt = vaddr_to_runtime(jit->image, jit->segs, image_vaddr); + return (uint64_t)rt; +} + CfreeArchKind cfree_jit_image_arch(CfreeJit* jit) { return jit->c->target.arch; } diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -2970,5 +2970,12 @@ LinkImage* link_resolve(Linker* l) { gc_live_free(&g, h); } + /* Hand the input ObjBuilders to the image so cfree_jit_view can + * surface .debug_* sections after link_free runs (layout/reloc are + * complete, so the builders are otherwise idle). Must be the last + * step before returning — any pass that walks LinkInputs.obj + * expecting a value would break otherwise. */ + link_capture_debug_inputs(l, img); + return img; }