commit 40c583cf135d77d972c82cb70a94c771d536ecac
parent 4bcdfd87a185d107e9685f63bdee5a4968e63763
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 8 May 2026 17:19:42 -0700
driver: subcommand updates
Diffstat:
| M | driver/ar.c | | | 173 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
| M | driver/as.c | | | 17 | +++++++++-------- |
| M | driver/cc.c | | | 300 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
| M | driver/dbg.c | | | 917 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
| M | driver/driver.h | | | 16 | ++++++++++++++++ |
| M | driver/env.c | | | 29 | +++++++++++++++++++++++++++++ |
| M | driver/ld.c | | | 208 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- |
| M | driver/objdump.c | | | 8 | ++++---- |
| M | driver/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);