kit

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

commit f415cd6cd9859e7ec49c82cf5ac4c368b073d242
parent eab8230948b78b2c4f18922b1ad5956a72e2c392
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:53:51 -0700

test: per-arch none-elf kernel-link fixtures + byte-golden map/sym + negatives

Extend test/buildcmds/run.sh (test-driver-build) with the remaining
KERNEL.md "Tests and fixtures" coverage:

- Per-arch host-side end-to-end kernel-link fixtures for all four
  first-pass freestanding targets (x86_64 / aarch64 / riscv64 / riscv32
  none-elf): compile a minimal C + asm kernel set via build-exe
  (-ffreestanding -nostdlib -static -no-pie -T kernel.ld -e _start, plus
  -mno-red-zone for x86_64), emit --map / --symbols, convert to a flat
  binary with kit image --format bin, and assert link success, non-empty
  image, byte-identical reproducibility (ELF + map + sym + image across
  two runs), and freestanding strict validation (--no-dynamic
  --require-entry). No qemu/emulator required.

- Byte-golden --map / --symbols for a fully-deterministic aarch64-none-elf
  kernel link (addresses pinned via the kernel.ld text base; goldens carry
  no host paths). Committed under test/buildcmds/golden/, diffed with
  same_file. Regenerate with KIT_REGEN_GOLDEN=1.

- Negative tests: region overflow (a MEMORY region too small for its
  sections is rejected with 'overflows MEMORY region', via build-exe and
  ld) and discarded sections (a /DISCARD/-routed section and a
  --gc-sections-collected function-section both appear in the map's
  discarded list).

All 225 build-driver cases pass.

Diffstat:
Atest/buildcmds/golden/aarch64_kernel.map | 38++++++++++++++++++++++++++++++++++++++
Atest/buildcmds/golden/aarch64_kernel.sym | 16++++++++++++++++
Mtest/buildcmds/run.sh | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 239 insertions(+), 0 deletions(-)

diff --git a/test/buildcmds/golden/aarch64_kernel.map b/test/buildcmds/golden/aarch64_kernel.map @@ -0,0 +1,38 @@ +target aarch64-elf +output exe +entry _start 0x80000000 + +segments + [1] r-x off=0x4000 vaddr=0x80000000 filesz=0x60 memsz=0x60 align=0x4 + [2] r-- off=0x8000 vaddr=0x80000060 filesz=0x4 memsz=0x4 align=0x4 + [3] rw- off=0xc000 vaddr=0x80000064 filesz=0x4 memsz=0x4 align=0x4 + [4] rw- off=0x10000 vaddr=0x80000068 filesz=0x0 memsz=0x4 align=0x4 + +sections + [1] .text seg=1 off=0x4000 vaddr=0x80000000 lma=0x80000000 size=0x4 align=0x1 + [2] .text seg=1 off=0x4004 vaddr=0x80000004 lma=0x80000004 size=0x5c align=0x4 + [3] .rodata seg=2 off=0x8000 vaddr=0x80000060 lma=0x80000060 size=0x4 align=0x4 + [4] .data seg=3 off=0xc000 vaddr=0x80000064 lma=0x80000064 size=0x4 align=0x4 + [5] .bss seg=4 off=0x10000 vaddr=0x80000068 lma=0x80000068 size=0x4 align=0x4 + +discarded + +unresolved + +symbols +0000000000000000 N _GLOBAL_OFFSET_TABLE_ +0000000000000000 N __dso_handle +0000000000000000 N __fini_array_end +0000000000000000 N __fini_array_start +0000000000000000 N __init_array_end +0000000000000000 N __init_array_start +0000000000000000 N __preinit_array_end +0000000000000000 N __preinit_array_start +0000000000000000 A __tbss_size +0000000000000000 N __tdata_end +0000000000000000 N __tdata_start +0000000080000000 T _start +0000000080000004 T kernel_main +0000000080000060 R kernel_ro +0000000080000064 D kernel_var +0000000080000068 B kernel_bss diff --git a/test/buildcmds/golden/aarch64_kernel.sym b/test/buildcmds/golden/aarch64_kernel.sym @@ -0,0 +1,16 @@ +0000000000000000 N _GLOBAL_OFFSET_TABLE_ +0000000000000000 N __dso_handle +0000000000000000 N __fini_array_end +0000000000000000 N __fini_array_start +0000000000000000 N __init_array_end +0000000000000000 N __init_array_start +0000000000000000 N __preinit_array_end +0000000000000000 N __preinit_array_start +0000000000000000 A __tbss_size +0000000000000000 N __tdata_end +0000000000000000 N __tdata_start +0000000080000000 T _start +0000000080000004 T kernel_main +0000000080000060 R kernel_ro +0000000080000064 D kernel_var +0000000080000068 B kernel_bss diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -478,5 +478,190 @@ fi # A same-arch freestanding link still succeeds (no false positive). run_ok fs-same-arch-ok "$KIT" ld -o fsok.elf fs_x64.o +# =========================================================================== +# Per-arch freestanding kernel-link fixtures (KERNEL.md acceptance criteria) +# =========================================================================== +# +# A HOST-SIDE (no qemu/emulator) end-to-end kernel-link per first-pass +# freestanding target: x86_64 / aarch64 / riscv64 / riscv32 none-elf. Each +# exercises the documented acceptance flow: +# - compile a tiny C + asm kernel set with `build-exe -ffreestanding +# -nostdlib -static -no-pie -T kernel.ld -e _start`, +# - emit a deterministic `--map` and `--symbols` side file, +# - convert the linked ELF to a flat binary with `kit image --format bin`, +# - assert the link succeeds, the image is non-empty, the link is byte-for-byte +# reproducible across two runs (ELF + map + symbols), and freestanding strict +# validation accepts it (no dynamic artifacts: --no-dynamic + --require-entry). +# +# The kernel source per arch is minimal: an asm `_start` self-loop stub (fine to +# never run), plus a C function touching .rodata / .data / .bss. The linker +# script pins .text at a fixed VMA so addresses are deterministic. + +# Shared linker script: a fixed text base makes every emitted address stable. +cat > kernel.ld <<'LDEOF' +ENTRY(_start) +SECTIONS { + . = 0x80000000; + .text : { *(.text*) } + .rodata : { *(.rodata*) } + .data : { *(.data*) } + .bss : { *(.bss*) } +} +LDEOF + +# Shared C kernel translation unit: one function plus .rodata / .data / .bss. +cat > kernel_main.c <<'CEOF' +int kernel_var = 42; /* .data */ +int kernel_bss; /* .bss */ +const int kernel_ro = 7; /* .rodata */ +int kernel_main(void) { return kernel_var + kernel_ro + kernel_bss; } +CEOF + +# kernel_link ARCH ASMBODY [EXTRA_CFLAGS...] : build + map/symbols + image + +# determinism + strict-validation for one none-elf target. The asm body is the +# instruction(s) for an `_start` self-loop stub. +kernel_link() { + _ka_arch=$1; _ka_asm=$2; shift 2 + _ka_t="${_ka_arch}-none-elf" + printf '.globl _start\n.section .text\n_start:\n %s\n' "$_ka_asm" \ + > "kstart_${_ka_arch}.s" + + # build-exe: C + asm freestanding kernel link with a pinned script + entry. + run_ok "kern-${_ka_arch}-link" "$KIT" build-exe -target "$_ka_t" \ + -ffreestanding -nostdlib -static -no-pie "$@" \ + -T kernel.ld -e _start \ + --map "kern_${_ka_arch}.map" --symbols "kern_${_ka_arch}.sym" \ + "kstart_${_ka_arch}.s" kernel_main.c -o "kern_${_ka_arch}.elf" + assert_file_exists "kern-${_ka_arch}-elf" "kern_${_ka_arch}.elf" + assert_file_exists "kern-${_ka_arch}-map" "kern_${_ka_arch}.map" + assert_file_exists "kern-${_ka_arch}-sym" "kern_${_ka_arch}.sym" + # The pinned _start address shows up in both the map and the symbols file. + contains "kern-${_ka_arch}-map-entry" "kern_${_ka_arch}.map" \ + "entry _start 0x80000000" + contains "kern-${_ka_arch}-sym-start" "kern_${_ka_arch}.sym" \ + "0000000080000000 T _start" + + # Flat binary image: succeeds and is non-empty. + run_ok "kern-${_ka_arch}-image" "$KIT" image --format bin \ + "kern_${_ka_arch}.elf" -o "kern_${_ka_arch}.bin" + if [ -s "kern_${_ka_arch}.bin" ]; then ok "kern-${_ka_arch}-image-nonempty" + else not_ok "kern-${_ka_arch}-image-nonempty"; fi + + # Freestanding strict validation accepts it: a static, entried, non-dynamic + # ELF passes --no-dynamic + --require-entry. + run_ok "kern-${_ka_arch}-no-dynamic" "$KIT" image --no-dynamic \ + --require-entry --format bin "kern_${_ka_arch}.elf" \ + -o "kern_${_ka_arch}.nd.bin" + + # Reproducibility: a second identical link is byte-identical (ELF + map + + # symbols + image). + run_ok "kern-${_ka_arch}-link2" "$KIT" build-exe -target "$_ka_t" \ + -ffreestanding -nostdlib -static -no-pie "$@" \ + -T kernel.ld -e _start \ + --map "kern_${_ka_arch}.2.map" --symbols "kern_${_ka_arch}.2.sym" \ + "kstart_${_ka_arch}.s" kernel_main.c -o "kern_${_ka_arch}.2.elf" + run_ok "kern-${_ka_arch}-image2" "$KIT" image --format bin \ + "kern_${_ka_arch}.2.elf" -o "kern_${_ka_arch}.2.bin" + same_file "kern-${_ka_arch}-elf-reproducible" \ + "kern_${_ka_arch}.elf" "kern_${_ka_arch}.2.elf" + same_file "kern-${_ka_arch}-map-reproducible" \ + "kern_${_ka_arch}.map" "kern_${_ka_arch}.2.map" + same_file "kern-${_ka_arch}-sym-reproducible" \ + "kern_${_ka_arch}.sym" "kern_${_ka_arch}.2.sym" + same_file "kern-${_ka_arch}-image-reproducible" \ + "kern_${_ka_arch}.bin" "kern_${_ka_arch}.2.bin" +} + +# x86_64 also disables the SysV red zone (kernel codegen requirement). +kernel_link x86_64 "jmp _start" -mno-red-zone +kernel_link aarch64 "b _start" +kernel_link riscv64 "j _start" +kernel_link riscv32 "j _start" + +# =========================================================================== +# Byte-golden --map / --symbols for a fully-deterministic kernel link +# =========================================================================== +# +# The grep-assertions above (and in the kernel-flag section) prove fields are +# present; this locks the *exact* emitted bytes against a checked-in golden so a +# layout/format regression is caught. We pin one arch (aarch64-none-elf, the +# reference backend) with the address-pinning kernel.ld above. The map/symbols +# carry no host paths (the writer uses basenames; this fixture's links contribute +# no input= lines at all), so a raw byte-golden is directory-independent. +golden_dir="$repo_root/test/buildcmds/golden" +run_ok be-golden-link "$KIT" build-exe -target aarch64-none-elf \ + -ffreestanding -nostdlib -static -no-pie \ + -T kernel.ld -e _start \ + --map golden.map --symbols golden.sym \ + kstart_aarch64.s kernel_main.c -o golden.elf +if [ "${KIT_REGEN_GOLDEN:-0}" = "1" ]; then + # Opt-in regeneration: refresh the committed goldens from this run. + mkdir -p "$golden_dir" + cp golden.map "$golden_dir/aarch64_kernel.map" + cp golden.sym "$golden_dir/aarch64_kernel.sym" + echo "regenerated goldens in $golden_dir" >&2 +fi +same_file be-golden-map "$golden_dir/aarch64_kernel.map" golden.map +same_file be-golden-sym "$golden_dir/aarch64_kernel.sym" golden.sym + +# =========================================================================== +# Negative tests: region overflow + discarded-section map reporting +# =========================================================================== + +# ---- region overflow: a MEMORY region too small for its sections is rejected. +cat > overflow.ld <<'LDEOF' +ENTRY(_start) +MEMORY { + TINY (rwx) : ORIGIN = 0x80000000, LENGTH = 8 +} +SECTIONS { + .text : { *(.text*) } > TINY + .data : { *(.data*) } > TINY +} +LDEOF +run_fail be-region-overflow "$KIT" build-exe -target aarch64-none-elf \ + -ffreestanding -nostdlib -static -no-pie -T overflow.ld -e _start \ + kstart_aarch64.s kernel_main.c -o overflow.elf +contains be-region-overflow-diag "$work/be-region-overflow.err" \ + "overflows MEMORY region" +# Via ld too: the same script over a precompiled object is rejected. +run_ok be-region-overflow-obj "$KIT" build-obj -target aarch64-none-elf \ + -ffreestanding -nostdlib kernel_main.c -o ovf_main.o +run_ok be-region-overflow-start "$KIT" build-obj -target aarch64-none-elf \ + -ffreestanding -nostdlib kstart_aarch64.s -o ovf_start.o +run_fail ld-region-overflow "$KIT" ld -nostdlib -static -no-pie \ + -T overflow.ld ovf_start.o ovf_main.o -o ld-overflow.elf +contains ld-region-overflow-diag "$work/ld-region-overflow.err" \ + "overflows MEMORY region" + +# ---- discarded sections: a /DISCARD/-removed section appears in the map's +# discarded list (positive assertion on the discarded-sections map field). +cat > discard.ld <<'LDEOF' +ENTRY(_start) +SECTIONS { + . = 0x80000000; + .text : { *(.text*) } + .data : { *(.data*) } + /DISCARD/ : { *(.rodata*) } +} +LDEOF +run_ok be-discard "$KIT" build-exe -target aarch64-none-elf \ + -ffreestanding -nostdlib -static -no-pie -T discard.ld -e _start \ + --map discard.map kstart_aarch64.s kernel_main.c -o discard.elf +# The map's discarded list (between the `discarded` and `unresolved` headers) +# names .rodata, the section routed into /DISCARD/. +awk '/^discarded/{f=1;next} /^unresolved/{f=0} f' discard.map > discard.list +contains be-discard-rodata discard.list ".rodata" + +# ---- discarded via --gc-sections: a GC-dropped function-section shows up in +# the discarded list. kernel_main is unreferenced from _start (the asm stub just +# self-loops), so with -ffunction-sections + --gc-sections it is collected. +run_ok be-gc-discard "$KIT" build-exe -target aarch64-none-elf \ + -ffreestanding -nostdlib -static -no-pie --gc-sections \ + -ffunction-sections -fdata-sections -T kernel.ld -e _start \ + --map gc.map kstart_aarch64.s kernel_main.c -o gc.elf +awk '/^discarded/{f=1;next} /^unresolved/{f=0} f' gc.map > gc.list +contains be-gc-discard-fn gc.list ".text.kernel_main" + kit_summary build-driver kit_exit