kit

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

commit 4595b0529150f7885e25e7231e0a0d1196c440fd
parent ef340b4cc5bea1a6a715058fc4d15559549af74c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:41:22 -0700

driver: wire kernel linker flags through cc/build-exe + ld

Route the new link policy through driver/lib/link_flags.* (shared by cc and
build-exe) and mirror it in driver/cmd/ld.c:

- --defsym NAME=EXPR (literal or alias), --section-start=.NAME=ADDR, and
  -Tdata/-Tbss (folded into section_starts as .data/.bss; the build-exe
  rejection is replaced). Both top-level and -Wl,/-Xlinker spellings.
- --orphan-handling=place|warn|error|discard, --fatal-warnings, --cref FILE,
  --print-memory-usage. cref writes a side file; memory-usage prints to stdout.
- Freestanding strict policy: build-exe/ld set freestanding_strict for a
  *-none-* (or -ffreestanding static non-PIE) EXE link, and reject foreign-arch
  / wrong-format object inputs up front via per-input kit_detect_target.

Diffstat:
Mdriver/cmd/build.c | 159++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/cmd/ld.c | 421++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdriver/lib/link_flags.c | 332++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdriver/lib/link_flags.h | 26++++++++++++++++++++++++++
4 files changed, 926 insertions(+), 12 deletions(-)

diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -2,6 +2,7 @@ #include <kit/compile.h> #include <kit/core.h> #include <kit/link.h> +#include <kit/object.h> #include <kit/preprocess.h> #include <stdint.h> #include <string.h> @@ -729,11 +730,101 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { if (driver_link_flags_record_text_base(&o->link, a + 7) != 0) return 1; continue; } - if (driver_streq(a, "-Tdata") || driver_strneq(a, "-Tdata=", 7) || - driver_streq(a, "-Tbss") || driver_strneq(a, "-Tbss=", 6)) { - driver_errf(o->tool, "%.*s is not supported yet", - KIT_SLICE_ARG(kit_slice_cstr(a))); - return 1; + if (driver_streq(a, "-Tdata") || driver_streq(a, "-Tbss")) { + const char* secname = driver_streq(a, "-Tdata") ? ".data" : ".bss"; + if (++i >= argc) { + driver_errf(o->tool, "%s requires an address", a); + return 1; + } + if (driver_link_flags_record_section_addr(&o->link, secname, argv[i]) != 0) + return 1; + continue; + } + if (driver_strneq(a, "-Tdata=", 7)) { + if (driver_link_flags_record_section_addr(&o->link, ".data", a + 7) != 0) + return 1; + continue; + } + if (driver_strneq(a, "-Tbss=", 6)) { + if (driver_link_flags_record_section_addr(&o->link, ".bss", a + 6) != 0) + return 1; + continue; + } + if (driver_streq(a, "--defsym")) { + if (++i >= argc) { + driver_errf(o->tool, "--defsym requires NAME=EXPR"); + return 1; + } + if (driver_link_flags_record_defsym(&o->link, argv[i], + driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--defsym=", 9)) { + if (driver_link_flags_record_defsym(&o->link, a + 9, + driver_strlen(a + 9)) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--section-start=", 16)) { + if (driver_link_flags_record_section_start(&o->link, a + 16, + driver_strlen(a + 16)) != 0) + return 1; + continue; + } + if (driver_streq(a, "--section-start")) { + if (++i >= argc) { + driver_errf(o->tool, "--section-start requires .NAME=ADDR"); + return 1; + } + if (driver_link_flags_record_section_start(&o->link, argv[i], + driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--orphan-handling=", 18)) { + if (driver_link_flags_record_orphan_handling( + &o->link, a + 18, driver_strlen(a + 18)) != 0) + return 1; + continue; + } + if (driver_streq(a, "--orphan-handling")) { + if (++i >= argc) { + driver_errf(o->tool, "--orphan-handling requires a mode"); + return 1; + } + if (driver_link_flags_record_orphan_handling( + &o->link, argv[i], driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (driver_streq(a, "--fatal-warnings")) { + o->link.fatal_warnings = 1; + continue; + } + if (driver_streq(a, "--no-fatal-warnings")) { + o->link.fatal_warnings = 0; + continue; + } + if (driver_streq(a, "--print-memory-usage")) { + o->link.print_memory_usage = 1; + continue; + } + if (driver_streq(a, "--cref")) { + if (++i >= argc) { + driver_errf(o->tool, "--cref requires a path"); + return 1; + } + if (driver_link_flags_record_cref(&o->link, argv[i], + driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--cref=", 7)) { + if (driver_link_flags_record_cref(&o->link, a + 7, + driver_strlen(a + 7)) != 0) + return 1; + continue; } if (driver_streq(a, "--gc-sections")) { o->link.gc_sections = 1; @@ -1437,6 +1528,37 @@ static KitStatus build_link_with_lto_reports( if (st == KIT_OK && build_write_link_report(o, ctx, link, o->link.symbols_path, 1) != 0) st = KIT_ERR; + /* --cref FILE: cross-reference table side file. */ + if (st == KIT_OK && o->link.cref_path) { + KitWriter* w = NULL; + if (build_open_output(ctx, o->env, o->tool, o->link.cref_path, &w) != 0) { + st = KIT_ERR; + } else { + KitStatus cst = kit_link_session_write_cref(link, w); + if (cst == KIT_OK) cst = kit_writer_status(w); + kit_writer_close(w); + if (cst != KIT_OK) { + driver_errf(o->tool, "failed to write cref: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(o->link.cref_path))); + st = KIT_ERR; + } + } + } + /* --print-memory-usage: GNU-ld-style per-region summary to stdout. */ + if (st == KIT_OK && o->link.print_memory_usage) { + KitWriter* w = NULL; + if (build_open_output(ctx, o->env, o->tool, "-", &w) != 0) { + st = KIT_ERR; + } else { + KitStatus mst = kit_link_session_write_memory_usage(link, w); + if (mst == KIT_OK) mst = kit_writer_status(w); + kit_writer_close(w); + if (mst != KIT_OK) { + driver_errf(o->tool, "failed to print memory usage"); + st = KIT_ERR; + } + } + } kit_link_session_free(link); if (preserved.syms) preserved.heap->free(preserved.heap, preserved.syms, @@ -1515,6 +1637,24 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, goto out; obj_names[i] = kit_slice_cstr(o->inputs.object_files[i]); } + /* Strict freestanding policy: pre-built object inputs must agree with the + * link target's arch and object format (a `*-none-*` exe link must not + * silently accept a foreign-arch object). */ + if (output_kind == KIT_LINK_OUTPUT_EXE && + o->target.os == KIT_OS_FREESTANDING) { + for (i = 0; i < o->inputs.nobject_files; ++i) { + KitTargetSpec t; + if (kit_detect_target(obj_in[i].data, obj_in[i].len, &t) != KIT_OK) + continue; + if (t.arch != o->target.arch || t.obj != o->target.obj) { + driver_errf(o->tool, + "freestanding link: input '%s' arch/format does not match " + "the link target", + o->inputs.object_files[i]); + goto out; + } + } + } for (i = 0; i < o->inputs.narchives; ++i) { if (driver_load_bytes(io, o->tool, o->inputs.archives[i].path, &arch_lf[i], &arch_in[i].bytes) != 0) @@ -1577,6 +1717,15 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, output_kind == KIT_LINK_OUTPUT_RELOCATABLE, output_kind, script, &lopts, &rpath_slices) != 0) goto out; + /* Strict-by-default freestanding policy: a `*-none-*` target, or an + * explicit -ffreestanding static non-PIE executable, must reject + * dynamic-link artifacts (PT_INTERP, .dynamic/.dynsym, PLT/GOT imports, + * DSO inputs) and cross-input target/format mismatches. The relocatable + * partial-link lane never imposes it. */ + if (output_kind == KIT_LINK_OUTPUT_EXE && + (o->target.os == KIT_OS_FREESTANDING || + (o->freestanding && !lopts.pie && o->static_link))) + lopts.freestanding_strict = true; memset(&li, 0, sizeof(li)); li.objs = objs; li.nobjs = nobjs; diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -139,6 +139,18 @@ typedef struct LdOptions { const char* map_path; /* --map / -Map */ const char* symbols_path; uint8_t symbols_format; /* KitLinkSymbolsFormat */ + const char* cref_path; /* --cref FILE */ + int print_memory_usage; /* --print-memory-usage */ + uint8_t orphan_handling; /* KitLinkOrphanHandling */ + int fatal_warnings; /* --fatal-warnings */ + /* --defsym NAME=EXPR (owned name/alias strings). */ + KitLinkDefsym* defsyms; + uint32_t ndefsyms; + uint32_t cap_defsyms; + /* --section-start / -Tdata / -Tbss section address overrides. */ + KitLinkSectionStart* section_starts; + uint32_t nsection_starts; + uint32_t cap_section_starts; int static_link; /* -static: hosted libc should pick static profile */ int wants_hosted_libc; /* libc request: expand crt + libc via hosted resolver */ int windows_ucrt_target; /* *-windows-gnullvm: MinGW UCRT hosted profile */ @@ -812,6 +824,140 @@ static int ld_record_symbols_format(LdOptions* o, const char* val, size_t n) { return 1; } +/* --defsym NAME=EXPR. `val`/`n` is the span after `--defsym `. */ +static int ld_record_defsym(LdOptions* o, const char* val, size_t n) { + size_t eq = 0; + const char* name_owned; + const char* rhs; + size_t rhs_len; + KitLinkDefsym* d; + while (eq < n && val[eq] != '=') ++eq; + if (eq == 0 || eq >= n) { + driver_errf(LD_TOOL, "--defsym requires NAME=EXPR: %.*s", (int)n, val); + return 1; + } + rhs = val + eq + 1u; + rhs_len = n - eq - 1u; + if (rhs_len == 0) { + driver_errf(LD_TOOL, "--defsym requires a value: %.*s", (int)n, val); + return 1; + } + if (o->ndefsyms >= o->cap_defsyms) { + uint32_t nc = o->cap_defsyms ? o->cap_defsyms * 2u : 4u; + KitLinkDefsym* nd = driver_alloc_zeroed(o->env, nc * sizeof(*nd)); + if (!nd) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + if (o->defsyms) { + driver_memcpy(nd, o->defsyms, o->ndefsyms * sizeof(*nd)); + driver_free(o->env, o->defsyms, o->cap_defsyms * sizeof(*nd)); + } + o->defsyms = nd; + o->cap_defsyms = nc; + } + if (ld_span_to_owned_cstr(o, val, eq, &name_owned) != 0) return 1; + d = &o->defsyms[o->ndefsyms]; + memset(d, 0, sizeof(*d)); + d->name = kit_slice_cstr(name_owned); + if (rhs[0] >= '0' && rhs[0] <= '9') { + const char* num; + uint64_t v; + if (ld_span_to_owned_cstr(o, rhs, rhs_len, &num) != 0) return 1; + if (driver_parse_u64(num, &v) != 0) { + driver_errf(LD_TOOL, "--defsym: invalid value: %.*s", (int)rhs_len, rhs); + return 1; + } + d->value = v; + } else { + const char* alias; + if (ld_span_to_owned_cstr(o, rhs, rhs_len, &alias) != 0) return 1; + d->alias = kit_slice_cstr(alias); + } + o->ndefsyms++; + return 0; +} + +/* --section-start: store a NAME -> ADDR override (NAME may be ".data"/".bss" + * synthesized from -Tdata/-Tbss). */ +static int ld_record_section_addr(LdOptions* o, const char* secname, + const char* addr) { + uint64_t v; + const char* name_owned; + KitLinkSectionStart* ss; + if (!secname || !*secname || !addr || !*addr) { + driver_errf(LD_TOOL, "--section-start requires .NAME=ADDR"); + return 1; + } + if (driver_parse_u64(addr, &v) != 0) { + driver_errf(LD_TOOL, "--section-start: invalid address: %s", addr); + return 1; + } + if (o->nsection_starts >= o->cap_section_starts) { + uint32_t nc = o->cap_section_starts ? o->cap_section_starts * 2u : 4u; + KitLinkSectionStart* ns = driver_alloc_zeroed(o->env, nc * sizeof(*ns)); + if (!ns) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + if (o->section_starts) { + driver_memcpy(ns, o->section_starts, o->nsection_starts * sizeof(*ns)); + driver_free(o->env, o->section_starts, + o->cap_section_starts * sizeof(*ns)); + } + o->section_starts = ns; + o->cap_section_starts = nc; + } + if (ld_span_to_owned_cstr(o, secname, driver_strlen(secname), &name_owned) != + 0) + return 1; + ss = &o->section_starts[o->nsection_starts]; + memset(ss, 0, sizeof(*ss)); + ss->name = kit_slice_cstr(name_owned); + ss->addr = v; + o->nsection_starts++; + return 0; +} + +static int ld_record_section_start(LdOptions* o, const char* val, size_t n) { + size_t eq = n; + size_t i; + const char* name_owned; + const char* addr_owned; + for (i = 0; i < n; ++i) + if (val[i] == '=') eq = i; + if (eq == n || eq == 0) { + driver_errf(LD_TOOL, "--section-start requires .NAME=ADDR: %.*s", (int)n, + val); + return 1; + } + if (ld_span_to_owned_cstr(o, val, eq, &name_owned) != 0) return 1; + if (ld_span_to_owned_cstr(o, val + eq + 1u, n - eq - 1u, &addr_owned) != 0) + return 1; + return ld_record_section_addr(o, name_owned, addr_owned); +} + +static int ld_record_orphan_handling(LdOptions* o, const char* val, size_t n) { + if (n == 5 && driver_strneq(val, "place", 5)) { + o->orphan_handling = KIT_LINK_ORPHAN_PLACE; + return 0; + } + if (n == 4 && driver_strneq(val, "warn", 4)) { + o->orphan_handling = KIT_LINK_ORPHAN_WARN; + return 0; + } + if (n == 5 && driver_strneq(val, "error", 5)) { + o->orphan_handling = KIT_LINK_ORPHAN_ERROR; + return 0; + } + if (n == 7 && driver_strneq(val, "discard", 7)) { + o->orphan_handling = KIT_LINK_ORPHAN_DISCARD; + return 0; + } + driver_errf(LD_TOOL, "unsupported --orphan-handling: %.*s", (int)n, val); + return 1; +} + static int ld_parse_wl(LdOptions* o, const char* arg) { const char* p = arg; int expect_rpath = 0; @@ -821,6 +967,10 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { int expect_emulation = 0; int expect_map = 0; int expect_symbols = 0; + int expect_defsym = 0; + int expect_section_start = 0; + int expect_cref = 0; + int expect_orphan = 0; while (*p) { const char* tok = p; size_t n = 0; @@ -828,8 +978,23 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { p = tok + n + (tok[n] == ',' ? 1u : 0u); if (expect_rpath || expect_soname || expect_interp || expect_z || - expect_emulation || expect_map || expect_symbols) { - if (expect_rpath) { + expect_emulation || expect_map || expect_symbols || expect_defsym || + expect_section_start || expect_cref || expect_orphan) { + if (expect_defsym) { + if (ld_record_defsym(o, tok, n) != 0) return 1; + } else if (expect_section_start) { + if (ld_record_section_start(o, tok, n) != 0) return 1; + } else if (expect_cref) { + const char* owned; + if (n == 0) { + driver_errf(LD_TOOL, "--cref requires a non-empty path"); + return 1; + } + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + o->cref_path = owned; + } else if (expect_orphan) { + if (ld_record_orphan_handling(o, tok, n) != 0) return 1; + } else if (expect_rpath) { const char* owned; if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; o->rpaths[o->nrpaths++] = owned; @@ -865,7 +1030,8 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { o->symbols_path = owned; } expect_rpath = expect_soname = expect_interp = expect_z = - expect_emulation = expect_map = expect_symbols = 0; + expect_emulation = expect_map = expect_symbols = expect_defsym = + expect_section_start = expect_cref = expect_orphan = 0; continue; } @@ -1032,12 +1198,87 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { o->interp_path = owned; continue; } + /* -Tdata / -Tbss section base overrides. */ + if (ld_tok_eq(tok, n, "-Tdata") || ld_tok_eq(tok, n, "-Tbss")) { + const char* secname = ld_tok_eq(tok, n, "-Tdata") ? ".data" : ".bss"; + const char* atok = p; + size_t an = 0; + const char* addr_owned; + if (!*atok) { + driver_errf(LD_TOOL, "%.*s requires an address", (int)n, tok); + return 1; + } + while (atok[an] && atok[an] != ',') ++an; + p = atok + an + (atok[an] == ',' ? 1u : 0u); + if (ld_span_to_owned_cstr(o, atok, an, &addr_owned) != 0) return 1; + if (ld_record_section_addr(o, secname, addr_owned) != 0) return 1; + continue; + } + if (ld_tok_prefix(tok, n, "-Tdata=")) { + const char* addr_owned; + if (ld_span_to_owned_cstr(o, tok + 7, n - 7u, &addr_owned) != 0) return 1; + if (ld_record_section_addr(o, ".data", addr_owned) != 0) return 1; + continue; + } + if (ld_tok_prefix(tok, n, "-Tbss=")) { + const char* addr_owned; + if (ld_span_to_owned_cstr(o, tok + 6, n - 6u, &addr_owned) != 0) return 1; + if (ld_record_section_addr(o, ".bss", addr_owned) != 0) return 1; + continue; + } + if (ld_tok_prefix(tok, n, "--defsym=")) { + if (ld_record_defsym(o, tok + 9, n - 9u) != 0) return 1; + continue; + } + if (ld_tok_eq(tok, n, "--defsym")) { + expect_defsym = 1; + continue; + } + if (ld_tok_prefix(tok, n, "--section-start=")) { + if (ld_record_section_start(o, tok + 16, n - 16u) != 0) return 1; + continue; + } + if (ld_tok_eq(tok, n, "--section-start")) { + expect_section_start = 1; + continue; + } + if (ld_tok_prefix(tok, n, "--cref=")) { + const char* owned; + if (ld_span_to_owned_cstr(o, tok + 7, n - 7u, &owned) != 0) return 1; + o->cref_path = owned; + continue; + } + if (ld_tok_eq(tok, n, "--cref")) { + expect_cref = 1; + continue; + } + if (ld_tok_prefix(tok, n, "--orphan-handling=")) { + if (ld_record_orphan_handling(o, tok + 18, n - 18u) != 0) return 1; + continue; + } + if (ld_tok_eq(tok, n, "--orphan-handling")) { + expect_orphan = 1; + continue; + } + if (ld_tok_eq(tok, n, "--fatal-warnings")) { + o->fatal_warnings = 1; + continue; + } + if (ld_tok_eq(tok, n, "--no-fatal-warnings")) { + o->fatal_warnings = 0; + continue; + } + if (ld_tok_eq(tok, n, "--print-memory-usage")) { + o->print_memory_usage = 1; + continue; + } driver_errf(LD_TOOL, "unsupported -Wl, token: %.*s", (int)n, tok); return 1; } if (expect_rpath || expect_soname || expect_interp || expect_z || - expect_emulation || expect_map || expect_symbols) { + expect_emulation || expect_map || expect_symbols || expect_defsym || + expect_section_start || expect_cref || expect_orphan) { driver_errf(LD_TOOL, "-Wl option requires another comma argument"); return 1; } @@ -2108,6 +2349,99 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { continue; } } + /* -Tdata / -Tbss ADDR (or =ADDR): per-section address override. Folded + * into the section_starts table as `.data` / `.bss`. */ + { + const char* secname = NULL; + const char* addr = NULL; + if (driver_streq(a, "-Tdata") || driver_streq(a, "-Tbss")) { + secname = driver_streq(a, "-Tdata") ? ".data" : ".bss"; + if (++i >= argc) { + driver_errf(LD_TOOL, "%s requires an address", a); + return 1; + } + addr = argv[i]; + } else if ((addr = arg_eq_value(a, "-Tdata")) != NULL) { + secname = ".data"; + } else if ((addr = arg_eq_value(a, "-Tbss")) != NULL) { + secname = ".bss"; + } + if (secname) { + if (ld_record_section_addr(o, secname, addr) != 0) return 1; + continue; + } + } + if (driver_streq(a, "--defsym")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "--defsym requires NAME=EXPR"); + return 1; + } + if (ld_record_defsym(o, argv[i], driver_strlen(argv[i])) != 0) return 1; + continue; + } + if ((val = arg_eq_value(a, "--defsym")) != NULL) { + if (ld_record_defsym(o, val, driver_strlen(val)) != 0) return 1; + continue; + } + if (driver_streq(a, "--section-start")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "--section-start requires .NAME=ADDR"); + return 1; + } + if (ld_record_section_start(o, argv[i], driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if ((val = arg_eq_value(a, "--section-start")) != NULL) { + if (ld_record_section_start(o, val, driver_strlen(val)) != 0) return 1; + continue; + } + if (driver_streq(a, "--orphan-handling")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "--orphan-handling requires a mode"); + return 1; + } + if (ld_record_orphan_handling(o, argv[i], driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if ((val = arg_eq_value(a, "--orphan-handling")) != NULL) { + if (ld_record_orphan_handling(o, val, driver_strlen(val)) != 0) return 1; + continue; + } + if (driver_streq(a, "--fatal-warnings")) { + o->fatal_warnings = 1; + continue; + } + if (driver_streq(a, "--no-fatal-warnings")) { + o->fatal_warnings = 0; + continue; + } + if (driver_streq(a, "--print-memory-usage")) { + o->print_memory_usage = 1; + continue; + } + if (driver_streq(a, "--cref")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "--cref requires a path"); + return 1; + } + { + const char* owned; + if (ld_span_to_owned_cstr(o, argv[i], driver_strlen(argv[i]), &owned) != + 0) + return 1; + o->cref_path = owned; + } + continue; + } + if ((val = arg_eq_value(a, "--cref")) != NULL) { + const char* owned; + if (ld_span_to_owned_cstr(o, val, driver_strlen(val), &owned) != 0) + return 1; + o->cref_path = owned; + continue; + } /* -nostdlib / --no-default-libs: do not auto-resolve and link kit's * compiler runtime. A freestanding image (e.g. riscv32-none-elf) supplies * its own libkit_rt.a on the command line, and a pure-linker invocation @@ -2654,6 +2988,11 @@ static void ld_options_release(LdOptions* o) { bound * sizeof(*o->owned_path_sizes)); driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths)); driver_free(o->env, o->rpath_links, bound * sizeof(*o->rpath_links)); + if (o->defsyms) + driver_free(o->env, o->defsyms, o->cap_defsyms * sizeof(*o->defsyms)); + if (o->section_starts) + driver_free(o->env, o->section_starts, + o->cap_section_starts * sizeof(*o->section_starts)); } /* ---------- input loading ---------- */ @@ -2712,6 +3051,43 @@ static KitStatus ld_write_link_report(const LdOptions* o, const KitFileIO* io, return st; } +/* --cref FILE: cross-reference table side file. */ +static KitStatus ld_write_cref(const LdOptions* o, const KitFileIO* io, + KitLinkSession* link) { + KitWriter* w = NULL; + KitStatus st; + if (!o->cref_path) return KIT_OK; + if (io->open_writer(io->user, o->cref_path, &w) != KIT_OK) { + driver_errf(LD_TOOL, "failed to open cref output: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(o->cref_path))); + return KIT_IO; + } + st = kit_link_session_write_cref(link, w); + if (st == KIT_OK) st = kit_writer_status(w); + kit_writer_close(w); + if (st != KIT_OK) + driver_errf(LD_TOOL, "failed to write cref: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(o->cref_path))); + return st; +} + +/* --print-memory-usage: GNU-ld-style per-region summary to stdout. */ +static KitStatus ld_print_memory_usage(LdOptions* o, KitLinkSession* link) { + KitWriter* w; + KitStatus st; + if (!o->print_memory_usage) return KIT_OK; + w = driver_stdout_writer(o->env); + if (!w) { + driver_errf(LD_TOOL, "failed to print memory usage"); + return KIT_IO; + } + st = kit_link_session_write_memory_usage(link, w); + if (st == KIT_OK) st = kit_writer_status(w); + kit_writer_close(w); + if (st != KIT_OK) driver_errf(LD_TOOL, "failed to print memory usage"); + return st; +} + static int ld_append_hosted_input(LdOptions* o, const DriverHostedInput* in, uint32_t insert_pos, int insert) { switch ((DriverHostedInputKind)in->kind) { @@ -2895,6 +3271,27 @@ static int ld_run_link(LdOptions* o) { goto out; } + /* Strict freestanding policy: every input object must agree with the link + * target's arch and object format. A `*-none-*` link silently mis-linking a + * foreign-arch object (the default detection trusts obj[0]'s arch) is a hard + * error here. */ + if (!o->relocatable && o->target.os == KIT_OS_FREESTANDING) { + uint32_t oi; + for (oi = 0; oi < initial_nobject_files; ++oi) { + KitTargetSpec t; + if (kit_detect_target(obj_lf[oi].data.data, obj_lf[oi].data.size, &t) != + KIT_OK) + continue; + if (t.arch != o->target.arch || t.obj != o->target.obj) { + driver_errf(LD_TOOL, + "freestanding link: input '%s' arch/format does not match " + "the link target", + o->object_files[oi]); + goto out; + } + } + } + if (ld_apply_hosted_before_after(o) != 0) goto out; /* Hosted dynamic link: default to PIE, matching what `kit cc` does. @@ -3061,6 +3458,12 @@ static int ld_run_link(LdOptions* o) { lopts.entry = kit_slice_cstr(o->entry); lopts.text_base_set = o->text_base_set; lopts.text_base = o->text_base; + lopts.defsyms = o->defsyms; + lopts.ndefsyms = o->ndefsyms; + lopts.section_starts = o->section_starts; + lopts.nsection_starts = o->nsection_starts; + lopts.orphan_handling = o->orphan_handling; + lopts.fatal_warnings = o->fatal_warnings ? true : false; lopts.linker_script = script; lopts.build_id_mode = o->build_id_mode; lopts.build_id_bytes = o->build_id_bytes; @@ -3097,6 +3500,14 @@ static int ld_run_link(LdOptions* o) { /* Executable links are strict by default; --allow-undefined is the * explicit escape hatch for freestanding bring-up and diagnostics. */ lopts.allow_undefined = o->allow_undefined ? 1 : 0; + /* Strict freestanding policy: a `*-none-*` (FREESTANDING) static non-PIE + * executable must reject dynamic-link artifacts and cross-input + * target/format mismatches. Only the EXE lane imposes it; -r/-shared do + * not. --allow-undefined still relaxes undef references but not these + * structural rejections. */ + if (lopts.output_kind == KIT_LINK_OUTPUT_EXE && !lopts.pie && + o->target.os == KIT_OS_FREESTANDING) + lopts.freestanding_strict = true; (void)o->export_dynamic; st = kit_link_session_new(compiler, &lopts, &link); @@ -3134,6 +3545,8 @@ static int ld_run_link(LdOptions* o) { if (st == KIT_OK) st = ld_write_link_report(o, io, link, o->map_path, 0); if (st == KIT_OK) st = ld_write_link_report(o, io, link, o->symbols_path, 1); + if (st == KIT_OK) st = ld_write_cref(o, io, link); + if (st == KIT_OK) st = ld_print_memory_usage(o, link); rc = st == KIT_OK ? 0 : 1; } diff --git a/driver/lib/link_flags.c b/driver/lib/link_flags.c @@ -241,6 +241,209 @@ int driver_link_flags_record_text_base(DriverLinkFlags* lf, const char* val) { return 0; } +/* Is this span a numeric (hex/dec) literal, or a symbol name? Used to decide + * the --defsym RHS form. A literal starts with a digit (0x.. or decimal). */ +static int lf_span_is_number(const char* s, size_t n) { + if (n == 0) return 0; + return s[0] >= '0' && s[0] <= '9'; +} + +/* Parse a possibly-NUL-unterminated numeric span as u64. */ +static int lf_parse_u64_span(DriverLinkFlags* lf, const char* s, size_t n, + uint64_t* out) { + char* buf = lf_dup_owned(lf, s, n); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + return driver_parse_u64(buf, out); +} + +static int lf_grow_defsyms(DriverLinkFlags* lf) { + uint32_t old_cap = lf->cap_defsyms; + uint32_t new_cap = old_cap ? old_cap * 2u : 4u; + KitLinkDefsym* nd; + if (new_cap <= old_cap) return 1; + nd = (KitLinkDefsym*)driver_alloc_zeroed(lf->env, + (size_t)new_cap * sizeof(*nd)); + if (!nd) return 1; + if (lf->defsyms) { + driver_memcpy(nd, lf->defsyms, (size_t)lf->ndefsyms * sizeof(*lf->defsyms)); + driver_free(lf->env, lf->defsyms, (size_t)old_cap * sizeof(*lf->defsyms)); + } + lf->defsyms = nd; + lf->cap_defsyms = new_cap; + return 0; +} + +static int lf_grow_section_starts(DriverLinkFlags* lf) { + uint32_t old_cap = lf->cap_section_starts; + uint32_t new_cap = old_cap ? old_cap * 2u : 4u; + KitLinkSectionStart* ns; + if (new_cap <= old_cap) return 1; + ns = (KitLinkSectionStart*)driver_alloc_zeroed( + lf->env, (size_t)new_cap * sizeof(*ns)); + if (!ns) return 1; + if (lf->section_starts) { + driver_memcpy(ns, lf->section_starts, + (size_t)lf->nsection_starts * sizeof(*lf->section_starts)); + driver_free(lf->env, lf->section_starts, + (size_t)old_cap * sizeof(*lf->section_starts)); + } + lf->section_starts = ns; + lf->cap_section_starts = new_cap; + return 0; +} + +int driver_link_flags_record_defsym(DriverLinkFlags* lf, const char* val, + size_t n) { + size_t eq = 0; + const char* name; + size_t name_len; + const char* rhs; + size_t rhs_len; + KitLinkDefsym* d; + char* name_buf; + while (eq < n && val[eq] != '=') ++eq; + if (eq == 0 || eq >= n) { + driver_errf(lf->tool, "--defsym requires NAME=EXPR: %.*s", (int)n, val); + return 1; + } + name = val; + name_len = eq; + rhs = val + eq + 1u; + rhs_len = n - eq - 1u; + if (rhs_len == 0) { + driver_errf(lf->tool, "--defsym requires a value: %.*s", (int)n, val); + return 1; + } + if (lf->ndefsyms >= lf->cap_defsyms && lf_grow_defsyms(lf) != 0) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + name_buf = lf_dup_owned(lf, name, name_len); + if (!name_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + d = &lf->defsyms[lf->ndefsyms]; + memset(d, 0, sizeof(*d)); + d->name = kit_slice_cstr(name_buf); + if (lf_span_is_number(rhs, rhs_len)) { + uint64_t v; + if (lf_parse_u64_span(lf, rhs, rhs_len, &v) != 0) { + driver_errf(lf->tool, "--defsym: invalid value: %.*s", (int)rhs_len, rhs); + return 1; + } + d->value = v; + } else { + char* alias_buf = lf_dup_owned(lf, rhs, rhs_len); + if (!alias_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + d->alias = kit_slice_cstr(alias_buf); + d->value = 0; + } + lf->ndefsyms++; + return 0; +} + +int driver_link_flags_record_section_addr(DriverLinkFlags* lf, + const char* secname, + const char* addr) { + uint64_t v; + KitLinkSectionStart* ss; + char* name_buf; + if (!secname || !*secname || !addr || !*addr) { + driver_errf(lf->tool, "--section-start requires .NAME=ADDR"); + return 1; + } + if (driver_parse_u64(addr, &v) != 0) { + driver_errf(lf->tool, "--section-start: invalid address: %s", addr); + return 1; + } + if (lf->nsection_starts >= lf->cap_section_starts && + lf_grow_section_starts(lf) != 0) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + name_buf = lf_dup_owned(lf, secname, driver_strlen(secname)); + if (!name_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + ss = &lf->section_starts[lf->nsection_starts]; + memset(ss, 0, sizeof(*ss)); + ss->name = kit_slice_cstr(name_buf); + ss->addr = v; + lf->nsection_starts++; + return 0; +} + +int driver_link_flags_record_section_start(DriverLinkFlags* lf, const char* val, + size_t n) { + /* --section-start=.NAME=ADDR — split on the LAST '='. */ + size_t eq = n; + size_t i; + char* name_buf; + char* addr_buf; + int rc; + for (i = 0; i < n; ++i) + if (val[i] == '=') eq = i; + if (eq == n || eq == 0) { + driver_errf(lf->tool, "--section-start requires .NAME=ADDR: %.*s", (int)n, + val); + return 1; + } + name_buf = lf_dup_owned(lf, val, eq); + addr_buf = lf_dup_owned(lf, val + eq + 1u, n - eq - 1u); + if (!name_buf || !addr_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + rc = driver_link_flags_record_section_addr(lf, name_buf, addr_buf); + return rc; +} + +int driver_link_flags_record_orphan_handling(DriverLinkFlags* lf, + const char* val, size_t n) { + if (lf_tok_eq(val, n, "place")) { + lf->orphan_handling = KIT_LINK_ORPHAN_PLACE; + return 0; + } + if (lf_tok_eq(val, n, "warn")) { + lf->orphan_handling = KIT_LINK_ORPHAN_WARN; + return 0; + } + if (lf_tok_eq(val, n, "error")) { + lf->orphan_handling = KIT_LINK_ORPHAN_ERROR; + return 0; + } + if (lf_tok_eq(val, n, "discard")) { + lf->orphan_handling = KIT_LINK_ORPHAN_DISCARD; + return 0; + } + driver_errf(lf->tool, "unsupported --orphan-handling: %.*s", (int)n, val); + return 1; +} + +int driver_link_flags_record_cref(DriverLinkFlags* lf, const char* val, + size_t n) { + char* buf; + if (!val || n == 0) { + driver_errf(lf->tool, "--cref requires a non-empty path"); + return 1; + } + buf = lf_dup_owned(lf, val, n); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + lf->cref_path = buf; + return 0; +} + int driver_link_flags_record_z(DriverLinkFlags* lf, const char* val, size_t n) { if (lf_tok_eq(val, n, "defs")) { lf->allow_undefined = 0; @@ -313,6 +516,12 @@ void driver_link_flags_fini(DriverLinkFlags* lf) { driver_free( lf->env, lf->owned_string_sizes, (size_t)lf->cap_owned_strings * sizeof(*lf->owned_string_sizes)); + if (lf->defsyms) + driver_free(lf->env, lf->defsyms, + (size_t)lf->cap_defsyms * sizeof(*lf->defsyms)); + if (lf->section_starts) + driver_free(lf->env, lf->section_starts, + (size_t)lf->cap_section_starts * sizeof(*lf->section_starts)); memset(lf, 0, sizeof(*lf)); } @@ -344,6 +553,10 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { int expect_map = 0; int expect_symbols = 0; int expect_ttext = 0; + int expect_defsym = 0; + int expect_section_start = 0; + int expect_cref = 0; + int expect_orphan = 0; while (*p) { const char* tok = p; size_t n = 0; @@ -351,7 +564,8 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { p = tok + n + (tok[n] == ',' ? 1u : 0u); if (expect_rpath || expect_soname || expect_interp || expect_subsystem || - expect_z || expect_map || expect_symbols || expect_ttext) { + expect_z || expect_map || expect_symbols || expect_ttext || + expect_defsym || expect_section_start || expect_cref || expect_orphan) { int rc = 0; if (expect_rpath) rc = lf_record_rpath(lf, tok, n); @@ -367,6 +581,14 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { rc = driver_link_flags_record_map(lf, tok, n); else if (expect_symbols) rc = driver_link_flags_record_symbols(lf, tok, n); + else if (expect_defsym) + rc = driver_link_flags_record_defsym(lf, tok, n); + else if (expect_section_start) + rc = driver_link_flags_record_section_start(lf, tok, n); + else if (expect_cref) + rc = driver_link_flags_record_cref(lf, tok, n); + else if (expect_orphan) + rc = driver_link_flags_record_orphan_handling(lf, tok, n); else if (expect_ttext) { char* buf = lf_dup_owned(lf, tok, n); if (!buf) { @@ -377,7 +599,9 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { } if (rc != 0) return 1; expect_rpath = expect_soname = expect_interp = expect_subsystem = - expect_z = expect_map = expect_symbols = expect_ttext = 0; + expect_z = expect_map = expect_symbols = expect_ttext = + expect_defsym = expect_section_start = expect_cref = + expect_orphan = 0; continue; } @@ -504,12 +728,108 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { return 1; continue; } + /* -Tdata / -Tbss: section base overrides, folded into section_starts. */ + if (lf_tok_eq(tok, n, "-Tdata") || lf_tok_eq(tok, n, "-Tbss")) { + /* The next comma token is the address. Reuse the section-start consume + * via a synthesized ".data=ADDR" / ".bss=ADDR" on the following token; + * simplest is a dedicated tiny state, but section_start expects NAME=ADDR. + * Stash the chosen section name and read the address as a fresh token. */ + const char* secname = lf_tok_eq(tok, n, "-Tdata") ? ".data" : ".bss"; + const char* atok = p; + size_t an = 0; + char* addr_buf; + if (!*atok) { + driver_errf(lf->tool, "%.*s requires an address", (int)n, tok); + return 1; + } + while (atok[an] && atok[an] != ',') ++an; + p = atok + an + (atok[an] == ',' ? 1u : 0u); + addr_buf = lf_dup_owned(lf, atok, an); + if (!addr_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + if (driver_link_flags_record_section_addr(lf, secname, addr_buf) != 0) + return 1; + continue; + } + if (lf_tok_prefix(tok, n, "-Tdata=")) { + char* addr_buf = lf_dup_owned(lf, tok + 7, n - 7u); + if (!addr_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + if (driver_link_flags_record_section_addr(lf, ".data", addr_buf) != 0) + return 1; + continue; + } + if (lf_tok_prefix(tok, n, "-Tbss=")) { + char* addr_buf = lf_dup_owned(lf, tok + 6, n - 6u); + if (!addr_buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + if (driver_link_flags_record_section_addr(lf, ".bss", addr_buf) != 0) + return 1; + continue; + } + /* --defsym NAME=EXPR */ + if (lf_tok_prefix(tok, n, "--defsym=")) { + if (driver_link_flags_record_defsym(lf, tok + 9, n - 9u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "--defsym")) { + expect_defsym = 1; + continue; + } + /* --section-start=.NAME=ADDR */ + if (lf_tok_prefix(tok, n, "--section-start=")) { + if (driver_link_flags_record_section_start(lf, tok + 16, n - 16u) != 0) + return 1; + continue; + } + if (lf_tok_eq(tok, n, "--section-start")) { + expect_section_start = 1; + continue; + } + /* --cref [FILE] */ + if (lf_tok_prefix(tok, n, "--cref=")) { + if (driver_link_flags_record_cref(lf, tok + 7, n - 7u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "--cref")) { + expect_cref = 1; + continue; + } + /* --orphan-handling=MODE */ + if (lf_tok_prefix(tok, n, "--orphan-handling=")) { + if (driver_link_flags_record_orphan_handling(lf, tok + 18, n - 18u) != 0) + return 1; + continue; + } + if (lf_tok_eq(tok, n, "--orphan-handling")) { + expect_orphan = 1; + continue; + } + if (lf_tok_eq(tok, n, "--fatal-warnings")) { + lf->fatal_warnings = 1; + continue; + } + if (lf_tok_eq(tok, n, "--no-fatal-warnings")) { + lf->fatal_warnings = 0; + continue; + } + if (lf_tok_eq(tok, n, "--print-memory-usage")) { + lf->print_memory_usage = 1; + continue; + } driver_errf(lf->tool, "unsupported -Wl, token: %.*s", (int)n, tok); return 1; } if (expect_rpath || expect_soname || expect_interp || expect_subsystem || - expect_z || expect_map || expect_symbols || expect_ttext) { + expect_z || expect_map || expect_symbols || expect_ttext || + expect_defsym || expect_section_start || expect_cref || expect_orphan) { driver_errf(lf->tool, "-Wl option requires another comma argument"); return 1; } @@ -547,6 +867,12 @@ int driver_link_flags_fill_options(const DriverLinkFlags* lf, lopts->strip_debug = lf->strip_debug != 0; lopts->text_base_set = lf->text_base_set != 0; lopts->text_base = lf->text_base; + lopts->defsyms = lf->defsyms; + lopts->ndefsyms = lf->ndefsyms; + lopts->section_starts = lf->section_starts; + lopts->nsection_starts = lf->nsection_starts; + lopts->orphan_handling = lf->orphan_handling; + lopts->fatal_warnings = lf->fatal_warnings != 0; lopts->pie = driver_link_pie(target, explicit_pie, shared, relocatable) != 0; lopts->pe_subsystem = lf->pe_subsystem; lopts->interp_path = kit_slice_cstr(lf->interp_path); diff --git a/driver/lib/link_flags.h b/driver/lib/link_flags.h @@ -28,6 +28,19 @@ typedef struct DriverLinkFlags { const char* map_path; const char* symbols_path; uint8_t symbols_format; /* KitLinkSymbolsFormat */ + const char* cref_path; /* --cref FILE: cross-reference table */ + int print_memory_usage; /* --print-memory-usage */ + uint8_t orphan_handling; /* KitLinkOrphanHandling */ + int fatal_warnings; /* --fatal-warnings */ + + /* --defsym NAME=EXPR. owned name/alias strings live in owned_strings. */ + KitLinkDefsym* defsyms; + uint32_t ndefsyms; + uint32_t cap_defsyms; + /* --section-start / -Tdata / -Tbss section address overrides. */ + KitLinkSectionStart* section_starts; + uint32_t nsection_starts; + uint32_t cap_section_starts; uint8_t build_id_mode; /* KitBuildIdMode */ uint8_t* build_id_bytes; @@ -56,6 +69,19 @@ int driver_link_flags_record_symbols(DriverLinkFlags* lf, const char* val, int driver_link_flags_record_symbols_format(DriverLinkFlags* lf, const char* val, size_t n); int driver_link_flags_record_text_base(DriverLinkFlags* lf, const char* val); +/* --defsym NAME=EXPR (literal hex/dec or alias-symbol form). */ +int driver_link_flags_record_defsym(DriverLinkFlags* lf, const char* val, + size_t n); +/* --section-start=.name=ADDR; also -Tdata/-Tbss via secname=".data"/".bss". */ +int driver_link_flags_record_section_start(DriverLinkFlags* lf, const char* val, + size_t n); +int driver_link_flags_record_section_addr(DriverLinkFlags* lf, + const char* secname, + const char* addr); +int driver_link_flags_record_orphan_handling(DriverLinkFlags* lf, + const char* val, size_t n); +int driver_link_flags_record_cref(DriverLinkFlags* lf, const char* val, + size_t n); int driver_link_flags_fill_options(const DriverLinkFlags* lf, KitTargetSpec target, int explicit_pie,