kit

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

commit 40c583cf135d77d972c82cb70a94c771d536ecac
parent 4bcdfd87a185d107e9685f63bdee5a4968e63763
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  8 May 2026 17:19:42 -0700

driver: subcommand updates

Diffstat:
Mdriver/ar.c | 173+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mdriver/as.c | 17+++++++++--------
Mdriver/cc.c | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdriver/dbg.c | 917+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdriver/driver.h | 16++++++++++++++++
Mdriver/env.c | 29+++++++++++++++++++++++++++++
Mdriver/ld.c | 208++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mdriver/objdump.c | 8++++----
Mdriver/run.c | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
9 files changed, 1528 insertions(+), 291 deletions(-)

diff --git a/driver/ar.c b/driver/ar.c @@ -3,7 +3,14 @@ /* `cfree ar` — POSIX ar archive front-end. * * Supported operations (mutually exclusive): - * cfree ar {r|c|rc|cr} archive.a in.o... create / replace archive + * cfree ar r archive.a in.o... replace listed members in + * place; preserve unlisted; + * append new ones; warn when + * creating the archive. + * cfree ar c archive.a in.o... overwrite the archive with + * exactly the listed members. + * cfree ar {rc|cr} archive.a in.o... `r` semantics, but quiet on + * archive creation. * cfree ar t archive.a list member names * cfree ar x archive.a [members...] extract members to cwd * cfree ar p archive.a [members...] print members to stdout @@ -168,65 +175,158 @@ static int ar_do_print(DriverEnv* env, const char* archive_path, return rc; } +/* Write the archive at `archive_path` from the given members. + * + * Modes: + * has_r=0, has_c=1 — `c`: overwrite, no warning. + * has_r=1, has_c=0 — `r`: replace listed members in place; preserve + * unlisted; append new ones; warn "creating + * archive" when the archive does not yet exist. + * has_r=1, has_c=1 — `rc`/`cr`: same as `r` but suppress the create warning. + */ static int ar_do_write(DriverEnv* env, const char* archive_path, - int nmembers, char** member_paths) + int nmembers, char** member_paths, + int has_r, int has_c) { - uint32_t nm = nmembers > 0 ? (uint32_t)nmembers : 0u; + uint32_t nnew = nmembers > 0 ? (uint32_t)nmembers : 0u; CfreeBytesInput* members = NULL; - CfreeFileData* fds = NULL; + CfreeFileData* new_fds = NULL; + size_t members_cap = 0; + uint32_t nm = 0; /* count of entries in `members` */ CfreeEnv cenv = driver_env_to_cfree(env); CfreeWriter* out = NULL; CfreeArWriteOptions opts = {0}; + CfreeFileData old_fd = {0}; + int have_old = 0; int rc = 0; - int i; + uint32_t i; opts.epoch = ar_epoch_from_env(); opts.long_names = 1; - if (nm > 0) { - members = (CfreeBytesInput*)driver_alloc_zeroed( - env, (size_t)nm * sizeof(*members)); - fds = (CfreeFileData*)driver_alloc_zeroed( - env, (size_t)nm * sizeof(*fds)); - if (!members || !fds) { + /* `r`: read existing archive (if any) and seed `members` with it. */ + if (has_r) { + CfreeBytesInput input; + CfreeArIter it; + CfreeArMember m; + uint32_t nold = 0; + + if (cenv.file_io->read_all(cenv.file_io->user, archive_path, &old_fd)) { + have_old = 1; + input.name = archive_path; + input.data = old_fd.data; + input.len = old_fd.size; + if (!cfree_ar_iter_init(&it, &input)) { + driver_errf(AR_TOOL, "not an archive: %s", archive_path); + rc = 1; + goto done; + } + /* Count first so we can size the array exactly. */ + while (cfree_ar_iter_next(&it, &m)) nold++; + } else if (!has_c) { + /* POSIX: warn (not an error) when `r` creates a new archive. */ + driver_errf(AR_TOOL, "creating %s", archive_path); + } + + members_cap = (size_t)nold + (size_t)nnew; + if (members_cap > 0) { + members = (CfreeBytesInput*)driver_alloc_zeroed( + env, members_cap * sizeof(*members)); + if (!members) { + driver_errf(AR_TOOL, "out of memory"); + rc = 1; + goto done; + } + } + + if (have_old) { + cfree_ar_iter_init(&it, &input); + while (cfree_ar_iter_next(&it, &m) && nm < nold) { + members[nm].name = m.name; + members[nm].data = m.data; + members[nm].len = m.size; + nm++; + } + } + } else { + /* `c`: overwrite. */ + members_cap = (size_t)nnew; + if (members_cap > 0) { + members = (CfreeBytesInput*)driver_alloc_zeroed( + env, members_cap * sizeof(*members)); + if (!members) { + driver_errf(AR_TOOL, "out of memory"); + rc = 1; + goto done; + } + } + } + + if (nnew > 0) { + new_fds = (CfreeFileData*)driver_alloc_zeroed( + env, (size_t)nnew * sizeof(*new_fds)); + if (!new_fds) { driver_errf(AR_TOOL, "out of memory"); rc = 1; goto done; } - for (i = 0; i < (int)nm && rc == 0; ++i) { + for (i = 0; i < nnew; ++i) { const char* path = member_paths[i]; - if (!cenv.file_io->read_all(cenv.file_io->user, path, &fds[i])) { + const char* base = driver_basename(path); + if (!cenv.file_io->read_all(cenv.file_io->user, path, &new_fds[i])) { driver_errf(AR_TOOL, "failed to read: %s", path); rc = 1; - break; + goto done; + } + if (has_r) { + /* Replace existing slot if a member with the same basename + * is already present; otherwise append. */ + uint32_t j; + int replaced = 0; + for (j = 0; j < nm; ++j) { + if (driver_streq(members[j].name, base)) { + members[j].name = base; + members[j].data = new_fds[i].data; + members[j].len = new_fds[i].size; + replaced = 1; + break; + } + } + if (!replaced) { + members[nm].name = base; + members[nm].data = new_fds[i].data; + members[nm].len = new_fds[i].size; + nm++; + } + } else { + members[nm].name = base; + members[nm].data = new_fds[i].data; + members[nm].len = new_fds[i].size; + nm++; } - members[i].name = driver_basename(path); - members[i].data = fds[i].data; - members[i].len = fds[i].size; } } - if (rc == 0) { - out = cenv.file_io->open_writer(cenv.file_io->user, archive_path); - if (!out) { - driver_errf(AR_TOOL, "failed to open: %s", archive_path); - rc = 1; - } else { - rc = cfree_ar_write(out, members, nm, &opts); - if (rc == 0 && cfree_writer_error(out)) rc = 1; - cfree_writer_close(out); - } + out = cenv.file_io->open_writer(cenv.file_io->user, archive_path); + if (!out) { + driver_errf(AR_TOOL, "failed to open: %s", archive_path); + rc = 1; + } else { + rc = cfree_ar_write(out, members, nm, &opts); + if (rc == 0 && cfree_writer_error(out)) rc = 1; + cfree_writer_close(out); } done: - if (fds) { - for (i = 0; i < (int)nm; ++i) { - if (fds[i].data) - cenv.file_io->release(cenv.file_io->user, &fds[i]); + if (new_fds) { + for (i = 0; i < nnew; ++i) { + if (new_fds[i].data) + cenv.file_io->release(cenv.file_io->user, &new_fds[i]); } - driver_free(env, fds, (size_t)nm * sizeof(*fds)); + driver_free(env, new_fds, (size_t)nnew * sizeof(*new_fds)); } - if (members) driver_free(env, members, (size_t)nm * sizeof(*members)); + if (members) driver_free(env, members, members_cap * sizeof(*members)); + if (have_old) cenv.file_io->release(cenv.file_io->user, &old_fd); return rc; } @@ -239,6 +339,8 @@ int driver_ar(int argc, char** argv) int do_list = 0; int do_extract = 0; int do_print = 0; + int has_r = 0; + int has_c = 0; int i; int rc; @@ -252,7 +354,8 @@ int driver_ar(int argc, char** argv) for (i = 0; mode[i]; ++i) { switch (mode[i]) { - case 'r': case 'c': do_write = 1; break; + case 'r': do_write = 1; has_r = 1; break; + case 'c': do_write = 1; has_c = 1; break; case 't': do_list = 1; break; case 'x': do_extract = 1; break; case 'p': do_print = 1; break; @@ -292,7 +395,7 @@ int driver_ar(int argc, char** argv) } else if (do_print) { rc = ar_do_print(&env, archive_path, argc, argv, 3); } else { - rc = ar_do_write(&env, archive_path, argc - 3, argv + 3); + rc = ar_do_write(&env, archive_path, argc - 3, argv + 3, has_r, has_c); } driver_env_fini(&env); diff --git a/driver/as.c b/driver/as.c @@ -3,9 +3,9 @@ #include <stdint.h> /* `cfree as` — standalone assembler. Reads a single text source, writes a - * relocatable object via cfree_assemble_obj_emit. The accepted input is a - * GAS subset (AT&T syntax on x86); see <cfree.h> for the contract on - * CfreeAsmOptions. */ + * relocatable object via cfree_compile_obj_emit with the input tagged + * CFREE_LANG_ASM. The accepted input is a GAS subset (AT&T syntax on x86); + * see <cfree.h>. */ #define AS_TOOL "as" @@ -82,8 +82,8 @@ int driver_as(int argc, char** argv) CfreeWriter* writer = NULL; CfreeFileData src = {0}; CfreeBytesInput input; - CfreeAsmOptions aopts; - CfreeAsmOptions zero = {0}; + CfreeCompileOptions copts; + CfreeCompileOptions zero = {0}; int rc = 1; int loaded = 0; @@ -114,14 +114,15 @@ int driver_as(int argc, char** argv) goto out; } - aopts = zero; - aopts.debug_info = o.debug_info; + copts = zero; + copts.debug_info = o.debug_info; input.name = o.source; input.data = src.data; input.len = src.size; + input.lang = CFREE_LANG_ASM; - rc = cfree_assemble_obj_emit(compiler, &aopts, &input, writer); + rc = cfree_compile_obj_emit(compiler, &copts, &input, writer); out: if (compiler) cfree_compiler_free(compiler); diff --git a/driver/cc.c b/driver/cc.c @@ -108,6 +108,15 @@ typedef struct CcOptions { const char* dep_file; /* -MF */ const char** dep_targets; /* -MT/-MQ */ uint32_t ndep_targets; + + /* Shared-library link state — reachable via -shared and -Wl,... pass- + * throughs. cc forwards these to cfree_link_shared in the same way GCC + * forwards -Wl,-soname=... to ld. rpaths/runpaths alias argv. */ + int shared; /* -shared */ + const char* soname; /* -Wl,-soname=NAME */ + const char** rpaths; /* -Wl,-rpath=DIR (repeatable) */ + uint32_t nrpaths; + int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */ } CcOptions; static void cc_usage(void) @@ -144,13 +153,16 @@ static int cc_alloc_arrays(CcOptions* o, int argc) o->path_map = driver_alloc_zeroed(o->env, bound * sizeof(*o->path_map)); o->owned_path_map_olds = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_olds)); o->owned_path_map_old_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_old_sizes)); + o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths)); if (!o->source_files || !o->source_memory || !o->object_files || !o->archives || !o->owned_archives || !o->owned_archive_sizes || !o->lib_search_paths || !o->pending_libs || !o->dep_targets || - !o->path_map || !o->owned_path_map_olds || !o->owned_path_map_old_sizes) { + !o->path_map || !o->owned_path_map_olds || !o->owned_path_map_old_sizes || + !o->rpaths) { driver_errf(CC_TOOL, "out of memory"); return 1; } + o->new_dtags = 1; if (driver_cflags_init(&o->cf, o->env, argc) != 0) { driver_errf(CC_TOOL, "out of memory"); return 1; @@ -186,6 +198,63 @@ static void cc_options_release(CcOptions* o) driver_free(o->env, o->path_map, bound * sizeof(*o->path_map)); driver_free(o->env, o->owned_path_map_olds, bound * sizeof(*o->owned_path_map_olds)); driver_free(o->env, o->owned_path_map_old_sizes, bound * sizeof(*o->owned_path_map_old_sizes)); + driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths)); +} + +/* Parse a single GCC-style -Wl,X[,Y...] pass-through argument. Each comma- + * separated token is forwarded to the linker layer. We honour the ones + * that map onto cfree_link_shared / cfree_link_exe directly; unknown + * tokens are an error so the user notices when something doesn't go + * through. Returns 0 on success. */ +static int cc_record_wl(CcOptions* o, const char* arg) +{ + /* Walk comma-separated tokens. argv tokens are NUL-terminated; we + * stage each segment into a heap-owned buffer only when we need to + * keep it past this call. For the values we currently honour, the + * driver only stores pointers to caller-owned memory, so we copy each + * token into a fresh buffer interned in argv-bounded storage. */ + const char* p = arg; + while (*p) { + const char* tok = p; + size_t n = 0; + while (p[n] && p[n] != ',') ++n; + /* Move p past this token (and its trailing comma) for the next + * iteration; do this before we mutate anything so a `continue` + * still advances. */ + p = tok + n + (tok[n] == ',' ? 1 : 0); + + if (n >= 8 && driver_strneq(tok, "-soname=", 8)) { + char* buf; + size_t bufsz = n - 8 + 1; + buf = driver_alloc(o->env, bufsz); + if (!buf) { driver_errf(CC_TOOL, "out of memory"); return 1; } + driver_memcpy(buf, tok + 8, n - 8); + buf[n - 8] = '\0'; + /* The buffer leaks until process exit — cc options release does + * not currently track per-Wl allocations; this matches how + * -ffile-prefix-map handles short-lived strings. To keep this + * tidy across many -Wl,-soname=... entries (which would be + * unusual), we record only the latest. */ + o->soname = buf; + continue; + } + if (n >= 7 && driver_strneq(tok, "-rpath=", 7)) { + char* buf; + size_t bufsz = n - 7 + 1; + buf = driver_alloc(o->env, bufsz); + if (!buf) { driver_errf(CC_TOOL, "out of memory"); return 1; } + driver_memcpy(buf, tok + 7, n - 7); + buf[n - 7] = '\0'; + o->rpaths[o->nrpaths++] = buf; + continue; + } + if (n == 18 && driver_strneq(tok, "--enable-new-dtags", 18)) { o->new_dtags = 1; continue; } + if (n == 19 && driver_strneq(tok, "--disable-new-dtags", 19)) { o->new_dtags = 0; continue; } + + driver_errf(CC_TOOL, "unsupported -Wl, token: %.*s", (int)n, tok); + return 1; + } + return 0; } /* Suffix predicate: is `s` a recognized C source suffix? */ @@ -419,6 +488,16 @@ static int cc_parse(int argc, char** argv, CcOptions* o) if (driver_streq(a, "-pie")) { o->target.pic = CFREE_PIC_PIE; continue; } if (driver_streq(a, "-no-pie")){ o->target.pic = CFREE_PIC_NONE; continue; } + if (driver_streq(a, "-shared")) { + o->shared = 1; + if (o->target.pic == CFREE_PIC_NONE) o->target.pic = CFREE_PIC_PIC; + continue; + } + if (driver_strneq(a, "-Wl,", 4)) { + if (cc_record_wl(o, a + 4) != 0) return 1; + continue; + } + if (driver_strneq(a, "-mcmodel=", 9)) { if (cc_record_mcmodel(o, a + 9) != 0) return 1; continue; @@ -536,6 +615,15 @@ static int cc_parse(int argc, char** argv, CcOptions* o) driver_errf(CC_TOOL, "-c and -E are mutually exclusive"); return 1; } + if (o->shared && (o->compile_only || o->preprocess_only)) { + driver_errf(CC_TOOL, "-shared is incompatible with -c/-E"); + return 1; + } + if (!o->shared && (o->soname || o->nrpaths)) { + driver_errf(CC_TOOL, + "-Wl,-soname / -Wl,-rpath require -shared"); + return 1; + } if (o->compile_only) { if (total_sources != 1 || total_link != 0) { driver_errf(CC_TOOL, "-c requires exactly one C source and no .o/.a inputs"); @@ -959,8 +1047,11 @@ out: return rc; } -static int cc_run_compile_with_deps(DriverEnv* env, const CcOptions* o, - const CfreePpOptions* pp) +/* -c path: compile a single C source to a writer. If dep_mode is MD/MMD the + * compiler is held open after the emit so cc_dep_finish can walk the + * SourceManager edges; otherwise it returns immediately after a clean emit. */ +static int cc_run_compile_obj(DriverEnv* env, const CcOptions* o, + const CfreePpOptions* pp) { CfreeEnv cenv = driver_env_to_cfree(env); CfreeCompiler* compiler = NULL; @@ -985,7 +1076,9 @@ static int cc_run_compile_with_deps(DriverEnv* env, const CcOptions* o, cc_fill_compile_opts(o, pp, &copts); if (cfree_compile_obj_emit(compiler, &copts, &input, obj_w) != 0) goto out; - rc = cc_dep_finish(env, &cenv, compiler, o); + rc = (o->dep_mode == CC_DEP_MD || o->dep_mode == CC_DEP_MMD) + ? cc_dep_finish(env, &cenv, compiler, o) + : 0; out: if (compiler) cfree_compiler_free(compiler); @@ -994,41 +1087,164 @@ out: return rc; } -static void cc_to_cfree(const CcOptions* o, CfreeOptions* out) +/* exe path: compile every C source through a CfreePipeline, load .o/.a/script + * inputs through env.file_io, and call cfree_pipeline_link_exe. The pipeline + * owns the per-source CfreeObjBuilders for the lifetime of the link. */ +static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, + const CfreePpOptions* pp) { - CfreeOptions z = {0}; - *out = z; - out->target = o->target; - out->env = driver_env_to_cfree(o->env); - out->output_kind = o->compile_only ? CFREE_OUTPUT_OBJ : CFREE_OUTPUT_EXE; - out->opt_level = o->opt_level; - out->debug_info = o->debug_info; - out->output_path = o->output_path; - - out->source_files = o->source_files; - out->nsource_files = o->nsource_files; - out->source_memory = o->source_memory; - out->nsource_memory = o->nsource_memory; - - driver_cflags_fill_pp(&o->cf, &out->pp); - - out->object_files = o->object_files; - out->nobject_files = o->nobject_files; - out->archives = o->archives; - out->narchives = o->narchives; - - out->linker_script = o->linker_script; - out->entry = o->entry; - - out->epoch = o->epoch; - out->path_map = o->npath_map ? o->path_map : NULL; - out->npath_map = o->npath_map; - out->build_id_mode = o->build_id_mode; - out->build_id_bytes = o->build_id_bytes; - out->build_id_len = o->build_id_len; - - out->warnings_are_errors = o->warnings_are_errors; - out->max_errors = o->max_errors; + CfreeEnv cenv = driver_env_to_cfree(env); + const CfreeFileIO* io = cenv.file_io; + CfreePipeline* pipe = NULL; + CfreeWriter* out_w = NULL; + DriverLoad* src_lf = NULL; + DriverLoad* obj_lf = NULL; + DriverLoad* arch_lf = NULL; + DriverLoad script_lf = {0}; + CfreeBytesInput* src_in = NULL; + CfreeBytesInput* obj_in = NULL; + CfreeBytesInputArchive* arch_in = NULL; + CfreeObjBuilder** objs = NULL; + const CfreeLinkScript* script = NULL; + CfreeCompileOptions copts; + CfreeLinkOptions link_opts; + uint32_t nsrc = o->nsource_files + o->nsource_memory; + uint32_t i; + int rc = 1; + + if (!io || !io->read_all || !io->open_writer) { + driver_errf(CC_TOOL, "host file I/O unavailable"); + return 1; + } + + /* Allocate scratch for parallel arrays. */ + src_in = driver_alloc_zeroed(env, nsrc * sizeof(*src_in)); + objs = driver_alloc_zeroed(env, nsrc * sizeof(*objs)); + if (!src_in || !objs) { driver_errf(CC_TOOL, "out of memory"); goto out; } + if (o->nsource_files) { + src_lf = driver_alloc_zeroed(env, o->nsource_files * sizeof(*src_lf)); + if (!src_lf) { driver_errf(CC_TOOL, "out of memory"); goto out; } + } + if (o->nobject_files) { + obj_lf = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_lf)); + obj_in = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_in)); + if (!obj_lf || !obj_in) { driver_errf(CC_TOOL, "out of memory"); goto out; } + } + if (o->narchives) { + arch_lf = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_lf)); + arch_in = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_in)); + if (!arch_lf || !arch_in) { driver_errf(CC_TOOL, "out of memory"); goto out; } + } + + /* Stage source inputs: paths first (paths-then-memory matches the old + * cfree_run order so reproducibility is unchanged), then memory. */ + for (i = 0; i < o->nsource_files; ++i) { + if (driver_load_bytes(io, CC_TOOL, o->source_files[i], + &src_lf[i], &src_in[i]) != 0) goto out; + } + for (i = 0; i < o->nsource_memory; ++i) { + src_in[o->nsource_files + i] = o->source_memory[i]; + } + + /* Load object inputs and archives. */ + for (i = 0; i < o->nobject_files; ++i) { + if (driver_load_bytes(io, CC_TOOL, o->object_files[i], + &obj_lf[i], &obj_in[i]) != 0) goto out; + } + for (i = 0; i < o->narchives; ++i) { + if (driver_load_bytes(io, CC_TOOL, o->archives[i], + &arch_lf[i], &arch_in[i].input) != 0) goto out; + arch_in[i].link_mode = CFREE_LM_DEFAULT; + arch_in[i].whole_archive = 0; + arch_in[i].group_id = 0; + } + + /* Linker script. */ + if (o->linker_script) { + CfreeBytesInput dummy; + if (driver_load_bytes(io, CC_TOOL, o->linker_script, + &script_lf, &dummy) != 0) goto out; + } + + pipe = cfree_pipeline_new(o->target, &cenv); + if (!pipe) { driver_errf(CC_TOOL, "failed to initialize compiler"); goto out; } + + if (script_lf.loaded) { + if (cfree_link_script_parse(cfree_pipeline_compiler(pipe), + (const char*)script_lf.fd.data, + script_lf.fd.size, &script) != 0) goto out; + } + + /* Compile each TU in order. */ + cc_fill_compile_opts(o, pp, &copts); + for (i = 0; i < nsrc; ++i) { + if (cfree_pipeline_compile_obj(pipe, &copts, &src_in[i], &objs[i]) != 0) + goto out; + } + + out_w = io->open_writer(io->user, o->output_path); + if (!out_w) { + driver_errf(CC_TOOL, "failed to open output: %s", o->output_path); + goto out; + } + + { + CfreeLinkInputs zero = {0}; + CfreeLinkInputs inputs; + inputs = zero; + inputs.objs = objs; + inputs.nobjs = nsrc; + inputs.obj_bytes = obj_in; + inputs.nobj_bytes = o->nobject_files; + inputs.archives = arch_in; + inputs.narchives = o->narchives; + inputs.linker_script = script; + inputs.entry = o->entry; + inputs.build_id_mode = o->build_id_mode; + inputs.build_id_bytes = o->build_id_bytes; + inputs.build_id_len = o->build_id_len; + + if (o->shared) { + CfreeLinkSharedOptions z = {0}; + CfreeLinkSharedOptions s; + s = z; + s.inputs = inputs; + s.soname = o->soname; + if (o->new_dtags) { s.runpaths = o->rpaths; s.nrunpaths = o->nrpaths; } + else { s.rpaths = o->rpaths; s.nrpaths = o->nrpaths; } + s.allow_undefined = 1; + rc = cfree_pipeline_link_shared(pipe, &s, out_w); + } else { + CfreeLinkOptions z = {0}; + link_opts = z; + link_opts.inputs = inputs; + rc = cfree_pipeline_link_exe(pipe, &link_opts, out_w); + } + } + +out: + if (out_w) cfree_writer_close(out_w); + if (script && pipe) + cfree_link_script_free(cfree_pipeline_compiler(pipe), script); + if (pipe) cfree_pipeline_free(pipe); + driver_release_bytes(io, &script_lf); + if (arch_lf) { + for (i = 0; i < o->narchives; ++i) driver_release_bytes(io, &arch_lf[i]); + } + if (obj_lf) { + for (i = 0; i < o->nobject_files; ++i) driver_release_bytes(io, &obj_lf[i]); + } + if (src_lf) { + for (i = 0; i < o->nsource_files; ++i) driver_release_bytes(io, &src_lf[i]); + } + if (arch_in) driver_free(env, arch_in, o->narchives * sizeof(*arch_in)); + if (arch_lf) driver_free(env, arch_lf, o->narchives * sizeof(*arch_lf)); + if (obj_in) driver_free(env, obj_in, o->nobject_files * sizeof(*obj_in)); + if (obj_lf) driver_free(env, obj_lf, o->nobject_files * sizeof(*obj_lf)); + if (src_lf) driver_free(env, src_lf, o->nsource_files * sizeof(*src_lf)); + if (objs) driver_free(env, objs, nsrc * sizeof(*objs)); + if (src_in) driver_free(env, src_in, nsrc * sizeof(*src_in)); + return rc; } int driver_cc(int argc, char** argv) @@ -1036,7 +1252,6 @@ int driver_cc(int argc, char** argv) DriverEnv env; CcOptions co = {0}; CfreePpOptions pp; - CfreeOptions copts; int rc; driver_env_init(&env); @@ -1054,11 +1269,10 @@ int driver_cc(int argc, char** argv) rc = cc_preprocess(&env, &co, &pp); } else if (co.dep_mode == CC_DEP_M || co.dep_mode == CC_DEP_MM) { rc = cc_run_deps_only(&env, &co, &pp); - } else if (co.dep_mode == CC_DEP_MD || co.dep_mode == CC_DEP_MMD) { - rc = cc_run_compile_with_deps(&env, &co, &pp); + } else if (co.compile_only) { + rc = cc_run_compile_obj(&env, &co, &pp); } else { - cc_to_cfree(&co, &copts); - rc = cfree_run(&copts); + rc = cc_run_link_exe(&env, &co, &pp); } cc_options_release(&co); diff --git a/driver/dbg.c b/driver/dbg.c @@ -144,24 +144,73 @@ static void dbg_options_release(DbgOpts* o) driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); } -static void dbg_to_cfree(const DbgOpts* o, CfreeOptions* out, CfreeJit** out_jit) +/* Compile every C source through a pipeline owned by the caller and JIT-link + * the result. Pipeline ownership stays with the caller so DWARF lookups + * during the REPL session can run against the live pipeline compiler. */ +static int dbg_compile_and_jit(DriverEnv* env, const DbgOpts* o, + CfreePipeline* pipe, CfreeJit** out_jit) { - CfreeOptions z = {0}; - *out = z; - out->target = driver_host_target(); - out->env = driver_env_to_cfree(o->env); - out->output_kind = CFREE_OUTPUT_JIT; - out->opt_level = o->opt_level; - out->debug_info = o->debug_info; - - out->source_files = o->sources; - out->nsource_files = o->nsources; - - driver_cflags_fill_pp(&o->cf, &out->pp); - - out->entry = o->entry; - out->extern_resolver = driver_dlsym_resolver; - out->out_jit = out_jit; + CfreeEnv cenv = driver_env_to_cfree(env); + const CfreeFileIO* io = cenv.file_io; + DriverLoad* src_lf = NULL; + CfreeBytesInput* src_in = NULL; + CfreeObjBuilder** objs = NULL; + CfreeCompileOptions copts; + CfreeLinkOptions link_opts; + uint32_t i; + int rc = 1; + + if (!io || !io->read_all) { + driver_errf(DBG_TOOL, "host file I/O unavailable"); + return 1; + } + + src_in = driver_alloc_zeroed(env, o->nsources * sizeof(*src_in)); + src_lf = driver_alloc_zeroed(env, o->nsources * sizeof(*src_lf)); + objs = driver_alloc_zeroed(env, o->nsources * sizeof(*objs)); + if (!src_in || !src_lf || !objs) { + driver_errf(DBG_TOOL, "out of memory"); + goto out; + } + + for (i = 0; i < o->nsources; ++i) { + if (driver_load_bytes(io, DBG_TOOL, o->sources[i], + &src_lf[i], &src_in[i]) != 0) goto out; + } + + { + 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); + + for (i = 0; i < o->nsources; ++i) { + if (cfree_pipeline_compile_obj(pipe, &copts, &src_in[i], &objs[i]) != 0) + goto out; + } + + { + CfreeLinkOptions z = {0}; + link_opts = z; + } + link_opts.inputs.objs = objs; + link_opts.inputs.nobjs = o->nsources; + link_opts.inputs.entry = o->entry; + link_opts.inputs.extern_resolver = driver_dlsym_resolver; + link_opts.inputs.extern_resolver_user = NULL; + + rc = cfree_pipeline_link_jit(pipe, &link_opts, out_jit); + +out: + if (src_lf) { + for (i = 0; i < o->nsources; ++i) driver_release_bytes(io, &src_lf[i]); + driver_free(env, src_lf, o->nsources * sizeof(*src_lf)); + } + if (objs) driver_free(env, objs, o->nsources * sizeof(*objs)); + if (src_in) driver_free(env, src_in, o->nsources * sizeof(*src_in)); + return rc; } /* ============================================================ @@ -185,6 +234,8 @@ typedef struct Bp { char* spec; /* heap-owned NUL-terminated copy */ size_t spec_size; uint64_t addr; + uint64_t skip_count; /* silent skips before the first stop */ + uint64_t max_hits; /* 0 = unlimited */ uint32_t session_id; /* libcfree handle; 0 when disarmed */ } Bp; @@ -551,9 +602,12 @@ static void dbg_render_stop(DbgState* s, const CfreeStopInfo* st) * job. */ typedef enum DbgRunMode { - RUN_FRESH, /* r — _call from entry */ - RUN_CONTINUE, /* c — _resume(continue) */ - RUN_STEP, /* s — _resume(step) */ + RUN_FRESH, /* r — _call from entry */ + RUN_CONTINUE, /* c — _resume(continue) */ + RUN_STEP_INSN, /* s — _resume(step_insn) */ + RUN_STEP_LINE, /* sl — _resume(step_line) */ + RUN_NEXT_LINE, /* n — _resume(next_line) */ + RUN_STEP_OUT, /* finish — _resume(step_out) */ } DbgRunMode; static int dbg_drive(DbgState* s, DbgRunMode mode) @@ -580,8 +634,15 @@ static int dbg_drive(DbgState* s, DbgRunMode mode) s->prog_argc, s->prog_argv, &s->last_stop); } else { - CfreeResumeMode rm = (mode == RUN_STEP) - ? CFREE_RESUME_STEP_INSN : CFREE_RESUME_CONTINUE; + CfreeResumeMode rm = CFREE_RESUME_CONTINUE; + switch (mode) { + case RUN_STEP_INSN: rm = CFREE_RESUME_STEP_INSN; break; + case RUN_STEP_LINE: rm = CFREE_RESUME_STEP_LINE; break; + case RUN_NEXT_LINE: rm = CFREE_RESUME_NEXT_LINE; break; + case RUN_STEP_OUT: rm = CFREE_RESUME_STEP_OUT; break; + case RUN_CONTINUE: rm = CFREE_RESUME_CONTINUE; break; + case RUN_FRESH: break; /* unreachable */ + } rc = cfree_jit_session_resume(s->session, rm, &s->last_stop); } @@ -600,9 +661,24 @@ 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 CfreeDwarfType*, + const uint8_t*, size_t got, int depth); +static int dbg_read_value (DbgState*, const CfreeDwarfVarLoc*, + const CfreeUnwindFrame*, + 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); + /* ============================================================ * Backtrace - * ============================================================ */ + * ============================================================ + * Renders one line per frame: + * #N 0xPC <sym+off> in func (arg1=val1, arg2=val2) at file:line + * Parameter rendering uses cfree_dwarf_param_iter_* against the frame's + * PC and the unwound register snapshot. Inlined frames are flagged. */ static void dbg_cmd_bt(DbgState* s) { @@ -620,17 +696,65 @@ static void dbg_cmd_bt(DbgState* s) frame = s->last_stop.regs; for (;;) { - const char* name = NULL; - uint64_t lo = 0; - uint64_t hi = 0; - int step; + CfreeDwarfSubprogram sp; + int have_sp; + int step; driver_printf("#%-2d ", level); - dbg_print_pc(s, frame.pc); - if (cfree_dwarf_func_at(s->dwarf, frame.pc, &name, &lo, &hi) == 0 - && name) + driver_printf("0x%llx", (unsigned long long)frame.pc); + { - driver_printf(" in %s", name); + const char* sym = NULL; + uint64_t off = 0; + if (cfree_jit_addr_to_sym(s->jit, frame.pc, &sym, &off) == 0 + && sym) { + if (off) driver_printf(" <%s+0x%llx>", sym, + (unsigned long long)off); + else driver_printf(" <%s>", sym); + } + } + + have_sp = (cfree_dwarf_subprogram_at(s->dwarf, 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); + if (it) { + while (cfree_dwarf_param_iter_next(it, &p)) { + uint8_t stack_buf[64]; + uint8_t* buf; + size_t alloc; + size_t got; + if (!first) driver_printf(", "); + driver_printf("%s=", p.name ? p.name : "?"); + 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 { + driver_printf("?"); + } + first = 0; + } + cfree_dwarf_param_iter_free(it); + } + driver_printf(")"); + } + + { + 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 + && file) { + driver_printf(" at %s:%u", file, line); + if (col) driver_printf(":%u", col); + } } driver_printf("\n"); @@ -648,20 +772,238 @@ static void dbg_cmd_bt(DbgState* s) } /* ============================================================ - * `p name` + * Type-aware value printer * ============================================================ - * Source-language print. Uses cfree_dwarf_var_at to find the variable - * (locals win over globals at the current PC), cfree_dwarf_loc_read to - * pull the bytes from the worker's address space, and prints up to 8 - * bytes as a hex value (the bulk of v0 use is scalar inspection). - * Globals not in DWARF fall back to cfree_jit_lookup, which gives an - * address but no type info — for that case we print just the address. */ + * Walks a CfreeDwarfType 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(" "); +} + +static void dbg_print_value(DbgState* s, const CfreeDwarfType* type, + const uint8_t* buf, size_t got, int depth) +{ + CfreeDwarfTypeInfo 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 = cfree_dwarf_type_info(type); + switch (ti.kind) { + case CFREE_DT_VOID: + driver_printf("void"); + return; + case CFREE_DT_SINT: + case CFREE_DT_CHAR: + driver_printf("%lld", (long long)dbg_load_le_s(buf, got)); + return; + case CFREE_DT_UINT: + case CFREE_DT_BOOL: + driver_printf("%llu", (unsigned long long)dbg_load_le_u(buf, got)); + return; + case CFREE_DT_PTR: + driver_printf("0x%llx", (unsigned long long)dbg_load_le_u(buf, got)); + return; + case CFREE_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 CFREE_DT_ENUM: { + int64_t v = dbg_load_le_s(buf, got); + CfreeDwarfEnumIter* it = cfree_dwarf_enum_iter_new(s->dwarf, type); + CfreeDwarfEnumVal ev; + const char* match = NULL; + if (it) { + while (cfree_dwarf_enum_iter_next(it, &ev)) { + if (ev.value == v) { match = ev.name; break; } + } + cfree_dwarf_enum_iter_free(it); + } + if (match) driver_printf("%s (%lld)", match, (long long)v); + else driver_printf("%lld", (long long)v); + return; + } + case CFREE_DT_TYPEDEF: + dbg_print_value(s, ti.inner, buf, got, depth); + return; + case CFREE_DT_ARRAY: { + uint32_t n = ti.element_count; + size_t esz = 0; + uint32_t i; + if (ti.inner) { + CfreeDwarfTypeInfo ein = cfree_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 CFREE_DT_STRUCT: + case CFREE_DT_UNION: { + CfreeDwarfFieldIter* it = cfree_dwarf_field_iter_new(s->dwarf, type); + CfreeDwarfField f; + driver_printf("{\n"); + if (it) { + while (cfree_dwarf_field_iter_next(it, &f)) { + size_t fsz = 0; + dbg_indent(depth + 1); + driver_printf(".%s = ", (f.name && *f.name) ? f.name : "<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) { + CfreeDwarfTypeInfo fti = cfree_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"); + } + cfree_dwarf_field_iter_free(it); + } + dbg_indent(depth); + driver_printf("}"); + return; + } + case CFREE_DT_FUNC: + driver_printf("<function@0x%llx>", + (unsigned long long)dbg_load_le_u(buf, got)); + return; + } + driver_printf("<?>"); +} + +/* 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 CfreeDwarfVarLoc* loc, + const CfreeUnwindFrame* 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 (cfree_dwarf_loc_read(s->dwarf, loc, frame, s->session, + buf, cap, &got) != 0) { + 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) { CfreeDwarfVarLoc loc; - uint8_t buf[16]; - size_t got = 0; + uint8_t stack_buf[64]; + uint8_t* buf; + size_t alloc; + size_t got; if (!s->has_stop) { driver_errf(DBG_TOOL, "no program is stopped"); @@ -671,31 +1013,16 @@ 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) { - if (cfree_dwarf_loc_read(s->dwarf, &loc, &s->last_stop.regs, - s->session, buf, sizeof(buf), &got) != 0) - { + 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); return; } - if (got == 0) { - driver_printf("%s = <empty>\n", name); - return; - } - /* Print up to 8 bytes as a single little-endian unsigned (matches - * DWARF base-encoding for integer scalars); larger reads dump as - * a hex byte sequence. */ - if (got <= 8) { - uint64_t v = 0; - size_t i; - for (i = 0; i < got; ++i) v |= ((uint64_t)buf[i]) << (8 * i); - driver_printf("%s = 0x%llx (%llu)\n", name, - (unsigned long long)v, (unsigned long long)v); - } else { - size_t i; - driver_printf("%s =", name); - for (i = 0; i < got; ++i) driver_printf(" %02x", buf[i]); - driver_printf("\n"); - } + driver_printf("%s = ", name); + dbg_print_value(s, loc.type, buf, got, 0); + driver_printf("\n"); + dbg_release_value_buf(s, buf, alloc); return; } @@ -714,6 +1041,213 @@ static void dbg_cmd_print(DbgState* s, const char* name) } /* ============================================================ + * `set NAME VALUE` + * ============================================================ + * Writes a 64-bit value into a variable. Routes to write_mem for + * frame-relative and global locations, set_regs for register-resident + * variables. v1 supports integer/pointer scalars only — float and + * aggregate writes are out of scope. */ + +static void dbg_cmd_set(DbgState* s, const char* name, uint64_t value) +{ + CfreeDwarfVarLoc loc; + uint8_t buf[8]; + size_t sz; + size_t i; + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + if (!s->dwarf + || cfree_dwarf_var_at(s->dwarf, s->last_stop.regs.pc, name, &loc) != 0) + { + driver_errf(DBG_TOOL, "no variable named '%s'", name); + return; + } + + 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 CFREE_DLOC_FRAME_OFS: { + uint64_t addr = s->last_stop.regs.cfa + + (uint64_t)(int64_t)loc.v.frame_ofs; + if (cfree_jit_session_write_mem(s->session, addr, buf, sz) != 0) { + driver_errf(DBG_TOOL, "memory write failed"); + } + return; + } + case CFREE_DLOC_GLOBAL: + if (cfree_jit_session_write_mem(s->session, loc.v.global, buf, sz) != 0) { + driver_errf(DBG_TOOL, "memory write failed"); + } + return; + case CFREE_DLOC_REG: { + CfreeUnwindFrame fr = s->last_stop.regs; + if (loc.v.reg >= 32) { + driver_errf(DBG_TOOL, + "register %u outside the snapshot range", loc.v.reg); + return; + } + fr.regs[loc.v.reg] = value; + if (cfree_jit_session_set_regs(s->session, &fr) != 0) { + driver_errf(DBG_TOOL, "register write failed"); + return; + } + s->last_stop.regs = fr; + return; + } + case CFREE_DLOC_EXPR: + driver_errf(DBG_TOOL, + "cannot set '%s': location is a DWARF expression", name); + return; + } +} + +/* ============================================================ + * `jump ADDR` + * ============================================================ + * Move PC without resuming. The session validates that the new PC lies + * within the JIT image. */ + +static void dbg_cmd_jump(DbgState* s, uint64_t pc) +{ + CfreeUnwindFrame fr; + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + fr = s->last_stop.regs; + fr.pc = pc; + if (cfree_jit_session_set_regs(s->session, &fr) != 0) { + driver_errf(DBG_TOOL, "jump failed (pc 0x%llx outside image?)", + (unsigned long long)pc); + return; + } + s->last_stop.regs = fr; + driver_printf("PC set to 0x%llx\n", (unsigned long long)pc); +} + +/* ============================================================ + * `info locals` / `info args` / `info reg` + * ============================================================ */ + +static void dbg_cmd_info_vars(DbgState* s, uint32_t mask, const char* label) +{ + CfreeDwarfVarIter* it; + CfreeDwarfVar v; + int printed = 0; + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + if (!s->dwarf) { + driver_errf(DBG_TOOL, "no DWARF: %s unavailable", label); + return; + } + + it = cfree_dwarf_vars_at_new(s->dwarf, s->last_stop.regs.pc, mask); + if (!it) { + driver_printf("No %s.\n", label); + return; + } + while (cfree_dwarf_vars_at_next(it, &v)) { + uint8_t stack_buf[64]; + uint8_t* buf; + size_t alloc; + size_t got; + printed = 1; + 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); + continue; + } + driver_printf(" %s = ", v.name); + dbg_print_value(s, v.loc.type, buf, got, 1); + driver_printf("\n"); + dbg_release_value_buf(s, buf, alloc); + } + cfree_dwarf_vars_at_free(it); + if (!printed) driver_printf("No %s.\n", label); +} + +static void dbg_cmd_info_reg(DbgState* s) +{ + CfreeArchKind arch = driver_host_target().arch; + CfreeArchRegIter* it; + CfreeArchReg r; + + if (!s->has_stop) { + driver_errf(DBG_TOOL, "no program is stopped"); + return; + } + it = cfree_arch_reg_iter_new(arch); + if (!it) { + driver_errf(DBG_TOOL, "no register table for this arch"); + return; + } + driver_printf("pc 0x%016llx\n", + (unsigned long long)s->last_stop.regs.pc); + driver_printf("cfa 0x%016llx\n", + (unsigned long long)s->last_stop.regs.cfa); + while (cfree_arch_reg_iter_next(it, &r)) { + if (r.dwarf_idx >= 32) continue; /* outside CfreeUnwindFrame.regs */ + driver_printf("%-6s 0x%016llx\n", r.name, + (unsigned long long)s->last_stop.regs.regs[r.dwarf_idx]); + } + cfree_arch_reg_iter_free(it); +} + +/* ============================================================ + * `info functions [PATTERN]` / `info variables [PATTERN]` + * ============================================================ */ + +/* Tiny glob matcher: '*' matches any run, '?' matches any single byte. + * NULL pattern matches every name. */ +static int dbg_glob(const char* pat, const char* s) +{ + if (!pat) return 1; + while (*pat && *s) { + if (*pat == '*') { + if (pat[1] == '\0') return 1; + while (*s) { + if (dbg_glob(pat + 1, s)) return 1; + ++s; + } + return dbg_glob(pat + 1, s); + } + if (*pat != '?' && *pat != *s) return 0; + ++pat; ++s; + } + while (*pat == '*') ++pat; + return *pat == '\0' && *s == '\0'; +} + +static void dbg_cmd_info_syms(DbgState* s, CfreeSymKind want, const char* pattern) +{ + CfreeJitSymIter* it = cfree_jit_sym_iter_new(s->jit); + CfreeJitSym sym; + int printed = 0; + + if (!it) { + driver_errf(DBG_TOOL, "symbol enumeration unavailable"); + return; + } + while (cfree_jit_sym_iter_next(it, &sym)) { + if (sym.kind != want) continue; + if (pattern && !dbg_glob(pattern, sym.name)) continue; + driver_printf("0x%016llx %s\n", + (unsigned long long)sym.addr, sym.name); + printed = 1; + } + cfree_jit_sym_iter_free(it); + if (!printed) driver_printf("(none)\n"); +} + +/* ============================================================ * `x ADDR [count]` * ============================================================ * Examine memory: reads `count` bytes (default 16) at `addr` from the @@ -750,9 +1284,31 @@ static void dbg_cmd_examine(DbgState* s, uint64_t addr, size_t count) } /* ============================================================ - * `info b`, `b LOC`, `d N`, `enable N` / `disable N` + * `info b`, `b LOC`, `d N`, `enable N` / `disable N`, `ignore N COUNT` * ============================================================ */ +/* Arm a Bp through the appropriate session entry. The plain breakpoint_set + * is used when no skip/cap is in effect; otherwise the spec form. The + * driver does not yet wire conditions, but the spec callback slot is left + * NULL so this remains forward-compatible. */ +static int dbg_bp_arm(DbgState* s, Bp* b) +{ + if (b->skip_count == 0 && b->max_hits == 0) { + return cfree_jit_session_breakpoint_set(s->session, b->addr, + &b->session_id); + } + { + CfreeBreakpointSpec spec; + CfreeBreakpointSpec z = {0}; + spec = z; + spec.addr = b->addr; + spec.skip_count = b->skip_count; + spec.max_hits = b->max_hits; + return cfree_jit_session_breakpoint_set_spec(s->session, &spec, + &b->session_id); + } +} + static void dbg_cmd_break(DbgState* s, const char* spec) { BpKind kind; @@ -782,12 +1338,11 @@ static void dbg_cmd_break(DbgState* s, const char* spec) return; } - if (cfree_jit_session_breakpoint_set(s->session, addr, &b->session_id) != 0) { + if (dbg_bp_arm(s, b) != 0) { driver_errf(DBG_TOOL, - "failed to arm breakpoint at 0x%llx — " - "JIT session implementation pending", + "failed to arm breakpoint at 0x%llx", (unsigned long long)addr); - b->session_id = 0; /* surface as disarmed in `info b` */ + b->session_id = 0; b->enabled = 0; } @@ -804,18 +1359,42 @@ static void dbg_cmd_info_b(DbgState* s) driver_printf("No breakpoints.\n"); return; } - driver_printf("Num Enb Address Spec\n"); + driver_printf("Num Enb Address Skip Max Spec\n"); for (i = 0; i < s->nbps; ++i) { Bp* b = &s->bps[i]; - driver_printf("%-4d %-4s 0x%-18llx %s%s\n", + driver_printf("%-4d %-4s 0x%-18llx %-6llu %-6llu %s%s\n", b->id, b->enabled ? "y" : "n", (unsigned long long)b->addr, + (unsigned long long)b->skip_count, + (unsigned long long)b->max_hits, b->spec ? b->spec : "", b->session_id ? "" : " [disarmed]"); } } +static void dbg_cmd_ignore(DbgState* s, int id, uint64_t count) +{ + Bp* b = dbg_bp_find(s, id); + if (!b) { + driver_errf(DBG_TOOL, "no breakpoint %d", id); + return; + } + if (b->session_id) { + cfree_jit_session_breakpoint_clear(s->session, b->session_id); + b->session_id = 0; + } + b->skip_count = count; + if (b->enabled) { + if (dbg_bp_arm(s, b) != 0) { + driver_errf(DBG_TOOL, "failed to re-arm breakpoint %d", id); + return; + } + } + driver_printf("Breakpoint %d will skip the next %llu hits\n", + id, (unsigned long long)count); +} + static void dbg_cmd_delete(DbgState* s, int id) { if (dbg_bp_remove(s, id) != 0) { @@ -831,9 +1410,7 @@ static void dbg_cmd_set_enabled(DbgState* s, int id, int enable) return; } if (enable && !b->enabled) { - if (cfree_jit_session_breakpoint_set(s->session, b->addr, - &b->session_id) != 0) - { + if (dbg_bp_arm(s, b) != 0) { driver_errf(DBG_TOOL, "failed to re-arm breakpoint %d", id); return; } @@ -860,15 +1437,25 @@ static void dbg_cmd_help(void) " r, run start fresh execution at entry\n" " c, cont continue after a stop\n" " s, step single-step one instruction\n" - " n, next step to next source line\n" - " bt, backtrace print stack trace\n" + " 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" + " jump ADDR set PC to ADDR (no resume)\n" + " bt, backtrace print stack trace with arguments\n" " b LOC set breakpoint at LOC:\n" " 0xADDR | sym[+off] | file.c:line\n" - " info b list breakpoints\n" + " ignore N COUNT skip the next COUNT hits of bp N\n" " d N, delete N delete breakpoint N\n" " enable N | disable N toggle breakpoint N\n" " p NAME print variable / global\n" - " x ADDR [count] examine memory (count bytes, default 16)\n"); + " set NAME VALUE write VALUE into NAME\n" + " x ADDR [count] examine memory (count bytes, default 16)\n" + " info b list breakpoints\n" + " info reg, info registers dump registers\n" + " info locals list locals at current PC\n" + " info args list args at current PC\n" + " info functions [PATTERN] list JIT functions matching PATTERN\n" + " info variables [PATTERN] list JIT globals matching PATTERN\n"); } /* ============================================================ @@ -915,44 +1502,23 @@ static int dbg_dispatch(DbgState* s, char* line) dbg_drive(s, RUN_CONTINUE); return 0; } - if (driver_streq(cmd, "s") || driver_streq(cmd, "step") - || driver_streq(cmd, "n") || driver_streq(cmd, "next")) - { - /* Both step (instruction) and next (source line). The session - * exposes step-instruction directly; "next" loops single-step - * until the source line changes, falling back gracefully when - * no DWARF is present. */ - if ((driver_streq(cmd, "n") || driver_streq(cmd, "next")) && s->dwarf) { - const char* start_file = NULL; - uint32_t start_line = 0; - uint32_t start_col = 0; - int budget = 4096; /* avoid infinite loops */ - - if (!s->has_stop) { - driver_errf(DBG_TOOL, "no program is stopped"); - return 0; - } - cfree_dwarf_addr_to_line(s->dwarf, s->last_stop.regs.pc, - &start_file, &start_line, &start_col); - for (;;) { - const char* f = NULL; - uint32_t l = 0; - uint32_t c = 0; - if (dbg_drive(s, RUN_STEP) != 0) return 0; - if (s->last_stop.kind != CFREE_STOP_BREAKPOINT - && s->last_stop.kind != CFREE_STOP_SIGNAL) return 0; - cfree_dwarf_addr_to_line(s->dwarf, s->last_stop.regs.pc, - &f, &l, &c); - if (!start_file || !f) break; /* nothing to compare */ - if (l != start_line || !driver_streq(f, start_file)) break; - if (--budget == 0) { - driver_errf(DBG_TOOL, "step-line budget exhausted"); - return 0; - } - } - } else { - dbg_drive(s, RUN_STEP); - } + if (driver_streq(cmd, "s") || driver_streq(cmd, "step")) { + /* Single instruction step. GDB's `s` (step-into-source-line) is + * spelled `sl` here so this command keeps its historical + * "instruction step" meaning. */ + dbg_drive(s, RUN_STEP_INSN); + return 0; + } + if (driver_streq(cmd, "sl")) { + dbg_drive(s, RUN_STEP_LINE); + return 0; + } + if (driver_streq(cmd, "n") || driver_streq(cmd, "next")) { + dbg_drive(s, RUN_NEXT_LINE); + return 0; + } + if (driver_streq(cmd, "finish")) { + dbg_drive(s, RUN_STEP_OUT); return 0; } if (driver_streq(cmd, "bt") || driver_streq(cmd, "backtrace") @@ -969,16 +1535,97 @@ static int dbg_dispatch(DbgState* s, char* line) } if (driver_streq(cmd, "info")) { char* what; - dbg_take_word(rest, &what); + rest = dbg_take_word(rest, &what); if (driver_streq(what, "b") || driver_streq(what, "break") || driver_streq(what, "breakpoints")) { dbg_cmd_info_b(s); + } else if (driver_streq(what, "reg") || driver_streq(what, "registers")) { + dbg_cmd_info_reg(s); + } else if (driver_streq(what, "locals")) { + dbg_cmd_info_vars(s, 1u << CFREE_DVR_LOCAL, "locals"); + } else if (driver_streq(what, "args")) { + dbg_cmd_info_vars(s, 1u << CFREE_DVR_ARG, "args"); + } else if (driver_streq(what, "functions") || driver_streq(what, "func")) { + char* pat; + dbg_take_word(rest, &pat); + dbg_cmd_info_syms(s, CFREE_SK_FUNC, *pat ? pat : NULL); + } else if (driver_streq(what, "variables") || driver_streq(what, "var")) { + char* pat; + dbg_take_word(rest, &pat); + dbg_cmd_info_syms(s, CFREE_SK_OBJ, *pat ? pat : NULL); } else { driver_errf(DBG_TOOL, "unknown 'info' subcommand: %s", what); } return 0; } + if (driver_streq(cmd, "set")) { + char* name; + char* val_s; + uint64_t v; + size_t used; + rest = dbg_take_word(rest, &name); + if (!*name) { driver_errf(DBG_TOOL, "usage: set NAME VALUE"); return 0; } + /* Accept an optional `=` between name and value: `set x = 5`. */ + if (driver_streq(name, "=")) { + driver_errf(DBG_TOOL, "usage: set NAME VALUE"); + return 0; + } + rest = dbg_take_word(rest, &val_s); + if (driver_streq(val_s, "=")) { + rest = dbg_take_word(rest, &val_s); + } + if (!*val_s) { + driver_errf(DBG_TOOL, "usage: set NAME VALUE"); + return 0; + } + used = dbg_parse_uint(val_s, &v); + if (!used || val_s[used] != '\0') { + driver_errf(DBG_TOOL, "expected integer value, got '%s'", val_s); + return 0; + } + dbg_cmd_set(s, name, v); + return 0; + } + if (driver_streq(cmd, "jump") || driver_streq(cmd, "j")) { + char* addr_s; + uint64_t addr; + size_t used; + dbg_take_word(rest, &addr_s); + if (!*addr_s) { driver_errf(DBG_TOOL, "usage: jump ADDR"); return 0; } + used = dbg_parse_uint(addr_s, &addr); + if (!used || addr_s[used] != '\0') { + driver_errf(DBG_TOOL, "bad address '%s'", addr_s); + return 0; + } + dbg_cmd_jump(s, addr); + return 0; + } + if (driver_streq(cmd, "ignore")) { + char* id_s; + char* cnt_s; + uint64_t id; + uint64_t cnt; + size_t used; + rest = dbg_take_word(rest, &id_s); + dbg_take_word(rest, &cnt_s); + if (!*id_s || !*cnt_s) { + driver_errf(DBG_TOOL, "usage: ignore N COUNT"); + return 0; + } + used = dbg_parse_uint(id_s, &id); + if (!used || id_s[used] != '\0') { + driver_errf(DBG_TOOL, "expected breakpoint id"); + return 0; + } + used = dbg_parse_uint(cnt_s, &cnt); + if (!used || cnt_s[used] != '\0') { + driver_errf(DBG_TOOL, "expected hit count"); + return 0; + } + dbg_cmd_ignore(s, (int)id, cnt); + return 0; + } if (driver_streq(cmd, "d") || driver_streq(cmd, "delete")) { char* arg; uint64_t id; @@ -1086,12 +1733,14 @@ static void dbg_repl(DbgState* s) int driver_dbg(int argc, char** argv) { - DriverEnv env; - DbgOpts o = {0}; - CfreeOptions copts; - CfreeJit* jit = NULL; - DbgState st = {0}; - int rc; + DriverEnv env; + DbgOpts o = {0}; + CfreePipeline* pipe = NULL; + CfreeJit* jit = NULL; + DbgState st = {0}; + CfreeEnv cenv; + CfreeTarget target; + int rc; driver_env_init(&env); o.env = &env; @@ -1102,9 +1751,19 @@ int driver_dbg(int argc, char** argv) return 2; } - dbg_to_cfree(&o, &copts, &jit); - rc = cfree_run(&copts); + cenv = driver_env_to_cfree(&env); + target = driver_host_target(); + pipe = cfree_pipeline_new(target, &cenv); + if (!pipe) { + driver_errf(DBG_TOOL, "failed to initialize compiler"); + dbg_options_release(&o); + driver_env_fini(&env); + return 1; + } + + rc = dbg_compile_and_jit(&env, &o, pipe, &jit); if (rc != 0) { + cfree_pipeline_free(pipe); dbg_options_release(&o); driver_env_fini(&env); return rc; @@ -1119,6 +1778,7 @@ int driver_dbg(int argc, char** argv) if (!st.entry_addr) { driver_errf(DBG_TOOL, "entry symbol not found: %s", o.entry); cfree_jit_free(jit); + cfree_pipeline_free(pipe); dbg_options_release(&o); driver_env_fini(&env); return 1; @@ -1130,18 +1790,12 @@ int driver_dbg(int argc, char** argv) "JIT session not yet implemented in libcfree — " "REPL will start in degraded mode (commands will surface " "'session implementation pending' until the lib lands)"); - /* We deliberately keep going so the surrounding driver code path - * is exercised end-to-end during development. */ + /* Keep going so the surrounding driver path is exercised. */ } st.view = cfree_jit_view(jit); if (st.view) { - /* DWARF needs a CfreeCompiler to allocate from. We don't have - * the compiler handle from cfree_run, so DWARF stays NULL until - * cfree_run grows an out-compiler param (or the dbg moves to - * driving the compile pipeline directly). When that lands, the - * call here is `cfree_dwarf_open(compiler, st.view)`. */ - st.dwarf = NULL; + st.dwarf = cfree_dwarf_open(cfree_pipeline_compiler(pipe), st.view); } dbg_repl(&st); @@ -1150,6 +1804,7 @@ int driver_dbg(int argc, char** argv) if (st.dwarf) cfree_dwarf_close(st.dwarf); if (st.session) cfree_jit_session_free(st.session); cfree_jit_free(jit); + cfree_pipeline_free(pipe); dbg_options_release(&o); driver_env_fini(&env); return 0; diff --git a/driver/driver.h b/driver/driver.h @@ -115,6 +115,22 @@ const char* driver_getenv(const char* name); * allocation failure. */ int driver_read_stdin(DriverEnv*, uint8_t** out_data, size_t* out_size); +/* Path-shaped input loader. Wraps env.file_io.read_all so each tool can + * convert a list of paths to a list of CfreeBytesInput without re-implementing + * load/release/error bookkeeping. `loaded` is set to 1 on success; release is + * idempotent and does nothing when loaded is already 0. driver_load_bytes + * fills `in.name = path` plus the loaded data/len; driver_release_bytes hands + * the buffer back through file_io.release. On failure an error is emitted via + * driver_errf using the supplied tool tag. */ +typedef struct DriverLoad { + CfreeFileData fd; + int loaded; +} DriverLoad; + +int driver_load_bytes (const CfreeFileIO*, const char* tool, const char* path, + DriverLoad* out, CfreeBytesInput* in); +void driver_release_bytes(const CfreeFileIO*, DriverLoad*); + /* Read one line from stdin into `buf` (cap >= 2). Strips the trailing * newline and NUL-terminates. Returns the line length on success, 0 at * EOF (with buf[0]='\0'), -1 on read error, or -2 when the read was diff --git a/driver/env.c b/driver/env.c @@ -324,6 +324,35 @@ const char* driver_getenv(const char* name) return getenv(name); } +int driver_load_bytes(const CfreeFileIO* io, const char* tool, const char* path, + DriverLoad* out, CfreeBytesInput* in) +{ + out->loaded = 0; + out->fd.data = NULL; + out->fd.size = 0; + out->fd.token = NULL; + if (!io || !io->read_all) { + driver_errf(tool, "host file I/O unavailable"); + return 1; + } + if (!io->read_all(io->user, path, &out->fd)) { + driver_errf(tool, "failed to read: %s", path); + return 1; + } + out->loaded = 1; + in->name = path; + in->data = out->fd.data; + in->len = out->fd.size; + return 0; +} + +void driver_release_bytes(const CfreeFileIO* io, DriverLoad* lf) +{ + if (!lf || !lf->loaded) return; + if (io && io->release) io->release(io->user, &lf->fd); + lf->loaded = 0; +} + int driver_read_stdin(DriverEnv* e, uint8_t** out_data, size_t* out_size) { /* stdin is unseekable in the general case (pipes, ttys), so grow a buffer diff --git a/driver/ld.c b/driver/ld.c @@ -3,11 +3,12 @@ #include <stdint.h> -/* `cfree ld` — link object/archive inputs into an executable. The driver - * loads each input via env.file_io, optionally parses a `-T` linker script - * into the structured form, and calls cfree_link_exe directly so the full - * link surface (per-archive flags, group cycling, build-id) is reachable - * without thickening the convenience CfreeOptions struct. +/* `cfree ld` — link object/archive inputs into an executable or shared + * library. The driver loads each input via env.file_io, optionally parses a + * `-T` linker script into the structured form, and calls cfree_link_exe or + * cfree_link_shared directly so the full link surface (per-archive flags, + * group cycling, build-id, soname/rpath/exports) is reachable without + * thickening the convenience CfreeOptions struct. * * Supported flags: * -o out output path (required, exactly one) @@ -16,24 +17,32 @@ * -L dir library search path (-l targets) * -l name resolves to lib<name>.a via -L * -static / -pie / -no-pie target.pic + * -shared emit a shared library / dylib + * -soname NAME DT_SONAME / LC_ID_DYLIB + * -rpath DIR DT_RPATH/DT_RUNPATH entry (repeatable) + * -rpath-link DIR shared-lib search at link time (repeatable; accepted, currently advisory) + * --enable-new-dtags rpath entries emit as DT_RUNPATH (default) + * --disable-new-dtags rpath entries emit as DT_RPATH + * -E / --export-dynamic promote all defined globals to the dynamic symbol table * --whole-archive / --no-whole-archive * positional state for following .a + * -Bstatic / -Bdynamic positional link-mode for following .a + * --as-needed / --no-as-needed positional link-mode for following .a * --start-group / --end-group cyclic-resolution group of archives * --build-id={none|sha256|uuid|0xHEX} - * - * Out of scope (until shared-library output exists): -shared, -soname, - * -rpath, -lname.so resolution. */ + */ #define LD_TOOL "ld" /* Per-archive metadata mirroring the relevant subset of * CfreeBytesInputArchive plus driver-side ownership info. */ typedef struct LdArchive { - const char* path; /* path used for both open and CfreeBytesInput.name */ - int owned; /* 1 if `path` was alloc'd by lib_resolve */ - size_t owned_size; /* allocation size (for driver_free) */ - uint8_t flags; /* bitmask of CfreeLinkArchFlag */ - uint8_t group_id; /* cyclic resolution group id; 0 = single-pass */ + const char* path; /* path used for both open and CfreeBytesInput.name */ + int owned; /* 1 if `path` was alloc'd by lib_resolve */ + size_t owned_size; /* allocation size (for driver_free) */ + uint8_t whole_archive; /* nonzero == --whole-archive */ + uint8_t link_mode; /* CfreeLinkMode (-Bstatic/-Bdynamic/--as-needed) */ + uint8_t group_id; /* cyclic resolution group id; 0 = single-pass */ } LdArchive; typedef struct LdOptions { @@ -56,6 +65,16 @@ typedef struct LdOptions { const char** lib_dirs; /* -L */ uint32_t nlib_dirs; + /* Shared-library output state. */ + int shared; /* -shared */ + const char* soname; /* -soname NAME */ + const char** rpaths; /* -rpath DIR (repeatable) */ + uint32_t nrpaths; + const char** rpath_links; /* -rpath-link DIR (advisory) */ + uint32_t nrpath_links; + int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */ + int export_dynamic; /* -E / --export-dynamic */ + /* --build-id state */ uint8_t build_id_mode; /* CfreeBuildIdMode */ uint8_t* build_id_bytes; /* USER mode: parsed hex, owned */ @@ -63,7 +82,8 @@ typedef struct LdOptions { size_t build_id_alloc; /* allocation size for free */ /* Mutable parse state for positional archive flags / groups. */ - uint8_t cur_arch_flags; /* pending CfreeLinkArchFlag bits */ + uint8_t cur_whole_archive; /* pending --whole-archive flag */ + uint8_t cur_link_mode; /* CfreeLinkMode for following inputs */ uint8_t cur_group_id; /* 0 outside any --start-group */ uint8_t next_group_id; /* increments on --start-group */ } LdOptions; @@ -89,10 +109,14 @@ static int ld_alloc_arrays(LdOptions* o, int argc) o->object_files = driver_alloc_zeroed(o->env, bound * sizeof(*o->object_files)); o->archives = driver_alloc_zeroed(o->env, bound * sizeof(*o->archives)); o->lib_dirs = driver_alloc_zeroed(o->env, bound * sizeof(*o->lib_dirs)); - if (!o->object_files || !o->archives || !o->lib_dirs) { + o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths)); + o->rpath_links = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpath_links)); + if (!o->object_files || !o->archives || !o->lib_dirs || + !o->rpaths || !o->rpath_links) { driver_errf(LD_TOOL, "out of memory"); return 1; } + o->new_dtags = 1; return 0; } @@ -102,11 +126,12 @@ static void ld_push_archive(LdOptions* o, const char* path, int owned, size_t owned_size) { LdArchive* a = &o->archives[o->narchives++]; - a->path = path; - a->owned = owned; - a->owned_size = owned_size; - a->flags = o->cur_arch_flags; - a->group_id = o->cur_group_id; + a->path = path; + a->owned = owned; + a->owned_size = owned_size; + a->whole_archive = o->cur_whole_archive; + a->link_mode = o->cur_link_mode; + a->group_id = o->cur_group_id; } /* ---------- --build-id parsing ---------- */ @@ -228,12 +253,70 @@ static int ld_parse(int argc, char** argv, LdOptions* o) if (driver_streq(a, "-pie")) { o->target.pic = CFREE_PIC_PIE; continue; } if (driver_streq(a, "-no-pie")) { o->target.pic = CFREE_PIC_NONE; continue; } + if (driver_streq(a, "-shared")) { + o->shared = 1; + /* Shared objects must be position-independent. Force PIC unless + * the caller has explicitly chosen PIE (which is also fine). */ + if (o->target.pic == CFREE_PIC_NONE) o->target.pic = CFREE_PIC_PIC; + continue; + } + if (driver_streq(a, "-soname")) { + if (++i >= argc) { driver_errf(LD_TOOL, "-soname requires an argument"); return 1; } + o->soname = argv[i]; + continue; + } + if ((val = arg_eq_value(a, "-soname")) != NULL) { + o->soname = val; + continue; + } + if (driver_streq(a, "-rpath")) { + if (++i >= argc) { driver_errf(LD_TOOL, "-rpath requires an argument"); return 1; } + o->rpaths[o->nrpaths++] = argv[i]; + continue; + } + if ((val = arg_eq_value(a, "-rpath")) != NULL) { + o->rpaths[o->nrpaths++] = val; + continue; + } + if (driver_streq(a, "-rpath-link")) { + if (++i >= argc) { driver_errf(LD_TOOL, "-rpath-link requires an argument"); return 1; } + o->rpath_links[o->nrpath_links++] = argv[i]; + continue; + } + if ((val = arg_eq_value(a, "-rpath-link")) != NULL) { + o->rpath_links[o->nrpath_links++] = val; + continue; + } + if (driver_streq(a, "--enable-new-dtags")) { o->new_dtags = 1; continue; } + if (driver_streq(a, "--disable-new-dtags")) { o->new_dtags = 0; continue; } + if (driver_streq(a, "-E") || + driver_streq(a, "--export-dynamic")) { + o->export_dynamic = 1; + continue; + } + if (driver_streq(a, "--whole-archive")) { - o->cur_arch_flags |= CFREE_LAF_WHOLE_ARCHIVE; + o->cur_whole_archive = 1; continue; } if (driver_streq(a, "--no-whole-archive")) { - o->cur_arch_flags &= (uint8_t)~CFREE_LAF_WHOLE_ARCHIVE; + o->cur_whole_archive = 0; + continue; + } + if (driver_streq(a, "-Bstatic")) { + o->cur_link_mode = CFREE_LM_STATIC; + continue; + } + if (driver_streq(a, "-Bdynamic")) { + o->cur_link_mode = CFREE_LM_DYNAMIC; + continue; + } + if (driver_streq(a, "--as-needed")) { + o->cur_link_mode = CFREE_LM_AS_NEEDED; + continue; + } + if (driver_streq(a, "--no-as-needed")) { + o->cur_link_mode = CFREE_LM_DYNAMIC; continue; } if (driver_streq(a, "--start-group")) { @@ -314,6 +397,8 @@ static void ld_options_release(LdOptions* o) driver_free(o->env, o->object_files, bound * sizeof(*o->object_files)); driver_free(o->env, o->archives, bound * sizeof(*o->archives)); driver_free(o->env, o->lib_dirs, bound * sizeof(*o->lib_dirs)); + driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths)); + driver_free(o->env, o->rpath_links, bound * sizeof(*o->rpath_links)); } /* ---------- input loading ---------- */ @@ -368,7 +453,9 @@ static int ld_run_link(LdOptions* o) CfreeBytesInput* obj_in = NULL; CfreeBytesInputArchive* arch_in = NULL; const CfreeLinkScript* script = NULL; + CfreeLinkInputs inputs; CfreeLinkOptions link_opts; + CfreeLinkSharedOptions shared_opts; uint32_t i; int rc = 1; @@ -411,11 +498,12 @@ static int ld_run_link(LdOptions* o) driver_errf(LD_TOOL, "failed to read: %s", a->path); goto out; } - arch_in[i].input.name = a->path; - arch_in[i].input.data = arch_lf[i].data.data; - arch_in[i].input.len = arch_lf[i].data.size; - arch_in[i].flags = a->flags; - arch_in[i].group_id = a->group_id; + arch_in[i].input.name = a->path; + arch_in[i].input.data = arch_lf[i].data.data; + arch_in[i].input.len = arch_lf[i].data.size; + arch_in[i].whole_archive = a->whole_archive; + arch_in[i].link_mode = a->link_mode; + arch_in[i].group_id = a->group_id; } /* Load and parse the linker script (if any). The structured script is @@ -451,20 +539,64 @@ static int ld_run_link(LdOptions* o) } { + CfreeLinkInputs zero = {0}; + inputs = zero; + } + inputs.obj_bytes = obj_in; + inputs.nobj_bytes = o->nobject_files; + inputs.archives = arch_in; + inputs.narchives = o->narchives; + inputs.linker_script = script; + inputs.entry = o->entry; + inputs.build_id_mode = o->build_id_mode; + inputs.build_id_bytes = o->build_id_bytes; + inputs.build_id_len = o->build_id_len; + + if (o->shared) { + CfreeLinkSharedOptions zero = {0}; + shared_opts = zero; + shared_opts.inputs = inputs; + shared_opts.soname = o->soname; + /* Per --enable-new-dtags / --disable-new-dtags: when new_dtags is + * set (the default), -rpath entries land in DT_RUNPATH; otherwise + * in DT_RPATH. -rpath-link is link-time-only and is forwarded as + * runpaths so the library has a record of the search paths used + * (advisory; ELF runtime ignores DT_RUNPATH entries it didn't + * write itself, so this matches GNU-ld behaviour where rpath-link + * does not appear in DT_*PATH). */ + if (o->new_dtags) { + shared_opts.runpaths = o->rpaths; + shared_opts.nrunpaths = o->nrpaths; + } else { + shared_opts.rpaths = o->rpaths; + shared_opts.nrpaths = o->nrpaths; + } + /* allow_undefined defaults to 1 for shared output (the produced + * object resolves against its loader at runtime). */ + shared_opts.allow_undefined = 1; + /* -E/--export-dynamic is exe-shaped (promote globals into the + * dynsym of an executable). For shared output every defined global + * is already exported, so the flag is a no-op here; we accept it + * silently. */ + (void)o->export_dynamic; + rc = cfree_link_shared(compiler, &shared_opts, writer); + } else { CfreeLinkOptions zero = {0}; link_opts = zero; + link_opts.inputs = inputs; + if (o->export_dynamic) { + /* TODO(#5/exe): once CfreeLinkOptions grows an export_dynamic + * field (or per-symbol export list for executables), wire it + * here. For now the flag is recorded but has no effect on the + * exe link. */ + } + if (o->soname || o->nrpaths || o->nrpath_links) { + driver_errf(LD_TOOL, + "-soname/-rpath/-rpath-link require -shared"); + goto out; + } + rc = cfree_link_exe(compiler, &link_opts, writer); } - link_opts.obj_bytes = obj_in; - link_opts.nobj_bytes = o->nobject_files; - link_opts.archives = arch_in; - link_opts.narchives = o->narchives; - link_opts.linker_script = script; - link_opts.entry = o->entry; - link_opts.build_id_mode = o->build_id_mode; - link_opts.build_id_bytes = o->build_id_bytes; - link_opts.build_id_len = o->build_id_len; - - rc = cfree_link_exe(compiler, &link_opts, writer); out: if (writer) cfree_writer_close(writer); diff --git a/driver/objdump.c b/driver/objdump.c @@ -306,7 +306,7 @@ static void dump_obj(CfreeCompiler* dc, const char* label, CfreeObjFile* f, } static int dump_archive(const char* path, const CfreeBytesInput* input, - const CfreeEnv* cenv, CfreeTarget target, + const CfreeEnv* cenv, CfreeCompiler* dc, const ObjdumpOpts* opts) { CfreeArIter it; @@ -333,7 +333,7 @@ static int dump_archive(const char* path, const CfreeBytesInput* input, min.data = member.data; min.len = member.size; - f = cfree_obj_open(cenv, target, &min); + f = cfree_obj_open(cenv, &min); if (!f) { driver_errf(OBJDUMP_TOOL, "failed to parse member: %s", label); continue; @@ -448,13 +448,13 @@ int driver_objdump(int argc, char** argv) bin = cfree_detect_fmt(input.data, input.len); switch (bin) { case CFREE_BIN_AR: - rc = dump_archive(a, &input, &cenv, driver_host_target(), dc, &opts); + rc = dump_archive(a, &input, &cenv, dc, &opts); break; case CFREE_BIN_ELF: case CFREE_BIN_COFF: case CFREE_BIN_MACHO: case CFREE_BIN_WASM: { - CfreeObjFile* f = cfree_obj_open(&cenv, driver_host_target(), &input); + CfreeObjFile* f = cfree_obj_open(&cenv, &input); if (!f) { driver_errf(OBJDUMP_TOOL, "failed to parse: %s", a); rc = 1; diff --git a/driver/run.c b/driver/run.c @@ -245,36 +245,125 @@ static void run_options_release(RunOptions* o) driver_free(o->env, o->prog_argv, bound * sizeof(*o->prog_argv)); } -static void run_to_cfree(const RunOptions* o, CfreeOptions* out, CfreeJit** out_jit) +static void run_fill_compile_opts(const RunOptions* o, CfreeCompileOptions* copts) { - CfreeOptions z = {0}; - *out = z; - out->target = o->target; - out->env = driver_env_to_cfree(o->env); - out->output_kind = CFREE_OUTPUT_JIT; - out->opt_level = o->opt_level; - out->debug_info = o->debug_info; - - out->source_files = o->sources; - out->nsource_files = o->nsources; - out->source_memory = o->source_memory; - out->nsource_memory = o->nsource_memory; - - driver_cflags_fill_pp(&o->cf, &out->pp); - - out->object_files = o->object_files; - out->nobject_files = o->nobject_files; - out->archives = o->archives; - out->narchives = o->narchives; - - out->entry = o->entry; - out->extern_resolver = driver_dlsym_resolver; - out->extern_resolver_user = NULL; - - out->warnings_are_errors = o->warnings_are_errors; - out->max_errors = o->max_errors; - - out->out_jit = out_jit; + 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); + copts->warnings_are_errors = o->warnings_are_errors; + copts->max_errors = o->max_errors; +} + +/* Compile every C source through a pipeline, load .o/.a inputs, and JIT-link. + * On success *out_jit owns the JIT image; caller releases via cfree_jit_free. */ +static int run_compile_and_jit(DriverEnv* env, const RunOptions* o, + CfreeJit** out_jit) +{ + CfreeEnv cenv = driver_env_to_cfree(env); + const CfreeFileIO* io = cenv.file_io; + CfreePipeline* pipe = NULL; + DriverLoad* src_lf = NULL; + DriverLoad* obj_lf = NULL; + DriverLoad* arch_lf = NULL; + CfreeBytesInput* src_in = NULL; + CfreeBytesInput* obj_in = NULL; + CfreeBytesInputArchive* arch_in = NULL; + CfreeObjBuilder** objs = NULL; + CfreeCompileOptions copts; + CfreeLinkOptions link_opts; + uint32_t nsrc = o->nsources + o->nsource_memory; + uint32_t i; + int rc = 1; + + if (!io || !io->read_all) { + driver_errf(RUN_TOOL, "host file I/O unavailable"); + return 1; + } + + src_in = driver_alloc_zeroed(env, nsrc * sizeof(*src_in)); + objs = driver_alloc_zeroed(env, nsrc * sizeof(*objs)); + if (!src_in || !objs) { driver_errf(RUN_TOOL, "out of memory"); goto out; } + if (o->nsources) { + src_lf = driver_alloc_zeroed(env, o->nsources * sizeof(*src_lf)); + if (!src_lf) { driver_errf(RUN_TOOL, "out of memory"); goto out; } + } + if (o->nobject_files) { + obj_lf = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_lf)); + obj_in = driver_alloc_zeroed(env, o->nobject_files * sizeof(*obj_in)); + if (!obj_lf || !obj_in) { driver_errf(RUN_TOOL, "out of memory"); goto out; } + } + if (o->narchives) { + arch_lf = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_lf)); + arch_in = driver_alloc_zeroed(env, o->narchives * sizeof(*arch_in)); + if (!arch_lf || !arch_in) { driver_errf(RUN_TOOL, "out of memory"); goto out; } + } + + for (i = 0; i < o->nsources; ++i) { + if (driver_load_bytes(io, RUN_TOOL, o->sources[i], + &src_lf[i], &src_in[i]) != 0) goto out; + } + for (i = 0; i < o->nsource_memory; ++i) { + src_in[o->nsources + i] = o->source_memory[i]; + } + + for (i = 0; i < o->nobject_files; ++i) { + if (driver_load_bytes(io, RUN_TOOL, o->object_files[i], + &obj_lf[i], &obj_in[i]) != 0) goto out; + } + for (i = 0; i < o->narchives; ++i) { + if (driver_load_bytes(io, RUN_TOOL, o->archives[i], + &arch_lf[i], &arch_in[i].input) != 0) goto out; + arch_in[i].link_mode = CFREE_LM_DEFAULT; + arch_in[i].whole_archive = 0; + arch_in[i].group_id = 0; + } + + pipe = cfree_pipeline_new(o->target, &cenv); + if (!pipe) { driver_errf(RUN_TOOL, "failed to initialize compiler"); goto out; } + + run_fill_compile_opts(o, &copts); + for (i = 0; i < nsrc; ++i) { + if (cfree_pipeline_compile_obj(pipe, &copts, &src_in[i], &objs[i]) != 0) + goto out; + } + + { + CfreeLinkOptions z = {0}; + link_opts = z; + } + link_opts.inputs.objs = objs; + link_opts.inputs.nobjs = nsrc; + link_opts.inputs.obj_bytes = obj_in; + link_opts.inputs.nobj_bytes = o->nobject_files; + link_opts.inputs.archives = arch_in; + link_opts.inputs.narchives = o->narchives; + link_opts.inputs.entry = o->entry; + link_opts.inputs.extern_resolver = driver_dlsym_resolver; + link_opts.inputs.extern_resolver_user = NULL; + + rc = cfree_pipeline_link_jit(pipe, &link_opts, out_jit); + +out: + if (pipe) cfree_pipeline_free(pipe); + if (arch_lf) { + for (i = 0; i < o->narchives; ++i) driver_release_bytes(io, &arch_lf[i]); + } + if (obj_lf) { + for (i = 0; i < o->nobject_files; ++i) driver_release_bytes(io, &obj_lf[i]); + } + if (src_lf) { + for (i = 0; i < o->nsources; ++i) driver_release_bytes(io, &src_lf[i]); + } + if (arch_in) driver_free(env, arch_in, o->narchives * sizeof(*arch_in)); + if (arch_lf) driver_free(env, arch_lf, o->narchives * sizeof(*arch_lf)); + if (obj_in) driver_free(env, obj_in, o->nobject_files * sizeof(*obj_in)); + if (obj_lf) driver_free(env, obj_lf, o->nobject_files * sizeof(*obj_lf)); + if (src_lf) driver_free(env, src_lf, o->nsources * sizeof(*src_lf)); + if (objs) driver_free(env, objs, nsrc * sizeof(*objs)); + if (src_in) driver_free(env, src_in, nsrc * sizeof(*src_in)); + return rc; } typedef int (*MainFn)(int, char**); @@ -283,7 +372,6 @@ int driver_run(int argc, char** argv) { DriverEnv env; RunOptions ro = {0}; - CfreeOptions copts; CfreeJit* jit = NULL; void* sym; MainFn entry_fn; @@ -298,8 +386,7 @@ int driver_run(int argc, char** argv) return 2; } - run_to_cfree(&ro, &copts, &jit); - rc = cfree_run(&copts); + rc = run_compile_and_jit(&env, &ro, &jit); if (rc != 0) { run_options_release(&ro); driver_env_fini(&env);