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:
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,
- <o_batch, out_w);
+ st = build_link_with_lto_reports(o, compiler, ctx, &lopts, &li,
+ &pending_lto, <o_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, ®ion_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, §ions)) goto done;
+ if (parse_sections_block(p, &top_asns, §ions, &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, ®ions)) 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