kit

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

commit 45f7a80c6a18a62218cfd7492ec39295a6650cb9
parent bd090cab78208465416789a8da66de910a241f22
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 12:55:17 -0700

Kernel build + image pipeline: first pass (doc/plan/KERNEL.md)

Implements the first phases of the kernel-build roadmap: freestanding
kernels build through the existing build-exe/ld front doors, gain a
structured linker-script subset and deterministic side-files, and convert
to flat load images. End-to-end verified: build-exe -> freestanding ELF
(MEMORY/region placement, NOLOAD, custom entry) -> --map/--symbols ->
kit image / objcopy -O binary, with image and objcopy emitting
byte-identical bytes.

Compile/backend flags (no new compile subcommand; build-exe/cc only):
- -mno-red-zone now drives x86_64 frame selection (suppresses the
  red-zone leaf tier), threaded via KitCodeOptions.disabled_backend_features
  -> NativeTarget. Codegen effect locked in by test/opt/prologue_tier.sh.
- -mgeneral-regs-only rejects FP/SIMD types, literals, and builtins in the
  C frontend (the only path that reaches XMM in the backend).
- -fstack-protector* is rejected explicitly; -fno-stack-protector is the
  supported (default) mode. -fno-builtin accepted (kit only recognizes
  __builtin_-prefixed names, so there is nothing to suppress).
- -mcmodel=kernel as a conservative alias for the large code model.

Linker:
- Script subset grows MEMORY/ORIGIN/LENGTH, region placement (> REGION),
  VMA/LMA split (AT/AT> REGION), PHDRS/:phdr/FLAGS/FILEHDR, PROVIDE/
  PROVIDE_HIDDEN/HIDDEN, ASSERT, EXTERN, richer input patterns
  (EXCLUDE_FILE, *(.a .b)), fills, and the ALIGN/ADDR/LOADADDR/SIZEOF/
  DEFINED/ABSOLUTE expression helpers. OUTPUT_ARCH/OUTPUT_FORMAT validate.
- Deterministic side-files owned by the link session: --map (target,
  entry, segments, sections w/ VMA, symbols) and --symbols
  (--symbols-format=nm), symbols sorted by (value, name).
- Undefined-symbol policy: strict by default for executable/freestanding
  links, with --no-undefined / --allow-undefined / -z defs|nodefs and
  -Ttext plumbed through both ld and build-exe.

Image:
- New `kit image` command and shared src/obj/image.c emitter, plus an
  objcopy -O binary compatibility path over the same backend. First pass
  supports --format bin --from segments with --addr vaddr|paddr|lma,
  --base/--bias, --fill, --fail-on-holes/--max-hole, --align/--pad-to/
  --max-size. ObjSegInfo gains paddr (ELF p_paddr; vaddr elsewhere).

Tests: link_script_test (56 checks) for the script subset, build-exe
link-report/undef cases, image cases under test/tools, and the red-zone
codegen assertion. Public API gated by KIT_IMAGE_ENABLED.

First-pass limitations (layout silently handles only the common path):
NOLOAD with PROGBITS content, inter-section `.` assignments, multi-byte
fills, and multiple sections sharing one :phdr are not yet honored;
--format sections/rom, --metadata, --cref, --defsym, --section-start,
and --orphan-handling remain for later phases.

Diffstat:
Mdriver/cmd/build.c | 322++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/cmd/cc.c | 32++++++++++++++++++++++++++++++--
Adriver/cmd/image.c | 423+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/cmd/ld.c | 193++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mdriver/cmd/objcopy.c | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdriver/driver.h | 7+++++--
Mdriver/lib/link_flags.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdriver/lib/link_flags.h | 14++++++++++++++
Mdriver/main.c | 4++++
Minclude/kit/config.h | 2++
Minclude/kit/core.h | 4++++
Ainclude/kit/image.h | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/link.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kit/object.h | 1+
Minclude/kit/target.h | 7++++---
Mlang/c/c.c | 3++-
Mlang/c/parse/parse.c | 11++++++++++-
Mlang/c/parse/parse.h | 4+++-
Mlang/c/parse/parse_expr.c | 4++++
Mlang/c/parse/parse_priv.h | 2++
Mlang/c/parse/parse_type.c | 2++
Mmk/driver_srcs.mk | 1+
Mmk/lib_srcs.mk | 10++++++++--
Mmk/test.mk | 4+++-
Mmk/test_unit.mk | 2++
Msrc/api/config_stubs.c | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/api/image.c | 30++++++++++++++++++++++++++++++
Msrc/api/link.c | 360++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/api/object_file.c | 1+
Msrc/api/target.c | 2+-
Msrc/arch/native_target.h | 1+
Msrc/arch/x64/native.c | 9++++++---
Msrc/cg/native_direct_target.c | 1+
Msrc/link/link.c | 5+++++
Msrc/link/link.h | 8++++++++
Msrc/link/link_internal.h | 21+++++++++++++++++++++
Msrc/link/link_jit.c | 1+
Msrc/link/link_layout.c | 542+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/link/link_reloc_layout.c | 1+
Msrc/link/link_resolve.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/link/link_script.c | 915+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/obj/coff/read_image.c | 1+
Msrc/obj/elf/link.c | 20+++++++++++++++++---
Msrc/obj/elf/link_dyn.c | 3+++
Msrc/obj/elf/read.c | 2++
Asrc/obj/image.c | 410+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/obj/image.h | 11+++++++++++
Msrc/obj/macho/read.c | 1+
Msrc/obj/obj.h | 1+
Mtest/buildcmds/run.sh | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/driver/run.sh | 2+-
Atest/link/link_script_test.c | 323+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/opt/prologue_tier.sh | 9++++++++-
Mtest/tools/run.sh | 44++++++++++++++++++++++++++++++++++++++++++++
54 files changed, 4191 insertions(+), 152 deletions(-)

diff --git a/driver/cmd/build.c b/driver/cmd/build.c @@ -113,7 +113,9 @@ typedef struct BuildOptions { int pie; /* -pie */ int function_sections; /* -ffunction-sections */ int data_sections; /* -fdata-sections */ + int auto_var_init; /* -ftrivial-auto-var-init= (KitAutoVarInit) */ int lto; /* -flto/-fno-lto */ + uint64_t disabled_backend_features; uint8_t default_visibility; /* KitSymVis */ int warnings_are_errors; uint32_t max_errors; @@ -357,13 +359,32 @@ static int build_is_global_flag(const char* a) { driver_strneq(a, "-fpie", 5) || driver_strneq(a, "-fvisibility=", 13) || driver_streq(a, "-ffunction-sections") || - driver_streq(a, "-fdata-sections") || driver_streq(a, "-flto") || - driver_streq(a, "-fno-lto") || driver_streq(a, "-static") || - driver_streq(a, "-dynamic") || driver_streq(a, "-shared") || - driver_streq(a, "-pie") || driver_streq(a, "-no-pie") || - driver_streq(a, "-target") || driver_strneq(a, "--target", 8) || - driver_strneq(a, "-l", 2) || driver_strneq(a, "-L", 2) || - driver_streq(a, "-e") || driver_streq(a, "-T") || + driver_streq(a, "-fdata-sections") || + driver_strneq(a, "-ftrivial-auto-var-init=", 24) || + driver_streq(a, "-fno-builtin") || + driver_strneq(a, "-fno-builtin-", 13) || + driver_streq(a, "-fno-stack-protector") || + driver_strneq(a, "-fstack-protector", 17) || + driver_streq(a, "-flto") || driver_streq(a, "-fno-lto") || + driver_streq(a, "-static") || driver_streq(a, "-dynamic") || + driver_streq(a, "-shared") || driver_streq(a, "-pie") || + driver_streq(a, "-no-pie") || driver_streq(a, "-target") || + driver_strneq(a, "--target", 8) || driver_strneq(a, "-l", 2) || + driver_strneq(a, "-L", 2) || driver_streq(a, "-e") || + driver_streq(a, "-T") || + driver_streq(a, "-Ttext") || driver_strneq(a, "-Ttext=", 7) || + driver_streq(a, "--gc-sections") || + driver_streq(a, "--no-gc-sections") || + driver_streq(a, "--no-undefined") || + driver_streq(a, "--allow-undefined") || + driver_streq(a, "--allow-shlib-undefined") || + driver_streq(a, "-z") || + (a[0] == '-' && a[1] == 'z' && a[2] != '\0') || + driver_streq(a, "--map") || driver_strneq(a, "--map=", 6) || + driver_streq(a, "-Map") || driver_strneq(a, "-Map=", 5) || + driver_streq(a, "--symbols") || + driver_strneq(a, "--symbols=", 10) || + driver_strneq(a, "--symbols-format=", 17) || driver_strneq(a, "-Wl,", 4) || driver_strneq(a, "--build-id", 10) || driver_streq(a, "-Werror") || driver_strneq(a, "-fmax-errors=", 13) || driver_strneq(a, "-m", 2); @@ -546,10 +567,43 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { o->data_sections = 1; continue; } + if (driver_strneq(a, "-ftrivial-auto-var-init=", 24)) { + const char* mode = a + 24; + if (driver_streq(mode, "zero")) { + o->auto_var_init = KIT_AUTOVAR_ZERO; + } else if (driver_streq(mode, "uninitialized")) { + o->auto_var_init = KIT_AUTOVAR_UNINIT; + } else if (driver_streq(mode, "pattern")) { + driver_errf(o->tool, + "-ftrivial-auto-var-init=pattern is not yet supported; " + "use =zero"); + return 1; + } else { + driver_errf(o->tool, + "-ftrivial-auto-var-init=: unknown mode '%s' " + "(expected zero, pattern, or uninitialized)", + mode); + return 1; + } + continue; + } if (driver_streq(a, "-fno-data-sections")) { o->data_sections = 0; continue; } + if (driver_streq(a, "-fno-builtin") || + driver_strneq(a, "-fno-builtin-", 13)) { + continue; + } + if (driver_streq(a, "-fno-stack-protector")) { + continue; + } + if (driver_strneq(a, "-fstack-protector", 17)) { + driver_errf(o->tool, + "%.*s is not supported yet; use -fno-stack-protector", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return 1; + } if (driver_streq(a, "-flto")) { o->lto = 1; continue; @@ -658,6 +712,103 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { o->link.linker_script = argv[i]; continue; } + if (driver_streq(a, "-Ttext")) { + if (++i >= argc) { + driver_errf(o->tool, "-Ttext requires an argument"); + return 1; + } + if (driver_link_flags_record_text_base(&o->link, argv[i]) != 0) + return 1; + continue; + } + if (driver_strneq(a, "-Ttext=", 7)) { + 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, "--gc-sections")) { + o->link.gc_sections = 1; + continue; + } + if (driver_streq(a, "--no-gc-sections")) { + o->link.gc_sections = 0; + continue; + } + if (driver_streq(a, "--no-undefined")) { + o->link.allow_undefined = 0; + continue; + } + if (driver_streq(a, "--allow-undefined") || + driver_streq(a, "--allow-shlib-undefined")) { + o->link.allow_undefined = 1; + continue; + } + if (driver_streq(a, "-z")) { + if (++i >= argc) { + driver_errf(o->tool, "-z requires an argument"); + return 1; + } + if (driver_link_flags_record_z(&o->link, argv[i], + driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (a[0] == '-' && a[1] == 'z' && a[2] != '\0') { + if (driver_link_flags_record_z(&o->link, a + 2, + driver_strlen(a + 2)) != 0) + return 1; + continue; + } + if (driver_streq(a, "--map") || driver_streq(a, "-Map")) { + if (++i >= argc) { + driver_errf(o->tool, "%.*s requires an argument", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return 1; + } + if (driver_link_flags_record_map(&o->link, argv[i], + driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--map=", 6)) { + if (driver_link_flags_record_map(&o->link, a + 6, + driver_strlen(a + 6)) != 0) + return 1; + continue; + } + if (driver_strneq(a, "-Map=", 5)) { + if (driver_link_flags_record_map(&o->link, a + 5, + driver_strlen(a + 5)) != 0) + return 1; + continue; + } + if (driver_streq(a, "--symbols")) { + if (++i >= argc) { + driver_errf(o->tool, "--symbols requires an argument"); + return 1; + } + if (driver_link_flags_record_symbols(&o->link, argv[i], + driver_strlen(argv[i])) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--symbols=", 10)) { + if (driver_link_flags_record_symbols(&o->link, a + 10, + driver_strlen(a + 10)) != 0) + return 1; + continue; + } + if (driver_strneq(a, "--symbols-format=", 17)) { + if (driver_link_flags_record_symbols_format( + &o->link, a + 17, driver_strlen(a + 17)) != 0) + return 1; + continue; + } if (driver_strneq(a, "-Wl,", 4)) { if (driver_link_flags_record_wl(&o->link, a + 4) != 0) return 1; continue; @@ -674,6 +825,10 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { if (driver_link_flags_record_build_id(&o->link, a + 11) != 0) return 1; continue; } + if (driver_streq(a, "--build-id")) { + if (driver_link_flags_record_build_id(&o->link, "sha256") != 0) return 1; + continue; + } if (driver_streq(a, "-mwindows")) { o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI; continue; @@ -686,6 +841,22 @@ static int build_parse(int argc, char** argv, BuildOptions* o) { if (driver_record_mcmodel(&o->target, o->tool, a + 9) != 0) return 1; continue; } + if (driver_streq(a, "-mno-red-zone")) { + o->disabled_backend_features |= KIT_CG_BACKEND_RED_ZONE; + continue; + } + if (driver_streq(a, "-mred-zone")) { + o->disabled_backend_features &= ~KIT_CG_BACKEND_RED_ZONE; + continue; + } + if (driver_streq(a, "-mgeneral-regs-only")) { + o->disabled_backend_features |= KIT_CG_BACKEND_SIMD; + continue; + } + if (driver_streq(a, "-mno-general-regs-only")) { + o->disabled_backend_features &= ~KIT_CG_BACKEND_SIMD; + continue; + } /* Target. */ if (driver_streq(a, "-target") || driver_streq(a, "--target")) { @@ -983,6 +1154,8 @@ static void build_fill_code(const BuildOptions* o, KitCodeOptions* code) { code->default_visibility = o->default_visibility; code->function_sections = o->function_sections ? true : false; code->data_sections = o->data_sections ? true : false; + code->disabled_backend_features = o->disabled_backend_features; + code->trivial_auto_var_init = (uint8_t)o->auto_var_init; code->lto = o->lto ? true : false; code->epoch = o->epoch; } @@ -1152,6 +1325,123 @@ out: return rc; } +typedef struct BuildPreservedVec { + KitHeap* heap; + KitCgSym* syms; + uint32_t nsyms; + uint32_t cap; + int oom; +} BuildPreservedVec; + +static void build_preserved_vec_add(void* user, KitCgSym sym) { + BuildPreservedVec* v = (BuildPreservedVec*)user; + KitCgSym* ns; + uint32_t ncap; + if (!v || v->oom) return; + if (v->nsyms == v->cap) { + ncap = v->cap ? v->cap * 2u : 32u; + ns = (KitCgSym*)v->heap->realloc( + v->heap, v->syms, sizeof(*v->syms) * v->cap, + sizeof(*v->syms) * ncap, _Alignof(KitCgSym)); + if (!ns) { + v->oom = 1; + return; + } + v->syms = ns; + v->cap = ncap; + } + v->syms[v->nsyms++] = sym; +} + +static KitStatus build_link_add_inputs(KitLinkSession* link, + const KitLinkInputs* in) { + KitStatus st = KIT_OK; + uint32_t i; + if (!link || !in) return KIT_INVALID; + for (i = 0; i < in->norder && st == KIT_OK; ++i) { + const KitLinkInputOrder* ord = &in->order[i]; + switch ((KitLinkInputOrderKind)ord->kind) { + case KIT_LINK_INPUT_OBJ: + st = kit_link_session_add_obj(link, in->objs[ord->index]); + break; + case KIT_LINK_INPUT_OBJ_BYTES: + st = kit_link_session_add_obj_bytes(link, in->obj_names[ord->index], + &in->obj_bytes[ord->index]); + break; + case KIT_LINK_INPUT_ARCHIVE: + st = + kit_link_session_add_archive_bytes(link, &in->archives[ord->index]); + break; + case KIT_LINK_INPUT_DSO: + st = kit_link_session_add_dso_bytes(link, in->dso_names[ord->index], + &in->dso_bytes[ord->index]); + break; + } + } + return st; +} + +static int build_write_link_report(BuildOptions* o, const KitContext* ctx, + KitLinkSession* link, const char* path, + int symbols) { + KitWriter* w = NULL; + KitStatus st; + if (!path) return 0; + if (build_open_output(ctx, o->env, o->tool, path, &w) != 0) return 1; + st = symbols ? kit_link_session_write_symbols(link, o->link.symbols_format, w) + : kit_link_session_write_map(link, w); + if (st == KIT_OK) st = kit_writer_status(w); + kit_writer_close(w); + if (st != KIT_OK) { + driver_errf(o->tool, "failed to write %s: %.*s", + symbols ? "symbols" : "map", + KIT_SLICE_ARG(kit_slice_cstr(path))); + return 1; + } + return 0; +} + +static KitStatus build_link_with_lto_reports( + BuildOptions* o, KitCompiler* compiler, const KitContext* ctx, + const KitLinkSessionOptions* lopts, const KitLinkInputs* in, + KitBuildPendingLto* pending_lto, const KitBuildBatchOptions* batch, + KitWriter* out) { + KitLinkSession* link = NULL; + BuildPreservedVec preserved; + KitStatus st; + if (!compiler || !lopts || !in || !out) { + if (pending_lto && pending_lto->active) kit_build_lto_abort(pending_lto); + return KIT_INVALID; + } + memset(&preserved, 0, sizeof preserved); + preserved.heap = o->env->heap; + st = kit_link_session_new(compiler, lopts, &link); + if (st == KIT_OK) st = build_link_add_inputs(link, in); + if (st == KIT_OK && pending_lto && pending_lto->active) { + st = kit_link_session_visit_lto_preserved( + link, pending_lto->obj, pending_lto->cg, build_preserved_vec_add, + &preserved); + if (st == KIT_OK && preserved.oom) st = KIT_NOMEM; + if (st == KIT_OK) + st = kit_build_lto_finish(pending_lto, batch, preserved.syms, + preserved.nsyms); + } + if (st == KIT_OK) st = kit_link_session_emit(link, out); + if (st == KIT_OK && + build_write_link_report(o, ctx, link, o->link.map_path, 0) != 0) + st = KIT_ERR; + if (st == KIT_OK && + build_write_link_report(o, ctx, link, o->link.symbols_path, 1) != 0) + st = KIT_ERR; + kit_link_session_free(link); + if (preserved.syms) + preserved.heap->free(preserved.heap, preserved.syms, + sizeof(*preserved.syms) * preserved.cap); + if (st != KIT_OK && pending_lto && pending_lto->active) + kit_build_lto_abort(pending_lto); + return st; +} + /* build-exe / shared build-lib: compile sources, load link inputs, link. */ static int build_run_link(BuildOptions* o, KitCompiler* compiler, const KitContext* ctx, const KitCodeOptions* code, @@ -1296,8 +1586,8 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler, li.ndsos = o->inputs.ndsos; li.order = order; li.norder = norder; - st = kit_build_link_with_lto(compiler, &lopts, &li, &pending_lto, - &lto_batch, out_w); + st = build_link_with_lto_reports(o, compiler, ctx, &lopts, &li, + &pending_lto, &lto_batch, out_w); rc = (st == KIT_OK) ? 0 : 1; } @@ -1725,6 +2015,10 @@ static int build_validate(BuildOptions* o) { driver_errf(o->tool, "no input files"); return 1; } + if (o->kind != BUILD_OUT_EXE && (o->link.map_path || o->link.symbols_path)) { + driver_errf(o->tool, "--map/--symbols are only valid for build-exe"); + return 1; + } /* -dynamic selects the default executable link mode for build-exe and the * not-yet-supported shared-library mode for build-lib. -shared is the GCC @@ -1753,6 +2047,11 @@ static int build_validate(BuildOptions* o) { "(no .o / .a / -l inputs)"); return 1; } + if (o->link.map_path || o->link.symbols_path) { + driver_errf(o->tool, + "--map/--symbols require the native linker path"); + return 1; + } } return 0; } @@ -1911,6 +2210,11 @@ static int build_main(int argc, char** argv, int kind, const char* tool) { rc = 1; goto done; } + if (o.target.os == KIT_OS_FREESTANDING && o.inputs.ndsos) { + driver_errf(tool, "freestanding executable links do not accept DSO inputs"); + rc = 1; + goto done; + } if (!o.no_stdlib && !o.no_defaultlibs) { DriverRuntimeArchive rt = {0}; if (driver_runtime_prepare_archive(&env, tool, &runtime, o.target, diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c @@ -26,7 +26,7 @@ * -I -isystem -D -U * -M -MM -MD -MMD -MF -MT -MQ -MP * -target TRIPLE - * -fPIC -fPIE -fpic -fpie -mcmodel=small|medium|large + * -fPIC -fPIE -fpic -fpie -mcmodel=small|medium|large|kernel * -Werror -fmax-errors=N * --build-id=none|sha256|uuid|0xHEX * -ffile-prefix-map=old=new @@ -93,6 +93,7 @@ typedef struct CcOptions { int data_sections; /* -fdata-sections */ int auto_var_init; /* -ftrivial-auto-var-init= (KitAutoVarInit) */ int lto; /* -flto/-fno-lto */ + uint64_t disabled_backend_features; int warnings_are_errors; /* -Werror */ uint32_t max_errors; /* -fmax-errors=N */ KitTargetSpec target; /* -target / host */ @@ -750,6 +751,15 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { o->data_sections = 0; continue; } + if (driver_streq(a, "-fno-stack-protector")) { + continue; + } + if (driver_strneq(a, "-fstack-protector", 17)) { + driver_errf(CC_TOOL, + "%.*s is not supported yet; use -fno-stack-protector", + KIT_SLICE_ARG(kit_slice_cstr(a))); + return 1; + } if (driver_streq(a, "-flto")) { o->lto = 1; continue; @@ -762,7 +772,8 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { o->nostdinc = 1; continue; } - if (driver_streq(a, "-fno-builtin") || driver_streq(a, "-pipe") || + if (driver_streq(a, "-fno-builtin") || + driver_strneq(a, "-fno-builtin-", 13) || driver_streq(a, "-pipe") || driver_streq(a, "-pthread")) { continue; } @@ -965,6 +976,22 @@ static int cc_parse(int argc, char** argv, CcOptions* o) { if (driver_record_mcmodel(&o->target, CC_TOOL, a + 9) != 0) return 1; continue; } + if (driver_streq(a, "-mno-red-zone")) { + o->disabled_backend_features |= KIT_CG_BACKEND_RED_ZONE; + continue; + } + if (driver_streq(a, "-mred-zone")) { + o->disabled_backend_features &= ~KIT_CG_BACKEND_RED_ZONE; + continue; + } + if (driver_streq(a, "-mgeneral-regs-only")) { + o->disabled_backend_features |= KIT_CG_BACKEND_SIMD; + continue; + } + if (driver_streq(a, "-mno-general-regs-only")) { + o->disabled_backend_features &= ~KIT_CG_BACKEND_SIMD; + continue; + } { int tr = driver_target_features_try_consume(&o->target_features, o->env, CC_TOOL, argc, argv, &i); @@ -1826,6 +1853,7 @@ static void cc_fill_c_opts(const CcOptions* o, KitCCompileOptions* copts) { copts->code.emit_asm_source = o->emit_asm_source ? true : false; copts->code.function_sections = o->function_sections ? true : false; copts->code.data_sections = o->data_sections ? true : false; + copts->code.disabled_backend_features = o->disabled_backend_features; copts->code.trivial_auto_var_init = (uint8_t)o->auto_var_init; copts->code.lto = o->lto ? true : false; copts->code.epoch = o->epoch; diff --git a/driver/cmd/image.c b/driver/cmd/image.c @@ -0,0 +1,423 @@ +#include <kit/image.h> +#include <stdint.h> +#include <string.h> + +#include "driver.h" + +#define IMAGE_TOOL "image" + +typedef struct ImageCliOpts { + KitImageOptions image; + KitSlice* segments; + uint32_t cap_segments; + const char* input; + const char* output; +} ImageCliOpts; + +void driver_help_image(void) { + driver_printf( + "%.*s", + KIT_SLICE_ARG(KIT_SLICE_LIT( + "kit image — emit flat load images from linked objects\n" + "\n" + "USAGE\n" + " kit image [OPTIONS] INPUT [-o OUTPUT]\n" + "\n" + "FORMAT\n" + " --format bin emit a flat binary image (default)\n" + " --from segments derive bytes from loadable segments " + "(default)\n" + " --segment PT_LOAD select a segment kind/name (may repeat)\n" + "\n" + "ADDRESSING\n" + " --addr vaddr|paddr|lma address field used for layout " + "(default: vaddr)\n" + " --base ADDR output offset 0 corresponds to ADDR\n" + " --bias N add signed bias to selected addresses\n" + "\n" + "LAYOUT\n" + " --fill BYTE byte used for holes/padding (default: 0)\n" + " --fail-on-holes reject gaps between selected ranges\n" + " --max-hole SIZE reject any gap larger than SIZE\n" + " --align SIZE pad final size up to SIZE\n" + " --pad-to SIZE pad final size to SIZE\n" + " --max-size SIZE reject output larger than SIZE\n" + "\n" + "OUTPUT\n" + " -o, --output FILE write output to FILE (default: stdout)\n" + "\n" + "SIZE suffixes K, M, and G are accepted.\n" + "\n" + "EXIT CODES\n" + " 0 success 1 image/I/O error 2 bad " + "usage\n"))); +} + +static int take_value(int* i, int argc, char** argv, const char* flag, + const char** out) { + const char* a = argv[*i]; + size_t flen = kit_slice_cstr(flag).len; + if (driver_strneq(a, flag, flen) && a[flen] == '=') { + *out = a + flen + 1; + return 1; + } + if (driver_streq(a, flag)) { + if (*i + 1 >= argc) return -1; + *out = argv[++(*i)]; + return 1; + } + return 0; +} + +static int digit_value(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + c - 'a'; + if (c >= 'A' && c <= 'F') return 10 + c - 'A'; + return -1; +} + +static int parse_size(const char* s, uint64_t* out) { + size_t len; + size_t end; + size_t pos = 0; + uint64_t val = 0; + uint64_t mult = 1; + int base = 10; + + if (!s || !s[0] || !out) return -1; + len = driver_strlen(s); + end = len; + switch (s[end - 1]) { + case 'k': + case 'K': + mult = 1024ull; + --end; + break; + case 'm': + case 'M': + mult = 1024ull * 1024ull; + --end; + break; + case 'g': + case 'G': + mult = 1024ull * 1024ull * 1024ull; + --end; + break; + default: + break; + } + if (end == 0) return -1; + if (end >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + base = 16; + pos = 2; + if (pos >= end) return -1; + } + for (; pos < end; ++pos) { + int d = digit_value(s[pos]); + if (d < 0 || d >= base) return -1; + if (val > (UINT64_MAX - (uint64_t)d) / (uint64_t)base) return -1; + val = val * (uint64_t)base + (uint64_t)d; + } + if (val > UINT64_MAX / mult) return -1; + *out = val * mult; + return 0; +} + +static int parse_i64_size(const char* s, int64_t* out) { + int neg = 0; + uint64_t mag; + uint64_t min_mag = (uint64_t)INT64_MAX + 1u; + if (!s || !s[0] || !out) return -1; + if (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-'; + ++s; + } + if (!s[0] || parse_size(s, &mag) != 0) return -1; + if (neg) { + if (mag > min_mag) return -1; + if (mag == min_mag) { + *out = INT64_MIN; + } else { + *out = -(int64_t)mag; + } + } else { + if (mag > (uint64_t)INT64_MAX) return -1; + *out = (int64_t)mag; + } + return 0; +} + +static int parse_format(const char* s, uint32_t* out) { + if (driver_streq(s, "bin") || driver_streq(s, "binary")) { + *out = KIT_IMAGE_FORMAT_BIN; + return 0; + } + if (driver_streq(s, "rom")) { + *out = KIT_IMAGE_FORMAT_ROM; + return 0; + } + if (driver_streq(s, "sections")) { + *out = KIT_IMAGE_FORMAT_SECTIONS; + return 0; + } + if (driver_streq(s, "elf")) { + *out = KIT_IMAGE_FORMAT_ELF; + return 0; + } + return -1; +} + +static int parse_from(const char* s, uint32_t* out) { + if (driver_streq(s, "segments")) { + *out = KIT_IMAGE_FROM_SEGMENTS; + return 0; + } + if (driver_streq(s, "sections")) { + *out = KIT_IMAGE_FROM_SECTIONS; + return 0; + } + return -1; +} + +static int parse_addr(const char* s, uint32_t* out) { + if (driver_streq(s, "vaddr")) { + *out = KIT_IMAGE_ADDR_VADDR; + return 0; + } + if (driver_streq(s, "paddr")) { + *out = KIT_IMAGE_ADDR_PADDR; + return 0; + } + if (driver_streq(s, "lma")) { + *out = KIT_IMAGE_ADDR_LMA; + return 0; + } + return -1; +} + +static int push_segment(DriverEnv* env, ImageCliOpts* o, const char* name) { + if (o->image.nsegments >= o->cap_segments) { + uint32_t newcap = o->cap_segments ? o->cap_segments * 2u : 4u; + KitSlice* next; + if (newcap <= o->cap_segments) return -1; + next = + (KitSlice*)driver_alloc_zeroed(env, (size_t)newcap * sizeof(*next)); + if (!next) return -1; + if (o->segments) { + memcpy(next, o->segments, + (size_t)o->image.nsegments * sizeof(*next)); + driver_free(env, o->segments, + (size_t)o->cap_segments * sizeof(*o->segments)); + } + o->segments = next; + o->cap_segments = newcap; + o->image.segments = o->segments; + } + o->segments[o->image.nsegments++] = kit_slice_cstr(name); + return 0; +} + +int driver_image(int argc, char** argv) { + DriverEnv env; + KitContext ctx; + ImageCliOpts o; + DriverLoad load = {0}; + KitSlice input = KIT_SLICE_NULL; + KitWriter* out = NULL; + int own_writer = 0; + int rc = 2; + int i; + + if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + driver_help_image(); + return 0; + } + + memset(&o, 0, sizeof o); + o.image.format = KIT_IMAGE_FORMAT_BIN; + o.image.from = KIT_IMAGE_FROM_SEGMENTS; + o.image.addr = KIT_IMAGE_ADDR_VADDR; + o.image.fill = 0; + driver_env_init(&env); + ctx = driver_env_to_context(&env); + + for (i = 1; i < argc; ++i) { + const char* a = argv[i]; + const char* val = NULL; + int matched; + if (driver_streq(a, "-o")) { + if (i + 1 >= argc) { + driver_errf(IMAGE_TOOL, "%s requires a path", a); + goto done; + } + o.output = argv[++i]; + continue; + } + matched = take_value(&i, argc, argv, "--output", &val); + if (matched < 0) goto missing_value; + if (matched) { + o.output = val; + continue; + } + matched = take_value(&i, argc, argv, "--format", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_format(val, &o.image.format) != 0) { + driver_errf(IMAGE_TOOL, "unknown format: %s", val); + goto done; + } + continue; + } + matched = take_value(&i, argc, argv, "--from", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_from(val, &o.image.from) != 0) { + driver_errf(IMAGE_TOOL, "unknown source: %s", val); + goto done; + } + continue; + } + matched = take_value(&i, argc, argv, "--segment", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (push_segment(&env, &o, val) != 0) goto oom; + continue; + } + matched = take_value(&i, argc, argv, "--addr", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_addr(val, &o.image.addr) != 0) { + driver_errf(IMAGE_TOOL, "unknown address kind: %s", val); + goto done; + } + continue; + } + matched = take_value(&i, argc, argv, "--base", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_size(val, &o.image.base) != 0) { + driver_errf(IMAGE_TOOL, "invalid --base value: %s", val); + goto done; + } + o.image.have_base = true; + continue; + } + matched = take_value(&i, argc, argv, "--bias", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_i64_size(val, &o.image.bias) != 0) { + driver_errf(IMAGE_TOOL, "invalid --bias value: %s", val); + goto done; + } + continue; + } + matched = take_value(&i, argc, argv, "--fill", &val); + if (matched < 0) goto missing_value; + if (matched) { + uint64_t fill; + if (parse_size(val, &fill) != 0 || fill > 255u) { + driver_errf(IMAGE_TOOL, "invalid --fill byte: %s", val); + goto done; + } + o.image.fill = (uint8_t)fill; + continue; + } + if (driver_streq(a, "--fail-on-holes")) { + o.image.fail_on_holes = true; + continue; + } + matched = take_value(&i, argc, argv, "--max-hole", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_size(val, &o.image.max_hole) != 0) { + driver_errf(IMAGE_TOOL, "invalid --max-hole value: %s", val); + goto done; + } + o.image.have_max_hole = true; + continue; + } + matched = take_value(&i, argc, argv, "--align", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_size(val, &o.image.align) != 0 || o.image.align == 0) { + driver_errf(IMAGE_TOOL, "invalid --align value: %s", val); + goto done; + } + o.image.have_align = true; + continue; + } + matched = take_value(&i, argc, argv, "--pad-to", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_size(val, &o.image.pad_to) != 0) { + driver_errf(IMAGE_TOOL, "invalid --pad-to value: %s", val); + goto done; + } + o.image.have_pad_to = true; + continue; + } + matched = take_value(&i, argc, argv, "--max-size", &val); + if (matched < 0) goto missing_value; + if (matched) { + if (parse_size(val, &o.image.max_size) != 0) { + driver_errf(IMAGE_TOOL, "invalid --max-size value: %s", val); + goto done; + } + o.image.have_max_size = true; + continue; + } + if (a[0] == '-' && a[1] != '\0') { + driver_errf(IMAGE_TOOL, "unknown option: %s", a); + goto done; + } + if (o.input) { + driver_errf(IMAGE_TOOL, "only one input may be given"); + goto done; + } + o.input = a; + } + + if (!o.input) { + driver_errf(IMAGE_TOOL, "missing input file"); + goto done; + } + + if (driver_load_bytes(&env.file_io, IMAGE_TOOL, o.input, &load, &input) != 0) { + rc = 1; + goto done; + } + + if (o.output) { + if (ctx.file_io->open_writer(ctx.file_io->user, o.output, &out) != KIT_OK) { + driver_errf(IMAGE_TOOL, "failed to open output: %s", o.output); + rc = 1; + goto done; + } + } else { + out = driver_stdout_writer(&env); + if (!out) goto oom; + } + own_writer = 1; + + rc = kit_image_emit_bytes(&ctx, kit_slice_cstr(o.input), &input, &o.image, + out, NULL) == KIT_OK + ? 0 + : 1; + if (rc != 0 && out) driver_writer_abort(out); + +done: + if (own_writer && out) kit_writer_close(out); + driver_release_bytes(&env.file_io, &load); + if (o.segments) + driver_free(&env, o.segments, (size_t)o.cap_segments * sizeof(*o.segments)); + driver_env_fini(&env); + return rc; + +missing_value: + driver_errf(IMAGE_TOOL, "%s requires a value", argv[i]); + goto done; +oom: + driver_errf(IMAGE_TOOL, "out of memory"); + rc = 1; + goto done; +} diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -135,7 +135,10 @@ typedef struct LdOptions { int export_dynamic; /* -E / --export-dynamic */ int gc_sections; /* --gc-sections / --no-gc-sections */ int strip_debug; /* -S / --strip-debug */ - int allow_undefined; /* shared output undefined-symbol policy */ + int allow_undefined; /* explicit unresolved-symbol escape hatch */ + const char* map_path; /* --map / -Map */ + const char* symbols_path; + uint8_t symbols_format; /* KitLinkSymbolsFormat */ 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 */ @@ -246,9 +249,12 @@ void driver_help_ld(void) { " (no-op for -shared; recorded for exe)\n" " --eh-frame-hdr Accepted for GNU ld compatibility\n" " -S, --strip-debug Omit debug info from linked output\n" - " --no-undefined Reject unresolved symbols in -shared " - "output\n" + " --no-undefined Reject unresolved symbols\n" + " --allow-undefined Permit unresolved strong symbols\n" " -z defs Same as --no-undefined\n" + " --map FILE, -Map FILE Write a deterministic link map\n" + " --symbols FILE Write post-link absolute symbols\n" + " --symbols-format=nm Symbol output format (default)\n" "\n" "BUILD ID\n" " --build-id Same as --build-id=sha256\n" @@ -284,7 +290,6 @@ static int ld_alloc_arrays(LdOptions* o, int argc) { return 1; } o->new_dtags = 1; - o->allow_undefined = 1; return 0; } @@ -798,6 +803,15 @@ static int ld_apply_z_option(LdOptions* o, const char* z, size_t n) { return 1; } +static int ld_record_symbols_format(LdOptions* o, const char* val, size_t n) { + if (n == 2 && driver_strneq(val, "nm", 2)) { + o->symbols_format = KIT_LINK_SYMBOLS_NM; + return 0; + } + driver_errf(LD_TOOL, "unsupported --symbols-format: %.*s", (int)n, val); + return 1; +} + static int ld_parse_wl(LdOptions* o, const char* arg) { const char* p = arg; int expect_rpath = 0; @@ -805,6 +819,8 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { int expect_interp = 0; int expect_z = 0; int expect_emulation = 0; + int expect_map = 0; + int expect_symbols = 0; while (*p) { const char* tok = p; size_t n = 0; @@ -812,7 +828,7 @@ 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_emulation || expect_map || expect_symbols) { if (expect_rpath) { const char* owned; if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; @@ -831,9 +847,25 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { const char* owned; if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; if (ld_set_gnu_emulation(o, owned) != 0) return 1; + } else if (expect_map) { + const char* owned; + if (n == 0) { + driver_errf(LD_TOOL, "--map requires a non-empty path"); + return 1; + } + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + o->map_path = owned; + } else if (expect_symbols) { + const char* owned; + if (n == 0) { + driver_errf(LD_TOOL, "--symbols requires a non-empty path"); + return 1; + } + if (ld_span_to_owned_cstr(o, tok, n, &owned) != 0) return 1; + o->symbols_path = owned; } expect_rpath = expect_soname = expect_interp = expect_z = - expect_emulation = 0; + expect_emulation = expect_map = expect_symbols = 0; continue; } @@ -846,6 +878,57 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { o->gc_sections = 0; continue; } + if (ld_tok_eq(tok, n, "--no-undefined")) { + o->allow_undefined = 0; + continue; + } + if (ld_tok_eq(tok, n, "--allow-undefined") || + ld_tok_eq(tok, n, "--allow-shlib-undefined")) { + o->allow_undefined = 1; + continue; + } + if (ld_tok_eq(tok, n, "--map") || ld_tok_eq(tok, n, "-Map")) { + expect_map = 1; + continue; + } + if (ld_tok_prefix(tok, n, "--map=")) { + const char* owned; + if (n == 6) { + driver_errf(LD_TOOL, "--map requires a non-empty path"); + return 1; + } + if (ld_span_to_owned_cstr(o, tok + 6, n - 6u, &owned) != 0) return 1; + o->map_path = owned; + continue; + } + if (ld_tok_prefix(tok, n, "-Map=")) { + const char* owned; + if (n == 5) { + driver_errf(LD_TOOL, "-Map requires a non-empty path"); + return 1; + } + if (ld_span_to_owned_cstr(o, tok + 5, n - 5u, &owned) != 0) return 1; + o->map_path = owned; + continue; + } + if (ld_tok_eq(tok, n, "--symbols")) { + expect_symbols = 1; + continue; + } + if (ld_tok_prefix(tok, n, "--symbols=")) { + const char* owned; + if (n == 10) { + driver_errf(LD_TOOL, "--symbols requires a non-empty path"); + return 1; + } + if (ld_span_to_owned_cstr(o, tok + 10, n - 10u, &owned) != 0) return 1; + o->symbols_path = owned; + continue; + } + if (ld_tok_prefix(tok, n, "--symbols-format=")) { + if (ld_record_symbols_format(o, tok + 17, n - 17u) != 0) return 1; + continue; + } if (ld_tok_eq(tok, n, "-Bstatic")) { o->cur_link_mode = KIT_LM_STATIC; continue; @@ -954,7 +1037,7 @@ static int ld_parse_wl(LdOptions* o, const char* arg) { return 1; } if (expect_rpath || expect_soname || expect_interp || expect_z || - expect_emulation) { + expect_emulation || expect_map || expect_symbols) { driver_errf(LD_TOOL, "-Wl option requires another comma argument"); return 1; } @@ -2302,6 +2385,58 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->gc_sections = 0; continue; } + if (driver_streq(a, "--map") || driver_streq(a, "-Map")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "%s requires an argument", a); + return 1; + } + if (!argv[i][0]) { + driver_errf(LD_TOOL, "%s requires a non-empty path", a); + return 1; + } + o->map_path = argv[i]; + continue; + } + if ((val = arg_eq_value(a, "--map")) != NULL) { + if (!val[0]) { + driver_errf(LD_TOOL, "--map requires a non-empty path"); + return 1; + } + o->map_path = val; + continue; + } + if ((val = arg_eq_value(a, "-Map")) != NULL) { + if (!val[0]) { + driver_errf(LD_TOOL, "-Map requires a non-empty path"); + return 1; + } + o->map_path = val; + continue; + } + if (driver_streq(a, "--symbols")) { + if (++i >= argc) { + driver_errf(LD_TOOL, "--symbols requires an argument"); + return 1; + } + if (!argv[i][0]) { + driver_errf(LD_TOOL, "--symbols requires a non-empty path"); + return 1; + } + o->symbols_path = argv[i]; + continue; + } + if ((val = arg_eq_value(a, "--symbols")) != NULL) { + if (!val[0]) { + driver_errf(LD_TOOL, "--symbols requires a non-empty path"); + return 1; + } + o->symbols_path = val; + continue; + } + if ((val = arg_eq_value(a, "--symbols-format")) != NULL) { + if (ld_record_symbols_format(o, val, driver_strlen(val)) != 0) return 1; + continue; + } if (driver_streq(a, "-S") || driver_streq(a, "--strip-debug")) { o->strip_debug = 1; continue; @@ -2328,7 +2463,8 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { o->allow_undefined = 0; continue; } - if (driver_streq(a, "--allow-shlib-undefined")) { + if (driver_streq(a, "--allow-undefined") || + driver_streq(a, "--allow-shlib-undefined")) { o->allow_undefined = 1; continue; } @@ -2475,6 +2611,10 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { driver_errf(LD_TOOL, "-r does not support linker scripts yet"); return 1; } + if (o->map_path || o->symbols_path) { + driver_errf(LD_TOOL, "--map/--symbols require executable output"); + return 1; + } } return 0; } @@ -2548,6 +2688,30 @@ static void release_all(LoadedFile* arr, uint32_t n) { for (i = 0; i < n; ++i) release_file(&arr[i]); } +static KitStatus ld_write_link_report(const LdOptions* o, const KitFileIO* io, + KitLinkSession* link, const char* path, + int symbols) { + KitWriter* w = NULL; + KitStatus st; + if (!path) return KIT_OK; + if (io->open_writer(io->user, path, &w) != KIT_OK) { + driver_errf(LD_TOOL, "failed to open %s output: %.*s", + symbols ? "symbols" : "map", + KIT_SLICE_ARG(kit_slice_cstr(path))); + return KIT_IO; + } + st = symbols ? kit_link_session_write_symbols(link, o->symbols_format, w) + : kit_link_session_write_map(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 %s: %.*s", + symbols ? "symbols" : "map", + KIT_SLICE_ARG(kit_slice_cstr(path))); + } + return st; +} + static int ld_append_hosted_input(LdOptions* o, const DriverHostedInput* in, uint32_t insert_pos, int insert) { switch ((DriverHostedInputKind)in->kind) { @@ -2725,6 +2889,12 @@ static int ld_run_link(LdOptions* o) { } } + if (!o->relocatable && o->target.os == KIT_OS_FREESTANDING && o->ndsos) { + driver_errf(LD_TOOL, + "freestanding executable links do not accept DSO inputs"); + goto out; + } + if (ld_apply_hosted_before_after(o) != 0) goto out; /* Hosted dynamic link: default to PIE, matching what `kit cc` does. @@ -2924,8 +3094,8 @@ static int ld_run_link(LdOptions* o) { lopts.rpaths = rpath_slices; lopts.nrpaths = o->nrpaths; } - /* By default shared output may resolve symbols against its loader at - * runtime; --no-undefined / -z defs tightens that policy. */ + /* 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; (void)o->export_dynamic; @@ -2961,6 +3131,9 @@ static int ld_run_link(LdOptions* o) { link, kit_slice_cstr(o->dsos[i].path), &dso_in[i]); } if (st == KIT_OK) st = kit_link_session_emit(link, writer); + 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); rc = st == KIT_OK ? 0 : 1; } diff --git a/driver/cmd/objcopy.c b/driver/cmd/objcopy.c @@ -1,5 +1,6 @@ #include <kit/archive.h> #include <kit/core.h> +#include <kit/image.h> #include <kit/object.h> #include <stdint.h> #include <string.h> @@ -63,7 +64,7 @@ void driver_help_objcopy(void) { " -O BFDNAME emit as a different format. " "Recognized\n" " names: elf*, mach-o / macho*, coff*, " - "wasm*\n" + "wasm*, binary\n" "\n" "EXIT CODES\n" " 0 success 1 I/O or strip error 2 bad " @@ -116,6 +117,7 @@ typedef struct CopyOpts { /* Format conversion */ int have_output_fmt; KitObjFmt output_fmt; + int output_binary; /* I/O */ const char* input; const char* output; @@ -221,6 +223,17 @@ static int parse_fmt_name(const char* name, KitObjFmt* out) { return -1; } +static int is_binary_fmt_name(const char* name) { + return driver_streq(name, "binary") || driver_streq(name, "bin") || + driver_streq(name, "raw"); +} + +static int has_object_transforms(const CopyOpts* opts) { + return opts->op != COPY_OP_NONE || opts->nremove || opts->nonly || + opts->nrename_sec || opts->nadd || opts->nupdate || opts->nredef || + opts->nglob || opts->nloc || opts->nweak; +} + /* Lookup a symbol by name; KIT_OBJ_SYMBOL_NONE if not found. */ static KitObjSymbol find_sym_id(KitObjFile* of, const char* name) { KitObjSymInfo si; @@ -439,6 +452,35 @@ static int copy_one_object(DriverEnv* env, const KitContext* ctx, return rc; } +static int copy_one_binary(const KitContext* ctx, const char* input_name, + const KitSlice* input, const char* output_path) { + KitImageOptions iopts; + KitWriter* w = NULL; + KitStatus st; + + memset(&iopts, 0, sizeof iopts); + iopts.format = KIT_IMAGE_FORMAT_BIN; + iopts.from = KIT_IMAGE_FROM_SEGMENTS; + iopts.addr = KIT_IMAGE_ADDR_VADDR; + iopts.fill = 0; + + if (ctx->file_io->open_writer(ctx->file_io->user, output_path, &w) != + KIT_OK) { + driver_errf(OBJCOPY_TOOL, "cannot open %.*s", + KIT_SLICE_ARG(kit_slice_cstr(output_path))); + return 1; + } + st = kit_image_emit_bytes(ctx, kit_slice_cstr(input_name), input, &iopts, w, + NULL); + if (st != KIT_OK || kit_writer_status(w) != KIT_OK) { + driver_writer_abort(w); + kit_writer_close(w); + return 1; + } + kit_writer_close(w); + return 0; +} + int driver_objcopy(int argc, char** argv) { DriverEnv env; KitContext ctx; @@ -482,13 +524,20 @@ int driver_objcopy(int argc, char** argv) { rc = 2; goto done; } - if (parse_fmt_name(argv[++i], &opts.output_fmt) != 0) { + ++i; + if (is_binary_fmt_name(argv[i])) { + opts.output_binary = 1; + opts.have_output_fmt = 0; + continue; + } + if (parse_fmt_name(argv[i], &opts.output_fmt) != 0) { driver_errf(OBJCOPY_TOOL, "unknown output format: %.*s", KIT_SLICE_ARG(kit_slice_cstr(argv[i]))); rc = 2; goto done; } opts.have_output_fmt = 1; + opts.output_binary = 0; continue; } matched = take_value(&i, argc, argv, "--remove-section", &val); @@ -615,6 +664,13 @@ int driver_objcopy(int argc, char** argv) { } out_path = opts.output ? opts.output : opts.input; + if (opts.output_binary && has_object_transforms(&opts)) { + driver_errf(OBJCOPY_TOOL, + "-O binary cannot be combined with object transform options"); + rc = 2; + goto done; + } + if (ctx.file_io->read_all(ctx.file_io->user, opts.input, &in_fd) != KIT_OK) { driver_errf(OBJCOPY_TOOL, "cannot read %.*s", KIT_SLICE_ARG(kit_slice_cstr(opts.input))); @@ -624,7 +680,11 @@ int driver_objcopy(int argc, char** argv) { input.data = in_fd.data; input.len = in_fd.size; - rc = copy_one_object(&env, &ctx, opts.input, &input, &opts, out_path); + if (opts.output_binary) { + rc = copy_one_binary(&ctx, opts.input, &input, out_path); + } else { + rc = copy_one_object(&env, &ctx, opts.input, &input, &opts, out_path); + } done: if (have_in) ctx.file_io->release(ctx.file_io->user, &in_fd); diff --git a/driver/driver.h b/driver/driver.h @@ -32,6 +32,7 @@ int driver_ar(int argc, char** argv); int driver_ranlib(int argc, char** argv); int driver_strip(int argc, char** argv); int driver_objcopy(int argc, char** argv); +int driver_image(int argc, char** argv); int driver_objdump(int argc, char** argv); int driver_dbg(int argc, char** argv); int driver_run(int argc, char** argv); @@ -74,6 +75,7 @@ void driver_help_ar(void); void driver_help_ranlib(void); void driver_help_strip(void); void driver_help_objcopy(void); +void driver_help_image(void); void driver_help_objdump(void); void driver_help_dbg(void); void driver_help_run(void); @@ -249,8 +251,9 @@ int driver_target_needs_sysroot_libdir(KitTargetSpec target); int driver_target_default_hosted_profile(KitTargetSpec target); /* Set `target->code_model` from a -mcmodel= value. Accepts the x86 spellings - * (small/medium/large) and the RISC-V aliases (medlow->small, medany->medium). - * Returns 0 on success, 1 on an unknown value (diagnostic via `tool`). */ + * (small/medium/large), the RISC-V aliases (medlow->small, medany->medium), + * and kernel as a conservative alias for large. Returns 0 on success, 1 on an + * unknown value (diagnostic via `tool`). */ int driver_record_mcmodel(KitTargetSpec* target, const char* tool, const char* val); diff --git a/driver/lib/link_flags.c b/driver/lib/link_flags.c @@ -188,6 +188,76 @@ static int lf_record_interp(DriverLinkFlags* lf, const char* s, size_t n) { return 0; } +int driver_link_flags_record_map(DriverLinkFlags* lf, const char* val, + size_t n) { + char* buf; + if (!val || n == 0) { + driver_errf(lf->tool, "--map 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->map_path = buf; + return 0; +} + +int driver_link_flags_record_symbols(DriverLinkFlags* lf, const char* val, + size_t n) { + char* buf; + if (!val || n == 0) { + driver_errf(lf->tool, "--symbols 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->symbols_path = buf; + return 0; +} + +int driver_link_flags_record_symbols_format(DriverLinkFlags* lf, + const char* val, size_t n) { + if (n == 2 && driver_strneq(val, "nm", 2)) { + lf->symbols_format = KIT_LINK_SYMBOLS_NM; + return 0; + } + driver_errf(lf->tool, "unsupported --symbols-format: %.*s", (int)n, val); + return 1; +} + +int driver_link_flags_record_text_base(DriverLinkFlags* lf, const char* val) { + uint64_t v; + if (!val || driver_parse_u64(val, &v) != 0) { + driver_errf(lf->tool, "-Ttext: invalid address: %s", val ? val : ""); + return 1; + } + lf->text_base = v; + lf->text_base_set = 1; + 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; + return 0; + } + if (lf_tok_eq(val, n, "nodefs")) { + lf->allow_undefined = 1; + return 0; + } + if (lf_tok_eq(val, n, "noexecstack") || lf_tok_eq(val, n, "relro") || + lf_tok_eq(val, n, "now") || lf_tok_eq(val, n, "text")) { + return 0; + } + driver_errf(lf->tool, "unsupported -z option: %.*s", (int)n, val); + return 1; +} + static int lf_subsystem_eq(const char* val, size_t n, const char* want) { size_t i; for (i = 0; want[i]; ++i) { @@ -270,13 +340,18 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { int expect_soname = 0; int expect_interp = 0; int expect_subsystem = 0; + int expect_z = 0; + int expect_map = 0; + int expect_symbols = 0; + int expect_ttext = 0; while (*p) { const char* tok = p; size_t n = 0; while (p[n] && p[n] != ',') ++n; p = tok + n + (tok[n] == ',' ? 1u : 0u); - if (expect_rpath || expect_soname || expect_interp || expect_subsystem) { + if (expect_rpath || expect_soname || expect_interp || expect_subsystem || + expect_z || expect_map || expect_symbols || expect_ttext) { int rc = 0; if (expect_rpath) rc = lf_record_rpath(lf, tok, n); @@ -286,8 +361,23 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { rc = lf_record_interp(lf, tok, n); else if (expect_subsystem) rc = driver_link_flags_record_pe_subsystem(lf, tok, n); + else if (expect_z) + rc = driver_link_flags_record_z(lf, tok, n); + else if (expect_map) + 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_ttext) { + char* buf = lf_dup_owned(lf, tok, n); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + rc = driver_link_flags_record_text_base(lf, buf); + } if (rc != 0) return 1; - expect_rpath = expect_soname = expect_interp = expect_subsystem = 0; + expect_rpath = expect_soname = expect_interp = expect_subsystem = + expect_z = expect_map = expect_symbols = expect_ttext = 0; continue; } @@ -335,6 +425,62 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { lf->strip_debug = 1; continue; } + if (lf_tok_eq(tok, n, "--no-undefined")) { + lf->allow_undefined = 0; + continue; + } + if (lf_tok_eq(tok, n, "--allow-undefined") || + lf_tok_eq(tok, n, "--allow-shlib-undefined")) { + lf->allow_undefined = 1; + continue; + } + if (lf_tok_eq(tok, n, "-z")) { + expect_z = 1; + continue; + } + if (n > 2 && tok[0] == '-' && tok[1] == 'z') { + if (driver_link_flags_record_z(lf, tok + 2, n - 2u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "-Ttext")) { + expect_ttext = 1; + continue; + } + if (lf_tok_prefix(tok, n, "-Ttext=")) { + char* buf = lf_dup_owned(lf, tok + 7, n - 7u); + if (!buf) { + driver_errf(lf->tool, "out of memory"); + return 1; + } + if (driver_link_flags_record_text_base(lf, buf) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "--map") || lf_tok_eq(tok, n, "-Map")) { + expect_map = 1; + continue; + } + if (lf_tok_prefix(tok, n, "--map=")) { + if (driver_link_flags_record_map(lf, tok + 6, n - 6u) != 0) return 1; + continue; + } + if (lf_tok_prefix(tok, n, "-Map=")) { + if (driver_link_flags_record_map(lf, tok + 5, n - 5u) != 0) return 1; + continue; + } + if (lf_tok_eq(tok, n, "--symbols")) { + expect_symbols = 1; + continue; + } + if (lf_tok_prefix(tok, n, "--symbols=")) { + if (driver_link_flags_record_symbols(lf, tok + 10, n - 10u) != 0) + return 1; + continue; + } + if (lf_tok_prefix(tok, n, "--symbols-format=")) { + if (driver_link_flags_record_symbols_format(lf, tok + 17, n - 17u) != 0) + return 1; + continue; + } if (lf_tok_prefix(tok, n, "--build-id=")) { if (lf_record_build_id_span(lf, tok + 11, n - 11u) != 0) return 1; continue; @@ -362,7 +508,8 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) { driver_errf(lf->tool, "unsupported -Wl, token: %.*s", (int)n, tok); return 1; } - if (expect_rpath || expect_soname || expect_interp || expect_subsystem) { + if (expect_rpath || expect_soname || expect_interp || expect_subsystem || + expect_z || expect_map || expect_symbols || expect_ttext) { driver_errf(lf->tool, "-Wl option requires another comma argument"); return 1; } @@ -398,6 +545,8 @@ int driver_link_flags_fill_options(const DriverLinkFlags* lf, lopts->build_id_len = lf->build_id_len; lopts->gc_sections = lf->gc_sections != 0; lopts->strip_debug = lf->strip_debug != 0; + lopts->text_base_set = lf->text_base_set != 0; + lopts->text_base = lf->text_base; 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); @@ -409,7 +558,7 @@ int driver_link_flags_fill_options(const DriverLinkFlags* lf, lopts->rpaths = rpath_slices; lopts->nrpaths = lf->nrpaths; } - lopts->allow_undefined = 1; + lopts->allow_undefined = lf->allow_undefined != 0; *rpath_slices_out = rpath_slices; return 0; } diff --git a/driver/lib/link_flags.h b/driver/lib/link_flags.h @@ -22,6 +22,12 @@ typedef struct DriverLinkFlags { int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */ int gc_sections; int strip_debug; + int allow_undefined; + int text_base_set; + uint64_t text_base; + const char* map_path; + const char* symbols_path; + uint8_t symbols_format; /* KitLinkSymbolsFormat */ uint8_t build_id_mode; /* KitBuildIdMode */ uint8_t* build_id_bytes; @@ -42,6 +48,14 @@ int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg); int driver_link_flags_record_build_id(DriverLinkFlags* lf, const char* val); int driver_link_flags_record_pe_subsystem(DriverLinkFlags* lf, const char* val, size_t n); +int driver_link_flags_record_z(DriverLinkFlags* lf, const char* val, size_t n); +int driver_link_flags_record_map(DriverLinkFlags* lf, const char* val, + size_t n); +int driver_link_flags_record_symbols(DriverLinkFlags* lf, const char* val, + size_t n); +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); int driver_link_flags_fill_options(const DriverLinkFlags* lf, KitTargetSpec target, int explicit_pie, diff --git a/driver/main.c b/driver/main.c @@ -86,6 +86,10 @@ static const DriverToolDesc driver_tools[] = { "Copy and transform an object file (rename / remove / format)", DRIVER_GROUP_TOOLCHAIN}, #endif +#if KIT_TOOL_IMAGE_ENABLED + {"image", driver_image, driver_help_image, + "Emit flat kernel/load images from linked objects", DRIVER_GROUP_TOOLCHAIN}, +#endif #if KIT_TOOL_OBJDUMP_ENABLED {"objdump", driver_objdump, driver_help_objdump, "Dump sections, symbols, disassembly, hex, and relocations", diff --git a/include/kit/config.h b/include/kit/config.h @@ -69,6 +69,7 @@ #define KIT_DISASM_ENABLED 1 #define KIT_DWARF_ENABLED 1 #define KIT_LINK_ENABLED 1 +#define KIT_IMAGE_ENABLED 1 #define KIT_JIT_ENABLED 1 #define KIT_DBG_ENABLED 1 #define KIT_EMU_ENABLED 1 @@ -115,6 +116,7 @@ #define KIT_TOOL_RANLIB_ENABLED 1 #define KIT_TOOL_STRIP_ENABLED 1 #define KIT_TOOL_OBJCOPY_ENABLED 1 +#define KIT_TOOL_IMAGE_ENABLED 1 #define KIT_TOOL_OBJDUMP_ENABLED 1 #define KIT_TOOL_DBG_ENABLED 1 #define KIT_TOOL_RUN_ENABLED 1 diff --git a/include/kit/core.h b/include/kit/core.h @@ -292,6 +292,10 @@ typedef struct KitCodeOptions { * per-symbol section when no explicit frontend section was requested. */ bool function_sections; bool data_sections; + /* Backend feature bits disabled for this compile (KitCgBackendFeatureFlag). + * Drivers use this for codegen-affecting machine policy such as + * -mno-red-zone and -mgeneral-regs-only. */ + uint64_t disabled_backend_features; /* KitAutoVarInit: implicitly initialize automatic variables that have no * explicit initializer. Frontends that lower locals honor it; others ignore * it. 0 (UNINIT) is the C default. */ diff --git a/include/kit/image.h b/include/kit/image.h @@ -0,0 +1,80 @@ +#ifndef KIT_IMAGE_H +#define KIT_IMAGE_H + +#include <kit/core.h> + +/* + * Kernel/load-image emission. + * + * The API consumes already-opened object/image state plus the original input + * bytes. It does not read files, choose boot protocols, or invoke emulators. + */ + +typedef enum KitImageFormat { + KIT_IMAGE_FORMAT_BIN = 0, + KIT_IMAGE_FORMAT_ROM, + KIT_IMAGE_FORMAT_SECTIONS, + KIT_IMAGE_FORMAT_ELF, +} KitImageFormat; + +typedef enum KitImageSource { + KIT_IMAGE_FROM_SEGMENTS = 0, + KIT_IMAGE_FROM_SECTIONS, +} KitImageSource; + +typedef enum KitImageAddrKind { + KIT_IMAGE_ADDR_VADDR = 0, + KIT_IMAGE_ADDR_PADDR, + KIT_IMAGE_ADDR_LMA, +} KitImageAddrKind; + +typedef struct KitImageOptions { + uint32_t format; /* KitImageFormat */ + uint32_t from; /* KitImageSource */ + uint32_t addr; /* KitImageAddrKind */ + + /* Segment selection. Empty means the format default: ELF PT_LOAD segments, + * or all mapped segments for formats that do not expose PT_* names. Names + * are matched against the neutral segment name; "PT_LOAD" and "LOAD" are + * treated equivalently. */ + const KitSlice* segments; + uint32_t nsegments; + + bool have_base; + uint64_t base; + + int64_t bias; + + uint8_t fill; + bool fail_on_holes; + bool have_max_hole; + uint64_t max_hole; + + bool have_align; + uint64_t align; + bool have_pad_to; + uint64_t pad_to; + bool have_max_size; + uint64_t max_size; +} KitImageOptions; + +typedef struct KitImageReport { + uint64_t base; + uint64_t size; + uint64_t payload_size; + uint64_t max_hole; + uint32_t nranges; + bool had_holes; +} KitImageReport; + +KIT_API KitStatus kit_image_emit(const KitContext*, KitObjFile*, + const KitSlice* object_bytes, + const KitImageOptions*, KitWriter* out, + KitImageReport* report_out); + +KIT_API KitStatus kit_image_emit_bytes(const KitContext*, KitSlice name, + const KitSlice* object_bytes, + const KitImageOptions*, KitWriter* out, + KitImageReport* report_out); + +#endif /* KIT_IMAGE_H */ diff --git a/include/kit/link.h b/include/kit/link.h @@ -40,8 +40,16 @@ typedef enum KitLinkExprKind { KIT_LE_SHL, KIT_LE_SHR, KIT_LE_ALIGN, + KIT_LE_BLOCK, KIT_LE_MAX, KIT_LE_MIN, + KIT_LE_NEG, + KIT_LE_ADDR, + KIT_LE_LOADADDR, + KIT_LE_SIZEOF, + KIT_LE_SIZEOF_HEADERS, + KIT_LE_DEFINED, + KIT_LE_ABSOLUTE, } KitLinkExprKind; struct KitLinkExpr { @@ -76,6 +84,8 @@ typedef struct KitLinkRegion { typedef struct KitLinkInputMatch { KitSlice file_pattern; /* empty means "*" */ KitSlice section_pattern; + const KitSlice* exclude_file_patterns; + uint32_t nexclude_file_patterns; int keep; } KitLinkInputMatch; @@ -83,6 +93,8 @@ typedef enum KitLinkAsnKind { KIT_LAS_DOT, KIT_LAS_SYM, KIT_LAS_PROVIDE, + KIT_LAS_PROVIDE_HIDDEN, + KIT_LAS_HIDDEN, } KitLinkAsnKind; typedef struct KitLinkAssignment { @@ -99,18 +111,45 @@ typedef struct KitLinkOutputSection { uint32_t ninputs; KitSlice region; KitSlice load_region; + const KitSlice* phdrs; + uint32_t nphdrs; const KitLinkAssignment* asns; uint32_t nasns; + const KitLinkExpr* subalign; + const KitLinkExpr* fill; + const KitLinkExpr* flags; + bool noload; } KitLinkOutputSection; +typedef struct KitLinkPhdr { + KitSlice name; + uint32_t type; + const KitLinkExpr* flags; + bool filehdr; + bool phdrs; +} KitLinkPhdr; + +typedef struct KitLinkAssertion { + const KitLinkExpr* expr; + KitSlice message; +} KitLinkAssertion; + typedef struct KitLinkScript { KitSlice entry; + KitSlice output_arch; + KitSlice output_format; const KitLinkRegion* regions; uint32_t nregions; + const KitLinkPhdr* phdrs; + uint32_t nphdrs; const KitLinkOutputSection* sections; uint32_t nsections; const KitLinkAssignment* top_asns; uint32_t ntop_asns; + const KitLinkAssertion* asserts; + uint32_t nasserts; + const KitSlice* externs; + uint32_t nexterns; } KitLinkScript; KIT_API KitStatus kit_link_script_parse(const KitContext*, KitSlice text, @@ -195,6 +234,10 @@ typedef enum KitLinkOutputKind { KIT_LINK_OUTPUT_JIT, } KitLinkOutputKind; +typedef enum KitLinkSymbolsFormat { + KIT_LINK_SYMBOLS_NM = 0, +} KitLinkSymbolsFormat; + typedef enum KitPeSubsystem { KIT_PE_SUBSYSTEM_DEFAULT = 0, KIT_PE_SUBSYSTEM_WINDOWS_GUI = 2, @@ -249,6 +292,10 @@ KIT_API KitStatus kit_link_session_visit_lto_preserved( KitLinkLtoPreservedCallback cb, void* user); KIT_API KitStatus kit_link_session_resolve(KitLinkSession*); KIT_API KitStatus kit_link_session_emit(KitLinkSession*, KitWriter* out); +KIT_API KitStatus kit_link_session_write_map(KitLinkSession*, KitWriter* out); +KIT_API KitStatus kit_link_session_write_symbols(KitLinkSession*, + uint8_t format, + KitWriter* out); KIT_API KitStatus kit_link_session_jit(KitLinkSession*, KitJit** out_jit); KIT_API void kit_link_session_free(KitLinkSession*); diff --git a/include/kit/object.h b/include/kit/object.h @@ -308,6 +308,7 @@ typedef struct KitObjImageInfo { typedef struct KitObjSegInfo { KitSlice name; /* PT_* spelling / Mach-O segname; empty if unnamed */ uint64_t vaddr; + uint64_t paddr; /* physical/load address where present; else vaddr */ uint64_t vsize; /* size in memory */ uint64_t file_off; /* offset of contents in the file */ uint64_t file_size; /* size on disk (< vsize when the segment has bss) */ diff --git a/include/kit/target.h b/include/kit/target.h @@ -59,9 +59,10 @@ KIT_API bool kit_target_from_triple(const char* triple, KitTargetSpec* out); KIT_API bool kit_target_to_triple(KitTargetSpec spec, char* buf, size_t cap); /* Map a -mcmodel= value to a KitCodeModel. Accepts the x86 spellings - * (small/medium/large) and the RISC-V aliases (medlow->small, medany->medium). - * Returns true and writes *out on a known value, false on an unknown one (no - * diagnostic; *out is untouched). `out` may be NULL to query validity. */ + * (small/medium/large), the RISC-V aliases (medlow->small, medany->medium), + * and kernel as a conservative alias for large. Returns true and writes *out on + * a known value, false on an unknown one (no diagnostic; *out is untouched). + * `out` may be NULL to query validity. */ KIT_API bool kit_target_code_model_from_name(const char* val, KitCodeModel* out); diff --git a/lang/c/c.c b/lang/c/c.c @@ -183,7 +183,8 @@ static KitStatus c_frontend_compile_cg(KitFrontendState* frontend, kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_PARSE_CODEGEN); parse_c(c, pool, pp, decls, cg, (KitSymVis)fe_opts->code.default_visibility, - (int)fe_opts->code.trivial_auto_var_init); + (int)fe_opts->code.trivial_auto_var_init, + fe_opts->code.disabled_backend_features); kit_frontend_profile_scope_end(c, C_PROFILE_SCOPE_PARSE_CODEGEN); kit_frontend_profile_scope_begin(c, C_PROFILE_SCOPE_CLEANUP); diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -90,6 +90,12 @@ _Noreturn void perr(Parser* p, const char* fmt, ...) { compiler_panicv(p->c, loc, fmt, ap); } +void reject_general_regs_only_fp(Parser* p, const char* what) { + if (!p->general_regs_only) return; + perr(p, "-mgeneral-regs-only rejects %.*s", + KIT_SLICE_ARG(kit_slice_cstr(what))); +} + /* ============================================================ * Token helpers * ============================================================ */ @@ -1649,7 +1655,8 @@ static u8 parser_default_visibility(KitSymVis vis) { } void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, - KitSymVis default_visibility, int auto_var_init) { + KitSymVis default_visibility, int auto_var_init, + uint64_t disabled_backend_features) { Parser p; CKw i; u32 syscall_i; @@ -1663,6 +1670,8 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, p.pool = pool; p.default_visibility = parser_default_visibility(default_visibility); p.auto_var_init = (u8)auto_var_init; + p.general_regs_only = + (disabled_backend_features & KIT_CG_BACKEND_SIMD) != 0; /* Index storage for the scope/tag tables and the external-function table all * comes from the arena via the pool's shared arena-heap facade. */ diff --git a/lang/c/parse/parse.h b/lang/c/parse/parse.h @@ -1,6 +1,8 @@ #ifndef KIT_LANG_C_PARSE_H #define KIT_LANG_C_PARSE_H +#include <stdint.h> + #include "decl/decl.h" #include "parse/cg.h" #include "pp/pp.h" @@ -8,6 +10,6 @@ /* C11 frontend. Reads preprocessed tokens, records C declarations, and drives * the public CG API for executable code and object data. */ void parse_c(Compiler*, Pool*, Pp*, DeclTable*, CG*, KitSymVis, - int auto_var_init); + int auto_var_init, uint64_t disabled_backend_features); #endif diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -1225,6 +1225,7 @@ static int parse_builtin_isnan_call(Parser* p, Sym name, SrcLoc loc) { const Type* arg_ty; if (name != p->sym_b_isnan) return 0; + reject_general_regs_only_fp(p, "floating-point builtins"); advance(p); /* IDENT */ expect_punct(p, '(', "'(' after __builtin_isnan"); parse_assign_expr(p); @@ -1270,6 +1271,7 @@ static int parse_builtin_fp_cmp_call(Parser* p, Sym name, SrcLoc loc) { return 0; } + reject_general_regs_only_fp(p, "floating-point builtins"); advance(p); /* IDENT */ expect_punct(p, '(', "'(' after floating comparison builtin"); parse_assign_expr(p); @@ -1325,6 +1327,7 @@ static int parse_builtin_fp_cmp_call(Parser* p, Sym name, SrcLoc loc) { } static const Type* builtin_math_fp_type(Parser* p, Sym name) { + reject_general_regs_only_fp(p, "floating-point builtins"); if (name == p->sym_b_fabsf || name == p->sym_b_inff || name == p->sym_b_huge_valf || name == p->sym_b_nanf) { return type_prim(p->pool, TY_FLOAT); @@ -1902,6 +1905,7 @@ static void parse_primary(Parser* p) { return; } if (t.kind == TOK_FLT) { + reject_general_regs_only_fp(p, "floating-point literals"); double v = parse_float_literal(p, &t); const Type* lty = float_literal_type(p, &t); c_const_guard_note_at(p, pp_materialize_loc(p->pp, t.loc), diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -246,6 +246,7 @@ typedef struct Parser { Pool* pool; u8 default_visibility; /* SymVis */ u8 auto_var_init; /* KitAutoVarInit: implicit init for uninit locals */ + u8 general_regs_only; /* -mgeneral-regs-only: reject C FP constructs */ Tok cur; Tok next; @@ -443,6 +444,7 @@ typedef struct TypeSpecAccum { * ============================================================ */ _Noreturn void perr(Parser* p, const char* fmt, ...); +void reject_general_regs_only_fp(Parser* p, const char* what); void advance(Parser* p); Tok peek1(Parser* p); void expect_punct(Parser* p, u32 punct, const char* what); diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -692,6 +692,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { advance(p); seen = 1; } else if (tkw == KW_FLOAT || tkw == KW_FLOAT16) { + reject_general_regs_only_fp(p, "floating-point types"); /* _Float16 is intentionally aliased to 32-bit float (a deliberate * approximation; see test/parse/cases/float16_01_decl, which asserts * sizeof(_Float16) == sizeof(float)). Real 16-bit semantics would be a @@ -701,6 +702,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { advance(p); seen = 1; } else if (tkw == KW_DOUBLE) { + reject_general_regs_only_fp(p, "floating-point types"); acc.saw_double = 1; acc.saw_explicit_type = 1; advance(p); diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk @@ -39,6 +39,7 @@ DRIVER_TOOL_SRCS = \ $(call tool-cmd,RANLIB,ranlib) \ $(call tool-cmd,STRIP,strip) \ $(call tool-cmd,OBJCOPY,objcopy) \ + $(call tool-cmd,IMAGE,image) \ $(call tool-cmd,OBJDUMP,objdump) \ $(call tool-cmd,DBG,dbg) \ $(call tool-cmd,RUN,run) \ diff --git a/mk/lib_srcs.mk b/mk/lib_srcs.mk @@ -32,13 +32,14 @@ endef LIB_SRCS_ABI_CORE = src/abi/abi.c src/abi/registry.c LIB_SRCS_API_CORE = $(filter-out src/api/archive.c src/api/disasm.c \ src/api/link.c src/api/build.c src/api/cas.c \ - src/api/package.c src/api/compress.c src/api/stubs.c, \ + src/api/package.c src/api/compress.c src/api/image.c \ + src/api/stubs.c, \ $(wildcard src/api/*.c)) LIB_SRCS_ARCH_CORE = $(filter-out src/arch/%_stubs.c,$(wildcard src/arch/*.c)) LIB_SRCS_ASM_CORE = $(wildcard src/asm/*.c) LIB_SRCS_CG_CORE = $(wildcard src/cg/*.c) LIB_SRCS_CORE = $(wildcard src/core/*.c) -LIB_SRCS_OBJ_CORE = $(filter-out src/obj/%_stubs.c,$(wildcard src/obj/*.c)) +LIB_SRCS_OBJ_CORE = $(filter-out src/obj/%_stubs.c src/obj/image.c,$(wildcard src/obj/*.c)) LIB_SRCS_OS_CORE = src/os/registry.c LIB_SRCS_OS_HOSTED_CORE = src/os/hosted.c LIB_SRCS_NONARCH = $(LIB_SRCS_ABI_CORE) \ @@ -117,6 +118,8 @@ LIB_SRCS_WASM_CORE := $(shell find src/wasm -name '*.c' 2>/dev/null) LIB_SRCS_API_AR = src/api/archive.c LIB_SRCS_API_DISASM = src/api/disasm.c LIB_SRCS_API_LINK = src/api/link.c +LIB_SRCS_API_IMAGE = src/api/image.c +LIB_SRCS_OBJ_IMAGE = src/obj/image.c # Build orchestration tier (batch compile + ordered link). It uses the linker # (kit_link_session_*), so it compiles only when KIT_LINK_ENABLED. LIB_SRCS_API_BUILD = src/api/build.c @@ -176,6 +179,9 @@ endif ifeq ($(KIT_LINK_ENABLED),1) LIB_SRCS += $(LIB_SRCS_API_LINK) $(LIB_SRCS_API_BUILD) $(LIB_SRCS_LINK) endif +ifeq ($(KIT_IMAGE_ENABLED),1) +LIB_SRCS += $(LIB_SRCS_API_IMAGE) $(LIB_SRCS_OBJ_IMAGE) +endif ifeq ($(KIT_DBG_ENABLED),1) LIB_SRCS += $(LIB_SRCS_DBG) endif diff --git a/mk/test.mk b/mk/test.mk @@ -668,6 +668,7 @@ TEST_RT_DEP = $(_TEST_RT_$(KIT_TEST_ARCH)) KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64 RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a))) LINK_EXE_RUNNER = build/test/link-exe-runner +LINK_SCRIPT_TEST_BIN = build/test/link_script_test test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER) @bash test/rt/run.sh @@ -846,7 +847,8 @@ test-coff-windows-vm: bin rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-s # The parse/asm/macho harnesses select a cross-target via KIT_TEST_ARCH # (default aa64); the link rt dependency is resolved through the shared # _TEST_RT_<arch> map defined above (near test-rt-runtime). -test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER) +test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER) $(LINK_SCRIPT_TEST_BIN) + $(LINK_SCRIPT_TEST_BIN) bash test/link/run.sh # x64 ELF link/reloc-application coverage. test-link defaults to aa64, so diff --git a/mk/test_unit.mk b/mk/test_unit.mk @@ -33,6 +33,7 @@ UNIT_TESTS_PUBLIC := \ ar_test target_test cg_api_test cg_switch_test cg_fp_cmp_test \ cg_control_test cg_const_test hash_test \ panic_recovery_test profile_test \ + link_script_test \ rv64_jit_test rv32_jit_test aa64_inline_test rv64_inline_test x64_inline_test \ strength_reduce_test ar_test_SRC := test/ar/ar_test.c @@ -40,6 +41,7 @@ target_test_SRC := test/api/target_test.c hash_test_SRC := test/api/hash_test.c panic_recovery_test_SRC := test/api/panic_recovery_test.c profile_test_SRC := test/api/profile_test.c +link_script_test_SRC := test/link/link_script_test.c cg_api_test_SRC := test/api/cg_type_test.c cg_switch_test_SRC := test/api/cg_switch_test.c cg_fp_cmp_test_SRC := test/api/cg_fp_cmp_test.c diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c @@ -4,6 +4,7 @@ #include <kit/disasm.h> #include <kit/dwarf.h> #include <kit/emu.h> +#include <kit/image.h> #include <kit/jit.h> #include <kit/link.h> @@ -83,6 +84,34 @@ KitStatus kit_disasm_obj_bytes(const KitContext* ctx, const KitSlice* bytes, } #endif +#if !KIT_IMAGE_ENABLED +KitStatus kit_image_emit(const KitContext* ctx, KitObjFile* obj, + const KitSlice* object_bytes, + const KitImageOptions* opts, KitWriter* out, + KitImageReport* report_out) { + (void)ctx; + (void)obj; + (void)object_bytes; + (void)opts; + (void)out; + (void)report_out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_image_emit_bytes(const KitContext* ctx, KitSlice name, + const KitSlice* object_bytes, + const KitImageOptions* opts, KitWriter* out, + KitImageReport* report_out) { + (void)ctx; + (void)name; + (void)object_bytes; + (void)opts; + (void)out; + (void)report_out; + return KIT_UNSUPPORTED; +} +#endif + #if !KIT_DWARF_ENABLED Debug* debug_new(Compiler* c, ObjBuilder* ob) { (void)c; @@ -553,6 +582,20 @@ KitStatus kit_link_session_emit(KitLinkSession* s, KitWriter* out) { return KIT_UNSUPPORTED; } +KitStatus kit_link_session_write_map(KitLinkSession* s, KitWriter* out) { + (void)s; + (void)out; + return KIT_UNSUPPORTED; +} + +KitStatus kit_link_session_write_symbols(KitLinkSession* s, uint8_t format, + KitWriter* out) { + (void)s; + (void)format; + (void)out; + return KIT_UNSUPPORTED; +} + KitStatus kit_link_session_jit(KitLinkSession* s, KitJit** out_jit) { (void)s; if (out_jit) *out_jit = NULL; diff --git a/src/api/image.c b/src/api/image.c @@ -0,0 +1,30 @@ +#include <kit/image.h> +#include <kit/object.h> + +#include "core/diag.h" +#include "obj/image.h" + +KitStatus kit_image_emit(const KitContext* ctx, KitObjFile* obj, + const KitSlice* object_bytes, + const KitImageOptions* opts, KitWriter* out, + KitImageReport* report_out) { + return obj_emit_image(ctx, obj, object_bytes, opts, out, report_out); +} + +KitStatus kit_image_emit_bytes(const KitContext* ctx, KitSlice name, + const KitSlice* object_bytes, + const KitImageOptions* opts, KitWriter* out, + KitImageReport* report_out) { + KitObjFile* obj = NULL; + KitStatus st; + if (!ctx || !object_bytes || !out) return KIT_INVALID; + st = kit_obj_open(ctx, name, object_bytes, &obj); + if (st != KIT_OK) { + kit_ctx_diagf(ctx, "image: %.*s is not a recognized linked object", + KIT_SLICE_ARG(name)); + return st; + } + st = obj_emit_image(ctx, obj, object_bytes, opts, out, report_out); + kit_obj_free(obj); + return st; +} diff --git a/src/api/link.c b/src/api/link.c @@ -16,7 +16,10 @@ #include <kit/core.h> #include <kit/jit.h> #include <kit/link.h> +#include <kit/target.h> #include <setjmp.h> +#include <stdarg.h> +#include <stdio.h> #include <string.h> #include "cg/internal.h" @@ -68,8 +71,8 @@ static KitStatus link_session_remember_publish_obj(KitLinkSession* s, } /* These KitLinkSessionOptions fields have no plumbing into the Linker yet: - * the shared-library DT_* knobs (soname/rpaths/runpaths/exports/ - * allow_undefined) and the build-id selector (build_id_mode/bytes/len; the + * the shared-library DT_* knobs (soname/rpaths/runpaths/exports) and the + * build-id selector (build_id_mode/bytes/len; the * ELF writer currently emits a fixed image-hash note regardless of mode). * Until they are wired through, warn rather than silently honor the default * instead of the caller's request. */ @@ -178,12 +181,14 @@ KitStatus kit_link_session_new(KitCompiler* c, link_set_emit_static_exe(l, 1); link_set_gc_sections(l, opts->gc_sections); link_set_strip_debug(l, opts->strip_debug); + link_set_allow_undefined(l, opts->allow_undefined); link_set_pie(l, opts->pie); link_set_interp_path(l, opts->interp_path); break; case KIT_LINK_OUTPUT_SHARED: link_set_gc_sections(l, opts->gc_sections); link_set_strip_debug(l, opts->strip_debug); + link_set_allow_undefined(l, opts->allow_undefined); link_set_pie(l, 1); if (opts->soname.s && opts->soname.len) link_warn_ignored_opt(s->c, "-soname"); @@ -192,8 +197,6 @@ KitStatus kit_link_session_new(KitCompiler* c, link_warn_ignored_opt(s->c, "-rpath (DT_RUNPATH)"); if (opts->exports && opts->nexports) link_warn_ignored_opt(s->c, "--export-symbol"); - if (opts->allow_undefined) - link_warn_ignored_opt(s->c, "--allow-shlib-undefined"); break; case KIT_LINK_OUTPUT_RELOCATABLE: break; @@ -559,6 +562,355 @@ KitStatus kit_link_session_resolve(KitLinkSession* s) { return link_session_guard(s, link_session_resolve_inner, NULL); } +static KitStatus link_report_write(KitWriter* out, const char* s, size_t n) { + return n ? kit_writer_write(out, s, n) : KIT_OK; +} + +static KitStatus link_report_cstr(KitWriter* out, const char* s) { + return link_report_write(out, s, s ? strlen(s) : 0); +} + +static KitStatus link_report_slice(KitWriter* out, KitSlice s) { + return link_report_write(out, s.s, s.s ? s.len : 0); +} + +static KitStatus link_reportf(KitWriter* out, const char* fmt, ...) { + char buf[512]; + va_list ap; + int n; + va_start(ap, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (n < 0) return KIT_ERR; + if ((size_t)n >= sizeof(buf)) return KIT_UNSUPPORTED; + return link_report_write(out, buf, (size_t)n); +} + +static const char* link_report_output_kind(uint8_t kind) { + switch ((KitLinkOutputKind)kind) { + case KIT_LINK_OUTPUT_EXE: + return "exe"; + case KIT_LINK_OUTPUT_SHARED: + return "shared"; + case KIT_LINK_OUTPUT_RELOCATABLE: + return "relocatable"; + case KIT_LINK_OUTPUT_JIT: + return "jit"; + } + return "unknown"; +} + +static KitSlice link_report_sym_name(LinkImage* img, Sym name) { + return name ? pool_slice(img->c->global, name) : KIT_SLICE_NULL; +} + +static int link_report_slice_cmp(KitSlice a, KitSlice b) { + size_t n = a.len < b.len ? a.len : b.len; + int c = n ? memcmp(a.s, b.s, n) : 0; + if (c) return c; + if (a.len < b.len) return -1; + if (a.len > b.len) return 1; + return 0; +} + +static int link_report_symbol_include(const LinkSymbol* s) { + if (!s || !s->name || !s->defined || s->imported) return 0; + if (s->kind == SK_FILE || s->kind == SK_SECTION) return 0; + return 1; +} + +typedef struct LinkReportSymbols { + LinkSymId* ids; + u32 n; + u32 cap; +} LinkReportSymbols; + +static int link_report_symbol_cmp(LinkImage* img, LinkSymId a, LinkSymId b) { + const LinkSymbol* sa = LinkSyms_at(&img->syms, a - 1u); + const LinkSymbol* sb = LinkSyms_at(&img->syms, b - 1u); + KitSlice na; + KitSlice nb; + if (sa->vaddr < sb->vaddr) return -1; + if (sa->vaddr > sb->vaddr) return 1; + na = link_report_sym_name(img, sa->name); + nb = link_report_sym_name(img, sb->name); + return link_report_slice_cmp(na, nb); +} + +static KitStatus link_report_collect_symbols(LinkImage* img, + LinkReportSymbols* out) { + Heap* h = img->heap; + u32 i; + memset(out, 0, sizeof(*out)); + out->cap = LinkSyms_count(&img->syms); + if (!out->cap) return KIT_OK; + out->ids = + (LinkSymId*)h->alloc(h, sizeof(*out->ids) * out->cap, _Alignof(LinkSymId)); + if (!out->ids) return KIT_NOMEM; + for (i = 0; i < out->cap; ++i) { + const LinkSymbol* s = LinkSyms_at(&img->syms, i); + if (link_report_symbol_include(s)) out->ids[out->n++] = s->id; + } + for (i = 1; i < out->n; ++i) { + LinkSymId key = out->ids[i]; + u32 j = i; + while (j > 0 && link_report_symbol_cmp(img, key, out->ids[j - 1u]) < 0) { + out->ids[j] = out->ids[j - 1u]; + --j; + } + out->ids[j] = key; + } + return KIT_OK; +} + +static void link_report_free_symbols(LinkImage* img, LinkReportSymbols* syms) { + if (syms->ids) + img->heap->free(img->heap, syms->ids, + sizeof(*syms->ids) * syms->cap); + memset(syms, 0, sizeof(*syms)); +} + +static char link_report_symbol_type(const LinkImage* img, + const LinkSymbol* s) { + char t = 'N'; + const LinkSection* sec = NULL; + if (!s->defined) { + t = 'U'; + } else if (s->kind == SK_ABS) { + t = 'A'; + } else if (s->kind == SK_COMMON) { + t = 'C'; + } else if (s->kind == SK_FUNC || s->kind == SK_IFUNC) { + t = 'T'; + } else { + if (s->section_id != LINK_SEC_NONE && s->section_id <= img->nsections) + sec = &img->sections[s->section_id - 1u]; + if (s->kind == SK_TLS) { + t = 'D'; + } else if (sec && sec->sem == SSEM_NOBITS) { + t = 'B'; + } else if (sec && (sec->flags & SF_EXEC)) { + t = 'T'; + } else if (sec && (sec->flags & SF_WRITE)) { + t = 'D'; + } else if (sec && (sec->flags & SF_ALLOC)) { + t = 'R'; + } + } + if (s->bind == SB_WEAK) { + if (t == 'D' || t == 'B' || t == 'R') + t = 'V'; + else if (t != 'U') + t = 'W'; + } else if (s->bind == SB_LOCAL && t >= 'A' && t <= 'Z') { + t = (char)(t - 'A' + 'a'); + } + return t; +} + +/* Final runtime address of a symbol, mirroring the ELF emitter's st_value + * rule: SK_FILE / undefined carry 0; SK_ABS carries its own value verbatim; + * every other defined symbol is image-relative and gets img->load_base added + * (0 for PIE/scripted, the static base or -Ttext override otherwise). Keeps + * the side-files consistent with the produced binary's symbol table. */ +static u64 link_report_final_addr(const LinkImage* img, const LinkSymbol* s) { + if (s->kind == SK_FILE || !s->defined) return 0; + if (s->kind == SK_ABS) return s->vaddr; + return img->load_base + s->vaddr; +} + +static KitStatus link_report_write_symbol_line(LinkImage* img, KitWriter* out, + const LinkSymbol* s) { + KitSlice name = link_report_sym_name(img, s->name); + KitStatus st = link_reportf(out, "%016llx %c ", + (unsigned long long)link_report_final_addr(img, s), + link_report_symbol_type(img, s)); + if (st != KIT_OK) return st; + st = link_report_slice(out, name); + if (st != KIT_OK) return st; + return link_report_cstr(out, "\n"); +} + +static KitStatus link_report_write_symbols_nm(LinkImage* img, KitWriter* out) { + LinkReportSymbols syms; + KitStatus st; + u32 i; + st = link_report_collect_symbols(img, &syms); + if (st != KIT_OK) return st; + for (i = 0; i < syms.n; ++i) { + const LinkSymbol* s = LinkSyms_at(&img->syms, syms.ids[i] - 1u); + st = link_report_write_symbol_line(img, out, s); + if (st != KIT_OK) break; + } + link_report_free_symbols(img, &syms); + return st == KIT_OK ? kit_writer_status(out) : st; +} + +static KitSlice link_report_input_name(LinkImage* img, LinkInputId id) { + LinkInput* in; + if (!img || !img->linker || id == LINK_INPUT_NONE || + id > LinkInputs_count(&img->linker->inputs)) + return KIT_SLICE_NULL; + in = LinkInputs_at(&img->linker->inputs, id - 1u); + return in && in->name ? pool_slice(img->c->global, in->name) : KIT_SLICE_NULL; +} + +static KitStatus link_report_write_map_image(KitLinkSession* s, + KitWriter* out) { + LinkImage* img = s->image; + char triple[96]; + KitStatus st; + u32 i; + if (!img) return KIT_INVALID; + if (!kit_target_to_triple(img->c->target, triple, sizeof(triple))) + strcpy(triple, "unknown"); + st = link_reportf(out, + "target %s\n" + "output %s\n", + triple, link_report_output_kind(s->opts.output_kind)); + if (st != KIT_OK) return st; + if (img->entry_sym != LINK_SYM_NONE) { + const LinkSymbol* es = LinkSyms_at(&img->syms, img->entry_sym - 1u); + KitSlice name = link_report_sym_name(img, es->name); + st = link_report_cstr(out, "entry "); + if (st != KIT_OK) return st; + st = link_report_slice(out, name); + if (st != KIT_OK) return st; + st = link_reportf(out, " 0x%llx\n", + (unsigned long long)link_report_final_addr(img, es)); + if (st != KIT_OK) return st; + } else { + st = link_report_cstr(out, "entry -\n"); + if (st != KIT_OK) return st; + } + + st = link_report_cstr(out, "\nsegments\n"); + if (st != KIT_OK) return st; + /* The synthesized read-only headers load segment (ehdr+phdrs) maps at the + * image base ahead of the content segments; the format emitter records it + * so the map shows where the image actually loads (e.g. a -Ttext base). */ + if (img->headers_present) { + st = link_reportf(out, + " [0] r-- off=0x0 vaddr=0x%llx " + "filesz=0x%llx memsz=0x%llx align=0x%x\n", + (unsigned long long)img->load_base, + (unsigned long long)img->headers_filesz, + (unsigned long long)img->headers_memsz, + img->headers_align); + if (st != KIT_OK) return st; + } + for (i = 0; i < img->nsegments; ++i) { + const LinkSegment* seg = &img->segments[i]; + char w = (seg->flags & SF_WRITE) ? 'w' : '-'; + char x = (seg->flags & SF_EXEC) ? 'x' : '-'; + st = link_reportf(out, + " [%u] r%c%c off=0x%llx vaddr=0x%llx " + "filesz=0x%llx memsz=0x%llx align=0x%x\n", + seg->id, w, x, (unsigned long long)seg->file_offset, + (unsigned long long)(img->load_base + seg->vaddr), + (unsigned long long)seg->file_size, + (unsigned long long)seg->mem_size, seg->align); + if (st != KIT_OK) return st; + } + + st = link_report_cstr(out, "\nsections\n"); + if (st != KIT_OK) return st; + for (i = 0; i < img->nsections; ++i) { + const LinkSection* sec = &img->sections[i]; + KitSlice name = link_report_sym_name(img, sec->name); + KitSlice input = link_report_input_name(img, sec->input_id); + /* Loaded sections report their final runtime address; file-only debug + * sections aren't mapped (sh_addr 0), so leave their image-relative + * bookkeeping vaddr untouched rather than biasing it by the load base. */ + u64 sec_vaddr = (sec->file_only || sec->segment_id == LINK_SEG_NONE) + ? sec->vaddr + : img->load_base + sec->vaddr; + st = link_reportf(out, + " [%u] ", sec->id); + if (st != KIT_OK) return st; + st = name.s ? link_report_slice(out, name) : link_report_cstr(out, "-"); + if (st != KIT_OK) return st; + st = link_reportf(out, + " seg=%u off=0x%llx vaddr=0x%llx size=0x%llx " + "align=0x%x", + sec->segment_id, (unsigned long long)sec->file_offset, + (unsigned long long)sec_vaddr, + (unsigned long long)sec->size, sec->align); + if (st != KIT_OK) return st; + if (input.s && input.len) { + st = link_report_cstr(out, " input="); + if (st != KIT_OK) return st; + st = link_report_slice(out, input); + if (st != KIT_OK) return st; + } + st = link_report_cstr(out, "\n"); + if (st != KIT_OK) return st; + } + + st = link_report_cstr(out, "\nsymbols\n"); + if (st != KIT_OK) return st; + return link_report_write_symbols_nm(img, out); +} + +typedef struct LinkReportArg { + KitWriter* out; + uint8_t format; + KitStatus st; +} LinkReportArg; + +static void link_session_map_inner(KitLinkSession* s, void* arg) { + LinkReportArg* a = (LinkReportArg*)arg; + a->st = link_report_write_map_image(s, a->out); +} + +static void link_session_symbols_inner(KitLinkSession* s, void* arg) { + LinkReportArg* a = (LinkReportArg*)arg; + if (a->format != KIT_LINK_SYMBOLS_NM) { + a->st = KIT_UNSUPPORTED; + return; + } + a->st = link_report_write_symbols_nm(s->image, a->out); +} + +KitStatus kit_link_session_write_map(KitLinkSession* s, KitWriter* out) { + LinkReportArg arg; + KitStatus st; + if (!s || !out) return KIT_INVALID; + if ((KitLinkOutputKind)s->opts.output_kind == KIT_LINK_OUTPUT_RELOCATABLE || + (KitLinkOutputKind)s->opts.output_kind == KIT_LINK_OUTPUT_JIT) + return KIT_INVALID; + if (!s->resolved) { + st = kit_link_session_resolve(s); + if (st != KIT_OK) return st; + } + arg.out = out; + arg.format = KIT_LINK_SYMBOLS_NM; + arg.st = KIT_OK; + st = link_session_guard(s, link_session_map_inner, &arg); + if (st != KIT_OK) return st; + return arg.st == KIT_OK ? kit_writer_status(out) : arg.st; +} + +KitStatus kit_link_session_write_symbols(KitLinkSession* s, uint8_t format, + KitWriter* out) { + LinkReportArg arg; + KitStatus st; + if (!s || !out) return KIT_INVALID; + if ((KitLinkOutputKind)s->opts.output_kind == KIT_LINK_OUTPUT_RELOCATABLE || + (KitLinkOutputKind)s->opts.output_kind == KIT_LINK_OUTPUT_JIT) + return KIT_INVALID; + if (!s->resolved) { + st = kit_link_session_resolve(s); + if (st != KIT_OK) return st; + } + arg.out = out; + arg.format = format; + arg.st = KIT_OK; + st = link_session_guard(s, link_session_symbols_inner, &arg); + if (st != KIT_OK) return st; + return arg.st == KIT_OK ? kit_writer_status(out) : arg.st; +} + typedef struct LinkEmitArg { KitWriter* out; } LinkEmitArg; diff --git a/src/api/object_file.c b/src/api/object_file.c @@ -809,6 +809,7 @@ KitIterResult kit_obj_segiter_next(KitObjSegIter* it, KitObjSegInfo* out) { s = obj_image_segment(im, it->idx++); out->name = pool_slice(it->file->compiler.global, s->name); out->vaddr = s->vaddr; + out->paddr = s->paddr; out->vsize = s->vsize; out->file_off = s->file_off; out->file_size = s->file_size; diff --git a/src/api/target.c b/src/api/target.c @@ -300,7 +300,7 @@ bool kit_target_code_model_from_name(const char* val, KitCodeModel* out) { cm = KIT_CM_SMALL; } else if (strcmp(val, "medium") == 0 || strcmp(val, "medany") == 0) { cm = KIT_CM_MEDIUM; - } else if (strcmp(val, "large") == 0) { + } else if (strcmp(val, "large") == 0 || strcmp(val, "kernel") == 0) { cm = KIT_CM_LARGE; } else { return false; diff --git a/src/arch/native_target.h b/src/arch/native_target.h @@ -462,6 +462,7 @@ struct NativeTarget { ObjBuilder* obj; MCEmitter* mc; const NativeRegInfo* regs; + u64 disabled_backend_features; NativeAllocClass (*class_for_type)(NativeTarget*, KitCgTypeId); int (*imm_legal)(NativeTarget*, NativeImmUse, u32 op, KitCgTypeId, i64); diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -2003,9 +2003,12 @@ static void x64_func_begin_known_frame(NativeTarget* t, const CGFuncDesc* fd, * may issue a `call` (clobbering the red zone) the optimizer can't see. slim * needs no such guard: it has no locals there and the return address lives on * the stack at [rbp+8], not in a clobberable register. */ - a->redzone_leaf = !a->slim_frame && a->abi->shadow_space == 0 && frame && - frame->is_leaf && !frame->has_asm && !a->frame.has_alloca && - a->frame.max_outgoing == 0 && frame_size <= 128u; + a->redzone_leaf = + !a->slim_frame && + (t->disabled_backend_features & KIT_CG_BACKEND_RED_ZONE) == 0 && + a->abi->shadow_space == 0 && frame && frame->is_leaf && + !frame->has_asm && !a->frame.has_alloca && a->frame.max_outgoing == 0 && + frame_size <= 128u; a->prologue_pos = mc_pos(mc); nbytes = x64_build_prologue(a, buf, sizeof buf, frame_size, cs_int, n_int, cs_fp, n_fp, a->slim_frame || a->redzone_leaf, diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c @@ -2598,6 +2598,7 @@ CgTarget* native_direct_backend_make(Compiler* c, ObjBuilder* o, if (cg_mc_debug_new(c, o, opts, &mc, &debug) != KIT_OK) return NULL; native = ctor(c, o, mc); if (!native) return NULL; + if (opts) native->disabled_backend_features = opts->disabled_backend_features; memset(&cfg, 0, sizeof cfg); cfg.native = native; cfg.ops = ops; diff --git a/src/link/link.c b/src/link/link.c @@ -363,6 +363,11 @@ void link_set_strip_debug(Linker* l, int enable) { * recorded so driver -S/--strip-debug flows through the linker surface. */ } +void link_set_allow_undefined(Linker* l, int enable) { + if (!l) return; + l->allow_undefined = enable ? 1 : 0; +} + void link_set_emit_static_exe(Linker* l, int enable) { if (!l) return; l->emit_static_exe = enable ? 1 : 0; diff --git a/src/link/link.h b/src/link/link.h @@ -107,10 +107,17 @@ typedef struct LinkSegment { u32 flags; /* SecFlag-like permissions after layout */ u64 file_offset; u64 vaddr; + u64 paddr; u64 mem_size; u64 file_size; u32 align; + u32 phdr_type; + u32 phdr_flags; u32 nsections; + u8 phdr_flags_set; + u8 phdr_filehdr; + u8 phdr_phdrs; + u8 pad[5]; } LinkSegment; typedef struct LinkSection { @@ -197,6 +204,7 @@ void link_set_extern_resolver(Linker*, LinkExternResolver, void* user); * script. Unreferenced sections are dropped from the output. */ void link_set_gc_sections(Linker*, int enable); void link_set_strip_debug(Linker*, int enable); +void link_set_allow_undefined(Linker*, int enable); /* Mark this link as targeting a static ET_EXEC ELF binary (vs. the * in-process JIT). Setter is called by kit_link_exe; the JIT path diff --git a/src/link/link_internal.h b/src/link/link_internal.h @@ -200,6 +200,7 @@ struct Linker { u64 text_base; int gc_sections; int strip_debug; + int allow_undefined; /* Set by kit_link_exe before link_resolve. When 1, layout_iplt * synthesizes a .init_array entry pointing at __kit_ifunc_init so * the emitted ET_EXEC binary fills its IFUNC slots at startup. The @@ -485,6 +486,26 @@ struct LinkImage { * from Linker at link_resolve time. Ignored if pie/scripted. */ int text_base_set; u64 text_base; + + /* Final emit facts recorded by the format emitter for the link-map / + * symbols side-files (kit_link_session_write_map / _write_symbols, which run + * on the format-neutral image *after* emit). The emitter owns runtime-base + * selection and headers synthesis, so the report writers read the resolved + * values back from here instead of re-deriving format-specific policy. + * + * load_base is the runtime base the emitter added to image-relative vaddrs: + * IMAGE_BASE_STATIC, or a -Ttext override, for a static ET_EXEC; 0 for + * PIE/scripted/JIT (which keep image-relative addresses). The headers_* + * fields describe the synthesized read-only headers load segment (ELF: the + * ehdr+phdrs PT_LOAD mapped at load_base); headers_present is 0 when the + * format emits no separate headers segment (scripted ELF, non-ELF). All + * stay zero until emit runs, so a report taken without an emit reports + * image-relative addresses, as before. */ + u64 load_base; + u64 headers_filesz; + u64 headers_memsz; + u32 headers_align; + u8 headers_present; }; /* Page granularity used for ELF segment alignment and the file-offset / diff --git a/src/link/link_jit.c b/src/link/link_jit.c @@ -1087,6 +1087,7 @@ static void jit_append_obj_inner(KitJit* jit, ObjBuilder* ob) { if (ps->bucket == SEG_TLS) seg->flags |= SF_TLS; seg->file_offset = ps->vaddr; seg->vaddr = ps->vaddr; + seg->paddr = ps->vaddr; seg->mem_size = ps->size; seg->file_size = ps->file_size; seg->align = (u32)page; diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -441,6 +441,7 @@ void link_layout_sections(Linker* l, LinkImage* img, const GcLive* g) { seg->flags = perms; seg->file_offset = cursor; seg->vaddr = cursor; + seg->paddr = cursor; seg->mem_size = mem_size; seg->file_size = file_size; seg->align = (u32)align; @@ -524,7 +525,171 @@ static int match_glob(const char* pat, const char* name) { return plen == nlen && memcmp(pat, name, plen) == 0; } +typedef struct ScriptOutInfo { + KitSlice name; + u64 vma; + u64 lma; + u64 size; + int defined; +} ScriptOutInfo; + +static const KitLinkRegion* script_find_region(const KitLinkScript* script, + KitSlice name) { + u32 i; + if (!script || !name.s) return NULL; + for (i = 0; i < script->nregions; ++i) + if (slice_eq(script->regions[i].name, name)) return &script->regions[i]; + return NULL; +} + +static int script_region_index(const KitLinkScript* script, KitSlice name, + u32* out) { + u32 i; + if (!script || !name.s) return 0; + for (i = 0; i < script->nregions; ++i) { + if (slice_eq(script->regions[i].name, name)) { + if (out) *out = i; + return 1; + } + } + return 0; +} + +static const ScriptOutInfo* script_find_out(const ScriptOutInfo* outs, + u32 nouts, KitSlice name) { + u32 i; + if (!name.s) return NULL; + for (i = 0; i < nouts; ++i) + if (outs[i].defined && slice_eq(outs[i].name, name)) return &outs[i]; + return NULL; +} + +static const KitLinkPhdr* script_find_phdr(const KitLinkScript* script, + KitSlice name) { + u32 i; + if (!script || !name.s) return NULL; + for (i = 0; i < script->nphdrs; ++i) + if (slice_eq(script->phdrs[i].name, name)) return &script->phdrs[i]; + return NULL; +} + +static u64 script_sizeof_headers(Linker* l) { + const KitLinkScript* s = l ? l->script : NULL; + u32 nph = 0; + u32 i; + u64 eh = (l && l->c && l->c->target.ptr_size == 4) ? 52u : 64u; + u64 ph = (l && l->c && l->c->target.ptr_size == 4) ? 32u : 56u; + if (!s) return eh; + for (i = 0; i < s->nsections; ++i) + if (!slice_eq_cstr(s->sections[i].name, "/DISCARD/")) ++nph; + if (s->nphdrs > nph) nph = s->nphdrs; + return eh + (u64)nph * ph; +} + +static char script_ascii_lower(char c) { + return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c; +} + +static int script_slice_contains_lit_ci(KitSlice s, const char* lit) { + size_t lit_len = 0; + size_t i, j; + if (!s.s || !lit) return 0; + while (lit[lit_len]) ++lit_len; + if (lit_len == 0 || s.len < lit_len) return 0; + for (i = 0; i <= s.len - lit_len; ++i) { + for (j = 0; j < lit_len; ++j) { + if (script_ascii_lower(s.s[i + j]) != script_ascii_lower(lit[j])) + break; + } + if (j == lit_len) return 1; + } + return 0; +} + +static int script_format_name(KitSlice name, KitObjFmt* out) { + if (script_slice_contains_lit_ci(name, "elf")) { + *out = KIT_OBJ_ELF; + return 1; + } + if (script_slice_contains_lit_ci(name, "mach-o") || + script_slice_contains_lit_ci(name, "macho")) { + *out = KIT_OBJ_MACHO; + return 1; + } + if (script_slice_contains_lit_ci(name, "coff") || + script_slice_contains_lit_ci(name, "pei-") || + script_slice_contains_lit_ci(name, "pe-")) { + *out = KIT_OBJ_COFF; + return 1; + } + if (script_slice_contains_lit_ci(name, "wasm")) { + *out = KIT_OBJ_WASM; + return 1; + } + return 0; +} + +static int script_arch_name(KitSlice name, KitArchKind* out) { + if (script_slice_contains_lit_ci(name, "aarch64") || + script_slice_contains_lit_ci(name, "arm64")) { + *out = KIT_ARCH_ARM_64; + return 1; + } + if (script_slice_contains_lit_ci(name, "x86_64") || + script_slice_contains_lit_ci(name, "x86-64") || + script_slice_contains_lit_ci(name, "amd64")) { + *out = KIT_ARCH_X86_64; + return 1; + } + if (script_slice_contains_lit_ci(name, "riscv64") || + script_slice_contains_lit_ci(name, "rv64")) { + *out = KIT_ARCH_RV64; + return 1; + } + if (script_slice_contains_lit_ci(name, "riscv32") || + script_slice_contains_lit_ci(name, "rv32")) { + *out = KIT_ARCH_RV32; + return 1; + } + if (script_slice_contains_lit_ci(name, "wasm")) { + *out = KIT_ARCH_WASM; + return 1; + } + if (script_slice_contains_lit_ci(name, "i386") || + script_slice_contains_lit_ci(name, "i686") || + script_slice_contains_lit_ci(name, "x86")) { + *out = KIT_ARCH_X86_32; + return 1; + } + if (script_slice_contains_lit_ci(name, "arm")) { + *out = KIT_ARCH_ARM_32; + return 1; + } + return 0; +} + +static void validate_script_target(Linker* l, const KitLinkScript* script) { + KitObjFmt fmt; + KitArchKind arch; + if (!l || !script) return; + if (script->output_format.s && script->output_format.len && + script_format_name(script->output_format, &fmt) && + fmt != l->c->target.obj) { + compiler_panic(l->c, SRCLOC_NONE, + "linker script: OUTPUT_FORMAT '%.*s' does not match target", + SLICE_ARG(script->output_format)); + } + if (script->output_arch.s && script->output_arch.len && + script_arch_name(script->output_arch, &arch) && + arch != l->c->target.arch) { + compiler_panic(l->c, SRCLOC_NONE, + "linker script: OUTPUT_ARCH '%.*s' does not match target", + SLICE_ARG(script->output_arch)); + } +} + static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, + const ScriptOutInfo* outs, u32 nouts, const KitLinkExpr* e, int* err) { if (!e) { *err = 1; @@ -545,48 +710,119 @@ static u64 eval_link_expr(Linker* l, LinkImage* img, u64 dot, } return LinkSyms_at(&img->syms, id - 1)->vaddr; } + case KIT_LE_NEG: + return (u64)(-(i64)eval_link_expr(l, img, dot, outs, nouts, + e->v.align.val, err)); case KIT_LE_ADD: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) + - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) + + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_SUB: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) - - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) - + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_MUL: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) * - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) * + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_DIV: { - u64 rhs = eval_link_expr(l, img, dot, e->v.bin.rhs, err); + u64 rhs = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); if (rhs == 0) { *err = 1; return 0; } - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) / rhs; + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) / rhs; } case KIT_LE_AND: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) & - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) & + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_OR: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) | - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) | + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_XOR: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) ^ - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) ^ + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_SHL: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) - << eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) + << eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); case KIT_LE_SHR: - return eval_link_expr(l, img, dot, e->v.bin.lhs, err) >> - eval_link_expr(l, img, dot, e->v.bin.rhs, err); + return eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err) >> + eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + case KIT_LE_MAX: { + u64 a = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err); + u64 b = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return a > b ? a : b; + } + case KIT_LE_MIN: { + u64 a = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.lhs, err); + u64 b = eval_link_expr(l, img, dot, outs, nouts, e->v.bin.rhs, err); + return a < b ? a : b; + } case KIT_LE_ALIGN: { - u64 v = eval_link_expr(l, img, dot, e->v.align.val, err); - u64 a = eval_link_expr(l, img, dot, e->v.align.align, err); + u64 v = eval_link_expr(l, img, dot, outs, nouts, e->v.align.val, err); + u64 a = + eval_link_expr(l, img, dot, outs, nouts, e->v.align.align, err); + if (a == 0) return v; + return ALIGN_UP(v, a); + } + case KIT_LE_BLOCK: { + u64 v = eval_link_expr(l, img, dot, outs, nouts, e->v.align.val, err); + u64 a = + eval_link_expr(l, img, dot, outs, nouts, e->v.align.align, err); if (a == 0) return v; return ALIGN_UP(v, a); } - case KIT_LE_REGION_ORIGIN: - case KIT_LE_REGION_LENGTH: - case KIT_LE_MAX: - case KIT_LE_MIN: + case KIT_LE_REGION_ORIGIN: { + const KitLinkRegion* r = script_find_region(l->script, e->v.name); + if (!r) { + compiler_panic(l->c, SRCLOC_NONE, + "linker script: unknown MEMORY region '%.*s'", + SLICE_ARG(e->v.name)); + } + return r->origin; + } + case KIT_LE_REGION_LENGTH: { + const KitLinkRegion* r = script_find_region(l->script, e->v.name); + if (!r) { + compiler_panic(l->c, SRCLOC_NONE, + "linker script: unknown MEMORY region '%.*s'", + SLICE_ARG(e->v.name)); + } + return r->length; + } + case KIT_LE_ADDR: { + const ScriptOutInfo* info = script_find_out(outs, nouts, e->v.name); + if (!info) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: ADDR of unknown output section '%.*s'", + SLICE_ARG(e->v.name)); + return info->vma; + } + case KIT_LE_LOADADDR: { + const ScriptOutInfo* info = script_find_out(outs, nouts, e->v.name); + if (!info) + compiler_panic( + l->c, SRCLOC_NONE, + "linker script: LOADADDR of unknown output section '%.*s'", + SLICE_ARG(e->v.name)); + return info->lma; + } + case KIT_LE_SIZEOF: { + const ScriptOutInfo* info = script_find_out(outs, nouts, e->v.name); + if (!info) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: SIZEOF of unknown output section '%.*s'", + SLICE_ARG(e->v.name)); + return info->size; + } + case KIT_LE_SIZEOF_HEADERS: + return script_sizeof_headers(l); + case KIT_LE_DEFINED: { + Sym name = pool_intern_slice(l->c->global, e->v.name); + LinkSymId id = symhash_get(&img->globals, name); + const LinkSymbol* s = + (id != LINK_SYM_NONE) ? LinkSyms_at(&img->syms, id - 1) : NULL; + return (s && s->defined) ? 1u : 0u; + } + case KIT_LE_ABSOLUTE: + return eval_link_expr(l, img, dot, outs, nouts, e->v.align.val, err); default: compiler_panic(l->c, SRCLOC_NONE, "linker script: expression kind %u not supported", @@ -683,10 +919,23 @@ static void upsert_global_sym(Linker* l, LinkImage* img, KitSlice name, } /* Apply one KitLinkAssignment. */ +static int script_symbol_defined(Linker* l, LinkImage* img, KitSlice name) { + Sym sym; + LinkSymId id; + const LinkSymbol* s; + if (!name.s) return 0; + sym = pool_intern_slice(l->c->global, name); + id = symhash_get(&img->globals, sym); + if (id == LINK_SYM_NONE) return 0; + s = LinkSyms_at(&img->syms, id - 1); + return s && s->defined; +} + static void apply_asn(Linker* l, LinkImage* img, u64* dot, + const ScriptOutInfo* outs, u32 nouts, const KitLinkAssignment* asn) { int err = 0; - u64 v = eval_link_expr(l, img, *dot, asn->expr, &err); + u64 v = eval_link_expr(l, img, *dot, outs, nouts, asn->expr, &err); if (err) return; switch ((KitLinkAsnKind)asn->kind) { case KIT_LAS_DOT: @@ -697,16 +946,56 @@ static void apply_asn(Linker* l, LinkImage* img, u64* dot, *dot = v; break; case KIT_LAS_SYM: - case KIT_LAS_PROVIDE: + case KIT_LAS_HIDDEN: if (asn->sym.s) upsert_global_sym(l, img, asn->sym, v); break; + case KIT_LAS_PROVIDE: + case KIT_LAS_PROVIDE_HIDDEN: + if (asn->sym.s && !script_symbol_defined(l, img, asn->sym)) + upsert_global_sym(l, img, asn->sym, v); + break; + } +} + +static const char* link_basename(const char* s) { + const char* base = s; + if (!s) return NULL; + for (; *s; ++s) + if (*s == '/' || *s == '\\') base = s + 1; + return base; +} + +static int input_match_file(Linker* l, u32 ii, const KitLinkInputMatch* m) { + const LinkInput* in; + Slice name = SLICE_NULL; + const char* full; + const char* base; + u32 i; + if (!m->file_pattern.s && m->nexclude_file_patterns == 0) return 1; + if (ii >= LinkInputs_count(&l->inputs)) return 0; + in = LinkInputs_at(&l->inputs, ii); + if (in->name) name = pool_slice(l->c->global, in->name); + full = name.s; + base = link_basename(full); + if (m->file_pattern.s) { + if (!full) return 0; + if (!match_glob(m->file_pattern.s, full) && + !match_glob(m->file_pattern.s, base)) + return 0; } + for (i = 0; i < m->nexclude_file_patterns; ++i) { + const char* pat = m->exclude_file_patterns[i].s; + if (!pat || !full) continue; + if (match_glob(pat, full) || match_glob(pat, base)) return 0; + } + return 1; } -static int input_match_section(const KitLinkInputMatch* m, const char* nm) { +static int input_match_section(Linker* l, u32 ii, const KitLinkInputMatch* m, + const char* nm) { /* section_pattern is an arena-interned, NUL-terminated span of the * script text; match_glob scans it as a C string. */ - return match_glob(m->section_pattern.s, nm); + return input_match_file(l, ii, m) && match_glob(m->section_pattern.s, nm); } static u32 script_output_input_align(Linker* l, LinkImage* img, @@ -730,7 +1019,7 @@ static u32 script_output_input_align(Linker* l, LinkImage* img, if (!s || !link_section_kept(s)) continue; nm = pool_slice(l->c->global, s->name).s; if (!nm) continue; - if (!input_match_section(im, nm)) continue; + if (!input_match_section(l, ii, im, nm)) continue; if (!live_section_units(g, m, ii, ob, j)) continue; align = s->align ? s->align : 1u; if (align > align_max) align_max = align; @@ -750,6 +1039,7 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, u32 total_kept = 0; img->scripted = 1; + validate_script_target(l, script); for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; @@ -769,6 +1059,9 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, compiler_panic(img->c, SRCLOC_NONE, "link: oom on sections"); u8** claimed = NULL; + ScriptOutInfo* outs = NULL; + u64* region_cursor = NULL; + u64* load_region_cursor = NULL; if (LinkInputs_count(&l->inputs)) { u32 ni = LinkInputs_count(&l->inputs); claimed = (u8**)h->alloc(h, sizeof(*claimed) * ni, _Alignof(u8*)); @@ -782,10 +1075,31 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, memset(claimed[ii], 0, nsec); } } + if (script->nsections) { + outs = (ScriptOutInfo*)h->alloc(h, sizeof(*outs) * script->nsections, + _Alignof(ScriptOutInfo)); + if (!outs) compiler_panic(img->c, SRCLOC_NONE, "link: oom on script map"); + memset(outs, 0, sizeof(*outs) * script->nsections); + } + if (script->nregions) { + region_cursor = + (u64*)h->alloc(h, sizeof(*region_cursor) * script->nregions, + _Alignof(u64)); + load_region_cursor = + (u64*)h->alloc(h, sizeof(*load_region_cursor) * script->nregions, + _Alignof(u64)); + if (!region_cursor || !load_region_cursor) + compiler_panic(img->c, SRCLOC_NONE, "link: oom on region cursors"); + for (ii = 0; ii < script->nregions; ++ii) { + region_cursor[ii] = script->regions[ii].origin; + load_region_cursor[ii] = script->regions[ii].origin; + } + } for (k = 0; k < script->ntop_asns; ++k) { const KitLinkAssignment* a = &script->top_asns[k]; - if (a->kind == KIT_LAS_DOT) apply_asn(l, img, &dot, a); + if (a->kind == KIT_LAS_DOT) + apply_asn(l, img, &dot, outs, script->nsections, a); } u32 nseg_max = 0; @@ -816,6 +1130,7 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, for (si = 0; si < script->nsections; ++si) { const KitLinkOutputSection* os = &script->sections[si]; int is_discard = slice_eq_cstr(os->name, "/DISCARD/"); + if (outs) outs[si].name = os->name; if (is_discard) { u32 mi; @@ -833,7 +1148,7 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, nm = pool_slice(l->c->global, s->name).s; } if (!nm) continue; - if (input_match_section(im, nm)) claimed[ii][j] = 1; + if (input_match_section(l, ii, im, nm)) claimed[ii][j] = 1; } } } @@ -841,6 +1156,9 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, } u64 sec_start_dot; + u64 lma_start; + u64 subalign_val = 0; + u8 fill_byte = 0; u32 perms = 0; LinkSegmentId seg_id = (LinkSegmentId)(img->nsegments + 1u); LinkSegment* seg; @@ -849,11 +1167,61 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, u32 align_max = 1; u32 nsec_in_seg = 0; u32 first_section_idx = img->nsections; + u32 region_idx = 0; + u32 load_region_idx = 0; + int has_region = 0; + int has_load_region = 0; + + if (os->region.s) { + has_region = script_region_index(script, os->region, &region_idx); + if (!has_region) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: unknown MEMORY region '%.*s'", + SLICE_ARG(os->region)); + } + if (os->load_region.s) { + has_load_region = + script_region_index(script, os->load_region, &load_region_idx); + if (!has_load_region) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: unknown load MEMORY region '%.*s'", + SLICE_ARG(os->load_region)); + } align_max = script_output_input_align(l, img, g, os, claimed); + if (os->vma) { + int err = 0; + dot = eval_link_expr(l, img, dot, outs, script->nsections, os->vma, &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid VMA expression for '%.*s'", + SLICE_ARG(os->name)); + } else if (has_region) { + dot = region_cursor[region_idx]; + } dot = ALIGN_UP(dot, (u64)align_max); + if (os->subalign) { + int err = 0; + subalign_val = + eval_link_expr(l, img, dot, outs, script->nsections, os->subalign, + &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid SUBALIGN for '%.*s'", + SLICE_ARG(os->name)); + } + if (os->fill) { + int err = 0; + u64 fv = + eval_link_expr(l, img, dot, outs, script->nsections, os->fill, &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid fill expression for '%.*s'", + SLICE_ARG(os->name)); + fill_byte = (u8)fv; + } for (k = 0; k < os->nasns; ++k) { - apply_asn(l, img, &dot, &os->asns[k]); + apply_asn(l, img, &dot, outs, script->nsections, &os->asns[k]); } sec_start_dot = dot; @@ -878,9 +1246,9 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, nm = pool_slice(l->c->global, s->name).s; } if (!nm) continue; - if (!input_match_section(im, nm)) continue; + if (!input_match_section(l, ii, im, nm)) continue; - align = s->align ? s->align : 1u; + align = subalign_val ? (u32)subalign_val : (s->align ? s->align : 1u); has_atoms = link_input_section_has_atoms(m, j); if (has_atoms) link_input_section_atoms(m, j, &first, &count); for (ai = 0; ai < count; ++ai) { @@ -938,6 +1306,54 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, } } + if (os->lma) { + int err = 0; + lma_start = + eval_link_expr(l, img, dot, outs, script->nsections, os->lma, &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid AT() expression for '%.*s'", + SLICE_ARG(os->name)); + } else if (has_load_region) { + lma_start = ALIGN_UP(load_region_cursor[load_region_idx], + (u64)(align_max ? align_max : 1u)); + load_region_cursor[load_region_idx] = lma_start + file_size_accum; + } else { + lma_start = sec_start_dot; + if (has_region && load_region_cursor) { + u64 lend = lma_start + file_size_accum; + if (lend > load_region_cursor[region_idx]) + load_region_cursor[region_idx] = lend; + } + } + + if (has_region) { + const KitLinkRegion* r = &script->regions[region_idx]; + u64 end = dot; + if (sec_start_dot < r->origin || end > r->origin + r->length) + compiler_panic( + l->c, SRCLOC_NONE, + "linker script: section '%.*s' overflows MEMORY region '%.*s'", + SLICE_ARG(os->name), SLICE_ARG(r->name)); + region_cursor[region_idx] = end; + } + if (has_load_region && file_size_accum) { + const KitLinkRegion* r = &script->regions[load_region_idx]; + if (lma_start < r->origin || + lma_start + file_size_accum > r->origin + r->length) + compiler_panic( + l->c, SRCLOC_NONE, + "linker script: load image for '%.*s' overflows MEMORY region " + "'%.*s'", + SLICE_ARG(os->name), SLICE_ARG(r->name)); + } + if (outs) { + outs[si].vma = sec_start_dot; + outs[si].lma = lma_start; + outs[si].size = mem_size_accum; + outs[si].defined = 1; + } + if (nsec_in_seg == 0) { continue; } @@ -947,12 +1363,45 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, seg->id = seg_id; seg->flags = SF_ALLOC | perms; seg->vaddr = sec_start_dot; + seg->paddr = lma_start; file_cursor = ALIGN_UP(file_cursor, (u64)PAGE_SIZE); seg->file_offset = file_cursor; seg->mem_size = mem_size_accum; seg->file_size = file_size_accum; seg->align = align_max; seg->nsections = nsec_in_seg; + if (os->nphdrs) { + const KitLinkPhdr* ph = script_find_phdr(script, os->phdrs[0]); + if (!ph) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: unknown PHDR '%.*s'", + SLICE_ARG(os->phdrs[0])); + seg->phdr_type = ph->type; + seg->phdr_filehdr = ph->filehdr ? 1u : 0u; + seg->phdr_phdrs = ph->phdrs ? 1u : 0u; + if (ph->flags) { + int err = 0; + seg->phdr_flags = + (u32)eval_link_expr(l, img, dot, outs, script->nsections, + ph->flags, &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid PHDR FLAGS for '%.*s'", + SLICE_ARG(ph->name)); + seg->phdr_flags_set = 1u; + } + } + if (os->flags) { + int err = 0; + seg->phdr_flags = + (u32)eval_link_expr(l, img, dot, outs, script->nsections, os->flags, + &err); + if (err) + compiler_panic(l->c, SRCLOC_NONE, + "linker script: invalid FLAGS for section '%.*s'", + SLICE_ARG(os->name)); + seg->phdr_flags_set = 1u; + } file_cursor += file_size_accum; if (file_size_accum && !l->jit_mode) { img->segment_bytes[img->nsegments] = @@ -961,7 +1410,8 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, compiler_panic(img->c, SRCLOC_NONE, "link: oom on scripted segment bytes"); img->segment_bytes_cap[img->nsegments] = (size_t)file_size_accum; - memset(img->segment_bytes[img->nsegments], 0, (size_t)file_size_accum); + memset(img->segment_bytes[img->nsegments], fill_byte, + (size_t)file_size_accum); } { @@ -977,8 +1427,19 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, for (k = 0; k < script->ntop_asns; ++k) { const KitLinkAssignment* a = &script->top_asns[k]; - if (a->kind == KIT_LAS_SYM || a->kind == KIT_LAS_PROVIDE) - apply_asn(l, img, &dot, a); + if (a->kind != KIT_LAS_DOT) + apply_asn(l, img, &dot, outs, script->nsections, a); + } + + for (k = 0; k < script->nasserts; ++k) { + int err = 0; + u64 ok = eval_link_expr(l, img, dot, outs, script->nsections, + script->asserts[k].expr, &err); + if (err || !ok) { + KitSlice msg = script->asserts[k].message; + compiler_panic(l->c, SRCLOC_NONE, "linker script ASSERT failed: %.*s", + SLICE_ARG(msg)); + } } if (claimed) { @@ -989,6 +1450,12 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, } h->free(h, claimed, sizeof(*claimed) * ni); } + if (outs) h->free(h, outs, sizeof(*outs) * script->nsections); + if (region_cursor) + h->free(h, region_cursor, sizeof(*region_cursor) * script->nregions); + if (load_region_cursor) + h->free(h, load_region_cursor, + sizeof(*load_region_cursor) * script->nregions); } /* ---- pass 2b: COMMON symbol BSS allocation ---- */ @@ -1033,6 +1500,7 @@ void link_layout_commons(Linker* l, LinkImage* img) { rw_seg->id = (LinkSegmentId)(seg_idx + 1u); rw_seg->flags = SF_ALLOC | SF_WRITE; rw_seg->vaddr = vaddr; + rw_seg->paddr = vaddr; rw_seg->file_offset = vaddr; rw_seg->file_size = 0; rw_seg->mem_size = 0; diff --git a/src/link/link_reloc_layout.c b/src/link/link_reloc_layout.c @@ -317,6 +317,7 @@ LinkSectionId link_synth_region(LinkImage* img, Linker* l, Sym name, u16 perms, seg->flags = perms; seg->file_offset = base_vaddr; seg->vaddr = base_vaddr; + seg->paddr = base_vaddr; seg->file_size = size; seg->mem_size = size; seg->align = (u32)page; diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c @@ -668,6 +668,12 @@ void link_resolve_undefs(Linker* l, LinkImage* img) { s->defined = 1; continue; } + if (l->allow_undefined) { + s->kind = SK_ABS; + s->vaddr = 0; + s->defined = 1; + continue; + } { Slice nm_s = s->name ? pool_slice(l->c->global, s->name) : SLICE_NULL; const char* nm = nm_s.s ? nm_s.s : ""; @@ -877,6 +883,103 @@ static void gc_promote_by_section_name(Linker* l, LinkImage* img, GcLive* g, } } +static int gc_match_glob(const char* pat, const char* name) { + size_t plen, nlen; + if (!pat || !name) return 0; + plen = slice_from_cstr(pat).len; + nlen = slice_from_cstr(name).len; + if (plen == 1 && pat[0] == '*') return 1; + if (plen >= 2 && pat[plen - 1] == '*') { + if (nlen + 1 < plen) return 0; + return memcmp(pat, name, plen - 1) == 0; + } + if (plen >= 2 && pat[0] == '*') { + if (nlen + 1 < plen) return 0; + return memcmp(pat + 1, name + (nlen - (plen - 1)), plen - 1) == 0; + } + return plen == nlen && memcmp(pat, name, plen) == 0; +} + +static const char* gc_basename(const char* s) { + const char* base = s; + if (!s) return NULL; + for (; *s; ++s) + if (*s == '/' || *s == '\\') base = s + 1; + return base; +} + +static int gc_match_input_name(Linker* l, u32 ii, + const KitLinkInputMatch* m) { + const LinkInput* in; + Slice nm = SLICE_NULL; + const char* full; + const char* base; + u32 i; + if (!m->file_pattern.s && m->nexclude_file_patterns == 0) return 1; + in = LinkInputs_at(&l->inputs, ii); + if (in->name) nm = pool_slice(l->c->global, in->name); + full = nm.s; + base = gc_basename(full); + if (m->file_pattern.s) { + if (!full) return 0; + if (!gc_match_glob(m->file_pattern.s, full) && + !gc_match_glob(m->file_pattern.s, base)) + return 0; + } + for (i = 0; i < m->nexclude_file_patterns; ++i) { + const char* pat = m->exclude_file_patterns[i].s; + if (!pat || !full) continue; + if (gc_match_glob(pat, full) || gc_match_glob(pat, base)) return 0; + } + return 1; +} + +static void gc_mark_script_keep_roots(Linker* l, LinkImage* img, GcLive* g, + GcQueue* q, Heap* h) { + u32 si, mi, ii, j; + if (!l->script) return; + for (si = 0; si < l->script->nsections; ++si) { + const KitLinkOutputSection* os = &l->script->sections[si]; + for (mi = 0; mi < os->ninputs; ++mi) { + const KitLinkInputMatch* im = &os->inputs[mi]; + if (!im->keep) continue; + for (ii = 0; ii < LinkInputs_count(&l->inputs); ++ii) { + ObjBuilder* ob = LinkInputs_at(&l->inputs, ii)->obj; + InputMap* m = &img->input_maps[ii]; + if (!gc_match_input_name(l, ii, im)) continue; + for (j = 1; j < obj_section_count(ob); ++j) { + const Section* s = obj_section_get(ob, j); + Slice sn; + if (!s || !link_section_kept(s)) continue; + if (m->comdat_discarded[j]) continue; + sn = pool_slice(l->c->global, s->name); + if (!gc_match_glob(im->section_pattern.s, sn.s)) continue; + gc_mark_section_or_atoms(g, q, h, ob, m, ii, j); + } + } + } + } +} + +static void gc_mark_script_extern_roots(Linker* l, LinkImage* img, GcLive* g, + GcQueue* q, Heap* h) { + u32 i; + if (!l->script) return; + for (i = 0; i < l->script->nexterns; ++i) { + Sym name = pool_intern_slice(l->c->global, l->script->externs[i]); + LinkSymId id = symhash_get(&img->globals, name); + u32 tii; + ObjSecId tsid; + ObjAtomId taid; + if (gc_def_site(img, l, id, &tii, &tsid, &taid)) { + if (taid != OBJ_ATOM_NONE) + gc_mark_atom(g, q, h, tii, taid); + else + gc_mark(g, q, h, tii, tsid); + } + } +} + void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { u32 ii, j, k; GcQueue q; @@ -936,6 +1039,9 @@ void link_gc_compute(Linker* l, LinkImage* img, GcLive* g) { } } + gc_mark_script_extern_roots(l, img, g, &q, h); + gc_mark_script_keep_roots(l, img, g, &q, h); + /* Keep executable definitions that a linked shared library references but * nothing in the executable does (e.g. FreeBSD libc.so.7's back-references * to crt-defined `environ` / `__progname`). Without rooting these, GC drops @@ -1195,6 +1301,14 @@ static int scan_archive_once(Linker* l, LinkArchive* ar, u32 max_order, if (want_ifunc_init != 0 && symhash_get(&defined, want_ifunc_init) == LINK_SYM_NONE) symhash_set(&undefs, want_ifunc_init, 1u); + if (l->script && l->script->externs) { + u32 ei; + for (ei = 0; ei < l->script->nexterns; ++ei) { + Sym ex = pool_intern_slice(l->c->global, l->script->externs[ei]); + if (symhash_get(&defined, ex) == LINK_SYM_NONE) + symhash_set(&undefs, ex, 1u); + } + } for (m = 0; m < ar->nmembers; ++m) { LinkArchiveMember* mem = &ar->members[m]; diff --git a/src/link/link_script.c b/src/link/link_script.c @@ -51,6 +51,7 @@ #include "core/diag.h" #include "core/heap.h" #include "core/slice.h" +#include "core/util.h" /* The public KitLinkScript has no place to carry its backing-arena * pointer, so we allocate a fixed-shape owner block via heap and arena- @@ -142,6 +143,22 @@ typedef struct VecSec { KitLinkOutputSection* p; u32 n, cap; } VecSec; +typedef struct VecRegion { + KitLinkRegion* p; + u32 n, cap; +} VecRegion; +typedef struct VecPhdr { + KitLinkPhdr* p; + u32 n, cap; +} VecPhdr; +typedef struct VecAssert { + KitLinkAssertion* p; + u32 n, cap; +} VecAssert; +typedef struct VecSlice { + KitSlice* p; + u32 n, cap; +} VecSlice; static int vec_reserve_(LSP* p, void** ptr, u32* cap, u32 want, size_t es) { u32 nc; @@ -246,6 +263,47 @@ static int lex_ident(LSP* p, const char** out, size_t* out_len) { return 0; } +static int lex_string(LSP* p, KitSlice* out) { + size_t start; + skip_ws(p); + if (p->err) return 1; + if (p->pos >= p->len || p->src[p->pos] != '"') { + lsp_errf(p, p->pos, "expected string literal"); + return 1; + } + ++p->pos; + start = p->pos; + while (p->pos < p->len && p->src[p->pos] != '"') { + if (p->src[p->pos] == '\\') { + lsp_errf(p, p->pos, "escapes in linker-script strings not supported"); + return 1; + } + ++p->pos; + } + if (p->pos >= p->len) { + lsp_errf(p, start, "unterminated string literal"); + return 1; + } + *out = lsp_slice(p, p->src + start, p->pos - start); + ++p->pos; + return out->s ? 0 : 1; +} + +static int lex_ident_or_string(LSP* p, KitSlice* out) { + int ch; + skip_ws(p); + if (p->err) return 1; + ch = peek_ch(p); + if (ch == '"') return lex_string(p, out); + { + const char* s; + size_t n; + if (lex_ident(p, &s, &n)) return 1; + *out = lsp_slice(p, s, n); + return out->s ? 0 : 1; + } +} + /* Match a literal keyword. Caller must have already peeked. */ static int match_kw(LSP* p, const char* kw) { size_t klen = slice_from_cstr(kw).len; @@ -318,6 +376,19 @@ static KitLinkExpr* parse_int(LSP* p) { return NULL; } } + if (p->pos < p->len) { + char suffix = p->src[p->pos]; + if (suffix == 'K' || suffix == 'k') { + v *= 1024ll; + ++p->pos; + } else if (suffix == 'M' || suffix == 'm') { + v *= 1024ll * 1024ll; + ++p->pos; + } else if (suffix == 'G' || suffix == 'g') { + v *= 1024ll * 1024ll * 1024ll; + ++p->pos; + } + } e = lsp_new_expr(p); if (!e) return NULL; e->kind = KIT_LE_INT; @@ -334,6 +405,21 @@ static KitLinkExpr* parse_atom(LSP* p) { lsp_errf(p, p->pos, "unexpected end of expression"); return NULL; } + if (ch == '-' || ch == '+') { + int neg = (ch == '-'); + KitLinkExpr* val; + KitLinkExpr* e; + ++p->pos; + val = parse_atom(p); + if (!val) return NULL; + if (!neg) return val; + e = lsp_new_expr(p); + if (!e) return NULL; + e->kind = KIT_LE_NEG; + e->v.align.val = val; + e->v.align.align = NULL; + return e; + } if (ch == '(') { KitLinkExpr* e; ++p->pos; @@ -361,13 +447,16 @@ static KitLinkExpr* parse_atom(LSP* p) { } if (ch >= '0' && ch <= '9') return parse_int(p); if (is_id_start(ch)) { - /* either ALIGN(...) or a symbol reference */ - if (match_kw(p, "ALIGN")) { + /* either a built-in helper or a symbol reference */ + if (match_kw(p, "ALIGN") || match_kw(p, "BLOCK")) { /* Two forms, matching GNU ld: * ALIGN(align) — align the current location `.` (val defaults * to dot); the common `. = ALIGN(N)` idiom. * ALIGN(val, align) — align an explicit expression. */ KitLinkExpr *val, *aln, *e; + KitLinkExprKind kind = + (memcmp(p->src + p->pos - 5, "BLOCK", 5) == 0) ? KIT_LE_BLOCK + : KIT_LE_ALIGN; if (expect_ch(p, '(')) return NULL; val = parse_expr(p); if (!val) return NULL; @@ -386,14 +475,84 @@ static KitLinkExpr* parse_atom(LSP* p) { if (expect_ch(p, ')')) return NULL; e = lsp_new_expr(p); if (!e) return NULL; - e->kind = KIT_LE_ALIGN; + e->kind = (uint8_t)kind; e->v.align.val = val; e->v.align.align = aln; return e; } if (match_kw(p, "MAX") || match_kw(p, "MIN")) { - lsp_errf(p, p->pos, "MAX/MIN not supported in this subset"); - return NULL; + KitLinkExprKind kind = + (memcmp(p->src + p->pos - 3, "MIN", 3) == 0) ? KIT_LE_MIN + : KIT_LE_MAX; + KitLinkExpr *lhs, *rhs, *e; + if (expect_ch(p, '(')) return NULL; + lhs = parse_expr(p); + if (!lhs) return NULL; + if (expect_ch(p, ',')) return NULL; + rhs = parse_expr(p); + if (!rhs) return NULL; + if (expect_ch(p, ')')) return NULL; + e = lsp_new_expr(p); + if (!e) return NULL; + e->kind = (uint8_t)kind; + e->v.bin.lhs = lhs; + e->v.bin.rhs = rhs; + return e; + } + if (match_kw(p, "ORIGIN") || match_kw(p, "LENGTH") || + match_kw(p, "ADDR") || match_kw(p, "LOADADDR") || + match_kw(p, "SIZEOF") || match_kw(p, "DEFINED")) { + size_t end = p->pos; + size_t start = end; + KitLinkExprKind kind; + const char* s; + size_t n; + KitLinkExpr* e; + while (start > 0 && is_id_cont((unsigned char)p->src[start - 1])) + --start; + if (end - start == 6 && memcmp(p->src + start, "ORIGIN", 6) == 0) + kind = KIT_LE_REGION_ORIGIN; + else if (end - start == 6 && memcmp(p->src + start, "LENGTH", 6) == 0) + kind = KIT_LE_REGION_LENGTH; + else if (end - start == 4 && memcmp(p->src + start, "ADDR", 4) == 0) + kind = KIT_LE_ADDR; + else if (end - start == 8 && memcmp(p->src + start, "LOADADDR", 8) == 0) + kind = KIT_LE_LOADADDR; + else if (end - start == 6 && memcmp(p->src + start, "SIZEOF", 6) == 0) + kind = KIT_LE_SIZEOF; + else + kind = KIT_LE_DEFINED; + if (expect_ch(p, '(')) return NULL; + if (lex_ident(p, &s, &n)) return NULL; + if (expect_ch(p, ')')) return NULL; + e = lsp_new_expr(p); + if (!e) return NULL; + e->kind = (uint8_t)kind; + e->v.name = lsp_slice(p, s, n); + return e; + } + if (match_kw(p, "SIZEOF_HEADERS")) { + KitLinkExpr* e; + if (match_ch(p, '(')) { + if (expect_ch(p, ')')) return NULL; + } + e = lsp_new_expr(p); + if (!e) return NULL; + e->kind = KIT_LE_SIZEOF_HEADERS; + return e; + } + if (match_kw(p, "ABSOLUTE")) { + KitLinkExpr *val, *e; + if (expect_ch(p, '(')) return NULL; + val = parse_expr(p); + if (!val) return NULL; + if (expect_ch(p, ')')) return NULL; + e = lsp_new_expr(p); + if (!e) return NULL; + e->kind = KIT_LE_ABSOLUTE; + e->v.align.val = val; + e->v.align.align = NULL; + return e; } { const char* s; @@ -530,11 +689,44 @@ static int push_dot_align(LSP* p, VecAsn* asns, KitLinkExpr* align_n) { /* ---- output section body ---- */ -static int parse_input_matchers(LSP* p, VecMatch* out) { - /* opening `*` already consumed by caller. expect `(p1 p2 ...)` */ +static int lex_pattern_token(LSP* p, const char** out, size_t* out_len) { + size_t start; + skip_ws(p); + if (p->err) return 1; + start = p->pos; + while (p->pos < p->len) { + char c = p->src[p->pos]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '(' || + c == ')' || c == '{' || c == '}' || c == ';' || c == ':' || + c == ',') + break; + ++p->pos; + } + if (p->pos == start) { + lsp_errf(p, p->pos, "expected pattern"); + return 1; + } + *out = p->src + start; + *out_len = p->pos - start; + return 0; +} + +static const KitSlice* copy_slice_vec(LSP* p, const VecSlice* v) { + KitSlice* arr; + if (!v || !v->n) return NULL; + arr = arena_array(p->arena, KitSlice, v->n); + if (!arr) return NULL; + memcpy(arr, v->p, sizeof(*arr) * v->n); + return arr; +} + +static int parse_file_excludes(LSP* p, VecSlice* excludes) { if (expect_ch(p, '(')) return 1; for (;;) { int ch; + const char* s; + size_t n; + KitSlice sl; skip_ws(p); if (p->err) return 1; ch = peek_ch(p); @@ -543,38 +735,109 @@ static int parse_input_matchers(LSP* p, VecMatch* out) { return 0; } if (ch < 0) { - lsp_errf(p, p->pos, "unterminated `*(...)`"); + lsp_errf(p, p->pos, "unterminated EXCLUDE_FILE"); return 1; } - /* a pattern is a section-name-like run: id-start chars plus '*'. */ + if (lex_pattern_token(p, &s, &n)) return 1; + sl = lsp_slice(p, s, n); + if (!sl.s) return 1; + if (VEC_PUSH(p, *excludes, sl)) return 1; + } +} + +static int parse_input_matchers_for_file(LSP* p, VecMatch* out, + KitSlice file_pattern, int keep) { + /* expect `(p1 p2 ...)` */ + VecSlice excludes = {0}; + if (expect_ch(p, '(')) return 1; + for (;;) { + int ch; + skip_ws(p); + if (p->err) return 1; + ch = peek_ch(p); + if (ch == ')') { + ++p->pos; + vec_free_(p, excludes.p, excludes.cap, sizeof(*excludes.p)); + return 0; + } + if (ch < 0) { + lsp_errf(p, p->pos, "unterminated `*(...)`"); + goto fail; + } + if (match_kw(p, "EXCLUDE_FILE")) { + if (parse_file_excludes(p, &excludes)) goto fail; + continue; + } { - size_t start; const char* s; size_t n; KitLinkInputMatch m; - start = p->pos; - while (p->pos < p->len) { - char c = p->src[p->pos]; - if (is_id_cont((unsigned char)c) || c == '*') - ++p->pos; - else - break; - } - n = p->pos - start; + if (lex_pattern_token(p, &s, &n)) goto fail; if (n == 0) { lsp_errf(p, p->pos, "expected section pattern"); - return 1; + goto fail; } - s = p->src + start; - m.file_pattern = KIT_SLICE_NULL; + memset(&m, 0, sizeof(m)); + m.file_pattern = file_pattern; m.section_pattern = lsp_slice(p, s, n); - m.keep = 0; - if (VEC_PUSH(p, *out, m)) return 1; + m.exclude_file_patterns = copy_slice_vec(p, &excludes); + m.nexclude_file_patterns = excludes.n; + m.keep = keep; + if (excludes.n && !m.exclude_file_patterns) goto fail; + if (VEC_PUSH(p, *out, m)) goto fail; } } +fail: + vec_free_(p, excludes.p, excludes.cap, sizeof(*excludes.p)); + return 1; +} + +static int parse_input_matchers(LSP* p, VecMatch* out, int keep) { + /* opening `*` already consumed by caller. */ + return parse_input_matchers_for_file(p, out, KIT_SLICE_NULL, keep); +} + +static int parse_assignment_call(LSP* p, KitLinkAsnKind kind, VecAsn* asns) { + const char* s; + size_t n; + KitLinkExpr* e; + KitLinkAssignment a; + if (expect_ch(p, '(')) return 1; + if (lex_ident(p, &s, &n)) return 1; + if (expect_ch(p, '=')) return 1; + e = parse_expr(p); + if (!e) return 1; + if (expect_ch(p, ')')) return 1; + (void)match_ch(p, ';'); + a.kind = (uint8_t)kind; + a.sym = lsp_slice(p, s, n); + a.expr = e; + return VEC_PUSH(p, *asns, a); +} + +static int parse_assertion(LSP* p, VecAssert* asserts) { + KitLinkAssertion a; + memset(&a, 0, sizeof(a)); + if (expect_ch(p, '(')) return 1; + a.expr = parse_expr(p); + if (!a.expr) return 1; + if (expect_ch(p, ',')) return 1; + if (lex_string(p, &a.message)) return 1; + if (expect_ch(p, ')')) return 1; + (void)match_ch(p, ';'); + return VEC_PUSH(p, *asserts, a); +} + +static int parse_file_qualified_matchers(LSP* p, VecMatch* inputs, + const char* file, size_t file_len, + int keep) { + KitSlice fp = lsp_slice(p, file, file_len); + if (!fp.s) return 1; + return parse_input_matchers_for_file(p, inputs, fp, keep); } -static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns) { +static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns, + VecAssert* asserts, KitLinkExpr** fill_out) { if (expect_ch(p, '{')) return 1; for (;;) { int ch; @@ -591,7 +854,7 @@ static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns) { } if (ch == '*') { ++p->pos; - if (parse_input_matchers(p, inputs)) return 1; + if (parse_input_matchers(p, inputs, 0)) return 1; continue; } if (ch == '.') { @@ -618,18 +881,58 @@ static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns) { continue; } if (is_id_start(ch)) { - /* sym = expr; */ + if (match_kw(p, "KEEP")) { + const char* fs; + size_t fn; + if (expect_ch(p, '(')) return 1; + skip_ws(p); + if (p->err) return 1; + if (peek_ch(p) == '*') { + ++p->pos; + if (parse_input_matchers(p, inputs, 1)) return 1; + } else { + if (lex_pattern_token(p, &fs, &fn)) return 1; + if (parse_file_qualified_matchers(p, inputs, fs, fn, 1)) return 1; + } + if (expect_ch(p, ')')) return 1; + continue; + } + if (match_kw(p, "FILL")) { + if (expect_ch(p, '(')) return 1; + *fill_out = parse_expr(p); + if (!*fill_out) return 1; + if (expect_ch(p, ')')) return 1; + (void)match_ch(p, ';'); + continue; + } + if (match_kw(p, "ASSERT")) { + if (parse_assertion(p, asserts)) return 1; + continue; + } + if (match_kw(p, "PROVIDE_HIDDEN")) { + if (parse_assignment_call(p, KIT_LAS_PROVIDE_HIDDEN, asns)) return 1; + continue; + } + if (match_kw(p, "PROVIDE")) { + if (parse_assignment_call(p, KIT_LAS_PROVIDE, asns)) return 1; + continue; + } + if (match_kw(p, "HIDDEN")) { + if (parse_assignment_call(p, KIT_LAS_HIDDEN, asns)) return 1; + continue; + } + /* sym = expr; or file(section-patterns) */ const char* s; size_t n; KitLinkExpr* e; KitLinkAssignment a; - if (match_kw(p, "PROVIDE") || match_kw(p, "KEEP")) { - lsp_errf(p, p->pos, "PROVIDE/KEEP not supported in this subset"); - return 1; - } if (lex_ident(p, &s, &n)) return 1; skip_ws(p); if (p->err) return 1; + if (p->pos < p->len && p->src[p->pos] == '(') { + if (parse_file_qualified_matchers(p, inputs, s, n, 0)) return 1; + continue; + } if (!match_ch(p, '=')) { lsp_errf(p, p->pos, "expected `=` after `%.*s`", (int)n, s); return 1; @@ -651,31 +954,55 @@ static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns) { /* ---- output section header ---- */ static int parse_output_section(LSP* p, const char* name_buf, size_t name_len, - VecSec* sections) { + KitLinkExpr* vma, bool noload, + VecSec* sections, VecAssert* asserts) { /* The `:` is the next non-ws char on entry. Header may carry * `: ALIGN(N)` then `{ body }`. */ KitLinkOutputSection sec; VecMatch inputs = {0}; VecAsn asns = {0}; + VecSlice phdrs = {0}; KitLinkExpr* align_n = NULL; + KitLinkExpr* subalign = NULL; + KitLinkExpr* fill = NULL; + KitLinkExpr* lma = NULL; + KitLinkExpr* flags = NULL; + KitSlice region = KIT_SLICE_NULL; + KitSlice load_region = KIT_SLICE_NULL; if (expect_ch(p, ':')) return 1; - skip_ws(p); - if (p->err) return 1; - if (match_kw(p, "ALIGN")) { - if (expect_ch(p, '(')) return 1; - align_n = parse_expr(p); - if (!align_n) return 1; - if (expect_ch(p, ')')) return 1; - } - /* Reject AT>, > REGION, >REGION before the body. */ - skip_ws(p); - if (p->err) return 1; - if (p->pos < p->len && - (p->src[p->pos] == '>' || (p->src[p->pos] == 'A' && match_kw(p, "AT")))) { - lsp_errf(p, p->pos, - "memory-region placement (>REGION / AT>) not supported"); - return 1; + for (;;) { + skip_ws(p); + if (p->err) return 1; + if (match_kw(p, "ALIGN")) { + if (expect_ch(p, '(')) return 1; + align_n = parse_expr(p); + if (!align_n) return 1; + if (expect_ch(p, ')')) return 1; + continue; + } + if (match_kw(p, "SUBALIGN")) { + if (expect_ch(p, '(')) return 1; + subalign = parse_expr(p); + if (!subalign) return 1; + if (expect_ch(p, ')')) return 1; + continue; + } + if (match_kw(p, "AT")) { + if (expect_ch(p, '(')) return 1; + lma = parse_expr(p); + if (!lma) return 1; + if (expect_ch(p, ')')) return 1; + continue; + } + if (match_kw(p, "FLAGS")) { + if (expect_ch(p, '(')) return 1; + flags = parse_expr(p); + if (!flags) return 1; + if (expect_ch(p, ')')) return 1; + continue; + } + break; } /* Section header alignment is encoded as the first asn — applicator @@ -684,16 +1011,73 @@ static int parse_output_section(LSP* p, const char* name_buf, size_t name_len, if (push_dot_align(p, &asns, align_n)) goto fail; } - if (parse_section_body(p, &inputs, &asns)) goto fail; + if (parse_section_body(p, &inputs, &asns, asserts, &fill)) goto fail; - /* Optional trailing `> REGION` / `AT> REGION` / `: NOLOAD` etc. — all - * unsupported. We allow an optional trailing `;` and nothing else. */ - (void)match_ch(p, ';'); + for (;;) { + int ch; + skip_ws(p); + if (p->err) goto fail; + ch = peek_ch(p); + if (ch == ';') { + ++p->pos; + break; + } + if (ch == '>') { + const char* s; + size_t n; + ++p->pos; + if (lex_ident(p, &s, &n)) goto fail; + region = lsp_slice(p, s, n); + if (!region.s) goto fail; + continue; + } + if (ch == ':') { + const char* s; + size_t n; + KitSlice sl; + ++p->pos; + if (lex_ident(p, &s, &n)) goto fail; + sl = lsp_slice(p, s, n); + if (!sl.s) goto fail; + if (VEC_PUSH(p, phdrs, sl)) goto fail; + continue; + } + if (ch == '=') { + ++p->pos; + fill = parse_expr(p); + if (!fill) goto fail; + continue; + } + if (match_kw(p, "AT")) { + const char* s; + size_t n; + if (match_ch(p, '>')) { + if (lex_ident(p, &s, &n)) goto fail; + load_region = lsp_slice(p, s, n); + if (!load_region.s) goto fail; + continue; + } + if (expect_ch(p, '(')) goto fail; + lma = parse_expr(p); + if (!lma) goto fail; + if (expect_ch(p, ')')) goto fail; + continue; + } + if (match_kw(p, "FILL")) { + if (expect_ch(p, '(')) goto fail; + fill = parse_expr(p); + if (!fill) goto fail; + if (expect_ch(p, ')')) goto fail; + continue; + } + break; + } /* Materialize. */ { KitLinkInputMatch* arr_in = NULL; KitLinkAssignment* arr_as = NULL; + KitSlice* arr_ph = NULL; if (inputs.n) { arr_in = arena_array(p->arena, KitLinkInputMatch, inputs.n); if (!arr_in) goto fail; @@ -704,28 +1088,46 @@ static int parse_output_section(LSP* p, const char* name_buf, size_t name_len, if (!arr_as) goto fail; memcpy(arr_as, asns.p, sizeof(*arr_as) * asns.n); } + if (phdrs.n) { + arr_ph = arena_array(p->arena, KitSlice, phdrs.n); + if (!arr_ph) goto fail; + memcpy(arr_ph, phdrs.p, sizeof(*arr_ph) * phdrs.n); + } memset(&sec, 0, sizeof(sec)); sec.name = lsp_slice(p, name_buf, name_len); + sec.vma = vma; + sec.lma = lma; sec.inputs = arr_in; sec.ninputs = inputs.n; + sec.region = region; + sec.load_region = load_region; + sec.phdrs = arr_ph; + sec.nphdrs = phdrs.n; sec.asns = arr_as; sec.nasns = asns.n; + sec.subalign = subalign; + sec.fill = fill; + sec.flags = flags; + sec.noload = noload; } vec_free_(p, inputs.p, inputs.cap, sizeof(*inputs.p)); vec_free_(p, asns.p, asns.cap, sizeof(*asns.p)); + vec_free_(p, phdrs.p, phdrs.cap, sizeof(*phdrs.p)); return VEC_PUSH(p, *sections, sec); fail: vec_free_(p, inputs.p, inputs.cap, sizeof(*inputs.p)); vec_free_(p, asns.p, asns.cap, sizeof(*asns.p)); + vec_free_(p, phdrs.p, phdrs.cap, sizeof(*phdrs.p)); return 1; } /* ---- SECTIONS{...} ---- */ -static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections) { +static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections, + VecAssert* asserts) { if (expect_ch(p, '{')) return 1; for (;;) { int ch; @@ -747,7 +1149,9 @@ static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections) { if (p->pos + klen <= p->len && memcmp(p->src + p->pos, kDiscard, klen) == 0) { p->pos += klen; - if (parse_output_section(p, kDiscard, klen, sections)) return 1; + if (parse_output_section(p, kDiscard, klen, NULL, false, sections, + asserts)) + return 1; continue; } lsp_errf(p, p->pos, "expected /DISCARD/ or section header"); @@ -787,18 +1191,71 @@ static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections) { const char* s; size_t n; size_t name_off; + KitLinkExpr* vma = NULL; + bool noload = false; + if (match_kw(p, "ASSERT")) { + if (parse_assertion(p, asserts)) return 1; + continue; + } + if (match_kw(p, "PROVIDE_HIDDEN")) { + if (parse_assignment_call(p, KIT_LAS_PROVIDE_HIDDEN, top_asns)) + return 1; + continue; + } if (match_kw(p, "PROVIDE")) { - lsp_errf(p, p->pos, "PROVIDE not supported in this subset"); - return 1; + if (parse_assignment_call(p, KIT_LAS_PROVIDE, top_asns)) return 1; + continue; + } + if (match_kw(p, "HIDDEN")) { + if (parse_assignment_call(p, KIT_LAS_HIDDEN, top_asns)) return 1; + continue; } name_off = p->pos; if (lex_ident(p, &s, &n)) return 1; skip_ws(p); if (p->err) return 1; + if (match_ch(p, '(')) { + const char* ts; + size_t tn; + if (lex_ident(p, &ts, &tn)) return 1; + if (tn == 6 && memcmp(ts, "NOLOAD", 6) == 0) + noload = true; + else { + lsp_errf(p, name_off, "unsupported output-section type `%.*s`", + (int)tn, ts); + return 1; + } + if (expect_ch(p, ')')) return 1; + skip_ws(p); + if (p->err) return 1; + } + if (p->pos < p->len && p->src[p->pos] != ':' && + p->src[p->pos] != '=') { + vma = parse_expr(p); + if (!vma) return 1; + skip_ws(p); + if (p->err) return 1; + if (match_ch(p, '(')) { + const char* ts; + size_t tn; + if (lex_ident(p, &ts, &tn)) return 1; + if (tn == 6 && memcmp(ts, "NOLOAD", 6) == 0) + noload = true; + else { + lsp_errf(p, name_off, "unsupported output-section type `%.*s`", + (int)tn, ts); + return 1; + } + if (expect_ch(p, ')')) return 1; + skip_ws(p); + if (p->err) return 1; + } + } if (p->pos < p->len && p->src[p->pos] == ':') { char* nm = lsp_strdup(p, s, n); if (!nm) return 1; - if (parse_output_section(p, nm, n, sections)) return 1; + if (parse_output_section(p, nm, n, vma, noload, sections, asserts)) + return 1; continue; } if (match_ch(p, '=')) { @@ -823,12 +1280,268 @@ static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections) { } } +static int eval_const_expr(LSP* p, const KitLinkExpr* e, u64* out) { + u64 a, b; + if (!e) return 1; + switch ((KitLinkExprKind)e->kind) { + case KIT_LE_INT: + *out = (u64)e->v.int_val; + return 0; + case KIT_LE_NEG: + if (eval_const_expr(p, e->v.align.val, &a)) return 1; + *out = (u64)(-(i64)a); + return 0; + case KIT_LE_ADD: + case KIT_LE_SUB: + case KIT_LE_MUL: + case KIT_LE_DIV: + case KIT_LE_AND: + case KIT_LE_OR: + case KIT_LE_XOR: + case KIT_LE_SHL: + case KIT_LE_SHR: + case KIT_LE_MAX: + case KIT_LE_MIN: + if (eval_const_expr(p, e->v.bin.lhs, &a) || + eval_const_expr(p, e->v.bin.rhs, &b)) + return 1; + switch ((KitLinkExprKind)e->kind) { + case KIT_LE_ADD: + *out = a + b; + return 0; + case KIT_LE_SUB: + *out = a - b; + return 0; + case KIT_LE_MUL: + *out = a * b; + return 0; + case KIT_LE_DIV: + if (b == 0) { + lsp_errf(p, p->pos, "division by zero in constant expression"); + return 1; + } + *out = a / b; + return 0; + case KIT_LE_AND: + *out = a & b; + return 0; + case KIT_LE_OR: + *out = a | b; + return 0; + case KIT_LE_XOR: + *out = a ^ b; + return 0; + case KIT_LE_SHL: + *out = a << b; + return 0; + case KIT_LE_SHR: + *out = a >> b; + return 0; + case KIT_LE_MAX: + *out = a > b ? a : b; + return 0; + case KIT_LE_MIN: + *out = a < b ? a : b; + return 0; + default: + break; + } + break; + case KIT_LE_ALIGN: + case KIT_LE_BLOCK: + if (eval_const_expr(p, e->v.align.val, &a) || + eval_const_expr(p, e->v.align.align, &b)) + return 1; + *out = b ? ALIGN_UP(a, b) : a; + return 0; + case KIT_LE_ABSOLUTE: + return eval_const_expr(p, e->v.align.val, out); + default: + break; + } + lsp_errf(p, p->pos, "non-constant expression in MEMORY region"); + return 1; +} + +static int parse_memory_block(LSP* p, VecRegion* regions) { + if (expect_ch(p, '{')) return 1; + for (;;) { + const char* ns; + size_t nn; + KitLinkRegion r; + int have_origin = 0, have_length = 0; + skip_ws(p); + if (p->err) return 1; + if (peek_ch(p) == '}') { + ++p->pos; + return 0; + } + if (lex_ident(p, &ns, &nn)) return 1; + memset(&r, 0, sizeof(r)); + r.name = lsp_slice(p, ns, nn); + if (!r.name.s) return 1; + skip_ws(p); + if (match_ch(p, '(')) { + while (!p->err && p->pos < p->len && p->src[p->pos] != ')') { + char c = p->src[p->pos++]; + if (c == 'r' || c == 'R') r.flags |= KIT_LRF_R; + if (c == 'w' || c == 'W') r.flags |= KIT_LRF_W; + if (c == 'x' || c == 'X') r.flags |= KIT_LRF_X; + } + if (expect_ch(p, ')')) return 1; + } + if (expect_ch(p, ':')) return 1; + while (!p->err) { + const char* ks; + size_t kn; + KitLinkExpr* e; + u64 v; + skip_ws(p); + if (p->err) return 1; + if (peek_ch(p) == '}') break; + if (lex_ident(p, &ks, &kn)) return 1; + if (expect_ch(p, '=')) return 1; + e = parse_expr(p); + if (!e) return 1; + if (eval_const_expr(p, e, &v)) return 1; + if ((kn == 6 && memcmp(ks, "ORIGIN", 6) == 0) || + (kn == 3 && memcmp(ks, "org", 3) == 0) || + (kn == 1 && (ks[0] == 'o' || ks[0] == 'O'))) { + r.origin = v; + have_origin = 1; + } else if ((kn == 6 && memcmp(ks, "LENGTH", 6) == 0) || + (kn == 3 && memcmp(ks, "len", 3) == 0) || + (kn == 1 && (ks[0] == 'l' || ks[0] == 'L'))) { + r.length = v; + have_length = 1; + } else { + lsp_errf(p, p->pos, "unknown MEMORY field `%.*s`", (int)kn, ks); + return 1; + } + skip_ws(p); + (void)match_ch(p, ','); + if (have_origin && have_length) break; + } + if (!have_origin || !have_length) { + lsp_errf(p, p->pos, "MEMORY region `%.*s` needs ORIGIN and LENGTH", + (int)nn, ns); + return 1; + } + if (VEC_PUSH(p, *regions, r)) return 1; + } +} + +static uint32_t phdr_type_from_name(const char* s, size_t n) { + if (n == 7 && memcmp(s, "PT_NULL", 7) == 0) return 0u; + if (n == 7 && memcmp(s, "PT_LOAD", 7) == 0) return 1u; + if (n == 10 && memcmp(s, "PT_DYNAMIC", 10) == 0) return 2u; + if (n == 9 && memcmp(s, "PT_INTERP", 9) == 0) return 3u; + if (n == 7 && memcmp(s, "PT_NOTE", 7) == 0) return 4u; + if (n == 8 && memcmp(s, "PT_SHLIB", 8) == 0) return 5u; + if (n == 7 && memcmp(s, "PT_PHDR", 7) == 0) return 6u; + if (n == 6 && memcmp(s, "PT_TLS", 6) == 0) return 7u; + if (n == 12 && memcmp(s, "PT_GNU_STACK", 12) == 0) return 0x6474e551u; + if (n == 12 && memcmp(s, "PT_GNU_RELRO", 12) == 0) return 0x6474e552u; + return 1u; +} + +static int parse_phdrs_block(LSP* p, VecPhdr* phdrs) { + if (expect_ch(p, '{')) return 1; + for (;;) { + const char *ns, *ts; + size_t nn, tn; + KitLinkPhdr ph; + skip_ws(p); + if (p->err) return 1; + if (peek_ch(p) == '}') { + ++p->pos; + return 0; + } + if (lex_ident(p, &ns, &nn)) return 1; + if (lex_ident(p, &ts, &tn)) return 1; + memset(&ph, 0, sizeof(ph)); + ph.name = lsp_slice(p, ns, nn); + ph.type = phdr_type_from_name(ts, tn); + if (!ph.name.s) return 1; + for (;;) { + skip_ws(p); + if (p->err) return 1; + if (match_ch(p, ';')) break; + if (match_kw(p, "FILEHDR")) { + ph.filehdr = true; + continue; + } + if (match_kw(p, "PHDRS")) { + ph.phdrs = true; + continue; + } + if (match_kw(p, "FLAGS")) { + if (expect_ch(p, '(')) return 1; + ph.flags = parse_expr(p); + if (!ph.flags) return 1; + if (expect_ch(p, ')')) return 1; + continue; + } + lsp_errf(p, p->pos, "expected PHDRS attribute or `;`"); + return 1; + } + if (VEC_PUSH(p, *phdrs, ph)) return 1; + } +} + +static int parse_extern_directive(LSP* p, VecSlice* externs) { + if (expect_ch(p, '(')) return 1; + for (;;) { + int ch; + const char* s; + size_t n; + KitSlice sl; + skip_ws(p); + if (p->err) return 1; + ch = peek_ch(p); + if (ch == ')') { + ++p->pos; + (void)match_ch(p, ';'); + return 0; + } + if (lex_ident(p, &s, &n)) return 1; + sl = lsp_slice(p, s, n); + if (!sl.s) return 1; + if (VEC_PUSH(p, *externs, sl)) return 1; + } +} + +static int parse_output_name_directive(LSP* p, KitSlice* out) { + if (expect_ch(p, '(')) return 1; + if (lex_ident_or_string(p, out)) return 1; + for (;;) { + KitSlice ignored; + skip_ws(p); + if (p->err) return 1; + if (match_ch(p, ')')) break; + if (match_ch(p, ',')) { + if (lex_ident_or_string(p, &ignored)) return 1; + continue; + } + lsp_errf(p, p->pos, "expected `,` or `)`"); + return 1; + } + (void)match_ch(p, ';'); + return 0; +} + /* ---- top level ---- */ static int parse_top(LSP* p, KitLinkScript* out) { VecAsn top_asns = {0}; VecSec sections = {0}; + VecRegion regions = {0}; + VecPhdr phdrs = {0}; + VecAssert asserts = {0}; + VecSlice externs = {0}; KitSlice entry_name = KIT_SLICE_NULL; + KitSlice output_arch = KIT_SLICE_NULL; + KitSlice output_format = KIT_SLICE_NULL; int saw_sections = 0; int rc = 1; @@ -856,15 +1569,51 @@ static int parse_top(LSP* p, KitLinkScript* out) { lsp_errf(p, p->pos, "duplicate SECTIONS block"); goto done; } - if (parse_sections_block(p, &top_asns, &sections)) goto done; + if (parse_sections_block(p, &top_asns, &sections, &asserts)) + goto done; saw_sections = 1; continue; } - if (match_kw(p, "MEMORY") || match_kw(p, "OVERLAY") || - match_kw(p, "INSERT") || match_kw(p, "OUTPUT_FORMAT") || - match_kw(p, "OUTPUT_ARCH") || match_kw(p, "INPUT") || - match_kw(p, "GROUP") || match_kw(p, "VERSION") || - match_kw(p, "PROVIDE") || match_kw(p, "STARTUP") || + if (match_kw(p, "MEMORY")) { + if (parse_memory_block(p, &regions)) goto done; + continue; + } + if (match_kw(p, "PHDRS")) { + if (parse_phdrs_block(p, &phdrs)) goto done; + continue; + } + if (match_kw(p, "EXTERN")) { + if (parse_extern_directive(p, &externs)) goto done; + continue; + } + if (match_kw(p, "ASSERT")) { + if (parse_assertion(p, &asserts)) goto done; + continue; + } + if (match_kw(p, "PROVIDE_HIDDEN")) { + if (parse_assignment_call(p, KIT_LAS_PROVIDE_HIDDEN, &top_asns)) + goto done; + continue; + } + if (match_kw(p, "PROVIDE")) { + if (parse_assignment_call(p, KIT_LAS_PROVIDE, &top_asns)) goto done; + continue; + } + if (match_kw(p, "HIDDEN")) { + if (parse_assignment_call(p, KIT_LAS_HIDDEN, &top_asns)) goto done; + continue; + } + if (match_kw(p, "OUTPUT_FORMAT")) { + if (parse_output_name_directive(p, &output_format)) goto done; + continue; + } + if (match_kw(p, "OUTPUT_ARCH")) { + if (parse_output_name_directive(p, &output_arch)) goto done; + continue; + } + if (match_kw(p, "OVERLAY") || match_kw(p, "INSERT") || + match_kw(p, "INPUT") || match_kw(p, "GROUP") || + match_kw(p, "VERSION") || match_kw(p, "STARTUP") || match_kw(p, "SEARCH_DIR") || match_kw(p, "TARGET")) { lsp_errf(p, p->pos, "directive not supported in this linker-script subset"); @@ -879,13 +1628,35 @@ static int parse_top(LSP* p, KitLinkScript* out) { /* Materialize. */ out->entry = entry_name; + out->output_arch = output_arch; + out->output_format = output_format; out->regions = NULL; out->nregions = 0; + out->phdrs = NULL; + out->nphdrs = 0; out->top_asns = NULL; out->ntop_asns = 0; out->sections = NULL; out->nsections = 0; + out->asserts = NULL; + out->nasserts = 0; + out->externs = NULL; + out->nexterns = 0; + if (regions.n) { + KitLinkRegion* r = arena_array(p->arena, KitLinkRegion, regions.n); + if (!r) goto done; + memcpy(r, regions.p, sizeof(*r) * regions.n); + out->regions = r; + out->nregions = regions.n; + } + if (phdrs.n) { + KitLinkPhdr* ph = arena_array(p->arena, KitLinkPhdr, phdrs.n); + if (!ph) goto done; + memcpy(ph, phdrs.p, sizeof(*ph) * phdrs.n); + out->phdrs = ph; + out->nphdrs = phdrs.n; + } if (top_asns.n) { KitLinkAssignment* a = arena_array(p->arena, KitLinkAssignment, top_asns.n); if (!a) goto done; @@ -901,11 +1672,29 @@ static int parse_top(LSP* p, KitLinkScript* out) { out->sections = s; out->nsections = sections.n; } + if (asserts.n) { + KitLinkAssertion* a = arena_array(p->arena, KitLinkAssertion, asserts.n); + if (!a) goto done; + memcpy(a, asserts.p, sizeof(*a) * asserts.n); + out->asserts = a; + out->nasserts = asserts.n; + } + if (externs.n) { + KitSlice* e = arena_array(p->arena, KitSlice, externs.n); + if (!e) goto done; + memcpy(e, externs.p, sizeof(*e) * externs.n); + out->externs = e; + out->nexterns = externs.n; + } rc = 0; done: vec_free_(p, top_asns.p, top_asns.cap, sizeof(*top_asns.p)); vec_free_(p, sections.p, sections.cap, sizeof(*sections.p)); + vec_free_(p, regions.p, regions.cap, sizeof(*regions.p)); + vec_free_(p, phdrs.p, phdrs.cap, sizeof(*phdrs.p)); + vec_free_(p, asserts.p, asserts.cap, sizeof(*asserts.p)); + vec_free_(p, externs.p, externs.cap, sizeof(*externs.p)); return rc; } diff --git a/src/obj/coff/read_image.c b/src/obj/coff/read_image.c @@ -349,6 +349,7 @@ ObjBuilder* read_coff_image(Compiler* c, const char* name, const u8* data, memset(&seg, 0, sizeof seg); seg.name = sn; seg.vaddr = image_base + vaddr; + seg.paddr = seg.vaddr; seg.vsize = vsize; seg.file_off = rawptr; seg.file_size = rawsize; diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c @@ -288,6 +288,7 @@ static void shift_image_addresses(LinkImage* img, u64 delta) { for (i = 0; i < img->nsegments; ++i) { img->segments[i].file_offset += delta; img->segments[i].vaddr += delta; + img->segments[i].paddr += delta; } for (i = 0; i < img->nsections; ++i) { /* File-only debug sections carry DWARF-section-relative bases, not @@ -982,6 +983,18 @@ void link_emit_elf(LinkImage* img, Writer* w) { u64 build_id_off = ehdr_sz + (u64)nphdr_total * phent_sz; u64 build_id_addr = img_base + build_id_off; + /* Record final emit facts for the link-map / symbols side-files (consumed + * after emit, off the format-neutral image). The headers PT_LOAD below maps + * at img_base with p_filesz == p_memsz == headers_size; scripted images + * emit no headers segment (see the nphdr_headers gate above). */ + img->load_base = img_base; + if (!scripted) { + img->headers_present = 1; + img->headers_filesz = headers_size; + img->headers_memsz = headers_size; + img->headers_align = PAGE_SIZE; + } + /* ---- shift image addresses, apply relocations ---- * * Must happen before segshdrs/symtab construction so they observe @@ -1435,11 +1448,12 @@ void link_emit_elf(LinkImage* img, Writer* w) { for (i = 0; i < img->nsegments; ++i) { const LinkSegment* seg = &img->segments[i]; Phdr64* p = &phdrs[pi++]; - p->p_type = PT_LOAD; - p->p_flags = perms_to_pflags(seg->flags); + p->p_type = seg->phdr_type ? seg->phdr_type : PT_LOAD; + p->p_flags = + seg->phdr_flags_set ? seg->phdr_flags : perms_to_pflags(seg->flags); p->p_offset = seg->file_offset; p->p_vaddr = img_base + seg->vaddr; /* post-shift */ - p->p_paddr = p->p_vaddr; + p->p_paddr = img_base + seg->paddr; p->p_filesz = seg->file_size; /* TLS .tbss is per-thread template space, not a loadable bss * region — PT_TLS already records the full memsz (incl. .tbss) diff --git a/src/obj/elf/link_dyn.c b/src/obj/elf/link_dyn.c @@ -944,6 +944,7 @@ void layout_dyn(Linker* l, LinkImage* img) { ro_seg->flags = SF_ALLOC; /* PF_R */ ro_seg->file_offset = ro_vaddr; ro_seg->vaddr = ro_vaddr; + ro_seg->paddr = ro_vaddr; ro_seg->file_size = ro_seg_size; ro_seg->mem_size = ro_seg_size; ro_seg->align = (u32)page; @@ -963,6 +964,7 @@ void layout_dyn(Linker* l, LinkImage* img) { rx_seg->flags = SF_ALLOC | SF_EXEC; rx_seg->file_offset = rx_vaddr; rx_seg->vaddr = rx_vaddr; + rx_seg->paddr = rx_vaddr; rx_seg->file_size = plt_bytes; rx_seg->mem_size = plt_bytes; rx_seg->align = (u32)page; @@ -1005,6 +1007,7 @@ void layout_dyn(Linker* l, LinkImage* img) { rw_seg->flags = SF_ALLOC | SF_WRITE; rw_seg->file_offset = rw_vaddr; rw_seg->vaddr = rw_vaddr; + rw_seg->paddr = rw_vaddr; rw_seg->file_size = rw_seg_size; rw_seg->mem_size = rw_seg_size; rw_seg->align = (u32)page; diff --git a/src/obj/elf/read.c b/src/obj/elf/read.c @@ -386,12 +386,14 @@ static void read_elf_image(Compiler* c, ObjBuilder* ob, const u8* data, u32 p_flags = is32 ? rd_u32_le(p + 24) : rd_u32_le(p + 4); u64 p_offset = is32 ? (u64)rd_u32_le(p + 4) : rd_u64_le(p + 8); u64 p_vaddr = is32 ? (u64)rd_u32_le(p + 8) : rd_u64_le(p + 16); + u64 p_paddr = is32 ? (u64)rd_u32_le(p + 12) : rd_u64_le(p + 24); u64 p_filesz = is32 ? (u64)rd_u32_le(p + 16) : rd_u64_le(p + 32); u64 p_memsz = is32 ? (u64)rd_u32_le(p + 20) : rd_u64_le(p + 40); u64 p_align = is32 ? (u64)rd_u32_le(p + 28) : rd_u64_le(p + 48); ObjSegment seg; seg.name = intern_cstr(c, pt_type_name(p_type)); seg.vaddr = p_vaddr; + seg.paddr = p_paddr; seg.vsize = p_memsz; seg.file_off = p_offset; seg.file_size = p_filesz; diff --git a/src/obj/image.c b/src/obj/image.c @@ -0,0 +1,410 @@ +#include "obj/image.h" + +#include <kit/object.h> +#include <string.h> + +#include "core/diag.h" + +typedef struct ImageRange { + KitObjSegInfo seg; + uint64_t addr; + uint64_t end; + uint32_t order; +} ImageRange; + +static int u64_add(uint64_t a, uint64_t b, uint64_t* out) { + if (UINT64_MAX - a < b) return 0; + *out = a + b; + return 1; +} + +static int u64_sub_bias(uint64_t a, int64_t bias, uint64_t* out) { + if (bias >= 0) return u64_add(a, (uint64_t)bias, out); + { + uint64_t mag = (uint64_t)(-(bias + 1)) + 1u; + if (a < mag) return 0; + *out = a - mag; + return 1; + } +} + +static int u64_align_up(uint64_t v, uint64_t align, uint64_t* out) { + uint64_t rem; + if (align <= 1) { + *out = v; + return 1; + } + rem = v % align; + if (!rem) { + *out = v; + return 1; + } + return u64_add(v, align - rem, out); +} + +static int ascii_lower(int c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +static int slice_ieq(KitSlice a, KitSlice b) { + size_t i; + if (a.len != b.len) return 0; + for (i = 0; i < a.len; ++i) { + if (ascii_lower((unsigned char)a.s[i]) != + ascii_lower((unsigned char)b.s[i])) + return 0; + } + return 1; +} + +static KitSlice drop_pt_prefix(KitSlice s) { + if (s.len > 3 && ascii_lower((unsigned char)s.s[0]) == 'p' && + ascii_lower((unsigned char)s.s[1]) == 't' && s.s[2] == '_') { + s.s += 3; + s.len -= 3; + } + return s; +} + +static int segment_name_match(KitSlice have, KitSlice want) { + have = drop_pt_prefix(have); + want = drop_pt_prefix(want); + return slice_ieq(have, want); +} + +static int is_load_segment(KitSlice name) { + return segment_name_match(name, KIT_SLICE_LIT("LOAD")); +} + +static int select_segment(KitObjFmt fmt, const KitObjSegInfo* seg, + const KitImageOptions* opts) { + uint32_t i; + if (opts->nsegments == 0) { + if (fmt == KIT_OBJ_ELF) return is_load_segment(seg->name); + return 1; + } + for (i = 0; i < opts->nsegments; ++i) { + if (segment_name_match(seg->name, opts->segments[i])) return 1; + } + return 0; +} + +static uint64_t seg_addr(const KitObjSegInfo* seg, + const KitImageOptions* opts) { + switch ((KitImageAddrKind)opts->addr) { + case KIT_IMAGE_ADDR_PADDR: + case KIT_IMAGE_ADDR_LMA: + return seg->paddr; + case KIT_IMAGE_ADDR_VADDR: + default: + return seg->vaddr; + } +} + +static KitStatus ranges_push(const KitContext* ctx, ImageRange** ranges, + uint32_t* nranges, uint32_t* cap, + const ImageRange* r) { + KitHeap* h = ctx->heap; + if (*nranges >= *cap) { + uint32_t newcap = *cap ? *cap * 2u : 4u; + ImageRange* next; + if (newcap <= *cap) return KIT_NOMEM; + next = (ImageRange*)h->alloc(h, (size_t)newcap * sizeof(*next), + _Alignof(ImageRange)); + if (!next) return KIT_NOMEM; + if (*ranges) { + memcpy(next, *ranges, (size_t)(*nranges) * sizeof(*next)); + h->free(h, *ranges, (size_t)(*cap) * sizeof(**ranges)); + } + *ranges = next; + *cap = newcap; + } + (*ranges)[(*nranges)++] = *r; + return KIT_OK; +} + +static void ranges_sort(ImageRange* r, uint32_t n) { + uint32_t i; + for (i = 1; i < n; ++i) { + ImageRange key = r[i]; + uint32_t j = i; + while (j > 0) { + int move = r[j - 1].addr > key.addr || + (r[j - 1].addr == key.addr && r[j - 1].order > key.order); + if (!move) break; + r[j] = r[j - 1]; + --j; + } + r[j] = key; + } +} + +static KitStatus collect_ranges(const KitContext* ctx, KitObjFile* obj, + const KitSlice* bytes, + const KitImageOptions* opts, + ImageRange** ranges_out, + uint32_t* nranges_out, uint32_t* cap_out) { + KitObjSegIter* it = NULL; + KitObjSegInfo seg; + ImageRange* ranges = NULL; + uint32_t nranges = 0, cap = 0, order = 0; + KitStatus st; + KitObjFmt fmt; + + *ranges_out = NULL; + *nranges_out = 0; + *cap_out = 0; + + if (kit_obj_kind(obj) == KIT_OBJ_KIND_REL) { + kit_ctx_diagf(ctx, "image: input is relocatable; linked image required"); + return KIT_NOT_FOUND; + } + + fmt = kit_obj_fmt(obj); + st = kit_obj_segiter_new(obj, &it); + if (st != KIT_OK) return st; + + while (kit_obj_segiter_next(it, &seg) == KIT_ITER_ITEM) { + ImageRange r; + uint64_t addr; + uint64_t end; + + if (!select_segment(fmt, &seg, opts)) { + ++order; + continue; + } + if (seg.file_size == 0) { + ++order; + continue; + } + if (seg.file_off > bytes->len || seg.file_size > bytes->len - seg.file_off) { + kit_ctx_diagf(ctx, "image: segment %.*s file range is out of bounds", + KIT_SLICE_ARG(seg.name)); + kit_obj_segiter_free(it); + if (ranges) ctx->heap->free(ctx->heap, ranges, + (size_t)cap * sizeof(*ranges)); + return KIT_MALFORMED; + } + if (!u64_sub_bias(seg_addr(&seg, opts), opts->bias, &addr) || + !u64_add(addr, seg.file_size, &end)) { + kit_ctx_diagf(ctx, "image: segment %.*s address range overflows", + KIT_SLICE_ARG(seg.name)); + kit_obj_segiter_free(it); + if (ranges) ctx->heap->free(ctx->heap, ranges, + (size_t)cap * sizeof(*ranges)); + return KIT_MALFORMED; + } + + memset(&r, 0, sizeof r); + r.seg = seg; + r.addr = addr; + r.end = end; + r.order = order; + st = ranges_push(ctx, &ranges, &nranges, &cap, &r); + if (st != KIT_OK) { + kit_obj_segiter_free(it); + if (ranges) ctx->heap->free(ctx->heap, ranges, + (size_t)cap * sizeof(*ranges)); + return st; + } + ++order; + } + kit_obj_segiter_free(it); + + if (!nranges) { + kit_ctx_diagf(ctx, "image: no selected loadable segment bytes"); + if (ranges) ctx->heap->free(ctx->heap, ranges, + (size_t)cap * sizeof(*ranges)); + return KIT_NOT_FOUND; + } + ranges_sort(ranges, nranges); + *ranges_out = ranges; + *nranges_out = nranges; + *cap_out = cap; + return KIT_OK; +} + +static KitStatus compute_layout(const KitContext* ctx, const ImageRange* ranges, + uint32_t nranges, + const KitImageOptions* opts, + KitImageReport* report) { + uint64_t base = opts->have_base ? opts->base : ranges[0].addr; + uint64_t cur = base; + uint64_t payload = 0; + uint64_t max_hole = 0; + uint32_t i; + int had_holes = 0; + + if (base > ranges[0].addr) { + kit_ctx_diagf(ctx, "image: base 0x%llx is above first selected byte 0x%llx", + (unsigned long long)base, + (unsigned long long)ranges[0].addr); + return KIT_INVALID; + } + + for (i = 0; i < nranges; ++i) { + const ImageRange* r = &ranges[i]; + uint64_t hole; + if (r->addr < cur) { + kit_ctx_diagf(ctx, + "image: selected segments overlap at address 0x%llx", + (unsigned long long)r->addr); + return KIT_MALFORMED; + } + hole = r->addr - cur; + if (hole) { + had_holes = 1; + if (hole > max_hole) max_hole = hole; + if (opts->fail_on_holes) { + kit_ctx_diagf(ctx, "image: hole of %llu bytes at address 0x%llx", + (unsigned long long)hole, (unsigned long long)cur); + return KIT_ERR; + } + if (opts->have_max_hole && hole > opts->max_hole) { + kit_ctx_diagf( + ctx, + "image: hole of %llu bytes at address 0x%llx exceeds limit %llu", + (unsigned long long)hole, (unsigned long long)cur, + (unsigned long long)opts->max_hole); + return KIT_ERR; + } + } + if (!u64_add(payload, r->seg.file_size, &payload)) { + kit_ctx_diagf(ctx, "image: payload size overflows"); + return KIT_MALFORMED; + } + cur = r->end; + } + + report->base = base; + report->size = cur - base; + report->payload_size = payload; + report->max_hole = max_hole; + report->nranges = nranges; + report->had_holes = had_holes ? true : false; + + if (opts->have_align) { + uint64_t aligned; + if (!u64_align_up(report->size, opts->align, &aligned)) { + kit_ctx_diagf(ctx, "image: aligned output size overflows"); + return KIT_MALFORMED; + } + report->size = aligned; + } + if (opts->have_pad_to) { + if (opts->pad_to < report->size) { + kit_ctx_diagf(ctx, "image: --pad-to %llu is smaller than image size %llu", + (unsigned long long)opts->pad_to, + (unsigned long long)report->size); + return KIT_ERR; + } + report->size = opts->pad_to; + } + if (opts->have_max_size && report->size > opts->max_size) { + kit_ctx_diagf(ctx, "image: output size %llu exceeds maximum %llu", + (unsigned long long)report->size, + (unsigned long long)opts->max_size); + return KIT_ERR; + } + return KIT_OK; +} + +static KitStatus write_fill(KitWriter* out, uint8_t fill, uint64_t n) { + uint8_t buf[256]; + memset(buf, fill, sizeof buf); + while (n) { + size_t chunk = n > sizeof buf ? sizeof buf : (size_t)n; + if (kit_writer_write(out, buf, chunk) != KIT_OK) return KIT_IO; + n -= chunk; + } + return KIT_OK; +} + +static KitStatus write_bytes(KitWriter* out, const uint8_t* data, size_t n) { + return n ? kit_writer_write(out, data, n) : KIT_OK; +} + +static KitStatus write_layout(const KitContext* ctx, const ImageRange* ranges, + uint32_t nranges, const KitSlice* bytes, + const KitImageOptions* opts, + const KitImageReport* report, KitWriter* out) { + uint64_t cur = report->base; + uint32_t i; + for (i = 0; i < nranges; ++i) { + const ImageRange* r = &ranges[i]; + uint64_t hole = r->addr - cur; + if (hole && write_fill(out, opts->fill, hole) != KIT_OK) { + kit_ctx_diagf(ctx, "image: failed to write hole fill"); + return KIT_IO; + } + if (write_bytes(out, bytes->data + (size_t)r->seg.file_off, + (size_t)r->seg.file_size) != KIT_OK) { + kit_ctx_diagf(ctx, "image: failed to write segment bytes"); + return KIT_IO; + } + cur = r->end; + } + if (report->size > cur - report->base) { + uint64_t pad = report->size - (cur - report->base); + if (write_fill(out, opts->fill, pad) != KIT_OK) { + kit_ctx_diagf(ctx, "image: failed to write trailing padding"); + return KIT_IO; + } + } + return kit_writer_status(out); +} + +KitStatus obj_emit_image(const KitContext* ctx, KitObjFile* obj, + const KitSlice* object_bytes, + const KitImageOptions* opts, KitWriter* out, + KitImageReport* report_out) { + KitImageOptions defopts; + KitImageReport report; + ImageRange* ranges = NULL; + uint32_t nranges = 0, cap = 0; + KitStatus st; + + if (!ctx || !ctx->heap || !obj || !object_bytes || !out) return KIT_INVALID; + if (!object_bytes->data && object_bytes->len) return KIT_INVALID; + + if (!opts) { + memset(&defopts, 0, sizeof defopts); + defopts.format = KIT_IMAGE_FORMAT_BIN; + defopts.from = KIT_IMAGE_FROM_SEGMENTS; + defopts.addr = KIT_IMAGE_ADDR_VADDR; + defopts.fill = 0; + opts = &defopts; + } + + if (opts->format != KIT_IMAGE_FORMAT_BIN || + opts->from != KIT_IMAGE_FROM_SEGMENTS) { + kit_ctx_diagf(ctx, "image: only --format bin --from segments is supported"); + return KIT_UNSUPPORTED; + } + if (opts->addr != KIT_IMAGE_ADDR_VADDR && + opts->addr != KIT_IMAGE_ADDR_PADDR && opts->addr != KIT_IMAGE_ADDR_LMA) { + kit_ctx_diagf(ctx, "image: invalid address kind"); + return KIT_INVALID; + } + if (opts->nsegments && !opts->segments) { + kit_ctx_diagf(ctx, "image: segment list is missing"); + return KIT_INVALID; + } + if (opts->have_align && opts->align == 0) { + kit_ctx_diagf(ctx, "image: --align must be non-zero"); + return KIT_INVALID; + } + + st = collect_ranges(ctx, obj, object_bytes, opts, &ranges, &nranges, &cap); + if (st == KIT_OK) { + memset(&report, 0, sizeof report); + st = compute_layout(ctx, ranges, nranges, opts, &report); + } + if (st == KIT_OK) { + st = write_layout(ctx, ranges, nranges, object_bytes, opts, &report, out); + } + if (st == KIT_OK && report_out) *report_out = report; + if (ranges) ctx->heap->free(ctx->heap, ranges, + (size_t)cap * sizeof(*ranges)); + return st; +} diff --git a/src/obj/image.h b/src/obj/image.h @@ -0,0 +1,11 @@ +#ifndef KIT_OBJ_IMAGE_H +#define KIT_OBJ_IMAGE_H + +#include <kit/image.h> + +KitStatus obj_emit_image(const KitContext*, KitObjFile*, + const KitSlice* object_bytes, + const KitImageOptions*, KitWriter* out, + KitImageReport* report_out); + +#endif diff --git a/src/obj/macho/read.c b/src/obj/macho/read.c @@ -199,6 +199,7 @@ static void read_macho_image(Compiler* c, ObjBuilder* ob, const u8* data, c->global, (Slice){.s = segname, .len = seg_len}) : 0; seg.vaddr = vmaddr; + seg.paddr = vmaddr; seg.vsize = vmsize; seg.file_off = fileoff; seg.file_size = filesize; diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -969,6 +969,7 @@ enum { /* ObjSegment.perms bits */ typedef struct ObjSegment { Sym name; /* PT_* spelling / Mach-O segname, or 0 */ u64 vaddr; /* virtual address */ + u64 paddr; /* physical/load address where present; else vaddr */ u64 vsize; /* size in memory */ u64 file_off; /* offset of segment contents in the file */ u64 file_size; /* size on disk (< vsize when the segment carries bss) */ diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -38,6 +38,7 @@ printf '#define ZERO 0\n' > "$work/inc/h.h" printf '#include "h.h"\nint helper(void);\nint main(void){return ZERO+helper();}\n' > "$work/hello.c" printf 'int helper(void){return 0;}\n' > "$work/helper.c" printf '#include <stdint.h>\nint32_t f(void){return 1;}\n' > "$work/std.c" +printf 'double fp(void){return 1.0;}\n' > "$work/fp.c" cp "$(ls "$repo_root"/test/toy/cases/*.toy | head -1)" "$work/prog.toy" cp "$repo_root/test/wasm/cases/if_return.wat" "$work/prog.wat" printf 'int wasm_add(int a, int b);\nint wasm_main(void){return wasm_add(1,2)==3?0:1;}\n' > "$work/wasm_main.c" @@ -60,6 +61,35 @@ contains bo-c-obj-has-main nm.out main # C freestanding system header (<stdint.h>) resolves via the rt include set. run_ok bo-c-freestanding-header "$KIT" build-obj -c std.c -o std.o +# Kernel-oriented compile policy flags are accepted by build-obj and flow into +# codegen/frontend policy instead of being silently ignored. +run_ok bo-kernel-policy-flags "$KIT" build-obj -target x86_64-none-elf \ + -ffreestanding -nostdlib -static -no-pie -fno-pic -fno-pie \ + -mcmodel=kernel -mno-red-zone -mgeneral-regs-only \ + -fno-builtin -fno-builtin-memcpy -fno-stack-protector \ + -ffunction-sections -fdata-sections -c -Iinc hello.c \ + -o kernel_policy.o +assert_file_exists bo-kernel-policy-flags-file kernel_policy.o + +run_ok bo-autovar-zero "$KIT" build-obj \ + -ftrivial-auto-var-init=zero -c -Iinc hello.c -o autovar_zero.o +assert_file_exists bo-autovar-zero-file autovar_zero.o + +run_fail bo-autovar-pattern-rejected "$KIT" build-obj \ + -ftrivial-auto-var-init=pattern -c -Iinc hello.c -o autovar_pattern.o +contains bo-autovar-pattern-rejected-diag \ + "$work/bo-autovar-pattern-rejected.err" "not yet supported" + +run_fail bo-stack-protector-rejected "$KIT" build-obj \ + -fstack-protector-strong -c -Iinc hello.c -o stack_protector.o +contains bo-stack-protector-rejected-diag \ + "$work/bo-stack-protector-rejected.err" "not supported yet" + +run_fail bo-general-regs-only-fp "$KIT" build-obj \ + -mgeneral-regs-only -c fp.c -o fp.o +contains bo-general-regs-only-fp-diag \ + "$work/bo-general-regs-only-fp.err" "floating-point" + # Default output name: <basename>.o next to cwd when -o is omitted. run_ok bo-c-default-name "$KIT" build-obj -c -Iinc hello.c assert_file_exists bo-c-default-name-file hello.o @@ -214,6 +244,36 @@ if [ "$e_type" = "0200" ]; then ok be-no-pie-et-exec; else not_ok be-no-pie-et-exec "$work/be-no-pie-et-exec.diag" fi +# Direct linker flag parity: build-exe accepts --gc-sections, -Ttext, --map, +# and --symbols without requiring -Wl, spellings. +run_ok be-link-reports "$KIT" build-exe -target x86_64-linux -nostdlib \ + -static -no-pie --gc-sections -Ttext=0x500000 \ + --map kernel.map --symbols kernel.sym --symbols-format=nm \ + no_pie_start.c -o kernel.elf +contains be-link-map-target kernel.map "target x86_64-linux" +contains be-link-map-segments kernel.map "segments" +contains be-link-map-ttext kernel.map "vaddr=0x500000" +contains be-link-symbol-start kernel.sym "_start" + +run_ok be-ld-report-obj "$KIT" build-obj -target x86_64-linux \ + no_pie_start.c -o no_pie_start.o +run_ok be-ld-reports "$KIT" ld -nostdlib -static -no-pie \ + -Ttext 0x510000 --map ld.map --symbols ld.sym \ + no_pie_start.o -o ld-kernel.elf +contains be-ld-map-target ld.map "target x86_64-linux" +contains be-ld-map-ttext ld.map "vaddr=0x510000" +contains be-ld-symbol-start ld.sym "_start" + +# Executable links are strict by default, with --allow-undefined as an explicit +# bring-up escape hatch. +printf 'void missing(void); void _start(void){ missing(); }\n' > undef_start.c +run_fail be-undef-strict "$KIT" build-exe -target x86_64-linux -nostdlib \ + -static -no-pie undef_start.c -o undef-strict +contains be-undef-strict-diag "$work/be-undef-strict.err" "undefined reference" +run_ok be-undef-allowed "$KIT" build-exe -target x86_64-linux -nostdlib \ + -static -no-pie --allow-undefined undef_start.c -o undef-allowed +assert_file_exists be-undef-allowed-file undef-allowed + # --group scoping is observable through the program's exit code: the bare TU # sees -DRET=0 (global), the grouped TU overrides it to a non-zero value. printf '#ifndef RET\n#define RET 7\n#endif\nint ret_val(void){return RET;}\n' > rv.c diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -1161,7 +1161,7 @@ fi inst_dir="$work/inst" run_ok "install-default" "$KIT" install "$inst_dir" for t in cc cpp as ld ar ranlib strip objcopy objdump nm size addr2line \ - strings xxd cmp sha256sum b2sum crc32 gzip gunzip lz4 lz4c; do + image strings xxd cmp sha256sum b2sum crc32 gzip gunzip lz4 lz4c; do assert_file_exists "install-has-$t" "$inst_dir/$t" done is_executable "install-cc-executable" "$inst_dir/cc" diff --git a/test/link/link_script_test.c b/test/link/link_script_test.c @@ -0,0 +1,323 @@ +/* Public linker-script parser coverage for the kernel-script subset. */ + +#include <kit/core.h> +#include <kit/link.h> +#include <kit/object.h> +#include <string.h> + +#include "lib/kit_unit.h" + +static KitUnit g_u; +#define EXPECT(c, ...) CU_EXPECT(&g_u, c, __VA_ARGS__) + +static void check_kernel_script_subset(void) { + static const char script_text[] = + "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n" + "OUTPUT_ARCH(aarch64)\n" + "ENTRY(_start)\n" + "EXTERN(retained)\n" + "MEMORY {\n" + " ROM (rx) : ORIGIN = 0x10000, LENGTH = 64K\n" + " RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128K\n" + "}\n" + "PHDRS {\n" + " text PT_LOAD FILEHDR PHDRS FLAGS(5);\n" + " data PT_LOAD FLAGS(6);\n" + "}\n" + "PROVIDE_HIDDEN(__image_base = ORIGIN(ROM));\n" + "SECTIONS {\n" + " . = ORIGIN(ROM) + SIZEOF_HEADERS;\n" + " .text : ALIGN(16) AT(ORIGIN(ROM)) {\n" + " KEEP(*(.text.start))\n" + " *(.text .text.*)\n" + " PROVIDE(__etext = .);\n" + " } > ROM :text =0x90\n" + " .data ORIGIN(RAM) : {\n" + " *(.rodata .rodata.*)\n" + " *(EXCLUDE_FILE (*crtend.o) .data .data.*)\n" + " } > RAM AT> ROM :data\n" + " .bss (NOLOAD) : {\n" + " __bss_start = .;\n" + " *(.bss .bss.* COMMON)\n" + " . = ALIGN(., 16);\n" + " } > RAM\n" + " ASSERT(LENGTH(ROM) - SIZEOF(.text), \"text too large\");\n" + "}\n"; + + KitLinkScript* s = NULL; + KitSlice text = {.s = script_text, .len = sizeof(script_text) - 1u}; + EXPECT(kit_link_script_parse(&g_u.ctx, text, &s) == KIT_OK, + "kernel linker script parses"); + EXPECT(s != NULL, "parser returns script"); + if (!s) return; + + EXPECT(kit_slice_eq_cstr(s->entry, "_start"), "ENTRY recorded"); + EXPECT(kit_slice_eq_cstr(s->output_format, "elf64-littleaarch64"), + "OUTPUT_FORMAT recorded"); + EXPECT(kit_slice_eq_cstr(s->output_arch, "aarch64"), + "OUTPUT_ARCH recorded"); + EXPECT(s->nexterns == 1 && + kit_slice_eq_cstr(s->externs[0], "retained"), + "EXTERN recorded"); + + EXPECT(s->nregions == 2, "MEMORY region count"); + EXPECT(kit_slice_eq_cstr(s->regions[0].name, "ROM"), "ROM region name"); + EXPECT(s->regions[0].origin == 0x10000u, "ROM origin"); + EXPECT(s->regions[0].length == 64u * 1024u, "ROM length suffix"); + EXPECT((s->regions[0].flags & (KIT_LRF_R | KIT_LRF_X)) == + (KIT_LRF_R | KIT_LRF_X), + "ROM flags"); + EXPECT(kit_slice_eq_cstr(s->regions[1].name, "RAM"), "RAM region name"); + + EXPECT(s->nphdrs == 2, "PHDRS count"); + EXPECT(kit_slice_eq_cstr(s->phdrs[0].name, "text"), "text PHDR name"); + EXPECT(s->phdrs[0].filehdr && s->phdrs[0].phdrs, + "text PHDR header attrs"); + EXPECT(s->phdrs[0].flags && + s->phdrs[0].flags->kind == KIT_LE_INT && + s->phdrs[0].flags->v.int_val == 5, + "text PHDR FLAGS"); + + EXPECT(s->ntop_asns == 2, "top-level dot and PROVIDE_HIDDEN assignments"); + const KitLinkAssignment* provide = NULL; + for (uint32_t i = 0; i < s->ntop_asns; ++i) + if (s->top_asns[i].kind == KIT_LAS_PROVIDE_HIDDEN) + provide = &s->top_asns[i]; + EXPECT(provide != NULL, "PROVIDE_HIDDEN assignment kind"); + EXPECT(provide && provide->expr && + provide->expr->kind == KIT_LE_REGION_ORIGIN, + "PROVIDE_HIDDEN ORIGIN expression"); + + EXPECT(s->nsections == 3, "output section count"); + const KitLinkOutputSection* text_sec = &s->sections[0]; + const KitLinkOutputSection* data_sec = &s->sections[1]; + const KitLinkOutputSection* bss_sec = &s->sections[2]; + EXPECT(kit_slice_eq_cstr(text_sec->name, ".text"), ".text section"); + EXPECT(kit_slice_eq_cstr(text_sec->region, "ROM"), ".text region"); + EXPECT(text_sec->lma && text_sec->lma->kind == KIT_LE_REGION_ORIGIN, + ".text AT(ORIGIN)"); + EXPECT(text_sec->nphdrs == 1 && + kit_slice_eq_cstr(text_sec->phdrs[0], "text"), + ".text PHDR reference"); + EXPECT(text_sec->fill && text_sec->fill->kind == KIT_LE_INT && + text_sec->fill->v.int_val == 0x90, + ".text fill"); + EXPECT(text_sec->ninputs >= 1 && text_sec->inputs[0].keep, + "KEEP input matcher"); + + EXPECT(data_sec->vma && data_sec->vma->kind == KIT_LE_REGION_ORIGIN, + ".data VMA expression"); + EXPECT(kit_slice_eq_cstr(data_sec->region, "RAM"), ".data VMA region"); + EXPECT(kit_slice_eq_cstr(data_sec->load_region, "ROM"), + ".data load region"); + EXPECT(data_sec->nphdrs == 1 && + kit_slice_eq_cstr(data_sec->phdrs[0], "data"), + ".data PHDR reference"); + EXPECT(data_sec->ninputs >= 4 && + data_sec->inputs[2].nexclude_file_patterns == 1 && + kit_slice_eq_cstr(data_sec->inputs[2].exclude_file_patterns[0], + "*crtend.o"), + "EXCLUDE_FILE matcher"); + + EXPECT(bss_sec->noload, ".bss NOLOAD"); + EXPECT(kit_slice_eq_cstr(bss_sec->region, "RAM"), ".bss region"); + EXPECT(bss_sec->nasns == 2, ".bss assignments"); + EXPECT(s->nasserts == 1 && + kit_slice_eq_cstr(s->asserts[0].message, "text too large"), + "ASSERT recorded"); + + kit_link_script_free(&g_u.ctx, s); +} + +static KitObjBuilder* build_layout_input(KitCompiler* c) { + static const uint8_t text_bytes[4] = {0xc0, 0x03, 0x5f, 0xd6}; + static const uint8_t data_bytes[4] = {1, 2, 3, 4}; + KitObjBuilder* ob = NULL; + KitObjSection text = KIT_SECTION_NONE; + KitObjSection data = KIT_SECTION_NONE; + KitObjSymbol sym = KIT_OBJ_SYMBOL_NONE; + KitObjSectionDesc text_desc; + KitObjSectionDesc data_desc; + KitObjSymbolDesc sym_desc; + + if (kit_obj_builder_new(c, &ob) != KIT_OK || !ob) { + EXPECT(0, "obj builder allocation"); + return NULL; + } + + memset(&text_desc, 0, sizeof(text_desc)); + text_desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".text")); + text_desc.kind = KIT_SEC_TEXT; + text_desc.flags = KIT_SF_ALLOC | KIT_SF_EXEC; + text_desc.align = 4; + if (kit_obj_builder_section(ob, &text_desc, &text) != KIT_OK) { + EXPECT(0, "create .text"); + goto fail; + } + if (kit_obj_builder_write(ob, text, text_bytes, sizeof(text_bytes)) != + KIT_OK) { + EXPECT(0, "write .text"); + goto fail; + } + + memset(&data_desc, 0, sizeof(data_desc)); + data_desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".data")); + data_desc.kind = KIT_SEC_DATA; + data_desc.flags = KIT_SF_ALLOC | KIT_SF_WRITE; + data_desc.align = 4; + if (kit_obj_builder_section(ob, &data_desc, &data) != KIT_OK) { + EXPECT(0, "create .data"); + goto fail; + } + if (kit_obj_builder_write(ob, data, data_bytes, sizeof(data_bytes)) != + KIT_OK) { + EXPECT(0, "write .data"); + goto fail; + } + + memset(&sym_desc, 0, sizeof(sym_desc)); + sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("_start")); + sym_desc.bind = KIT_SB_GLOBAL; + sym_desc.kind = KIT_SK_FUNC; + sym_desc.section = text; + sym_desc.value = 0; + sym_desc.size = sizeof(text_bytes); + if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) { + EXPECT(0, "define _start"); + goto fail; + } + + sym = KIT_OBJ_SYMBOL_NONE; + memset(&sym_desc, 0, sizeof(sym_desc)); + sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("global_data")); + sym_desc.bind = KIT_SB_GLOBAL; + sym_desc.kind = KIT_SK_OBJ; + sym_desc.section = data; + sym_desc.value = 0; + sym_desc.size = sizeof(data_bytes); + if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) { + EXPECT(0, "define global_data"); + goto fail; + } + + if (kit_obj_builder_finalize(ob) != KIT_OK) { + EXPECT(0, "finalize input object"); + goto fail; + } + return ob; + +fail: + kit_obj_builder_free(ob); + return NULL; +} + +static void check_scripted_layout_vma_lma(void) { + static const char script_text[] = + "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n" + "OUTPUT_ARCH(aarch64)\n" + "ENTRY(_start)\n" + "MEMORY {\n" + " ROM (rx) : ORIGIN = 0x10000, LENGTH = 64K\n" + " RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 64K\n" + "}\n" + "PHDRS {\n" + " text PT_LOAD FLAGS(5);\n" + " data PT_LOAD FLAGS(6);\n" + "}\n" + "SECTIONS {\n" + " .text : { KEEP(*(.text .text.*)) } > ROM :text\n" + " .data ORIGIN(RAM) : { *(.data .data.*) } > RAM AT> ROM :data\n" + "}\n"; + KitTargetSpec target = + kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF); + KitCompiler* c = NULL; + KitObjBuilder* ob = NULL; + KitLinkScript* s = NULL; + KitLinkSession* sess = NULL; + KitWriter* w = NULL; + KitObjFile* f = NULL; + KitObjSegIter* it = NULL; + KitSlice script; + KitSlice bytes; + KitLinkSessionOptions opts; + size_t len = 0; + uint32_t nload = 0; + int saw_text = 0; + int saw_data = 0; + + script.s = script_text; + script.len = sizeof(script_text) - 1u; + EXPECT(kit_link_script_parse(&g_u.ctx, script, &s) == KIT_OK && s, + "layout script parses"); + if (!s) goto done; + + EXPECT(kit_unit_compiler_new(&g_u, target, &c) == KIT_OK && c, + "compiler allocation"); + if (!c) goto done; + + ob = build_layout_input(c); + if (!ob) goto done; + + memset(&opts, 0, sizeof(opts)); + opts.output_kind = KIT_LINK_OUTPUT_EXE; + opts.linker_script = s; + EXPECT(kit_link_session_new(c, &opts, &sess) == KIT_OK && sess, + "link session allocation"); + if (!sess) goto done; + EXPECT(kit_link_session_add_obj(sess, ob) == KIT_OK, "add input object"); + EXPECT(kit_writer_mem(&g_u.heap, &w) == KIT_OK && w, "memory writer"); + if (!w) goto done; + EXPECT(kit_link_session_emit(sess, w) == KIT_OK, "emit scripted image"); + bytes.data = kit_writer_mem_bytes(w, &len); + bytes.len = len; + EXPECT(bytes.data && bytes.len > 0, "scripted image bytes"); + if (!bytes.data || bytes.len == 0) goto done; + EXPECT(kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<scripted-layout>"), &bytes, + &f) == KIT_OK && + f, + "open scripted image"); + if (!f) goto done; + EXPECT(kit_obj_kind(f) == KIT_OBJ_KIND_EXEC, "scripted image is executable"); + EXPECT(kit_obj_segiter_new(f, &it) == KIT_OK && it, "segment iterator"); + if (!it) goto done; + for (;;) { + KitObjSegInfo seg; + KitIterResult r = kit_obj_segiter_next(it, &seg); + if (r == KIT_ITER_END) break; + EXPECT(r == KIT_ITER_ITEM, "segment iteration"); + if (r != KIT_ITER_ITEM) break; + if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue; + ++nload; + if (seg.vaddr == 0x10000u) { + saw_text = 1; + EXPECT(seg.paddr == 0x10000u, ".text LMA follows ROM origin"); + EXPECT(seg.file_size == 4u && seg.vsize == 4u, ".text segment size"); + EXPECT(seg.perms == (KIT_SEG_R | KIT_SEG_X), ".text PHDR flags"); + } else if (seg.vaddr == 0x80000000u) { + saw_data = 1; + EXPECT(seg.paddr == 0x10004u, ".data AT> ROM follows .text bytes"); + EXPECT(seg.file_size == 4u && seg.vsize == 4u, ".data segment size"); + EXPECT(seg.perms == (KIT_SEG_R | KIT_SEG_W), ".data PHDR flags"); + } + } + EXPECT(nload == 2, "scripted image LOAD segment count"); + EXPECT(saw_text, "scripted image text LOAD"); + EXPECT(saw_data, "scripted image data LOAD"); + +done: + if (it) kit_obj_segiter_free(it); + if (f) kit_obj_free(f); + if (w) kit_writer_close(w); + if (sess) kit_link_session_free(sess); + if (ob) kit_obj_builder_free(ob); + if (s) kit_link_script_free(&g_u.ctx, s); + if (c) kit_compiler_free(c); +} + +int main(void) { + kit_unit_init(&g_u); + check_kernel_script_subset(); + check_scripted_layout_vma_lma(); + kit_unit_summary(&g_u, "link_script_test"); + return kit_unit_status(&g_u); +} diff --git a/test/opt/prologue_tier.sh b/test/opt/prologue_tier.sh @@ -41,7 +41,8 @@ slice_func() { compile_case() { local triple="$1" name="$2" src="$3" - "$KIT" cc -target "$triple" -O1 -c "$src" \ + shift 3 + "$KIT" cc -target "$triple" -O1 "$@" -c "$src" \ -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err" "$KIT" objdump -d "$WORK/$name.o" \ > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err" @@ -127,6 +128,12 @@ grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_redzone.fn" && grep -Eq '\(%rbp\)' "$WORK/x64_redzone.fn" || fail 'x64 red-zone leaf does not address locals rbp-relative' "$WORK/x64_redzone.fn" +compile_case x86_64-linux-gnu x64_no_redzone "$WORK/small_locals.c" \ + -mno-red-zone +slice_func "$WORK/x64_no_redzone.dis" small_locals "$WORK/x64_no_redzone.fn" +grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_no_redzone.fn" || + fail 'x64 -mno-red-zone leaf skipped stack reservation' "$WORK/x64_no_redzone.fn" + # ===================== rv64 leaf (no frame at all) ========================== compile_case riscv64-linux-gnu rv64_leaf "$WORK/leaf.c" slice_func "$WORK/rv64_leaf.dis" leaf "$WORK/rv64_leaf.fn" diff --git a/test/tools/run.sh b/test/tools/run.sh @@ -169,5 +169,49 @@ else kit_skip_na compress-interop-lz4 "system lz4 not found" fi +# ---- image --------------------------------------------------------------- +cat > "$work/kernel.s" <<'EOF' +.globl _start +.section .text +_start: + ret +.section .rodata + .byte 0x11, 0x22, 0x33, 0x44 +EOF + +run_ok image-build-elf "$KIT" build-exe -target x86_64-none-elf \ + -nostdlib -static -no-pie -e _start "$work/kernel.s" -o "$work/kernel.elf" +run_ok image-bin "$KIT" image --format bin "$work/kernel.elf" \ + -o "$work/kernel.bin" +run_ok image-paddr "$KIT" image --format bin --addr paddr "$work/kernel.elf" \ + -o "$work/kernel.paddr.bin" +same_file image-paddr-matches-vaddr "$work/kernel.bin" "$work/kernel.paddr.bin" +run_ok objcopy-binary "$KIT" objcopy -O binary "$work/kernel.elf" \ + "$work/kernel.objcopy.bin" +same_file image-objcopy-binary "$work/kernel.bin" "$work/kernel.objcopy.bin" +if [ -s "$work/kernel.bin" ]; then + ok image-bin-nonempty +else + not_ok image-bin-nonempty +fi + +image_size=$(wc -c < "$work/kernel.bin" | tr -d ' ') +pad_size=$((image_size + 16)) +run_ok image-pad-fill "$KIT" image --format bin --pad-to "$pad_size" \ + --fill 0xaa "$work/kernel.elf" -o "$work/kernel.pad.bin" +pad_got=$(wc -c < "$work/kernel.pad.bin" | tr -d ' ') +if [ "$pad_got" = "$pad_size" ]; then + ok image-pad-size +else + echo "got $pad_got want $pad_size" > "$work/image-pad-size.diag" + not_ok image-pad-size "$work/image-pad-size.diag" +fi +tail -c 16 "$work/kernel.pad.bin" > "$work/kernel.pad.tail" +"$KIT" xxd -p "$work/kernel.pad.tail" > "$work/kernel.pad.hex" +contains image-pad-fill-byte "$work/kernel.pad.hex" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +run_fail image-max-size "$KIT" image --format bin --max-size 1 \ + "$work/kernel.elf" -o "$work/kernel.too-small.bin" + kit_summary tools-driver kit_exit