commit 4a2ca1694db800696de3b45245d3b33f277d715c
parent b21ac961e49c3c700f728a81b4abbad9eb48e50e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 15:19:45 -0700
link: let --defsym satisfy an otherwise-undefined reference (Bug 4)
link_resolve runs link_resolve_undefs before link_apply_defsyms, so an
undefined symbol that only a --defsym would define hit the strict
'undefined reference' panic before the defsym was ever applied --
breaking the kernel use case (--defsym __stack_chk_guard=..., _kernel_base
referenced from C).
In link_resolve_undefs, skip the panic for any undef whose name matches a
--defsym NAME (interned-Sym compare), leaving the record undefined-for-now.
link_apply_defsyms then updates that same record in place (its
existing != LINK_SYM_NONE branch), so no duplicate-symbol artifact appears.
A genuine undef not named by any defsym still fails strictly.
Tests (test/buildcmds): fx2-defsym-* cover build-exe + ld, the absolute
value, single-record (no dup), and the still-strict negative.
Diffstat:
2 files changed, 128 insertions(+), 0 deletions(-)
diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c
@@ -674,6 +674,29 @@ void link_resolve_undefs(Linker* l, LinkImage* img) {
s->defined = 1;
continue;
}
+ /* --defsym NAME=EXPR: a defsym whose NAME matches this undef satisfies the
+ * reference. Defsyms are applied AFTER undef resolution (link_apply_defsyms
+ * runs in link_resolve, once the alias target's vaddr is settled), so we
+ * cannot define it here; instead, skip the panic and leave the record
+ * undefined-for-now. link_apply_defsyms then UPDATES this same record in
+ * place (its `existing != LINK_SYM_NONE` branch) — leaving s->defined=0
+ * avoids the duplicate-symbol artifact a synthetic definition would add. */
+ if (s->name != 0 && l->ndefsyms) {
+ int is_defsym = 0;
+ u32 k;
+ for (k = 0; k < l->ndefsyms; ++k) {
+ const KitLinkDefsym* d = &l->defsyms[k];
+ Sym dn;
+ if (!d->name.s || d->name.len == 0) continue;
+ dn = pool_intern_slice(l->c->global,
+ (Slice){.s = d->name.s, .len = d->name.len});
+ if (dn == s->name) {
+ is_defsym = 1;
+ break;
+ }
+ }
+ if (is_defsym) continue;
+ }
{
Slice nm_s = s->name ? pool_slice(l->c->global, s->name) : SLICE_NULL;
const char* nm = nm_s.s ? nm_s.s : "";
diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh
@@ -663,5 +663,110 @@ run_ok be-gc-discard "$KIT" build-exe -target aarch64-none-elf \
awk '/^discarded/{f=1;next} /^unresolved/{f=0} f' gc.map > gc.list
contains be-gc-discard-fn gc.list ".text.kernel_main"
+# ===========================================================================
+# kernel-FX2: resolve/validate/section-pinning correctness fixes (Bugs 3-6)
+# ===========================================================================
+# Self-contained block (kept near the end to ease merge with the other agent's
+# additions). Each test corresponds to one of the FX2 linker correctness bugs.
+
+# ---- Bug 4: --defsym satisfies an otherwise-undefined reference ------------
+# A C source references an extern with no providing input; --defsym supplies its
+# absolute value. Without the fix the link aborts with "undefined reference"
+# before defsyms are applied (the documented kernel use case for
+# --defsym __stack_chk_guard / _kernel_base referenced from C).
+printf 'extern char fx2_marker[];\nchar* fx2_use(void){ return fx2_marker; }\nvoid _start(void){ (void)fx2_use(); for(;;){} }\n' > fx2_defsym.c
+run_ok fx2-defsym-undef-be "$KIT" build-exe -target x86_64-linux -nostdlib \
+ -static -no-pie --defsym fx2_marker=0x1000 -e _start \
+ --symbols fx2_defsym.sym fx2_defsym.c -o fx2_defsym.elf
+contains fx2-defsym-undef-be-abs fx2_defsym.sym "0000000000001000 A fx2_marker"
+# Same through ld over a precompiled object (no --allow-undefined escape).
+run_ok fx2-defsym-undef-obj "$KIT" build-obj -target x86_64-linux \
+ fx2_defsym.c -o fx2_defsym.o
+run_ok fx2-defsym-undef-ld "$KIT" ld -nostdlib -static -no-pie \
+ --defsym fx2_marker=0x2000 -e _start --symbols fx2_defsym_ld.sym \
+ fx2_defsym.o -o fx2_defsym_ld.elf
+contains fx2-defsym-undef-ld-abs fx2_defsym_ld.sym \
+ "0000000000002000 A fx2_marker"
+# Exactly one fx2_marker symbol appears (no duplicate-symbol artifact: the skip
+# path leaves the undef record for link_apply_defsyms to update in place).
+_fx2_ndef=$(grep -c ' A fx2_marker$' fx2_defsym.sym)
+if [ "$_fx2_ndef" -eq 1 ]; then ok fx2-defsym-undef-no-dup; else
+ { echo "expected 1 fx2_marker, got $_fx2_ndef"; cat fx2_defsym.sym; } \
+ > "$work/fx2-defsym-undef-no-dup.diag"
+ not_ok fx2-defsym-undef-no-dup "$work/fx2-defsym-undef-no-dup.diag"
+fi
+# A genuine undef NOT named by any --defsym still fails (no over-broad escape).
+printf 'extern char fx2_other[];\nchar* g(void){return fx2_other;}\nvoid _start(void){(void)g();for(;;){}}\n' > fx2_undef2.c
+run_fail fx2-defsym-still-strict "$KIT" build-exe -target x86_64-linux \
+ -nostdlib -static -no-pie --defsym fx2_marker=0x1000 -e _start \
+ fx2_undef2.c -o fx2_undef2.elf
+contains fx2-defsym-still-strict-diag "$work/fx2-defsym-still-strict.err" \
+ "undefined reference to 'fx2_other'"
+
+# ---- Bug 6: --section-start / -Tdata land at the EXACT requested vaddr ------
+# The requested absolute address is honored exactly (GNU-ld semantics); without
+# the fix the section/entry lands headers_load (one page) too high.
+run_ok fx2-section-start "$KIT" build-exe -target x86_64-linux -nostdlib \
+ -static -no-pie --section-start=.text=0x500000 -Tdata=0x600000 \
+ --map fx2_ss.map --symbols fx2_ss.sym kstart.c -o fx2_ss.elf
+contains fx2-ss-text-exact fx2_ss.map "vaddr=0x500000"
+contains fx2-ss-data-exact fx2_ss.map "vaddr=0x600000"
+# _start sits at the pinned .text base, and the entry reflects it.
+contains fx2-ss-start-sym fx2_ss.sym "0000000000500000 T _start"
+contains fx2-ss-entry fx2_ss.map "entry _start 0x500000"
+# Below-base requests are honored exactly too (no re-add of img_base).
+run_ok fx2-section-start-low "$KIT" build-exe -target x86_64-linux -nostdlib \
+ -static -no-pie --section-start=.text=0x1000 -e _start \
+ --map fx2_ss_low.map --symbols fx2_ss_low.sym kstart.c -o fx2_ss_low.elf
+contains fx2-ss-low-text-exact fx2_ss_low.map "vaddr=0x1000"
+contains fx2-ss-low-entry fx2_ss_low.map "entry _start 0x1000"
+# Via ld too: --section-start lands the section exactly.
+run_ok fx2-ss-ld "$KIT" ld -nostdlib -static -no-pie \
+ --section-start=.text=0x700000 -e _start --map fx2_ss_ld.map \
+ kstart.o -o fx2_ss_ld.elf
+contains fx2-ss-ld-text-exact fx2_ss_ld.map "vaddr=0x700000"
+
+# ---- Bug 3: relocation into a (NOLOAD) output section does not crash --------
+# A PROGBITS input carrying a pointer initializer (R_ABS64) routed into a NOLOAD
+# output section. NOLOAD makes the segment NOBITS (no on-disk bytes); the reloc
+# must be skipped, not written through a NULL segment buffer (UBSan: store to
+# null pointer). Assert the link succeeds and emits a binary.
+cat > fx2_noload.ld <<'LDEOF'
+ENTRY(_start)
+SECTIONS {
+ . = 0x80000000;
+ .text : { *(.text*) }
+ .data : { *(.data*) }
+ .shadow (NOLOAD) : { *(.shadow*) }
+ .bss : { *(.bss*) }
+}
+LDEOF
+cat > fx2_noload.c <<'CEOF'
+int fx2_target = 7;
+__attribute__((section(".shadow"))) int* fx2_shadow_ptr = &fx2_target;
+void _start(void){ for(;;){} }
+CEOF
+run_ok fx2-noload-reloc "$KIT" build-exe -target aarch64-none-elf \
+ -ffreestanding -nostdlib -static -no-pie -T fx2_noload.ld -e _start \
+ fx2_noload.c -o fx2_noload.elf
+assert_file_exists fx2-noload-reloc-file fx2_noload.elf
+
+# ---- Bug 5: hosted -ffreestanding -static -no-pie also rejects cross-arch --
+# build.c sets freestanding_strict for BOTH the *-none-* target AND the hosted
+# -ffreestanding static-non-PIE trigger; the per-input arch/format guard must
+# run for the hosted trigger too (else a silent mis-link of a foreign-arch .o).
+run_ok fx2-fs-hosted-x64 "$KIT" build-obj -target x86_64-linux -ffreestanding \
+ -nostdlib kstart.c -o fx2_fs_x64.o
+run_ok fx2-fs-hosted-aa64 "$KIT" build-obj -target aarch64-linux \
+ -ffreestanding -nostdlib fsother.c -o fx2_fs_aa64.o
+run_fail fx2-fs-hosted-cross "$KIT" build-exe -target x86_64-linux \
+ -ffreestanding -nostdlib -static -no-pie kstart.c fx2_fs_aa64.o \
+ -o fx2_fs_cross.elf
+contains fx2-fs-hosted-cross-diag "$work/fx2-fs-hosted-cross.err" \
+ "does not match the link target"
+# Same-arch hosted freestanding link still succeeds (no false positive).
+run_ok fx2-fs-hosted-same "$KIT" build-exe -target x86_64-linux -ffreestanding \
+ -nostdlib -static -no-pie kstart.c fx2_fs_x64.o -o fx2_fs_same.elf
+
kit_summary build-driver
kit_exit