kit

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

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:
Msrc/link/link_resolve.c | 23+++++++++++++++++++++++
Mtest/buildcmds/run.sh | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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