kit

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

commit 0bfde91143b108d562e7bc2754f9335c212745d5
parent 064cd29d7879a73a15ed06cbd9860f4ac4d59384
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 14 May 2026 19:32:31 -0700

Add append-only JIT snippets to dbg

Diffstat:
Mdriver/dbg.c | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/cfree.h | 10++++++++++
Msrc/api/pipeline.c | 2+-
Msrc/dbg/dbg.h | 3+++
Msrc/dbg/session.c | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/link/link_jit.c | 663++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 1012 insertions(+), 6 deletions(-)

diff --git a/driver/dbg.c b/driver/dbg.c @@ -95,6 +95,8 @@ void driver_help_dbg(void) { " sl step to next source line (into calls)\n" " n, next step to next source line (over calls)\n" " finish run until current frame returns\n" + " jit [LANG|NAME] { ... } compile and append a frontend snippet\n" + " call SYMBOL [INT_OR_ADDR...] call function, returns uint64_t\n" " jump ADDR set PC to ADDR (no resume)\n" " bt, backtrace print stack trace with arguments\n" " b LOC set breakpoint at LOC:\n" @@ -248,6 +250,16 @@ static int dbg_compile_and_jit(DbgOpts* o, CfreePipeline* pipe, driver_dlsym_resolver, NULL, out_jit); } +static void dbg_fill_compile_options(DbgOpts* o, CfreeCompileOptions* copts) { + { + CfreeCompileOptions z = {0}; + *copts = z; + } + copts->opt_level = o->opt_level; + copts->debug_info = o->debug_info; + driver_cflags_fill_pp(&o->cf, &copts->pp); +} + /* ============================================================ * Breakpoint table (driver-side) * ============================================================ @@ -280,6 +292,8 @@ typedef struct Bp { typedef struct DbgState { DriverEnv* env; + CfreePipeline* pipe; + CfreeCompileOptions copts; CfreeJit* jit; CfreeJitSession* session; const CfreeObjFile* view; @@ -295,6 +309,7 @@ typedef struct DbgState { int has_stop; CfreeStopInfo last_stop; + uint64_t jit_counter; } DbgState; /* SIGINT trampoline. The handler in env.c calls our cb with this state; @@ -786,6 +801,7 @@ static int dbg_read_value(DbgState*, const CfreeDwarfVarLoc*, 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); +static char* dbg_take_word(char* line, char** word_out); /* ============================================================ * Backtrace @@ -1391,6 +1407,228 @@ static void dbg_cmd_info_syms(DbgState* s, CfreeSymKind want, if (!printed) driver_printf("(none)\n"); } +static int dbg_refresh_dwarf(DbgState* s) { + if (s->dwarf) { + cfree_dwarf_close(s->dwarf); + s->dwarf = NULL; + } + s->view = cfree_jit_view(s->jit); + if (s->view) { + s->dwarf = cfree_dwarf_open(cfree_pipeline_compiler(s->pipe), s->view); + if (s->dwarf && s->session) { + cfree_jit_session_attach_dwarf(s->session, s->dwarf); + } + } else if (s->session) { + cfree_jit_session_attach_dwarf(s->session, NULL); + } + return 0; +} + +static int dbg_buf_append(DbgState* s, char** buf, size_t* len, size_t* cap, + const char* src, size_t n) { + if (*len + n + 1u > *cap) { + size_t nc = *cap ? *cap * 2u : 1024u; + char* nb; + while (nc < *len + n + 1u) nc *= 2u; + nb = (char*)s->env->heap->realloc(s->env->heap, *buf, *cap, nc, + _Alignof(char)); + if (!nb) return 1; + *buf = nb; + *cap = nc; + } + driver_memcpy(*buf + *len, src, n); + *len += n; + (*buf)[*len] = '\0'; + return 0; +} + +static int dbg_brace_delta(const char* p) { + int d = 0; + while (*p) { + if (*p == '{') ++d; + else if (*p == '}') --d; + ++p; + } + return d; +} + +static CfreeLanguage dbg_jit_language_for_tag(const char* tag, + const char** name_out) { + if (!tag || !*tag || driver_streq(tag, "c")) { + if (name_out) *name_out = "<dbg-jit.c>"; + return CFREE_LANG_C; + } + if (driver_streq(tag, "toy")) { + if (name_out) *name_out = "<dbg-jit.toy>"; + return CFREE_LANG_TOY; + } + if (driver_streq(tag, "asm") || driver_streq(tag, "s")) { + if (name_out) *name_out = "<dbg-jit.s>"; + return CFREE_LANG_ASM; + } + if (name_out) *name_out = tag; + return cfree_language_for_path(tag); +} + +static void dbg_cmd_jit(DbgState* s, const char* rest) { + char* src = NULL; + size_t len = 0, cap = 0; + CfreeBytesInput in; + CfreeObjBuilder* ob = NULL; + const char* p = rest; + const char* input_name = "<dbg-jit.c>"; + CfreeLanguage lang = CFREE_LANG_C; + int depth; + + while (*p && dbg_isspace((unsigned char)*p)) ++p; + if (*p != '{') { + const char* tag = p; + size_t tag_n; + char tag_buf[64]; + while (*p && !dbg_isspace((unsigned char)*p) && *p != '{') ++p; + tag_n = (size_t)(p - tag); + if (tag_n == 0 || tag_n >= sizeof(tag_buf)) { + driver_errf(DBG_TOOL, "usage: jit [c|toy|asm|name.ext] { ... }"); + return; + } + driver_memcpy(tag_buf, tag, tag_n); + tag_buf[tag_n] = '\0'; + lang = dbg_jit_language_for_tag(tag_buf, &input_name); + while (*p && dbg_isspace((unsigned char)*p)) ++p; + } + if (*p != '{') { + driver_errf(DBG_TOOL, "usage: jit [c|toy|asm|name.ext] { ... }"); + return; + } + ++p; + depth = 1 + dbg_brace_delta(p); + { + const char* end = p + driver_strlen(p); + if (depth <= 0) { + while (end > p && end[-1] != '}') --end; + if (end > p) --end; + } + if (dbg_buf_append(s, &src, &len, &cap, p, (size_t)(end - p)) != 0) + goto oom; + if (dbg_buf_append(s, &src, &len, &cap, "\n", 1) != 0) goto oom; + } + while (depth > 0) { + char line[LINE_CAP]; + int n; + driver_printf(" > "); + driver_flush_stdout(); + n = driver_read_line(line, sizeof(line)); + if (n <= 0) { + driver_errf(DBG_TOOL, "unterminated jit block"); + goto out; + } + depth += dbg_brace_delta(line); + if (depth <= 0) { + char* close = line; + while (*close && *close != '}') ++close; + if (dbg_buf_append(s, &src, &len, &cap, line, + (size_t)(close - line)) != 0) + goto oom; + if (dbg_buf_append(s, &src, &len, &cap, "\n", 1) != 0) goto oom; + break; + } + if (dbg_buf_append(s, &src, &len, &cap, line, driver_strlen(line)) != 0) + goto oom; + if (dbg_buf_append(s, &src, &len, &cap, "\n", 1) != 0) goto oom; + } + + s->jit_counter++; + in.name = input_name; + in.data = (const uint8_t*)src; + in.len = len; + in.lang = lang; + if (cfree_pipeline_compile_obj(s->pipe, &s->copts, &in, &ob) != 0 || !ob) { + driver_errf(DBG_TOOL, "jit compile failed"); + goto out; + } + if (cfree_jit_append_obj(s->jit, ob) != 0) { + driver_errf(DBG_TOOL, "jit append failed"); + goto out; + } + dbg_refresh_dwarf(s); + driver_printf("JIT generation %llu\n", + (unsigned long long)cfree_jit_generation(s->jit)); + goto out; + +oom: + driver_errf(DBG_TOOL, "out of memory"); +out: + if (src) driver_free(s->env, src, cap); +} + +static void dbg_cmd_call(DbgState* s, const char* rest) { + char* tmp; + size_t tmp_size; + char* sym; + char* p; + uint64_t args[8]; + uint32_t nargs = 0; + void* entry; + uint64_t ret = 0; + CfreeStopInfo stop; + + tmp = dbg_dup(s->env, rest, driver_strlen(rest), &tmp_size); + if (!tmp) { + driver_errf(DBG_TOOL, "out of memory"); + return; + } + p = dbg_take_word(tmp, &sym); + if (!*sym) { + driver_errf(DBG_TOOL, "usage: call SYMBOL [INT_OR_ADDR ...]"); + goto out; + } + entry = cfree_jit_lookup(s->jit, sym); + if (!entry) { + driver_errf(DBG_TOOL, "symbol not found: %s", sym); + goto out; + } + while (*p) { + char* a; + uint64_t v; + size_t used; + p = dbg_take_word(p, &a); + if (!*a) break; + if (nargs == 8u) { + driver_errf(DBG_TOOL, "call supports at most 8 arguments"); + goto out; + } + used = dbg_parse_uint(a, &v); + if (!used || a[used] != '\0') { + driver_errf(DBG_TOOL, "expected integer/address argument, got '%s'", a); + goto out; + } + args[nargs++] = v; + } + if (driver_install_sigint(dbg_on_sigint, s) != 0) { + driver_errf(DBG_TOOL, "failed to install SIGINT handler"); + goto out; + } + if (cfree_jit_session_call_u64(s->session, entry, args, nargs, &ret, &stop) != + 0) { + driver_restore_sigint(); + driver_errf(DBG_TOOL, "call failed (debuggee must be idle or exited)"); + goto out; + } + driver_restore_sigint(); + s->last_stop = stop; + s->has_stop = (stop.kind != CFREE_STOP_EXIT); + if (stop.kind == CFREE_STOP_EXIT) { + driver_printf("= %llu (0x%llx)\n", (unsigned long long)ret, + (unsigned long long)ret); + } else { + s->has_stop = 1; + dbg_render_stop(s, &stop); + } + +out: + driver_free(s->env, tmp, tmp_size); +} + /* ============================================================ * `x ADDR [count]` * ============================================================ @@ -1681,6 +1919,8 @@ static void dbg_cmd_help(void) { " sl step to next source line (into calls)\n" " n, next step to next source line (over calls)\n" " finish run until current frame returns\n" + " jit [LANG|NAME] { ... } compile and append a frontend snippet\n" + " call SYMBOL [INT_OR_ADDR...] call function, returns uint64_t\n" " jump ADDR set PC to ADDR (no resume)\n" " bt, backtrace print stack trace with arguments\n" " b LOC set breakpoint at LOC:\n" @@ -1769,6 +2009,18 @@ static int dbg_dispatch(DbgState* s, char* line) { dbg_drive(s, RUN_STEP_OUT); return 0; } + if (driver_streq(cmd, "jit")) { + dbg_cmd_jit(s, rest); + return 0; + } + if (driver_streq(cmd, "call")) { + if (!s->session) { + driver_errf(DBG_TOOL, "no JIT session"); + return 0; + } + dbg_cmd_call(s, rest); + return 0; + } if (driver_streq(cmd, "bt") || driver_streq(cmd, "backtrace") || driver_streq(cmd, "where")) { dbg_cmd_bt(s); @@ -2035,6 +2287,8 @@ int driver_dbg(int argc, char** argv) { } st.env = &env; + st.pipe = pipe; + dbg_fill_compile_options(&o, &st.copts); st.jit = jit; st.prog_argc = (int)o.prog_argc; st.prog_argv = o.prog_argv; diff --git a/include/cfree.h b/include/cfree.h @@ -470,6 +470,12 @@ const uint8_t* cfree_writer_mem_bytes(CfreeWriter*, size_t* len_out); * `main`). Returns NULL on miss. */ void cfree_jit_free(CfreeJit*); void* cfree_jit_lookup(CfreeJit*, const char* name); +/* Experimental append-only JIT growth. Appends one finalized object into + * reserved JIT slack without moving existing code/data. Returns nonzero on + * duplicate strong definitions, unresolved references, capacity exhaustion, + * or relocation/protection failure. */ +int cfree_jit_append_obj(CfreeJit*, CfreeObjBuilder*); +uint64_t cfree_jit_generation(CfreeJit*); /* Run all fini_array destructors in reverse order. Call after the last * use of JITed code, before cfree_jit_free. */ void cfree_jit_run_dtors(CfreeJit*); @@ -580,6 +586,7 @@ typedef enum CfreeResumeMode { * is shape-agnostic. New entry shapes extend the enum. */ typedef enum CfreeEntryKind { CFREE_ENTRY_INT_ARGV, /* int(int, char**) */ + CFREE_ENTRY_U64, /* uint64_t(uint64_t, ... up to 8 args) */ } CfreeEntryKind; CfreeJitSession* cfree_jit_session_new(CfreeJit*); @@ -599,6 +606,9 @@ int cfree_jit_session_attach_dwarf(CfreeJitSession*, CfreeDebugInfo*); * populated. */ int cfree_jit_session_call(CfreeJitSession*, void* entry, CfreeEntryKind, int argc, char** argv, CfreeStopInfo* stop_out); +int cfree_jit_session_call_u64(CfreeJitSession*, void* entry, + const uint64_t* args, uint32_t nargs, + uint64_t* ret_out, CfreeStopInfo* stop_out); /* Resume the parked worker. Blocks until the next stop. Returns 0 on * success, nonzero if no worker is parked. */ diff --git a/src/api/pipeline.c b/src/api/pipeline.c @@ -328,7 +328,7 @@ int cfree_link_jit(CfreeCompiler* c, const CfreeLinkOptions* opts, image = link_resolve(linker); /* deferred-cleanup-registered */ *out_jit = cfree_jit_from_image(image); /* undefers + transfers ownership */ metrics_scope_end(c, "link_jit.all"); - link_free(linker); + /* cfree_jit_from_image keeps the Linker alive for append-only JIT growth. */ compiler_panic_restore(c, &saved); return 0; } diff --git a/src/dbg/dbg.h b/src/dbg/dbg.h @@ -148,6 +148,9 @@ struct CfreeJitSession { int entry_argc; char** entry_argv; int entry_ret; + uint64_t entry_u64_args[8]; + uint32_t entry_u64_nargs; + uint64_t entry_u64_ret; /* current stop slot (filled by the fault handler / worker exit path) */ CfreeStopInfo stop; diff --git a/src/dbg/session.c b/src/dbg/session.c @@ -163,11 +163,79 @@ static void worker_main(void* arg) { if (s->worker_should_exit) return; if (s->state == DBG_STATE_RUNNING && s->entry) { typedef int (*EntryIntArgv)(int, char**); + typedef uint64_t (*EntryU64_0)(void); + typedef uint64_t (*EntryU64_1)(uint64_t); + typedef uint64_t (*EntryU64_2)(uint64_t, uint64_t); + typedef uint64_t (*EntryU64_3)(uint64_t, uint64_t, uint64_t); + typedef uint64_t (*EntryU64_4)(uint64_t, uint64_t, uint64_t, uint64_t); + typedef uint64_t (*EntryU64_5)(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t); + typedef uint64_t (*EntryU64_6)(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t, uint64_t); + typedef uint64_t (*EntryU64_7)(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t, uint64_t, uint64_t); + typedef uint64_t (*EntryU64_8)(uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t, uint64_t, uint64_t, uint64_t); int ret = 0; switch (s->entry_kind) { case CFREE_ENTRY_INT_ARGV: ret = ((EntryIntArgv)s->entry)(s->entry_argc, s->entry_argv); break; + case CFREE_ENTRY_U64: + switch (s->entry_u64_nargs) { + case 0: + s->entry_u64_ret = ((EntryU64_0)s->entry)(); + break; + case 1: + s->entry_u64_ret = + ((EntryU64_1)s->entry)(s->entry_u64_args[0]); + break; + case 2: + s->entry_u64_ret = ((EntryU64_2)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1]); + break; + case 3: + s->entry_u64_ret = ((EntryU64_3)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1], + s->entry_u64_args[2]); + break; + case 4: + s->entry_u64_ret = ((EntryU64_4)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1], + s->entry_u64_args[2], s->entry_u64_args[3]); + break; + case 5: + s->entry_u64_ret = ((EntryU64_5)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1], + s->entry_u64_args[2], s->entry_u64_args[3], + s->entry_u64_args[4]); + break; + case 6: + s->entry_u64_ret = ((EntryU64_6)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1], + s->entry_u64_args[2], s->entry_u64_args[3], + s->entry_u64_args[4], s->entry_u64_args[5]); + break; + case 7: + s->entry_u64_ret = ((EntryU64_7)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1], + s->entry_u64_args[2], s->entry_u64_args[3], + s->entry_u64_args[4], s->entry_u64_args[5], + s->entry_u64_args[6]); + break; + case 8: + s->entry_u64_ret = ((EntryU64_8)s->entry)( + s->entry_u64_args[0], s->entry_u64_args[1], + s->entry_u64_args[2], s->entry_u64_args[3], + s->entry_u64_args[4], s->entry_u64_args[5], + s->entry_u64_args[6], s->entry_u64_args[7]); + break; + default: + s->entry_u64_ret = 0; + break; + } + ret = (int)s->entry_u64_ret; + break; } memset(&s->stop, 0, sizeof(s->stop)); s->stop.kind = CFREE_STOP_EXIT; @@ -291,7 +359,7 @@ void cfree_jit_session_free(CfreeJitSession* s) { int cfree_jit_session_call(CfreeJitSession* s, void* entry, CfreeEntryKind kind, int argc, char** argv, CfreeStopInfo* stop_out) { if (!s || !entry) return 1; - if (s->state == DBG_STATE_RUNNING) return 1; + if (s->state == DBG_STATE_RUNNING || s->state == DBG_STATE_STOPPED) return 1; s->entry = entry; s->entry_kind = kind; s->entry_argc = argc; @@ -308,6 +376,22 @@ int cfree_jit_session_call(CfreeJitSession* s, void* entry, CfreeEntryKind kind, return 0; } +int cfree_jit_session_call_u64(CfreeJitSession* s, void* entry, + const uint64_t* args, uint32_t nargs, + uint64_t* ret_out, CfreeStopInfo* stop_out) { + uint32_t i; + int rc; + if (!s || !entry || nargs > 8u) return 1; + if (s->state == DBG_STATE_RUNNING || s->state == DBG_STATE_STOPPED) return 1; + for (i = 0; i < nargs; ++i) s->entry_u64_args[i] = args ? args[i] : 0; + for (; i < 8u; ++i) s->entry_u64_args[i] = 0; + s->entry_u64_nargs = nargs; + s->entry_u64_ret = 0; + rc = cfree_jit_session_call(s, entry, CFREE_ENTRY_U64, 0, NULL, stop_out); + if (rc == 0 && ret_out) *ret_out = s->entry_u64_ret; + return rc; +} + int cfree_jit_session_resume(CfreeJitSession* s, CfreeResumeMode mode, CfreeStopInfo* stop_out) { if (!s) return 1; diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -72,6 +72,10 @@ struct CfreeJit { * means "not yet built"; view_built distinguishes "tried and gave up" * (multi-input v1, etc.) from "untried". */ CfreeObjFile* view; + Linker* linker; /* kept alive for append-time resolver/input context */ + u64 append_cursor[SEG_NBUCKETS]; + u64 append_limit[SEG_NBUCKETS]; + u64 generation; /* Mach-O TLV runtime state. Lazily allocated by jit_patch_tlv_descriptors * when the image contains any in-image TLV descriptor. `tls_vtable` is * borrowed from CfreeEnv (lives across the env's lifetime); `tls_ctx` @@ -83,6 +87,11 @@ struct CfreeJit { u8 pad[7]; }; +#define JIT_APPEND_RX_SLACK (64ull * 1024ull * 1024ull) +#define JIT_APPEND_R_SLACK (16ull * 1024ull * 1024ull) +#define JIT_APPEND_RW_SLACK (16ull * 1024ull * 1024ull) +#define JIT_APPEND_TLS_SLACK (4ull * 1024ull * 1024ull) + /* AArch64 ELF ABI: TP points 16 bytes before the TLS image; TLSLE * encodes (target_offset_in_image + 16). */ #define AARCH64_TCB_SIZE 16ull @@ -299,6 +308,8 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) { u64 page; u64 image_base = (u64)-1; u64 image_end = 0; + u64 append_start; + u64 append_total; u64 master_size; int needs_exec = 0; int needs_input_materialize = 0; @@ -328,7 +339,10 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) { if (image_base & (page - 1u)) compiler_panic(c, no_loc(), "cfree_jit_from_image: segment vaddr not page-aligned"); - master_size = image_end - image_base; + append_start = image_end; + append_total = JIT_APPEND_RX_SLACK + JIT_APPEND_R_SLACK + + JIT_APPEND_RW_SLACK + JIT_APPEND_TLS_SLACK; + master_size = (image_end - image_base) + append_total; metrics_count(c, "jit.master_size", master_size); metrics_count(c, "jit.nsegments", img->nsegments); @@ -478,6 +492,12 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) { "cfree_jit_from_image: execmem.protect failed"); } } + if (append_total) { + void* slack_rt = + (u8*)master.runtime + (uintptr_t)(append_start - image_base); + (void)mem->protect(mem->user, slack_rt, (size_t)append_total, + CFREE_PROT_NONE); + } metrics_scope_end(c, "jit.protect"); /* Flush only the segments that will be executed, against the @@ -526,6 +546,17 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) { jit->segs = segs; jit->nsegs = img->nsegments; jit->view = NULL; + jit->linker = img->linker; + jit->append_cursor[SEG_RX] = append_start; + jit->append_limit[SEG_RX] = jit->append_cursor[SEG_RX] + JIT_APPEND_RX_SLACK; + jit->append_cursor[SEG_R] = jit->append_limit[SEG_RX]; + jit->append_limit[SEG_R] = jit->append_cursor[SEG_R] + JIT_APPEND_R_SLACK; + jit->append_cursor[SEG_RW] = jit->append_limit[SEG_R]; + jit->append_limit[SEG_RW] = jit->append_cursor[SEG_RW] + JIT_APPEND_RW_SLACK; + jit->append_cursor[SEG_TLS] = jit->append_limit[SEG_RW]; + jit->append_limit[SEG_TLS] = + jit->append_cursor[SEG_TLS] + JIT_APPEND_TLS_SLACK; + jit->generation = 0; jit->view_built = 0u; jit->tls_vtable = NULL; jit->tls_ctx = NULL; @@ -536,6 +567,10 @@ CfreeJit* cfree_jit_from_image(LinkImage* img) { compiler_undefer(c, img->deferred); img->deferred = NULL; } + if (jit->linker && jit->linker->deferred) { + compiler_undefer(c, jit->linker->deferred); + jit->linker->deferred = NULL; + } /* Mach-O TLV descriptor pass: install our thunk into descriptor[+0], * stash the per-image TLS ctx in [+8], and overwrite [+16] with the @@ -595,6 +630,10 @@ void cfree_jit_free(CfreeJit* jit) { /* link_image_free unfederes (no-op now) and releases storage. */ link_image_free(jit->image); } + if (jit->linker) { + link_free(jit->linker); + jit->linker = NULL; + } heap->free(heap, jit, sizeof(*jit)); } @@ -615,6 +654,610 @@ void* cfree_jit_lookup(CfreeJit* jit, const char* name) { return (void*)vaddr_to_runtime(jit->image, jit->segs, s->vaddr); } +uint64_t cfree_jit_generation(CfreeJit* jit) { + return jit ? jit->generation : 0; +} + +typedef struct JitAppendSec { + ObjSecId obj_sec; + LinkSectionId link_sec; + LinkSegmentId link_seg; + SegBucket bucket; + u64 vaddr; + u64 size; + u64 file_size; + u32 align; + u16 flags; + u16 sem; + Sym name; +} JitAppendSec; + +static int jit_bind_strength(u8 bind) { + switch (bind) { + case SB_GLOBAL: + return 3; + case SB_WEAK: + return 2; + case SB_LOCAL: + return 1; + default: + return 0; + } +} + +static int jit_obj_sym_is_def(const ObjSym* s) { + return s && s->kind != SK_UNDEF && + (s->kind == SK_ABS || s->kind == SK_COMMON || s->kind == SK_FILE || + s->section_id != OBJ_SEC_NONE); +} + +static int jit_obj_sym_is_spurious_undef(const ObjSym* s) { + return s && s->section_id == OBJ_SEC_NONE && s->kind != SK_ABS && + s->kind != SK_COMMON && !s->referenced && + (s->bind == SB_GLOBAL || s->bind == SB_WEAK); +} + +static u8 jit_reloc_width_local(RelocKind k) { + switch (k) { + case R_ABS64: + case R_REL64: + case R_PC64: + case R_X64_TPOFF64: + return 8; + case R_AARCH64_ABS16: + case R_AARCH64_PREL16: + case R_RV_RVC_BRANCH: + case R_RV_RVC_JUMP: + return 2; + case R_RV_ADD8: + case R_RV_SUB8: + case R_RV_SUB6: + case R_RV_SET6: + case R_RV_SET8: + return 1; + default: + return 4; + } +} + +static InputMap jit_input_map_alloc(CfreeJit* jit, ObjBuilder* ob) { + InputMap m; + ObjSymIter* it; + ObjSymEntry e; + u32 nsyms = 0; + Heap* h = jit->image->heap; + memset(&m, 0, sizeof(m)); + it = obj_symiter_new(ob); + while (obj_symiter_next(it, &e)) ++nsyms; + obj_symiter_free(it); + m.nsym = nsyms + 1u; + m.sym = (LinkSymId*)h->alloc(h, sizeof(*m.sym) * m.nsym, + _Alignof(LinkSymId)); + m.nsection = obj_section_count(ob); + m.section = (LinkSectionId*)h->alloc(h, sizeof(*m.section) * m.nsection, + _Alignof(LinkSectionId)); + if (!m.sym || !m.section) + compiler_panic(jit->c, no_loc(), "cfree_jit_append_obj: oom on input map"); + memset(m.sym, 0, sizeof(*m.sym) * m.nsym); + memset(m.section, 0, sizeof(*m.section) * m.nsection); + return m; +} + +static void jit_invalidate_view(CfreeJit* jit) { + if (jit->view) { + cfree_obj_close(jit->view); + jit->view = NULL; + } + jit->view_built = 0u; +} + +static void jit_apply_one_reloc(CfreeJit* jit, const LinkRelocApply* r) { + LinkImage* img = jit->image; + const LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1); + u64 S; + u64 P; + u8* P_bytes; + if (reloc_is_tlsle(r->kind)) { + S = (tgt->vaddr - img->tls_vaddr) + AARCH64_TCB_SIZE; + } else if (tgt->kind == SK_ABS) { + S = tgt->vaddr; + } else { + S = (u64)vaddr_to_runtime(img, jit->segs, tgt->vaddr); + } + P = (u64)vaddr_to_runtime(img, jit->segs, r->write_vaddr); + P_bytes = (u8*)vaddr_to_write(img, jit->segs, r->write_vaddr); + if (!P_bytes) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: relocation site is unmapped"); + if (tgt->bind == SB_WEAK && tgt->kind == SK_ABS && tgt->vaddr == 0) { + if (r->kind == R_AARCH64_ADR_PREL_PG_HI21 || + r->kind == R_AARCH64_ADR_PREL_PG_HI21_NC) { + u32 instr = rd_u32_le(P_bytes); + u32 rd = instr & 0x1fu; + wr_u32_le(P_bytes, 0xd2800000u | rd); + return; + } + if (r->kind == R_AARCH64_ADD_ABS_LO12_NC) return; + } + if (r->kind == R_AARCH64_TLVP_LOAD_PAGEOFF12) { + u64 v = ((u64)S + (u64)r->addend) & 0xfffu; + u32 instr = rd_u32_le(P_bytes); + instr = 0x91000000u | (instr & 0x3ffu) | ((u32)v << 10); + wr_u32_le(P_bytes, instr); + return; + } + link_reloc_apply(jit->c, r->kind, P_bytes, S, r->addend, P); +} + +static void jit_append_obj_inner(CfreeJit* jit, ObjBuilder* ob) { + LinkImage* img = jit->image; + Heap* h = img->heap; + const CfreeExecMem* mem = require_execmem(jit->c); + u64 page = jit_page_size(jit->c); + u32 old_nsections = img->nsections; + u32 old_nsegments = img->nsegments; + u32 old_nsegs = jit->nsegs; + u32 old_nmaps = img->ninput_maps; + u32 old_dbg_n = img->dbg_objs_n; + u32 old_nrelocs = LinkRelocs_count(&img->relocs); + u64 old_cursor[SEG_NBUCKETS]; + InputMap m; + JitAppendSec* secs = NULL; + u32 nsecs = 0, sec_cap = 0; + u32 obj_sec_count; + u64 cursor[SEG_NBUCKETS]; + LinkInputId new_input_id; + u32 new_input_idx; + ObjSymIter* it; + ObjSymEntry e; + u32 i; + + for (i = 0; i < SEG_NBUCKETS; ++i) old_cursor[i] = jit->append_cursor[i]; + + if (!jit || !ob || !jit->linker || obj_compiler(ob) != jit->c) + compiler_panic(jit ? jit->c : NULL, no_loc(), + "cfree_jit_append_obj: object is not appendable"); + + /* Preflight duplicate strong definitions and unresolved references before + * mutating image-visible state. */ + it = obj_symiter_new(ob); + while (obj_symiter_next(it, &e)) { + const ObjSym* s = e.sym; + if (jit_obj_sym_is_spurious_undef(s)) continue; + if (jit_obj_sym_is_def(s) && s->name != 0 && + (s->bind == SB_GLOBAL || s->bind == SB_WEAK)) { + LinkSymId existing = symhash_get(&img->globals, s->name); + if (existing != LINK_SYM_NONE) { + LinkSymbol* prev = LinkSyms_at(&img->syms, existing - 1); + if (prev && prev->defined && + jit_bind_strength((u8)s->bind) == jit_bind_strength(SB_GLOBAL) && + jit_bind_strength(prev->bind) == jit_bind_strength(SB_GLOBAL)) { + size_t namelen; + const char* nm = pool_str(jit->c->global, s->name, &namelen); + obj_format_demangle_c(jit->c, &nm, &namelen); + obj_symiter_free(it); + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: duplicate global '%.*s'", + (int)namelen, nm); + } + } + } else if (!jit_obj_sym_is_def(s) && s->name != 0) { + LinkSymId hit = symhash_get(&img->globals, s->name); + int ok = 0; + if (hit != LINK_SYM_NONE) { + LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1); + if (def && def->defined) ok = 1; + } + if (!ok) { + ObjSymIter* it2 = obj_symiter_new(ob); + ObjSymEntry e2; + while (obj_symiter_next(it2, &e2)) { + const ObjSym* d = e2.sym; + if (d && d->name == s->name && jit_obj_sym_is_def(d) && + (d->bind == SB_GLOBAL || d->bind == SB_WEAK)) { + ok = 1; + break; + } + } + obj_symiter_free(it2); + } + if (!ok && jit->linker->resolver) { + size_t namelen; + const char* nm = pool_str(jit->c->global, s->name, &namelen); + (void)namelen; + if (jit->linker->resolver(jit->linker->resolver_user, nm)) ok = 1; + } + if (!ok && s->bind == SB_WEAK) ok = 1; + if (!ok) { + size_t nlen; + const char* nm = pool_str(jit->c->global, s->name, &nlen); + if (nm && nlen == 15u && memcmp(nm, "__tlv_bootstrap", 15u) == 0) + ok = 1; + if (!ok) { + obj_format_demangle_c(jit->c, &nm, &nlen); + obj_symiter_free(it); + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: undefined reference to '%.*s'", + (int)nlen, nm); + } + } + } + } + obj_symiter_free(it); + + m = jit_input_map_alloc(jit, ob); + obj_sec_count = obj_section_count(ob); + for (i = 0; i < SEG_NBUCKETS; ++i) cursor[i] = jit->append_cursor[i]; + + for (i = 1; i < obj_sec_count; ++i) { + const Section* s = obj_section_get(ob, (ObjSecId)i); + SegBucket b; + u64 size; + u64 vaddr; + if (!s || !link_section_kept(s)) continue; + size = (s->sem == SSEM_NOBITS) ? (u64)s->bss_size : (u64)s->bytes.total; + if (size == 0) continue; + b = link_bucket_for(s->flags); + vaddr = ALIGN_UP(cursor[b], (u64)(s->align ? s->align : 1u)); + if (vaddr + size > jit->append_limit[b]) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: append slack exhausted"); + if (nsecs == sec_cap) { + u32 ncap = sec_cap ? sec_cap * 2u : 8u; + JitAppendSec* ns = + (JitAppendSec*)h->realloc(h, secs, sizeof(*secs) * sec_cap, + sizeof(*secs) * ncap, + _Alignof(JitAppendSec)); + if (!ns) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: oom on section plan"); + secs = ns; + sec_cap = ncap; + } + secs[nsecs].obj_sec = (ObjSecId)i; + secs[nsecs].link_sec = (LinkSectionId)(old_nsections + nsecs + 1u); + secs[nsecs].link_seg = (LinkSegmentId)(old_nsegments + nsecs + 1u); + secs[nsecs].bucket = b; + secs[nsecs].vaddr = vaddr; + secs[nsecs].size = size; + secs[nsecs].file_size = (s->sem == SSEM_NOBITS) ? 0 : size; + secs[nsecs].align = s->align ? s->align : 1u; + secs[nsecs].flags = s->flags; + secs[nsecs].sem = s->sem; + secs[nsecs].name = s->name; + m.section[i] = secs[nsecs].link_sec; + cursor[b] = vaddr + size; + ++nsecs; + } + for (i = 0; i < SEG_NBUCKETS; ++i) jit->append_cursor[i] = cursor[i]; + + it = obj_symiter_new(ob); + while (obj_symiter_next(it, &e)) { + const ObjSym* s = e.sym; + int is_def; + if (e.id >= m.nsym || jit_obj_sym_is_spurious_undef(s)) continue; + is_def = jit_obj_sym_is_def(s); + if (is_def && (s->bind == SB_GLOBAL || s->bind == SB_WEAK) && + s->name != 0) { + LinkSymId existing = symhash_get(&img->globals, s->name); + if (existing != LINK_SYM_NONE) { + LinkSymbol* prev = LinkSyms_at(&img->syms, existing - 1); + if (prev && prev->defined && + jit_bind_strength((u8)s->bind) == jit_bind_strength(SB_GLOBAL) && + jit_bind_strength(prev->bind) == jit_bind_strength(SB_GLOBAL)) { + size_t namelen; + const char* nm = pool_str(jit->c->global, s->name, &namelen); + obj_format_demangle_c(jit->c, &nm, &namelen); + obj_symiter_free(it); + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: duplicate global '%.*s'", + (int)namelen, nm); + } + if (prev && prev->defined) { + m.sym[e.id] = existing; + continue; + } + } + } + m.sym[e.id] = (LinkSymId)(LinkSyms_count(&img->syms) + 1u); + { + LinkSymbol rec; + memset(&rec, 0, sizeof(rec)); + rec.name = s->name; + rec.input_id = (LinkInputId)(LinkInputs_count(&jit->linker->inputs) + 1u); + rec.obj_sym = e.id; + rec.section_id = LINK_SEC_NONE; + rec.value = s->value; + rec.size = s->size; + rec.common_align = (s->kind == SK_COMMON) ? (u32)s->common_align : 0u; + rec.bind = (u8)s->bind; + rec.kind = (u8)s->kind; + rec.defined = (u8)is_def; + if (is_def && s->section_id != OBJ_SEC_NONE && + s->section_id < m.nsection) { + rec.section_id = m.section[s->section_id]; + if (rec.section_id != LINK_SEC_NONE) { + u32 sj; + for (sj = 0; sj < nsecs; ++sj) { + if (secs[sj].link_sec == rec.section_id) { + rec.vaddr = secs[sj].vaddr + s->value; + break; + } + } + } + } else if (s->kind == SK_ABS) { + rec.vaddr = s->value; + } + m.sym[e.id] = link_append_symbol(img, &rec); + if (is_def && (s->bind == SB_GLOBAL || s->bind == SB_WEAK) && + s->name != 0) { + LinkSymId existing; + if (symhash_insert(&img->globals, s->name, m.sym[e.id], &existing)) { + } else { + m.sym[e.id] = existing; + } + } + } + } + obj_symiter_free(it); + + /* Resolve newly appended undefs before any bytes become executable. */ + for (i = 0; i < LinkSyms_count(&img->syms); ++i) { + LinkSymbol* s = LinkSyms_at(&img->syms, i); + if (s->input_id != (LinkInputId)(LinkInputs_count(&jit->linker->inputs) + 1u)) + continue; + if (s->defined) continue; + if (s->name != 0) { + LinkSymId hit = symhash_get(&img->globals, s->name); + if (hit != LINK_SYM_NONE && hit != s->id) { + LinkSymbol* def = LinkSyms_at(&img->syms, hit - 1); + if (def->defined) { + s->section_id = def->section_id; + s->value = def->value; + s->vaddr = def->vaddr; + s->kind = def->kind; + s->bind = def->bind; + s->defined = 1; + continue; + } + } + } + if (jit->linker->resolver && s->name != 0) { + size_t namelen; + const char* nm = pool_str(jit->c->global, s->name, &namelen); + void* p = jit->linker->resolver(jit->linker->resolver_user, nm); + (void)namelen; + if (p) { + s->kind = SK_ABS; + s->vaddr = (u64)(uintptr_t)p; + s->defined = 1; + continue; + } + } + if (s->bind == SB_WEAK) { + s->kind = SK_ABS; + s->vaddr = 0; + s->defined = 1; + continue; + } + if (s->name != 0) { + size_t nlen; + const char* nm = pool_str(jit->c->global, s->name, &nlen); + if (nm && nlen == 15u && memcmp(nm, "__tlv_bootstrap", 15u) == 0) { + s->kind = SK_ABS; + s->vaddr = 0; + s->defined = 1; + continue; + } + obj_format_demangle_c(jit->c, &nm, &nlen); + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: undefined reference to '%.*s'", + (int)nlen, nm); + } + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: undefined anonymous symbol"); + } + + new_input_id = link_add_obj(jit->linker, ob); + new_input_idx = new_input_id - 1u; + + { + InputMap* maps = (InputMap*)h->realloc( + h, img->input_maps, sizeof(*img->input_maps) * img->ninput_maps, + sizeof(*img->input_maps) * (new_input_idx + 1u), _Alignof(InputMap)); + if (!maps) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: oom growing input maps"); + img->input_maps = maps; + while (img->ninput_maps < new_input_idx) { + memset(&img->input_maps[img->ninput_maps], 0, sizeof(InputMap)); + img->ninput_maps++; + } + img->input_maps[new_input_idx] = m; + img->ninput_maps = new_input_idx + 1u; + } + + if (nsecs) { + LinkSection* nsections = (LinkSection*)h->realloc( + h, img->sections, sizeof(*img->sections) * old_nsections, + sizeof(*img->sections) * (old_nsections + nsecs), + _Alignof(LinkSection)); + LinkSegment* nsegments = (LinkSegment*)h->realloc( + h, img->segments, sizeof(*img->segments) * old_nsegments, + sizeof(*img->segments) * (old_nsegments + nsecs), + _Alignof(LinkSegment)); + u8** nsegbytes = (u8**)h->realloc( + h, img->segment_bytes, sizeof(*img->segment_bytes) * old_nsegments, + sizeof(*img->segment_bytes) * (old_nsegments + nsecs), _Alignof(u8*)); + size_t* nsegcaps = (size_t*)h->realloc( + h, img->segment_bytes_cap, + sizeof(*img->segment_bytes_cap) * old_nsegments, + sizeof(*img->segment_bytes_cap) * (old_nsegments + nsecs), + _Alignof(size_t)); + CfreeExecMemRegion* njsegs = (CfreeExecMemRegion*)h->realloc( + h, jit->segs, sizeof(*jit->segs) * old_nsegs, + sizeof(*jit->segs) * (old_nsegs + nsecs), + _Alignof(CfreeExecMemRegion)); + if (!nsections || !nsegments || !nsegbytes || !nsegcaps || !njsegs) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: oom growing image tables"); + img->sections = nsections; + img->segments = nsegments; + img->segment_bytes = nsegbytes; + img->segment_bytes_cap = nsegcaps; + jit->segs = njsegs; + } + + for (i = 0; i < nsecs; ++i) { + JitAppendSec* ps = &secs[i]; + LinkSection* ls = &img->sections[old_nsections + i]; + LinkSegment* seg = &img->segments[old_nsegments + i]; + CfreeExecMemRegion* js = &jit->segs[old_nsegs + i]; + const Section* os = obj_section_get(ob, ps->obj_sec); + u64 page_lo = ps->vaddr & ~(page - 1u); + u64 page_hi = ALIGN_UP(ps->vaddr + ps->size, page); + memset(ls, 0, sizeof(*ls)); + ls->id = ps->link_sec; + ls->input_id = new_input_id; + ls->obj_section_id = ps->obj_sec; + ls->segment_id = ps->link_seg; + ls->input_offset = 0; + ls->file_offset = ps->vaddr; + ls->vaddr = ps->vaddr; + ls->size = ps->size; + ls->flags = ps->flags; + ls->align = ps->align; + ls->name = ps->name; + ls->sem = ps->sem; + + memset(seg, 0, sizeof(*seg)); + seg->id = ps->link_seg; + seg->flags = SF_ALLOC; + if (ps->bucket == SEG_RX) seg->flags |= SF_EXEC; + if (ps->bucket == SEG_RW) seg->flags |= SF_WRITE; + if (ps->bucket == SEG_TLS) seg->flags |= SF_TLS; + seg->file_offset = ps->vaddr; + seg->vaddr = ps->vaddr; + seg->mem_size = ps->size; + seg->file_size = ps->file_size; + seg->align = (u32)page; + seg->nsections = 1; + img->segment_bytes[old_nsegments + i] = NULL; + img->segment_bytes_cap[old_nsegments + i] = 0; + + memset(js, 0, sizeof(*js)); + js->write = (u8*)jit->master.write + (uintptr_t)(page_lo - jit->image_base); + js->runtime = + (u8*)jit->master.runtime + (uintptr_t)(page_lo - jit->image_base); + js->size = (size_t)(page_hi - page_lo); + js->token = NULL; + + if (os && os->sem != SSEM_NOBITS && os->bytes.total) { + u8* dst = (u8*)js->write + (uintptr_t)(ps->vaddr - page_lo); + buf_flatten(&os->bytes, dst); + } + img->nsections++; + img->nsegments++; + jit->nsegs++; + } + + { + u32 total = obj_reloc_total(ob); + for (i = 0; i < total; ++i) { + const Reloc* r = obj_reloc_at(ob, i); + LinkRelocApply rec; + LinkSectionId ls_id; + const LinkSection* ls; + if (!r || r->section_id == OBJ_SEC_NONE || r->section_id >= m.nsection) + continue; + ls_id = m.section[r->section_id]; + if (ls_id == LINK_SEC_NONE) continue; + if (r->sym == OBJ_SYM_NONE || r->sym >= m.nsym || + m.sym[r->sym] == LINK_SYM_NONE) + continue; + ls = &img->sections[ls_id - 1]; + memset(&rec, 0, sizeof(rec)); + rec.input_id = new_input_id; + rec.section_id = r->section_id; + rec.link_section_id = ls_id; + rec.offset = r->offset; + rec.width = jit_reloc_width_local((RelocKind)r->kind); + rec.write_vaddr = ls->vaddr + r->offset; + rec.write_file_offset = rec.write_vaddr; + rec.kind = (RelocKind)r->kind; + rec.target = m.sym[r->sym]; + rec.addend = r->addend; + *link_append_reloc_slot(img) = rec; + } + } + + for (i = old_nrelocs; i < LinkRelocs_count(&img->relocs); ++i) { + const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i); + jit_apply_one_reloc(jit, r); + } + + for (i = old_nsegs; i < jit->nsegs; ++i) { + const LinkSegment* seg = &img->segments[i]; + if (mem->protect(mem->user, jit->segs[i].runtime, jit->segs[i].size, + perms_for(seg->flags)) != 0) { + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: execmem.protect failed"); + } + } + if (mem->flush_icache) { + for (i = old_nsegs; i < jit->nsegs; ++i) { + const LinkSegment* seg = &img->segments[i]; + if (seg->flags & SF_EXEC) + mem->flush_icache(mem->user, jit->segs[i].runtime, jit->segs[i].size); + } + } + + { + ObjBuilder** nd = (ObjBuilder**)h->realloc( + h, img->dbg_objs, sizeof(*img->dbg_objs) * old_dbg_n, + sizeof(*img->dbg_objs) * (new_input_idx + 1u), _Alignof(ObjBuilder*)); + u8* no = (u8*)h->realloc(h, img->dbg_objs_owned, + sizeof(*img->dbg_objs_owned) * old_dbg_n, + sizeof(*img->dbg_objs_owned) * (new_input_idx + 1u), + 1u); + if (!nd || !no) + compiler_panic(jit->c, no_loc(), + "cfree_jit_append_obj: oom growing debug inputs"); + img->dbg_objs = nd; + img->dbg_objs_owned = no; + while (img->dbg_objs_n < new_input_idx) { + img->dbg_objs[img->dbg_objs_n] = NULL; + img->dbg_objs_owned[img->dbg_objs_n] = 0u; + img->dbg_objs_n++; + } + img->dbg_objs[new_input_idx] = ob; + img->dbg_objs_owned[new_input_idx] = 0u; + img->dbg_objs_n = new_input_idx + 1u; + } + + jit_invalidate_view(jit); + jit->generation++; + if (secs) h->free(h, secs, sizeof(*secs) * sec_cap); + (void)old_cursor; + (void)old_nmaps; +} + +int cfree_jit_append_obj(CfreeJit* jit, CfreeObjBuilder* ob) { + PanicSave saved; + Compiler* c; + if (!jit || !ob) return 1; + c = jit->c; + compiler_panic_save(c, &saved); + if (setjmp(c->panic)) { + compiler_run_cleanups(c); + compiler_panic_restore(c, &saved); + return 1; + } + jit_append_obj_inner(jit, ob); + compiler_panic_restore(c, &saved); + return 0; +} + /* ---- inspector entries ---- */ /* True if `name` (NUL-terminated) is a debug section the DWARF consumer @@ -931,6 +1574,9 @@ int cfree_jit_addr_to_sym(CfreeJit* jit, uint64_t addr, const char** name_out, uint64_t* off_out) { u32 n; u32 i; + const LinkSymbol* best = NULL; + uint64_t best_base = 0; + uint64_t best_off = (uint64_t)-1; if (name_out) *name_out = NULL; if (off_out) *off_out = 0; if (!jit) return 1; @@ -943,11 +1589,20 @@ int cfree_jit_addr_to_sym(CfreeJit* jit, uint64_t addr, const char** name_out, base = jit_sym_runtime_addr(jit, s); if (!base) continue; if (addr >= base && (s->size == 0 || addr < base + s->size)) { - if (name_out) *name_out = jit_sym_name_cstr(jit, s); - if (off_out) *off_out = addr - base; - return 0; + uint64_t off = addr - base; + if (off < best_off || + (off == best_off && best && best->size == 0 && s->size != 0)) { + best = s; + best_base = base; + best_off = off; + } } } + if (best) { + if (name_out) *name_out = jit_sym_name_cstr(jit, best); + if (off_out) *off_out = addr - best_base; + return 0; + } return 1; }