kit

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

run.sh (49371B)


      1 #!/bin/sh
      2 # Driver-level checks for the kit-native build verbs `build-exe` / `build-lib` /
      3 # `build-obj` (the trio that replaced `compile`). Self-checking (no golden
      4 # files): we assert exit status, output existence, symbol/text markers, ld -r
      5 # parity, and — for build-exe — native execution of the produced binary.
      6 #
      7 # Coverage:
      8 #   build-obj : per-language compile (C / toy / wasm), --emit=obj|asm|c|ir,
      9 #               -fsyntax-only, default output naming, -o - to stdout, the
     10 #               -X<lang> frontend-flag router, target features, the multi-source
     11 #               relocatable combine (with ld -r parity), and the negative paths.
     12 #   build-lib : static .a from mixed sources, then link against it; single-TU
     13 #               C-source emission with cross-TU references and local names.
     14 #   build-exe : polyglot relocatable combine, native link+exec, --group scoping
     15 #               (verified through the produced exit code), and -L/-l.
     16 
     17 set -u
     18 
     19 script_dir=$(cd "$(dirname "$0")" && pwd)
     20 repo_root=$(cd "$script_dir/../.." && pwd)
     21 
     22 KIT="${KIT:-$repo_root/build/kit}"
     23 
     24 if [ ! -x "$KIT" ]; then
     25     echo "build: kit binary not found at $KIT" >&2
     26     exit 2
     27 fi
     28 
     29 work=$(mktemp -d "${TMPDIR:-/tmp}/kit-build-test.XXXXXX")
     30 trap 'rm -rf "$work"' EXIT
     31 
     32 KIT_KIT_DIR="$repo_root/test/lib"
     33 . "$repo_root/test/lib/kit_sh_kit.sh"
     34 kit_report_init
     35 
     36 # ---- fixtures (all local to $work so default-name tests are isolated) -------
     37 mkdir -p "$work/inc"
     38 printf '#define ZERO 0\n' > "$work/inc/h.h"
     39 printf '#include "h.h"\nint helper(void);\nint main(void){return ZERO+helper();}\n' > "$work/hello.c"
     40 printf 'int helper(void){return 0;}\n' > "$work/helper.c"
     41 printf '#include <stdint.h>\nint32_t f(void){return 1;}\n' > "$work/std.c"
     42 printf 'double fp(void){return 1.0;}\n' > "$work/fp.c"
     43 cp "$(ls "$repo_root"/test/toy/cases/*.toy | head -1)" "$work/prog.toy"
     44 cp "$repo_root/test/wasm/cases/if_return.wat" "$work/prog.wat"
     45 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"
     46 printf 'int wasm_add(int a, int b){return a+b;}\n' > "$work/wasm_helper.c"
     47 printf 'int wexe_add(int a, int b);\nint test_main(void){return wexe_add(2,3)==5?0:1;}\n' > "$work/wexe_main.c"
     48 printf 'int wexe_add(int a, int b){return a+b;}\nint dead_fn(void){return 99;}\n' > "$work/wexe_add.c"
     49 printf '#ifndef AMAL_VALUE\n#error missing AMAL_VALUE\n#endif\nstatic int slot=AMAL_VALUE; static int local(void){return slot;}\nint amal_left(void){return local();}\n' > "$work/amal_left.c"
     50 printf '#ifndef AMAL_VALUE\n#error missing AMAL_VALUE\n#endif\nstatic int slot=AMAL_VALUE; static int local(void){return slot;}\nint amal_right(void){return local();}\n' > "$work/amal_right.c"
     51 printf 'int amal_left(void); int amal_right(void);\nint main(void){return amal_left()+amal_right()==42?0:1;}\n' > "$work/amal_main.c"
     52 printf '.text\n' > "$work/opaque.s"
     53 
     54 cd "$work"
     55 
     56 # ===========================================================================
     57 # build-obj — the compile replacement
     58 # ===========================================================================
     59 
     60 # ---- C: preprocessor flags + object output ---------------------------------
     61 run_ok bo-c-obj "$KIT" build-obj -c -Iinc -DUNUSED=1 hello.c -o hello.o
     62 assert_file_exists bo-c-obj-exists hello.o
     63 "$KIT" nm hello.o > nm.out 2>/dev/null
     64 contains bo-c-obj-has-main nm.out main
     65 
     66 # C freestanding system header (<stdint.h>) resolves via the rt include set.
     67 run_ok bo-c-freestanding-header "$KIT" build-obj -c std.c -o std.o
     68 
     69 # Kernel-oriented compile policy flags are accepted by build-obj and flow into
     70 # codegen/frontend policy instead of being silently ignored.
     71 run_ok bo-kernel-policy-flags "$KIT" build-obj -target x86_64-none-elf \
     72     -ffreestanding -nostdlib -static -no-pie -fno-pic -fno-pie \
     73     -mcmodel=kernel -mno-red-zone -mgeneral-regs-only \
     74     -fno-builtin -fno-builtin-memcpy -fno-stack-protector \
     75     -ffunction-sections -fdata-sections -c -Iinc hello.c \
     76     -o kernel_policy.o
     77 assert_file_exists bo-kernel-policy-flags-file kernel_policy.o
     78 
     79 run_ok bo-autovar-zero "$KIT" build-obj \
     80     -ftrivial-auto-var-init=zero -c -Iinc hello.c -o autovar_zero.o
     81 assert_file_exists bo-autovar-zero-file autovar_zero.o
     82 
     83 run_fail bo-autovar-pattern-rejected "$KIT" build-obj \
     84     -ftrivial-auto-var-init=pattern -c -Iinc hello.c -o autovar_pattern.o
     85 contains bo-autovar-pattern-rejected-diag \
     86     "$work/bo-autovar-pattern-rejected.err" "not yet supported"
     87 
     88 run_ok bo-stack-protector-strong "$KIT" build-obj \
     89     -fstack-protector-strong -c -Iinc hello.c -o stack_protector.o
     90 assert_file_exists bo-stack-protector-strong-file stack_protector.o
     91 
     92 run_fail bo-general-regs-only-fp "$KIT" build-obj \
     93     -mgeneral-regs-only -c fp.c -o fp.o
     94 contains bo-general-regs-only-fp-diag \
     95     "$work/bo-general-regs-only-fp.err" "floating-point"
     96 
     97 # Default output name: <basename>.o next to cwd when -o is omitted.
     98 run_ok bo-c-default-name "$KIT" build-obj -c -Iinc hello.c
     99 assert_file_exists bo-c-default-name-file hello.o
    100 
    101 # ---- toy + wasm: non-C frontends through the generic driver -----------------
    102 run_ok bo-toy "$KIT" build-obj -c prog.toy -o prog_toy.o
    103 assert_file_exists bo-toy-exists prog_toy.o
    104 run_ok bo-wasm "$KIT" build-obj -c prog.wat -o prog_wat.o
    105 assert_file_exists bo-wasm-exists prog_wat.o
    106 
    107 # Target-owned option: the driver lowers feature flags into KitTargetOptions.
    108 run_ok bo-wasm-target-feature "$KIT" build-obj -target wasm32-none -c \
    109     -mattr=-tail-calls prog.wat -o prog_wat2.o
    110 
    111 # Per-language frontend flag via -X<lang> (routed to the wasm frontend parser).
    112 run_ok bo-Xwasm "$KIT" build-obj -target wasm32-none -c \
    113     -Xwasm -mno-feature=tail-calls prog.wat -o prog_wat3.o
    114 
    115 # ---- emit modes ------------------------------------------------------------
    116 run_ok bo-emit-asm "$KIT" build-obj -S -Iinc hello.c -o hello.s
    117 contains bo-emit-asm-text hello.s .text
    118 run_ok bo-emit-asm-long "$KIT" build-obj --emit=asm -Iinc hello.c -o hello2.s
    119 run_ok bo-emit-c "$KIT" build-obj --emit=c prog.toy -o prog.c
    120 assert_file_exists bo-emit-c-file prog.c
    121 run_ok bo-emit-ir "$KIT" build-obj --emit=ir -O1 prog.toy -o prog.ir
    122 assert_file_exists bo-emit-ir-file prog.ir
    123 
    124 # -o - sends the emit to stdout (reusing the cc stdout writer).
    125 "$KIT" build-obj --emit=ir -O1 prog.toy -o - > ir.stdout 2>/dev/null
    126 if [ -s ir.stdout ]; then ok bo-emit-ir-stdout; else not_ok bo-emit-ir-stdout; fi
    127 
    128 # ---- check-only writes no object -------------------------------------------
    129 rm -f hello.o
    130 run_ok bo-check-only "$KIT" build-obj -fsyntax-only -Iinc hello.c helper.c
    131 if [ ! -f hello.o ]; then ok bo-check-no-output; else not_ok bo-check-no-output; fi
    132 
    133 # ---- multi-source relocatable combine (ld -r) ------------------------------
    134 run_ok bo-reloc-combine "$KIT" build-obj -Iinc hello.c helper.c -o combined.o
    135 assert_file_exists bo-reloc-combine-file combined.o
    136 "$KIT" nm combined.o > combined.nm 2>/dev/null
    137 contains bo-reloc-has-helper combined.nm helper
    138 contains bo-reloc-has-main combined.nm main
    139 
    140 # ld -r parity: the in-memory combine matches a separate compile + `ld -r`.
    141 "$KIT" build-obj -c -Iinc hello.c -o sep_hello.o 2>/dev/null
    142 "$KIT" build-obj -c helper.c -o sep_helper.o 2>/dev/null
    143 "$KIT" ld -r sep_hello.o sep_helper.o -o ldr.o 2>/dev/null
    144 "$KIT" nm combined.o | sort > combine.sorted 2>/dev/null
    145 "$KIT" nm ldr.o | sort > ldr.sorted 2>/dev/null
    146 same_file bo-reloc-ld-r-parity ldr.sorted combine.sorted
    147 
    148 # Polyglot combine: C + toy in one relocatable object.
    149 run_ok bo-polyglot-combine "$KIT" build-obj -Iinc helper.c prog.toy -o poly.o
    150 
    151 # ---- wasm multi-source combine (CG-merge path) -----------------------------
    152 # Two C files merged into one .wasm module (magic \0asm + non-empty content).
    153 run_ok bo-wasm-multi "$KIT" build-obj -target wasm32-none \
    154     wasm_main.c wasm_helper.c -o multi.wasm
    155 assert_file_exists bo-wasm-multi-file multi.wasm
    156 # Wasm magic (\0asm = 00 61 73 6d): check first 4 bytes via od (avoids null-byte
    157 # issues with cmp on BSD).
    158 _wasm_hdr=$(od -A n -t x1 -N 4 multi.wasm | tr -d ' \t\n')
    159 [ "$_wasm_hdr" = "0061736d" ] \
    160     && ok bo-wasm-multi-magic || not_ok bo-wasm-multi-magic
    161 # Non-trivial module: size must exceed the 8-byte magic+version stub.
    162 _wasm_size=$(wc -c < multi.wasm | tr -d ' ')
    163 [ "$_wasm_size" -gt 8 ] \
    164     && ok bo-wasm-multi-nontrivial || not_ok bo-wasm-multi-nontrivial
    165 
    166 # Polyglot wasm: C + toy in one .wasm module.
    167 run_ok bo-wasm-polyglot "$KIT" build-obj -target wasm32-none \
    168     wasm_helper.c prog.toy -o poly.wasm
    169 assert_file_exists bo-wasm-polyglot-file poly.wasm
    170 _poly_hdr=$(od -A n -t x1 -N 4 poly.wasm | tr -d ' \t\n')
    171 [ "$_poly_hdr" = "0061736d" ] \
    172     && ok bo-wasm-polyglot-magic || not_ok bo-wasm-polyglot-magic
    173 
    174 # Negative: duplicate strong symbol definition must be rejected (at -O1 the
    175 # optimizer runs opt_resolve_duplicate_funcs which fires the ODR check).
    176 printf 'int wasm_add(int a, int b){return a-b;}\n' > wasm_dup.c
    177 run_fail bo-wasm-dup "$KIT" build-obj -O1 -target wasm32-none \
    178     wasm_helper.c wasm_dup.c -o dup.wasm
    179 contains bo-wasm-dup-diag "$work/bo-wasm-dup.err" "duplicate definition"
    180 
    181 # ---- negative paths --------------------------------------------------------
    182 # A frontend with an unknown -X flag is rejected by that frontend's parser.
    183 run_fail bo-neg-bad-Xflag "$KIT" build-obj -c -Xtoy --bogus prog.toy -o x.o
    184 # The target parser rejects an unknown feature name.
    185 run_fail bo-neg-bad-feature "$KIT" build-obj -target wasm32-none \
    186     -mattr=+nope -c prog.wat -o x.o
    187 # build-obj never links: object/archive inputs are refused.
    188 run_fail bo-neg-link-input "$KIT" build-obj hello.o
    189 # --emit=c needs an explicit destination.
    190 run_fail bo-neg-emit-c-needs-o "$KIT" build-obj --emit=c prog.toy
    191 # IR is only available with the optimizer on.
    192 run_fail bo-neg-ir-needs-opt "$KIT" build-obj --emit=ir prog.toy -o x.ir
    193 # -o cannot fan out across multiple sources in a per-source emit.
    194 run_fail bo-neg-o-multi-asm "$KIT" build-obj -S -Iinc hello.c std.c -o both.s
    195 # A per-output flag is not allowed inside a --group.
    196 run_fail bo-neg-global-in-group "$KIT" build-obj -Iinc --group -O2 -- hello.c
    197 # A missing -x argument before a --group source separator should not treat `--`
    198 # as a language name.
    199 run_fail bo-neg-group-x-missing-arg "$KIT" build-obj --group -x -- hello.c
    200 contains bo-neg-group-x-missing-arg-diag "$work/bo-neg-group-x-missing-arg.err" \
    201     "requires an argument"
    202 # -dynamic is a build-lib concept.
    203 run_fail bo-neg-dynamic "$KIT" build-obj -dynamic prog.toy
    204 # unknown flag.
    205 run_fail bo-neg-unknown-flag "$KIT" build-obj --nope -c prog.toy -o x.o
    206 
    207 # ===========================================================================
    208 # build-lib — static archive
    209 # ===========================================================================
    210 
    211 run_ok bl-static "$KIT" build-lib -Iinc -o libmix.a helper.c std.c prog.toy
    212 assert_file_exists bl-static-file libmix.a
    213 "$KIT" nm libmix.a > libmix.nm 2>/dev/null
    214 contains bl-static-has-helper libmix.nm helper
    215 
    216 # One semantic batch becomes one C translation unit. Reusing `local` and
    217 # `slot` in both inputs verifies that source-level internal linkage remains
    218 # isolated after the original translation-unit boundary disappears.
    219 run_ok bl-emit-c "$KIT" build-lib --emit=c -o libamal.c \
    220     --group -DAMAL_VALUE=20 -- amal_left.c \
    221     --group -DAMAL_VALUE=22 -- amal_right.c
    222 assert_file_exists bl-emit-c-file libamal.c
    223 run_ok bl-emit-c-host-compile "${CC:-cc}" -std=c11 -Wall -Wextra -Werror \
    224     libamal.c amal_main.c -o amal-app
    225 run_ok bl-emit-c-run ./amal-app
    226 
    227 # Standalone assembly is an object-only frontend and cannot be represented in
    228 # the generated C translation unit.
    229 run_fail bl-emit-c-opaque "$KIT" build-lib --emit=c -o opaque.c opaque.s
    230 contains bl-emit-c-opaque-diag "$work/bl-emit-c-opaque.err" \
    231     "cannot emit C"
    232 # -o is required (no obvious base name across N sources).
    233 run_fail bl-neg-needs-o "$KIT" build-lib helper.c std.c
    234 # build-lib takes only sources.
    235 run_fail bl-neg-link-input "$KIT" build-lib -o x.a helper.o
    236 # Dynamic/shared libraries are not yet supported.
    237 run_fail bl-neg-dynamic "$KIT" build-lib -dynamic -o x.a helper.c
    238 
    239 # ===========================================================================
    240 # build-exe — native link + exec (the headline path)
    241 # ===========================================================================
    242 
    243 # A hosted executable (-lc) that returns 0; build, then run it.
    244 run_ok be-link "$KIT" build-exe -lc -Iinc hello.c helper.c -o app
    245 is_executable be-link-exec app
    246 run_ok be-run ./app
    247 
    248 # build-exe should reject the shared-library spelling instead of ignoring it.
    249 run_fail be-neg-shared "$KIT" build-exe -shared -lc -Iinc hello.c helper.c \
    250     -o shared-nope
    251 contains be-neg-shared-diag "$work/be-neg-shared.err" "shared"
    252 
    253 # Default output name a.out when -o is omitted.
    254 run_ok be-default-name "$KIT" build-exe -lc -Iinc hello.c helper.c
    255 assert_file_exists be-default-name-file a.out
    256 
    257 # -no-pie must cancel an earlier -pie for build-exe's link state.
    258 printf 'void _start(void) { for (;;) {} }\n' > no_pie_start.c
    259 run_ok be-no-pie "$KIT" build-exe -target x86_64-linux -nostdlib \
    260     -pie -no-pie no_pie_start.c -o no-pie-exe
    261 e_type=$(od -An -tx1 -j 16 -N 2 no-pie-exe | tr -d ' \n')
    262 if [ "$e_type" = "0200" ]; then ok be-no-pie-et-exec; else
    263     printf 'e_type bytes=%s want=0200\n' "$e_type" \
    264         > "$work/be-no-pie-et-exec.diag"
    265     not_ok be-no-pie-et-exec "$work/be-no-pie-et-exec.diag"
    266 fi
    267 
    268 # Direct linker flag parity: build-exe accepts --gc-sections, -Ttext, --map,
    269 # and --symbols without requiring -Wl, spellings.
    270 run_ok be-link-reports "$KIT" build-exe -target x86_64-linux -nostdlib \
    271     -static -no-pie --gc-sections -Ttext=0x500000 \
    272     --map kernel.map --symbols kernel.sym --symbols-format=nm \
    273     no_pie_start.c -o kernel.elf
    274 contains be-link-map-target kernel.map "target x86_64-linux"
    275 contains be-link-map-segments kernel.map "segments"
    276 contains be-link-map-ttext kernel.map "vaddr=0x500000"
    277 contains be-link-symbol-start kernel.sym "_start"
    278 
    279 run_ok be-ld-report-obj "$KIT" build-obj -target x86_64-linux \
    280     no_pie_start.c -o no_pie_start.o
    281 run_ok be-ld-reports "$KIT" ld -nostdlib -static -no-pie \
    282     -Ttext 0x510000 --map ld.map --symbols ld.sym \
    283     no_pie_start.o -o ld-kernel.elf
    284 contains be-ld-map-target ld.map "target x86_64-linux"
    285 contains be-ld-map-ttext ld.map "vaddr=0x510000"
    286 contains be-ld-symbol-start ld.sym "_start"
    287 
    288 # Executable links are strict by default, with --allow-undefined as an explicit
    289 # bring-up escape hatch.
    290 printf 'void missing(void); void _start(void){ missing(); }\n' > undef_start.c
    291 run_fail be-undef-strict "$KIT" build-exe -target x86_64-linux -nostdlib \
    292     -static -no-pie undef_start.c -o undef-strict
    293 contains be-undef-strict-diag "$work/be-undef-strict.err" "undefined reference"
    294 run_ok be-undef-allowed "$KIT" build-exe -target x86_64-linux -nostdlib \
    295     -static -no-pie --allow-undefined undef_start.c -o undef-allowed
    296 assert_file_exists be-undef-allowed-file undef-allowed
    297 
    298 # --group scoping is observable through the program's exit code: the bare TU
    299 # sees -DRET=0 (global), the grouped TU overrides it to a non-zero value.
    300 printf '#ifndef RET\n#define RET 7\n#endif\nint ret_val(void){return RET;}\n' > rv.c
    301 printf 'int ret_val(void);\nint main(void){return ret_val();}\n' > rmain.c
    302 run_ok be-group-link "$KIT" build-exe -lc -DRET=0 rmain.c \
    303     --group -DRET=3 -- rv.c -o appg
    304 if ./appg; then code=0; else code=$?; fi
    305 if [ "$code" -eq 3 ]; then ok be-group-scope; else
    306     echo "expected exit 3 from grouped -DRET, got $code" > "$work/be-group-scope.diag"
    307     not_ok be-group-scope "$work/be-group-scope.diag"
    308 fi
    309 
    310 # Link against the static library built above via -L/-l, then run.
    311 printf 'int helper(void);\nint main(void){return helper();}\n' > usemix.c
    312 run_ok be-link-archive "$KIT" build-exe -lc usemix.c -L. -lmix -o app2
    313 run_ok be-run-archive ./app2
    314 
    315 # ===========================================================================
    316 # build-exe — wasm target
    317 # ===========================================================================
    318 
    319 # Multi-source: produces a .wasm with DCE/internalization, runnable via kit run.
    320 run_ok be-wasm-exe "$KIT" build-exe -target wasm32-none \
    321     wexe_main.c wexe_add.c -e test_main -o wexe.wasm
    322 assert_file_exists be-wasm-exe-file wexe.wasm
    323 _wexe_hdr=$(od -A n -t x1 -N 4 wexe.wasm | tr -d ' \t\n')
    324 [ "$_wexe_hdr" = "0061736d" ] && ok be-wasm-exe-magic || not_ok be-wasm-exe-magic
    325 run_ok be-wasm-exe-run "$KIT" run -e test_main wexe.wasm
    326 
    327 # Default output name a.wasm when -o is omitted.
    328 run_ok be-wasm-exe-default-name "$KIT" build-exe -target wasm32-none \
    329     wexe_main.c wexe_add.c -e test_main
    330 assert_file_exists be-wasm-exe-default-name-file a.wasm
    331 
    332 # kit run without -e reads the kit-entry custom section from the module.
    333 run_ok be-wasm-exe-autoentry "$KIT" run wexe.wasm
    334 
    335 # Unknown entry symbol is rejected with a clear diagnostic.
    336 run_fail be-wasm-exe-bad-entry "$KIT" build-exe -target wasm32-none \
    337     wexe_main.c wexe_add.c -e no_such_fn -o bad_entry.wasm
    338 contains be-wasm-exe-bad-entry-diag "$work/be-wasm-exe-bad-entry.err" \
    339     "entry symbol"
    340 
    341 # ===========================================================================
    342 # build-exe / ld — kernel linker flags, map completeness, freestanding policy
    343 # ===========================================================================
    344 
    345 # A bare freestanding _start used by the flag/policy tests below.
    346 printf 'void _start(void){ for(;;){} }\nint mydata = 42;\n' > kstart.c
    347 
    348 # ---- --defsym: absolute literal + symbol alias ----------------------------
    349 # my_abs becomes an absolute symbol at 0x1234; my_alias takes _start's address.
    350 run_ok be-defsym "$KIT" build-exe -target x86_64-linux -nostdlib -static -no-pie \
    351     --defsym my_abs=0x1234 --defsym my_alias=_start \
    352     --symbols defsym.sym kstart.c -o defsym.elf
    353 contains be-defsym-abs defsym.sym "0000000000001234 A my_abs"
    354 # my_alias takes _start's address (the alias inherits the target's placement,
    355 # so it rebases to the same final address _start carries).
    356 _start_addr=$(grep -E ' T _start$' defsym.sym | cut -d' ' -f1)
    357 if grep -qE "^$_start_addr . my_alias\$" defsym.sym; then ok be-defsym-alias; else
    358     { echo "my_alias != _start ($_start_addr)"; cat defsym.sym; } \
    359         > "$work/be-defsym-alias.diag"
    360     not_ok be-defsym-alias "$work/be-defsym-alias.diag"
    361 fi
    362 # defsym via ld too.
    363 run_ok be-ld-report-kstart "$KIT" build-obj -target x86_64-linux \
    364     kstart.c -o kstart.o
    365 run_ok ld-defsym "$KIT" ld -nostdlib -static -no-pie \
    366     --defsym ld_abs=0x4321 --symbols lddefsym.sym kstart.o -o lddefsym.elf
    367 contains ld-defsym-abs lddefsym.sym "0000000000004321 A ld_abs"
    368 # --defsym with no value is rejected.
    369 run_fail be-defsym-bad "$KIT" build-exe -target x86_64-linux -nostdlib -static \
    370     -no-pie --defsym broken kstart.c -o defsymbad.elf
    371 contains be-defsym-bad-diag "$work/be-defsym-bad.err" "NAME=EXPR"
    372 
    373 # ---- --section-start / -Tdata / -Tbss: per-section address overrides -------
    374 # The requested absolute address is the section's EXACT final runtime address
    375 # (GNU-ld semantics). (-Ttext is the ET_EXEC header-page model — img_base plus
    376 # the ehdr+phdrs page — so it is intentionally NOT exact; see the FX2 block at
    377 # the end for the dedicated exact-vaddr coverage.)
    378 run_ok be-section-start "$KIT" build-exe -target x86_64-linux -nostdlib \
    379     -static -no-pie --section-start=.text=0x500000 -Tdata 0x600000 \
    380     --map secstart.map kstart.c -o secstart.elf
    381 contains be-section-start-text secstart.map "vaddr=0x500000"
    382 contains be-section-start-data secstart.map "vaddr=0x600000"
    383 run_ok ld-tbss "$KIT" ld -nostdlib -static -no-pie -Tdata 0x600000 \
    384     kstart.o -o tbss.elf
    385 run_ok ld-section-start "$KIT" ld -nostdlib -static -no-pie \
    386     --section-start=.text=0x700000 kstart.o -o secstart2.elf
    387 
    388 # ---- --orphan-handling / --fatal-warnings: parse + plumb -------------------
    389 run_ok be-orphan "$KIT" build-exe -target x86_64-linux -nostdlib -static \
    390     -no-pie --orphan-handling=warn kstart.c -o orphan.elf
    391 run_fail be-orphan-bad "$KIT" build-exe -target x86_64-linux -nostdlib \
    392     -static -no-pie --orphan-handling=nonsense kstart.c -o orphanbad.elf
    393 contains be-orphan-bad-diag "$work/be-orphan-bad.err" "orphan-handling"
    394 run_ok be-fatal-warnings "$KIT" build-exe -target x86_64-linux -nostdlib \
    395     -static -no-pie --fatal-warnings kstart.c -o fatal.elf
    396 run_ok ld-orphan "$KIT" ld -nostdlib -static -no-pie --orphan-handling=error \
    397     kstart.o -o ldorphan.elf
    398 
    399 # ---- map completeness: LMA, discarded, unresolved, normalized paths --------
    400 run_ok be-map-complete "$KIT" build-exe -target x86_64-linux -nostdlib \
    401     -static -no-pie --gc-sections -ffunction-sections \
    402     --map complete.map kstart.c -o complete.elf
    403 contains be-map-has-lma complete.map "lma=0x"
    404 contains be-map-has-discarded complete.map "discarded"
    405 contains be-map-has-unresolved complete.map "unresolved"
    406 # No absolute host path leaks into the map (paths are normalized to basenames).
    407 if grep -qE 'input=/|input=.:[\\/]' complete.map; then
    408     { echo "absolute path leaked into map"; grep input= complete.map; } \
    409         > "$work/be-map-no-abspath.diag"
    410     not_ok be-map-no-abspath "$work/be-map-no-abspath.diag"
    411 else
    412     ok be-map-no-abspath
    413 fi
    414 # The map is deterministic across two identical links.
    415 run_ok be-map-det1 "$KIT" build-exe -target x86_64-linux -nostdlib -static \
    416     -no-pie --map det1.map kstart.c -o det1.elf
    417 run_ok be-map-det2 "$KIT" build-exe -target x86_64-linux -nostdlib -static \
    418     -no-pie --map det2.map kstart.c -o det2.elf
    419 same_file be-map-deterministic det1.map det2.map
    420 
    421 # ---- --cref: cross-reference table (deterministic, basenames only) ---------
    422 printf 'int xref_helper(void);\nvoid _start(void){ xref_helper(); for(;;){} }\n' \
    423     > xmain.c
    424 printf 'int xref_helper(void){ return 9; }\n' > xhelp.c
    425 run_ok be-cref "$KIT" build-exe -target x86_64-linux -nostdlib -static -no-pie \
    426     --cref cref.out xmain.c xhelp.c -o cref.elf
    427 contains be-cref-header cref.out "cross reference table"
    428 contains be-cref-symbol cref.out "xref_helper"
    429 if grep -qE '^  def /|^  ref /' cref.out; then
    430     { echo "absolute path leaked into cref"; cat cref.out; } \
    431         > "$work/be-cref-no-abspath.diag"
    432     not_ok be-cref-no-abspath "$work/be-cref-no-abspath.diag"
    433 else
    434     ok be-cref-no-abspath
    435 fi
    436 # cref via ld attributes def/ref to the right object basenames.
    437 run_ok ld-cref-objs "$KIT" build-obj -target x86_64-linux xmain.c -o xmain.o
    438 run_ok ld-cref-objs2 "$KIT" build-obj -target x86_64-linux xhelp.c -o xhelp.o
    439 run_ok ld-cref "$KIT" ld -nostdlib -static -no-pie --cref ldcref.out \
    440     xmain.o xhelp.o -o ldcref.elf
    441 # xref_helper is defined in xhelp.o, referenced from xmain.o.
    442 if awk '/^xref_helper$/{f=1;next} f&&/^  def xhelp.o$/{ok=1} f&&/^[^ ]/&&!/^xref_helper$/{f=0} END{exit !ok}' ldcref.out; then
    443     ok ld-cref-def
    444 else
    445     cp ldcref.out "$work/ld-cref-def.diag"; not_ok ld-cref-def "$work/ld-cref-def.diag"
    446 fi
    447 
    448 # ---- --print-memory-usage: per-MEMORY-region summary (script-driven) -------
    449 cat > memuse.ld <<'LDEOF'
    450 MEMORY {
    451   RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x100000
    452   FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 0x40000
    453 }
    454 SECTIONS {
    455   . = 0x80000000;
    456   .text : { *(.text*) }
    457   .data : { *(.data*) }
    458   .bss : { *(.bss*) }
    459 }
    460 LDEOF
    461 printf 'void _start(void){for(;;){}}\nint memd=7;\n' > memk.c
    462 run_ok be-memuse "$KIT" build-exe -target riscv64-none-elf -ffreestanding \
    463     -nostdlib -static -no-pie -T memuse.ld --print-memory-usage memk.c \
    464     -o memk.elf
    465 contains be-memuse-header "$work/be-memuse.out" "Memory region"
    466 contains be-memuse-ram "$work/be-memuse.out" "RAM"
    467 contains be-memuse-flash "$work/be-memuse.out" "FLASH"
    468 run_ok be-memuse-obj "$KIT" build-obj -target riscv64-none-elf -ffreestanding \
    469     -nostdlib memk.c -o memk.o
    470 run_ok ld-memuse "$KIT" ld -T memuse.ld --print-memory-usage memk.o -o memkld.elf
    471 contains ld-memuse-header "$work/ld-memuse.out" "Memory region"
    472 
    473 # ---- freestanding strict validation (build-exe + ld) -----------------------
    474 # A foreign-arch object in a freestanding link is rejected (the link target's
    475 # arch is authoritative; a silent mis-link is a hard error).
    476 run_ok fs-x64-obj "$KIT" build-obj -target x86_64-none-elf -ffreestanding \
    477     -nostdlib kstart.c -o fs_x64.o
    478 printf 'int other(void){return 1;}\n' > fsother.c
    479 run_ok fs-aa64-obj "$KIT" build-obj -target aarch64-none-elf -ffreestanding \
    480     -nostdlib fsother.c -o fs_aa64.o
    481 run_fail fs-cross-arch-ld "$KIT" ld -o fscross.elf fs_x64.o fs_aa64.o
    482 contains fs-cross-arch-ld-diag "$work/fs-cross-arch-ld.err" \
    483     "does not match the link target"
    484 run_fail fs-cross-arch-be "$KIT" build-exe -target x86_64-none-elf \
    485     -ffreestanding -nostdlib -static -no-pie kstart.c fs_aa64.o -o fscross2.elf
    486 contains fs-cross-arch-be-diag "$work/fs-cross-arch-be.err" \
    487     "does not match the link target"
    488 # A DSO (shared object) input into a freestanding link is rejected. The fixture
    489 # libc.so is an ET_DYN x86_64 shared object copied in locally.
    490 if [ -f "$repo_root/test/driver/fixtures/libc.so" ]; then
    491     cp "$repo_root/test/driver/fixtures/libc.so" .
    492     run_fail fs-dso-ld "$KIT" ld -o fsdso.elf fs_x64.o libc.so
    493     contains fs-dso-ld-diag "$work/fs-dso-ld.err" "DSO"
    494     run_fail fs-dso-be "$KIT" build-exe -target x86_64-none-elf -ffreestanding \
    495         -nostdlib -static -no-pie kstart.c libc.so -o fsdso2.elf
    496     contains fs-dso-be-diag "$work/fs-dso-be.err" "DSO"
    497 else
    498     skip_test fs-dso-ld "no libc.so fixture"
    499     skip_test fs-dso-be "no libc.so fixture"
    500 fi
    501 # A same-arch freestanding link still succeeds (no false positive).
    502 run_ok fs-same-arch-ok "$KIT" ld -o fsok.elf fs_x64.o
    503 
    504 # ===========================================================================
    505 # Per-arch freestanding kernel-link fixtures (KERNEL.md acceptance criteria)
    506 # ===========================================================================
    507 #
    508 # A HOST-SIDE (no qemu/emulator) end-to-end kernel-link per first-pass
    509 # freestanding target: x86_64 / aarch64 / riscv64 / riscv32 none-elf. Each
    510 # exercises the documented acceptance flow:
    511 #   - compile a tiny C + asm kernel set with `build-exe -ffreestanding
    512 #     -nostdlib -static -no-pie -T kernel.ld -e _start`,
    513 #   - emit a deterministic `--map` and `--symbols` side file,
    514 #   - convert the linked ELF to a flat binary with `kit image --format bin`,
    515 #   - assert the link succeeds, the image is non-empty, the link is byte-for-byte
    516 #     reproducible across two runs (ELF + map + symbols), and freestanding strict
    517 #     validation accepts it (no dynamic artifacts: --no-dynamic + --require-entry).
    518 #
    519 # The kernel source per arch is minimal: an asm `_start` self-loop stub (fine to
    520 # never run), plus a C function touching .rodata / .data / .bss. The linker
    521 # script pins .text at a fixed VMA so addresses are deterministic.
    522 
    523 # Shared linker script: a fixed text base makes every emitted address stable.
    524 cat > kernel.ld <<'LDEOF'
    525 ENTRY(_start)
    526 SECTIONS {
    527   . = 0x80000000;
    528   .text   : { *(.text*) }
    529   .rodata : { *(.rodata*) }
    530   .data   : { *(.data*) }
    531   .bss    : { *(.bss*) }
    532 }
    533 LDEOF
    534 
    535 # Shared C kernel translation unit: one function plus .rodata / .data / .bss.
    536 cat > kernel_main.c <<'CEOF'
    537 int kernel_var = 42;        /* .data */
    538 int kernel_bss;             /* .bss  */
    539 const int kernel_ro = 7;    /* .rodata */
    540 int kernel_main(void) { return kernel_var + kernel_ro + kernel_bss; }
    541 CEOF
    542 
    543 # kernel_link ARCH ASMBODY [EXTRA_CFLAGS...] : build + map/symbols + image +
    544 # determinism + strict-validation for one none-elf target. The asm body is the
    545 # instruction(s) for an `_start` self-loop stub.
    546 kernel_link() {
    547     _ka_arch=$1; _ka_asm=$2; shift 2
    548     _ka_t="${_ka_arch}-none-elf"
    549     printf '.globl _start\n.section .text\n_start:\n  %s\n' "$_ka_asm" \
    550         > "kstart_${_ka_arch}.s"
    551 
    552     # build-exe: C + asm freestanding kernel link with a pinned script + entry.
    553     run_ok "kern-${_ka_arch}-link" "$KIT" build-exe -target "$_ka_t" \
    554         -ffreestanding -nostdlib -static -no-pie "$@" \
    555         -T kernel.ld -e _start \
    556         --map "kern_${_ka_arch}.map" --symbols "kern_${_ka_arch}.sym" \
    557         "kstart_${_ka_arch}.s" kernel_main.c -o "kern_${_ka_arch}.elf"
    558     assert_file_exists "kern-${_ka_arch}-elf" "kern_${_ka_arch}.elf"
    559     assert_file_exists "kern-${_ka_arch}-map" "kern_${_ka_arch}.map"
    560     assert_file_exists "kern-${_ka_arch}-sym" "kern_${_ka_arch}.sym"
    561     # The pinned _start address shows up in both the map and the symbols file.
    562     contains "kern-${_ka_arch}-map-entry" "kern_${_ka_arch}.map" \
    563         "entry _start 0x80000000"
    564     contains "kern-${_ka_arch}-sym-start" "kern_${_ka_arch}.sym" \
    565         "0000000080000000 T _start"
    566 
    567     # Flat binary image: succeeds and is non-empty.
    568     run_ok "kern-${_ka_arch}-image" "$KIT" image --format bin \
    569         "kern_${_ka_arch}.elf" -o "kern_${_ka_arch}.bin"
    570     if [ -s "kern_${_ka_arch}.bin" ]; then ok "kern-${_ka_arch}-image-nonempty"
    571     else not_ok "kern-${_ka_arch}-image-nonempty"; fi
    572 
    573     # Freestanding strict validation accepts it: a static, entried, non-dynamic
    574     # ELF passes --no-dynamic + --require-entry.
    575     run_ok "kern-${_ka_arch}-no-dynamic" "$KIT" image --no-dynamic \
    576         --require-entry --format bin "kern_${_ka_arch}.elf" \
    577         -o "kern_${_ka_arch}.nd.bin"
    578 
    579     # Reproducibility: a second identical link is byte-identical (ELF + map +
    580     # symbols + image).
    581     run_ok "kern-${_ka_arch}-link2" "$KIT" build-exe -target "$_ka_t" \
    582         -ffreestanding -nostdlib -static -no-pie "$@" \
    583         -T kernel.ld -e _start \
    584         --map "kern_${_ka_arch}.2.map" --symbols "kern_${_ka_arch}.2.sym" \
    585         "kstart_${_ka_arch}.s" kernel_main.c -o "kern_${_ka_arch}.2.elf"
    586     run_ok "kern-${_ka_arch}-image2" "$KIT" image --format bin \
    587         "kern_${_ka_arch}.2.elf" -o "kern_${_ka_arch}.2.bin"
    588     same_file "kern-${_ka_arch}-elf-reproducible" \
    589         "kern_${_ka_arch}.elf" "kern_${_ka_arch}.2.elf"
    590     same_file "kern-${_ka_arch}-map-reproducible" \
    591         "kern_${_ka_arch}.map" "kern_${_ka_arch}.2.map"
    592     same_file "kern-${_ka_arch}-sym-reproducible" \
    593         "kern_${_ka_arch}.sym" "kern_${_ka_arch}.2.sym"
    594     same_file "kern-${_ka_arch}-image-reproducible" \
    595         "kern_${_ka_arch}.bin" "kern_${_ka_arch}.2.bin"
    596 }
    597 
    598 # x86_64 also disables the SysV red zone (kernel codegen requirement).
    599 kernel_link x86_64 "jmp _start" -mno-red-zone
    600 kernel_link aarch64 "b _start"
    601 kernel_link riscv64 "j _start"
    602 kernel_link riscv32 "j _start"
    603 
    604 # ===========================================================================
    605 # Byte-golden --map / --symbols for a fully-deterministic kernel link
    606 # ===========================================================================
    607 #
    608 # The grep-assertions above (and in the kernel-flag section) prove fields are
    609 # present; this locks the *exact* emitted bytes against a checked-in golden so a
    610 # layout/format regression is caught. We pin one arch (aarch64-none-elf, the
    611 # reference backend) with the address-pinning kernel.ld above. The map/symbols
    612 # carry no host paths (the writer uses basenames; this fixture's links contribute
    613 # no input= lines at all), so a raw byte-golden is directory-independent.
    614 golden_dir="$repo_root/test/buildcmds/golden"
    615 run_ok be-golden-link "$KIT" build-exe -target aarch64-none-elf \
    616     -ffreestanding -nostdlib -static -no-pie \
    617     -T kernel.ld -e _start \
    618     --map golden.map --symbols golden.sym \
    619     kstart_aarch64.s kernel_main.c -o golden.elf
    620 if [ "${KIT_REGEN_GOLDEN:-0}" = "1" ]; then
    621     # Opt-in regeneration: refresh the committed goldens from this run.
    622     mkdir -p "$golden_dir"
    623     cp golden.map "$golden_dir/aarch64_kernel.map"
    624     cp golden.sym "$golden_dir/aarch64_kernel.sym"
    625     echo "regenerated goldens in $golden_dir" >&2
    626 fi
    627 same_file be-golden-map "$golden_dir/aarch64_kernel.map" golden.map
    628 same_file be-golden-sym "$golden_dir/aarch64_kernel.sym" golden.sym
    629 
    630 # ===========================================================================
    631 # Negative tests: region overflow + discarded-section map reporting
    632 # ===========================================================================
    633 
    634 # ---- region overflow: a MEMORY region too small for its sections is rejected.
    635 cat > overflow.ld <<'LDEOF'
    636 ENTRY(_start)
    637 MEMORY {
    638   TINY (rwx) : ORIGIN = 0x80000000, LENGTH = 8
    639 }
    640 SECTIONS {
    641   .text : { *(.text*) } > TINY
    642   .data : { *(.data*) } > TINY
    643 }
    644 LDEOF
    645 run_fail be-region-overflow "$KIT" build-exe -target aarch64-none-elf \
    646     -ffreestanding -nostdlib -static -no-pie -T overflow.ld -e _start \
    647     kstart_aarch64.s kernel_main.c -o overflow.elf
    648 contains be-region-overflow-diag "$work/be-region-overflow.err" \
    649     "overflows MEMORY region"
    650 # Via ld too: the same script over a precompiled object is rejected.
    651 run_ok be-region-overflow-obj "$KIT" build-obj -target aarch64-none-elf \
    652     -ffreestanding -nostdlib kernel_main.c -o ovf_main.o
    653 run_ok be-region-overflow-start "$KIT" build-obj -target aarch64-none-elf \
    654     -ffreestanding -nostdlib kstart_aarch64.s -o ovf_start.o
    655 run_fail ld-region-overflow "$KIT" ld -nostdlib -static -no-pie \
    656     -T overflow.ld ovf_start.o ovf_main.o -o ld-overflow.elf
    657 contains ld-region-overflow-diag "$work/ld-region-overflow.err" \
    658     "overflows MEMORY region"
    659 
    660 # ---- discarded sections: a /DISCARD/-removed section appears in the map's
    661 # discarded list (positive assertion on the discarded-sections map field).
    662 cat > discard.ld <<'LDEOF'
    663 ENTRY(_start)
    664 SECTIONS {
    665   . = 0x80000000;
    666   .text : { *(.text*) }
    667   .data : { *(.data*) }
    668   /DISCARD/ : { *(.rodata*) }
    669 }
    670 LDEOF
    671 run_ok be-discard "$KIT" build-exe -target aarch64-none-elf \
    672     -ffreestanding -nostdlib -static -no-pie -T discard.ld -e _start \
    673     --map discard.map kstart_aarch64.s kernel_main.c -o discard.elf
    674 # The map's discarded list (between the `discarded` and `unresolved` headers)
    675 # names .rodata, the section routed into /DISCARD/.
    676 awk '/^discarded/{f=1;next} /^unresolved/{f=0} f' discard.map > discard.list
    677 contains be-discard-rodata discard.list ".rodata"
    678 
    679 # ---- discarded via --gc-sections: a GC-dropped function-section shows up in
    680 # the discarded list. kernel_main is unreferenced from _start (the asm stub just
    681 # self-loops), so with -ffunction-sections + --gc-sections it is collected.
    682 run_ok be-gc-discard "$KIT" build-exe -target aarch64-none-elf \
    683     -ffreestanding -nostdlib -static -no-pie --gc-sections \
    684     -ffunction-sections -fdata-sections -T kernel.ld -e _start \
    685     --map gc.map kstart_aarch64.s kernel_main.c -o gc.elf
    686 awk '/^discarded/{f=1;next} /^unresolved/{f=0} f' gc.map > gc.list
    687 contains be-gc-discard-fn gc.list ".text.kernel_main"
    688 
    689 # ===========================================================================
    690 # kernel-FX2: resolve/validate/section-pinning correctness fixes (Bugs 3-6)
    691 # ===========================================================================
    692 # Self-contained block (kept near the end to ease merge with the other agent's
    693 # additions). Each test corresponds to one of the FX2 linker correctness bugs.
    694 
    695 # ---- Bug 4: --defsym satisfies an otherwise-undefined reference ------------
    696 # A C source references an extern with no providing input; --defsym supplies its
    697 # absolute value. Without the fix the link aborts with "undefined reference"
    698 # before defsyms are applied (the documented kernel use case for
    699 # --defsym __stack_chk_guard / _kernel_base referenced from C).
    700 printf 'extern char fx2_marker[];\nchar* fx2_use(void){ return fx2_marker; }\nvoid _start(void){ (void)fx2_use(); for(;;){} }\n' > fx2_defsym.c
    701 run_ok fx2-defsym-undef-be "$KIT" build-exe -target x86_64-linux -nostdlib \
    702     -static -no-pie --defsym fx2_marker=0x1000 -e _start \
    703     --symbols fx2_defsym.sym fx2_defsym.c -o fx2_defsym.elf
    704 contains fx2-defsym-undef-be-abs fx2_defsym.sym "0000000000001000 A fx2_marker"
    705 # Same through ld over a precompiled object (no --allow-undefined escape).
    706 run_ok fx2-defsym-undef-obj "$KIT" build-obj -target x86_64-linux \
    707     fx2_defsym.c -o fx2_defsym.o
    708 run_ok fx2-defsym-undef-ld "$KIT" ld -nostdlib -static -no-pie \
    709     --defsym fx2_marker=0x2000 -e _start --symbols fx2_defsym_ld.sym \
    710     fx2_defsym.o -o fx2_defsym_ld.elf
    711 contains fx2-defsym-undef-ld-abs fx2_defsym_ld.sym \
    712     "0000000000002000 A fx2_marker"
    713 # Exactly one fx2_marker symbol appears (no duplicate-symbol artifact: the skip
    714 # path leaves the undef record for link_apply_defsyms to update in place).
    715 _fx2_ndef=$(grep -c ' A fx2_marker$' fx2_defsym.sym)
    716 if [ "$_fx2_ndef" -eq 1 ]; then ok fx2-defsym-undef-no-dup; else
    717     { echo "expected 1 fx2_marker, got $_fx2_ndef"; cat fx2_defsym.sym; } \
    718         > "$work/fx2-defsym-undef-no-dup.diag"
    719     not_ok fx2-defsym-undef-no-dup "$work/fx2-defsym-undef-no-dup.diag"
    720 fi
    721 # A genuine undef NOT named by any --defsym still fails (no over-broad escape).
    722 printf 'extern char fx2_other[];\nchar* g(void){return fx2_other;}\nvoid _start(void){(void)g();for(;;){}}\n' > fx2_undef2.c
    723 run_fail fx2-defsym-still-strict "$KIT" build-exe -target x86_64-linux \
    724     -nostdlib -static -no-pie --defsym fx2_marker=0x1000 -e _start \
    725     fx2_undef2.c -o fx2_undef2.elf
    726 contains fx2-defsym-still-strict-diag "$work/fx2-defsym-still-strict.err" \
    727     "undefined reference to 'fx2_other'"
    728 
    729 # ---- Bug 6: --section-start / -Tdata land at the EXACT requested vaddr ------
    730 # The requested absolute address is honored exactly (GNU-ld semantics); without
    731 # the fix the section/entry lands headers_load (one page) too high.
    732 run_ok fx2-section-start "$KIT" build-exe -target x86_64-linux -nostdlib \
    733     -static -no-pie --section-start=.text=0x500000 -Tdata=0x600000 \
    734     --map fx2_ss.map --symbols fx2_ss.sym kstart.c -o fx2_ss.elf
    735 contains fx2-ss-text-exact fx2_ss.map "vaddr=0x500000"
    736 contains fx2-ss-data-exact fx2_ss.map "vaddr=0x600000"
    737 # _start sits at the pinned .text base, and the entry reflects it.
    738 contains fx2-ss-start-sym fx2_ss.sym "0000000000500000 T _start"
    739 contains fx2-ss-entry fx2_ss.map "entry _start 0x500000"
    740 # Below-base requests are honored exactly too (no re-add of img_base).
    741 run_ok fx2-section-start-low "$KIT" build-exe -target x86_64-linux -nostdlib \
    742     -static -no-pie --section-start=.text=0x1000 -e _start \
    743     --map fx2_ss_low.map --symbols fx2_ss_low.sym kstart.c -o fx2_ss_low.elf
    744 contains fx2-ss-low-text-exact fx2_ss_low.map "vaddr=0x1000"
    745 contains fx2-ss-low-entry fx2_ss_low.map "entry _start 0x1000"
    746 # Via ld too: --section-start lands the section exactly.
    747 run_ok fx2-ss-ld "$KIT" ld -nostdlib -static -no-pie \
    748     --section-start=.text=0x700000 -e _start --map fx2_ss_ld.map \
    749     kstart.o -o fx2_ss_ld.elf
    750 contains fx2-ss-ld-text-exact fx2_ss_ld.map "vaddr=0x700000"
    751 
    752 # ---- Bug 3: relocation into a (NOLOAD) output section does not crash --------
    753 # A PROGBITS input carrying a pointer initializer (R_ABS64) routed into a NOLOAD
    754 # output section. NOLOAD makes the segment NOBITS (no on-disk bytes); the reloc
    755 # must be skipped, not written through a NULL segment buffer (UBSan: store to
    756 # null pointer). Assert the link succeeds and emits a binary.
    757 cat > fx2_noload.ld <<'LDEOF'
    758 ENTRY(_start)
    759 SECTIONS {
    760   . = 0x80000000;
    761   .text   : { *(.text*) }
    762   .data   : { *(.data*) }
    763   .shadow (NOLOAD) : { *(.shadow*) }
    764   .bss    : { *(.bss*) }
    765 }
    766 LDEOF
    767 cat > fx2_noload.c <<'CEOF'
    768 int fx2_target = 7;
    769 __attribute__((section(".shadow"))) int* fx2_shadow_ptr = &fx2_target;
    770 void _start(void){ for(;;){} }
    771 CEOF
    772 run_ok fx2-noload-reloc "$KIT" build-exe -target aarch64-none-elf \
    773     -ffreestanding -nostdlib -static -no-pie -T fx2_noload.ld -e _start \
    774     fx2_noload.c -o fx2_noload.elf
    775 assert_file_exists fx2-noload-reloc-file fx2_noload.elf
    776 
    777 # ---- Bug 5: hosted -ffreestanding -static -no-pie also rejects cross-arch --
    778 # build.c sets freestanding_strict for BOTH the *-none-* target AND the hosted
    779 # -ffreestanding static-non-PIE trigger; the per-input arch/format guard must
    780 # run for the hosted trigger too (else a silent mis-link of a foreign-arch .o).
    781 # fsother.c (defined earlier) provides only `other()`, so it can be linked
    782 # alongside kstart.c's `_start` without a duplicate-symbol clash.
    783 run_ok fx2-fs-hosted-x64 "$KIT" build-obj -target x86_64-linux -ffreestanding \
    784     -nostdlib fsother.c -o fx2_fs_x64.o
    785 run_ok fx2-fs-hosted-aa64 "$KIT" build-obj -target aarch64-linux \
    786     -ffreestanding -nostdlib fsother.c -o fx2_fs_aa64.o
    787 run_fail fx2-fs-hosted-cross "$KIT" build-exe -target x86_64-linux \
    788     -ffreestanding -nostdlib -static -no-pie kstart.c fx2_fs_aa64.o \
    789     -o fx2_fs_cross.elf
    790 contains fx2-fs-hosted-cross-diag "$work/fx2-fs-hosted-cross.err" \
    791     "does not match the link target"
    792 # Same-arch hosted freestanding link still succeeds (no false positive).
    793 run_ok fx2-fs-hosted-same "$KIT" build-exe -target x86_64-linux -ffreestanding \
    794     -nostdlib -static -no-pie -e _start kstart.c fx2_fs_x64.o -o fx2_fs_same.elf
    795 # Link report-writer correctness fixes (kernel-FY): --print-memory-usage alias
    796 # double-count + high-half region overflow, and --cref imported-symbol coverage.
    797 # ===========================================================================
    798 
    799 # Helper: extract a region's "used=0x..." token from a --print-memory-usage
    800 # output file by region name (first match wins).
    801 fy_memuse_used() {
    802     awk -v rg="$1" '$1==rg { for (i=1;i<=NF;i++) if ($i ~ /^used=/) { print substr($i,6); exit } }' "$2"
    803 }
    804 
    805 # ---- Bug 7: a section placed into N program headers (alias segments) must be
    806 # charged to its MEMORY region ONCE, not N times. We compare the single-phdr
    807 # `used` to the dual-phdr `used` for an otherwise-identical layout; they must be
    808 # equal (no double-count). (Repro: dual-phdr was used=0x84 vs single 0x44.)
    809 printf 'void _start(void){for(;;){}}\nint fydata=7;\n' > fymem.c
    810 cat > fy_single.ld <<'LDEOF'
    811 PHDRS { a PT_LOAD; }
    812 MEMORY { RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x100000 }
    813 SECTIONS {
    814   . = 0x80000000;
    815   .text : { *(.text*) } :a
    816   .data : { *(.data*) } :a
    817   .bss : { *(.bss*) } :a
    818 }
    819 LDEOF
    820 cat > fy_dual.ld <<'LDEOF'
    821 PHDRS { a PT_LOAD; b PT_LOAD; }
    822 MEMORY { RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 0x100000 }
    823 SECTIONS {
    824   . = 0x80000000;
    825   .text : { *(.text*) } :a :b
    826   .data : { *(.data*) } :a
    827   .bss : { *(.bss*) } :a
    828 }
    829 LDEOF
    830 run_ok fy-memuse-single "$KIT" build-exe -target riscv64-none-elf -ffreestanding \
    831     -nostdlib -static -no-pie -T fy_single.ld --print-memory-usage fymem.c \
    832     -o fymem_single.elf
    833 run_ok fy-memuse-dual "$KIT" build-exe -target riscv64-none-elf -ffreestanding \
    834     -nostdlib -static -no-pie -T fy_dual.ld --print-memory-usage fymem.c \
    835     -o fymem_dual.elf
    836 fy_used_single=$(fy_memuse_used RAM "$work/fy-memuse-single.out")
    837 fy_used_dual=$(fy_memuse_used RAM "$work/fy-memuse-dual.out")
    838 if [ -n "$fy_used_single" ] && [ "$fy_used_single" = "$fy_used_dual" ]; then
    839     ok fy-memuse-alias-not-doubled
    840 else
    841     { printf 'alias segment double-counts region usage\n';
    842       printf '  single-phdr used=%s\n' "$fy_used_single";
    843       printf '  dual-phdr   used=%s (expected equal)\n' "$fy_used_dual";
    844     } > "$work/fy-memuse-alias-not-doubled.diag"
    845     not_ok fy-memuse-alias-not-doubled "$work/fy-memuse-alias-not-doubled.diag"
    846 fi
    847 
    848 # ---- Bug 8: a MEMORY region touching the TOP of the address space
    849 # (ORIGIN=0xFFFFFFFF80000000, LENGTH=0x80000000 → origin+length wraps to 0) must
    850 # still attribute the image that lives inside it (nonzero used / nonzero %age).
    851 # Under an ASan/UBSan build the linker-script hex parser traps on the 16-digit
    852 # ORIGIN (a separate, pre-existing defect in src/link/link_script.c, owned by
    853 # another agent); in that case the high-half script can't even be parsed, so we
    854 # skip rather than fail for an unrelated reason.
    855 cat > fy_high.ld <<'LDEOF'
    856 MEMORY { RAM (rwx) : ORIGIN = 0xFFFFFFFF80000000, LENGTH = 0x80000000 }
    857 SECTIONS {
    858   . = 0xFFFFFFFF80000000;
    859   .text : { *(.text*) }
    860   .data : { *(.data*) }
    861   .bss : { *(.bss*) }
    862 }
    863 LDEOF
    864 if "$KIT" build-exe -target riscv64-none-elf -ffreestanding -nostdlib -static \
    865     -no-pie -T fy_high.ld --print-memory-usage fymem.c -o fymem_high.elf \
    866     > "$work/fy-memuse-high.out" 2> "$work/fy-memuse-high.err"; then
    867     fy_used_high=$(fy_memuse_used RAM "$work/fy-memuse-high.out")
    868     if [ -n "$fy_used_high" ] && [ "$fy_used_high" != "0x0" ]; then
    869         ok fy-memuse-high-half-nonzero
    870     else
    871         { printf 'high-half region reports zero usage (origin+length wrap)\n';
    872           sed 's/^/out: /' "$work/fy-memuse-high.out";
    873         } > "$work/fy-memuse-high-half-nonzero.diag"
    874         not_ok fy-memuse-high-half-nonzero "$work/fy-memuse-high-half-nonzero.diag"
    875     fi
    876 elif grep -q 'link_script.c' "$work/fy-memuse-high.err"; then
    877     skip_test fy-memuse-high-half-nonzero \
    878         "linker-script hex parser traps on 16-digit ORIGIN (pre-existing UBSan in link_script.c)"
    879 else
    880     not_ok fy-memuse-high-half-nonzero "$work/fy-memuse-high.err"
    881 fi
    882 
    883 # ---- Bug 9: --cref must list imported (DSO-resolved) symbols and their
    884 # referencing files. Two PIC objects reference libc_marker, resolved from the
    885 # hand-built libc.so DSO fixture; libc_marker must appear in the cref table.
    886 if [ -f "$repo_root/test/driver/fixtures/libc.so" ]; then
    887     cp "$repo_root/test/driver/fixtures/libc.so" fy_libc.so
    888     printf 'extern int libc_marker(void);\nint fy_use_a(void){return libc_marker();}\nvoid _start(void){fy_use_a();for(;;){}}\n' > fy_crefa.c
    889     printf 'extern int libc_marker(void);\nint fy_use_b(void){return libc_marker()+1;}\n' > fy_crefb.c
    890     run_ok fy-cref-obja "$KIT" build-obj -target x86_64-linux -fPIC fy_crefa.c -o fy_crefa.o
    891     run_ok fy-cref-objb "$KIT" build-obj -target x86_64-linux -fPIC fy_crefb.c -o fy_crefb.o
    892     run_ok fy-cref-link "$KIT" ld -pie --cref fy_cref.out \
    893         fy_crefa.o fy_crefb.o fy_libc.so -o fy_crefexe.elf
    894     contains fy-cref-imported-symbol fy_cref.out "libc_marker"
    895     # The imported symbol's def line should name the providing DSO (basename),
    896     # never "-" and never an absolute host path.
    897     if awk '/^libc_marker$/{f=1;next} f&&/^  def fy_libc.so$/{ok=1} f&&/^[^ ]/&&!/^libc_marker$/{f=0} END{exit !ok}' fy_cref.out; then
    898         ok fy-cref-imported-def-dso
    899     else
    900         cp fy_cref.out "$work/fy-cref-imported-def-dso.diag"
    901         not_ok fy-cref-imported-def-dso "$work/fy-cref-imported-def-dso.diag"
    902     fi
    903 else
    904     skip_test fy-cref-imported-symbol "no libc.so fixture"
    905     skip_test fy-cref-imported-def-dso "no libc.so fixture"
    906 fi
    907 # ==== BEGIN kernel-FZ Bug 11: cc honors -Wl link side-reports ===============
    908 # cc routes -Wl,/-Xlinker report flags through driver_link_flags, but its link
    909 # ran inside a build helper that created/emitted/freed the session internally,
    910 # so --cref / --print-memory-usage / -Map / --symbols were silently dropped
    911 # (exit 0, no output). cc must now write them from the live session, matching
    912 # ld and build-exe. Reuses the xmain.c/xhelp.c cref fixtures above.
    913 run_ok cc-wl-cref "$KIT" cc -target x86_64-linux -nostdlib -static -no-pie \
    914     -e _start -Wl,--cref=cc_cref.out xmain.c xhelp.c -o cc_cref.elf
    915 assert_file_exists cc-wl-cref-file cc_cref.out
    916 contains cc-wl-cref-header cc_cref.out "cross reference table"
    917 contains cc-wl-cref-symbol cc_cref.out "xref_helper"
    918 # --print-memory-usage emits the per-region summary to stdout (captured in .out).
    919 run_ok cc-wl-memuse "$KIT" cc -target x86_64-linux -nostdlib -static -no-pie \
    920     -e _start -Wl,--print-memory-usage xmain.c xhelp.c -o cc_memuse.elf
    921 contains cc-wl-memuse-header "$work/cc-wl-memuse.out" "Memory region"
    922 # -Map / --symbols side files (the pre-existing silent drop) also get written.
    923 run_ok cc-wl-map "$KIT" cc -target x86_64-linux -nostdlib -static -no-pie \
    924     -e _start -Wl,-Map=cc_map.out -Wl,--symbols=cc_sym.out \
    925     xmain.c xhelp.c -o cc_map.elf
    926 assert_file_exists cc-wl-map-file cc_map.out
    927 contains cc-wl-map-segments cc_map.out "segments"
    928 assert_file_exists cc-wl-symbols-file cc_sym.out
    929 contains cc-wl-symbols-start cc_sym.out "_start"
    930 # ==== END kernel-FZ Bug 11 ==================================================
    931 
    932 # ==== BEGIN kernel-FZ Bug 12: ld -Wl owned-string pool grows ================
    933 # A single -Wl,--section-start=...,...(many) token adds only 1 to argc but each
    934 # --section-start minted up to THREE owned strings (name dup'd twice + addr),
    935 # overrunning ld's fixed argc+32 owned-paths bound and failing valid input with
    936 # "too many sysroot-expanded paths". The pool now grows independently of argc,
    937 # and each --section-start consumes exactly two slots. Reuses no_pie_start.o.
    938 ld_b12_tok="--section-start=.s00=0x10000"
    939 ld_b12_i=1
    940 while [ "$ld_b12_i" -le 19 ]; do
    941     ld_b12_addr=$(printf '0x%x' $((0x10000 + ld_b12_i * 0x1000)))
    942     ld_b12_tok="$ld_b12_tok,--section-start=.s$(printf '%02d' "$ld_b12_i")=$ld_b12_addr"
    943     ld_b12_i=$((ld_b12_i + 1))
    944 done
    945 # 20 --section-start entries in one -Wl token links successfully (was exit 2).
    946 run_ok ld-wl-section-start-many "$KIT" ld -nostdlib -static -no-pie \
    947     -Ttext 0x500000 no_pie_start.o -Wl,"$ld_b12_tok" -o ld-secstart-many.elf
    948 assert_file_exists ld-wl-section-start-many-file ld-secstart-many.elf
    949 # A -Wl,--defsym burst (two owned strings per alias entry) likewise links.
    950 run_ok ld-wl-defsym-many "$KIT" ld -nostdlib -static -no-pie \
    951     -Ttext 0x500000 no_pie_start.o \
    952     -Wl,--defsym=a0=_start,--defsym=a1=_start,--defsym=a2=_start,--defsym=a3=_start,--defsym=a4=_start,--defsym=a5=_start,--defsym=a6=_start,--defsym=a7=_start,--defsym=a8=_start,--defsym=a9=_start \
    953     -o ld-defsym-many.elf
    954 assert_file_exists ld-wl-defsym-many-file ld-defsym-many.elf
    955 # ==== END kernel-FZ Bug 12 ==================================================
    956 
    957 kit_summary build-driver
    958 kit_exit