boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit 7eb6f0aca7c2e70dc13a72a3be5b46f6de5ff588
parent 6ec738c133517a886e87ecdbc76c0322a8c60d7f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  5 May 2026 18:49:15 -0700

seed-kernel: tcc3 self-hosts the link step; boot6 closes the loop

`scripts/boot6.sh aarch64` builds + links the seed kernel end-to-end
with tcc3 — no `ld -T kernel.lds`, no objcopy.  Link line is just
three flags: `-Wl,-Ttext=0x40080000 -Wl,--oformat=binary` plus
freestanding `-nostdlib -static`.

Got the surface that small by leaning on existing tcc conventions
rather than growing a linker-script subset:

- `_start` is tcc's hard-coded entry symbol, so kernel.S renames the
  arm64 Image-header label `_head` -> `_start`.
- `_end` is auto-defined by tcc at the end of `.bss` (existing
  `tcc_add_linker_symbols`); kernel.S uses it for both the bss-zero
  upper bound and the Image-header `image_size` field, replacing
  the lds's `__bss_end` and `_image_end` assignments.
- `__bss_start` is the one missing piece — added via a 9-line
  `bss-start-symbol` simple-patch that mirrors tcc's existing `_end`
  synthesis.
- The arm64 Image header moves from a custom `.head.text` section
  into `.text`; linking kernel.S first lands it at file offset 0,
  so the `ARM\x64` magic ends up at offset 0x38 with no script-side
  ordering.  `--oformat=binary` then writes a flat Image directly,
  which QEMU's `-kernel` boot-protocol path needs to put DTB in x0
  (the ELF-load path doesn't).

`DRIVER=seed scripts/boot.sh aarch64` runs the entire boot0..boot6
pipeline on top of the tcc3-built kernel, including a re-run of
boot6 that rebuilds the same kernel under itself.

Diffstat:
Mdocs/OS-TODO.md | 54+++++++++++++++++++++---------------------------------
Mscripts/boot.sh | 36+++++++++++++++++++++++++++++-------
Mscripts/boot0.sh | 4++--
Mscripts/boot1.sh | 4++--
Mscripts/boot2.sh | 4++--
Mscripts/boot3.sh | 4++--
Mscripts/boot4.sh | 4++--
Mscripts/boot5.sh | 4++--
Ascripts/boot6-gen-runscm.sh | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/boot6.sh | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/seed-accept-boot34.sh | 11++++++-----
Mscripts/seed-accept-boot5.sh | 11++++++-----
Mscripts/seed-accept.sh | 4++--
Ascripts/simple-patches/tcc-0.9.26/bss-start-symbol.after | 13+++++++++++++
Ascripts/simple-patches/tcc-0.9.26/bss-start-symbol.before | 4++++
Mscripts/stage1-flatten.sh | 5+++++
Mseed-kernel/kernel.S | 42+++++++++++++++++++++++++++++++++++-------
Mseed-kernel/kernel.lds | 27+++++++++++++--------------
18 files changed, 330 insertions(+), 85 deletions(-)

diff --git a/docs/OS-TODO.md b/docs/OS-TODO.md @@ -11,40 +11,28 @@ ELF, dispatches the eight Tier-1 syscalls plus atomic `sys_spawn` acceptance; `boot{0..5}.sh DRIVER=seed` is byte-identical to the podman path. HVF acceleration enabled. -A kernel built end-to-end with the patched -[`build/aarch64/boot4/tcc3`](../scripts/boot4.sh) (`tcc3 -c -kernel.{S,c}` + `tcc3 -c tcc-cc/mem.c` → `ld -T kernel.lds`) boots -under qemu-system-aarch64 and produces console output identical to -the gcc-built kernel through `[seed] user exit_group(0)`. +[`scripts/boot6.sh`](../scripts/boot6.sh) builds and links the seed +kernel end-to-end with the patched +[`build/aarch64/boot4/tcc3`](../scripts/boot4.sh) — no `ld -T +kernel.lds`, no objcopy. The link line is just three flags: + +``` +tcc3 -nostdlib -static \ + -Wl,-Ttext=0x40080000 \ + -Wl,--oformat=binary \ + -o kernel.elf kernel.S.o kernel.c.o mem.c.o +``` + +`DRIVER=seed scripts/boot.sh aarch64` runs the entire boot0→boot6 +pipeline (including a re-run of boot6 itself) on top of the +tcc3-built kernel, closing the self-host loop at the OS layer. This file tracks remaining polish. -## tcc3 link with `kernel.lds` +## boot6 output filename -`tcc -Wl,-T,kernel.lds` errors with `unsupported linker option`; -tcc3's `-Wl,` accepts only `-Ttext=` / `-image-base=` / -`-section-alignment=`. The seed kernel's layout needs: - -- `KEEP(*(.head.text))` first (boot header at `0x40080000`). -- Link-time symbol assignments: `__bss_start`, `__bss_end`, - `kstack_top`, `_end`, `_image_end`. -- A custom `.stack` section sized to 64 KB. - -Two paths: - -- **Inline.** Define the symbols and reserve a 64 KB stack inside - `kernel.S` using `.skip` / `.balign`. Brackets the kernel's `.bss` - by putting `__bss_start:` in a `.bss.0_start` section and - `__bss_end:` in `.bss.9_end` — but tcc/ld won't merge these into - the main `.bss` without a script, so each becomes its own output - section. Workable only if BSS is replaced with explicit storage in - `kernel.S`; brittle otherwise. -- **Tcc gains a small ld-script subset.** `KEEP(*(...))`, simple - symbol assignment (`sym = .;`), and a single explicit-storage - section block. ~150 LoC, reusable across kernel-style targets, - doesn't restructure `kernel.S` further. - -The post-refactor balance tips toward the script subset. - -Until either lands, the tcc-built seed kernel still uses gcc's -`ld -T kernel.lds` for the final link step. +[`scripts/boot6.sh`](../scripts/boot6.sh) writes its output to +`build/aarch64/boot6/kernel.elf`, but with `--oformat=binary` the +file is a flat arm64 boot Image, not ELF. Misleading; rename to +`Image` (matching `seed-kernel/build/Image` from the gcc Makefile) +and update boot{0..5}.sh's `KERNEL_IMAGE=` to follow. diff --git a/scripts/boot.sh b/scripts/boot.sh @@ -6,7 +6,13 @@ ## DRIVER=podman scripts/boot.sh <amd64|aarch64|riscv64> ## ## DRIVER (default podman) is exported and consumed by each bootN.sh. -## DRIVER=seed is aarch64-only and requires seed-kernel/build/Image. +## DRIVER=seed is aarch64-only and runs on build/$ARCH/boot6/kernel.elf — +## the tcc3-built seed kernel produced by boot6. First-time setup +## therefore requires one prior podman pass to produce that kernel: +## ./scripts/boot.sh aarch64 # default DRIVER=podman +## DRIVER=seed ./scripts/boot.sh aarch64 # re-run on tcc-built kernel +## Subsequent DRIVER=seed runs reuse the kernel.elf from the prior boot6 +## (stashed across the build/$ARCH wipe below). set -e @@ -14,22 +20,32 @@ ARCH=$1 DRIVER=${DRIVER:-podman} case "$DRIVER" in - podman) ;; seed) [ "$ARCH" = aarch64 ] || { echo "[boot] DRIVER=seed: aarch64 only" >&2; exit 2; } - if [ ! -f seed-kernel/build/Image ]; then - echo "[boot] building seed-kernel/build/Image" - podman run --rm --pull=never --platform linux/arm64 \ - -v "$PWD/seed-kernel:/work" -w /work boot2-alpine-gcc:aarch64 \ - sh -c 'apk add --no-progress --quiet make >/dev/null 2>&1; make -s' + KERNEL_ELF=build/$ARCH/boot6/kernel.elf + if [ ! -f "$KERNEL_ELF" ]; then + echo "[boot] DRIVER=seed: missing $KERNEL_ELF" >&2 + echo "[boot] run './scripts/boot.sh $ARCH' first (default DRIVER=podman) to produce it" >&2 + exit 1 fi + # Stash the kernel outside build/$ARCH so it survives the wipe + # below; restored before stage 0 runs. + STASH=build/.seed-bootstrap/$ARCH + mkdir -p "$STASH" + cp "$KERNEL_ELF" "$STASH/kernel.elf" ;; + podman) ;; *) echo "[boot] unknown DRIVER=$DRIVER (expected podman|seed)" >&2; exit 2 ;; esac export DRIVER rm -rf build/$ARCH +if [ "$DRIVER" = seed ]; then + mkdir -p build/$ARCH/boot6 + cp build/.seed-bootstrap/$ARCH/kernel.elf build/$ARCH/boot6/kernel.elf +fi + T0=$(date +%s) trap 'echo "[boot/$DRIVER $ARCH] elapsed at exit: $(($(date +%s) - T0))s"' EXIT @@ -47,3 +63,9 @@ stage boot2 ./scripts/boot2.sh $ARCH stage boot3 ./scripts/boot3.sh $ARCH stage boot4 ./scripts/boot4.sh $ARCH stage boot5 ./scripts/boot5.sh $ARCH + +# boot6 builds the seed-kernel ELF with boot4's tcc3 (no `ld -T`, +# no objcopy). Arm64-only — the seed kernel is arm64-specific. +if [ "$ARCH" = aarch64 ]; then + stage boot6 ./scripts/boot6.sh $ARCH +fi diff --git a/scripts/boot0.sh b/scripts/boot0.sh @@ -48,9 +48,9 @@ case "$DRIVER" in export PLATFORM IMAGE ;; seed) [ "$ARCH" = "aarch64" ] || { echo "[boot0] DRIVER=seed: aarch64 only" >&2; exit 2; } - KERNEL_IMAGE=$ROOT/seed-kernel/build/Image + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh - [ -f "$KERNEL_IMAGE" ] || { echo "[boot0] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; } + [ -f "$KERNEL_IMAGE" ] || { echo "[boot0] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } export KERNEL_IMAGE EXTRACT ;; *) echo "[boot0] unknown DRIVER=$DRIVER" >&2; exit 2 ;; esac diff --git a/scripts/boot1.sh b/scripts/boot1.sh @@ -58,9 +58,9 @@ case "$DRIVER" in export PLATFORM IMAGE ;; seed) [ "$ARCH" = "aarch64" ] || { echo "[boot1] DRIVER=seed: aarch64 only" >&2; exit 2; } - KERNEL_IMAGE=$ROOT/seed-kernel/build/Image + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh - [ -f "$KERNEL_IMAGE" ] || { echo "[boot1] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; } + [ -f "$KERNEL_IMAGE" ] || { echo "[boot1] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } export KERNEL_IMAGE EXTRACT ;; *) echo "[boot1] unknown DRIVER=$DRIVER" >&2; exit 2 ;; esac diff --git a/scripts/boot2.sh b/scripts/boot2.sh @@ -66,9 +66,9 @@ case "$DRIVER" in export PLATFORM IMAGE ;; seed) [ "$ARCH" = "aarch64" ] || { echo "[boot2] DRIVER=seed: aarch64 only" >&2; exit 2; } - KERNEL_IMAGE=$ROOT/seed-kernel/build/Image + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh - [ -f "$KERNEL_IMAGE" ] || { echo "[boot2] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; } + [ -f "$KERNEL_IMAGE" ] || { echo "[boot2] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } export KERNEL_IMAGE EXTRACT ;; *) echo "[boot2] unknown DRIVER=$DRIVER" >&2; exit 2 ;; esac diff --git a/scripts/boot3.sh b/scripts/boot3.sh @@ -85,9 +85,9 @@ if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then fi if [ "$DRIVER" = seed ]; then [ "$ARCH" = aarch64 ] || { echo "[boot3] DRIVER=seed: aarch64 only" >&2; exit 2; } - KERNEL_IMAGE=$ROOT/seed-kernel/build/Image + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh - [ -f "$KERNEL_IMAGE" ] || { echo "[boot3] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; } + [ -f "$KERNEL_IMAGE" ] || { echo "[boot3] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } export KERNEL_IMAGE EXTRACT fi export IMAGE PLATFORM DRIVER diff --git a/scripts/boot4.sh b/scripts/boot4.sh @@ -121,9 +121,9 @@ if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then fi if [ "$DRIVER" = seed ]; then [ "$ARCH" = aarch64 ] || { echo "[boot4] DRIVER=seed: aarch64 only" >&2; exit 2; } - KERNEL_IMAGE=$ROOT/seed-kernel/build/Image + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh - [ -f "$KERNEL_IMAGE" ] || { echo "[boot4] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; } + [ -f "$KERNEL_IMAGE" ] || { echo "[boot4] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } export KERNEL_IMAGE EXTRACT fi export IMAGE PLATFORM DRIVER diff --git a/scripts/boot5.sh b/scripts/boot5.sh @@ -103,9 +103,9 @@ if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then -f scripts/Containerfile.empty scripts/ fi if [ "$DRIVER" = seed ]; then - KERNEL_IMAGE=$ROOT/seed-kernel/build/Image + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh - [ -f "$KERNEL_IMAGE" ] || { echo "[boot5] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; } + [ -f "$KERNEL_IMAGE" ] || { echo "[boot5] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } export KERNEL_IMAGE EXTRACT fi export IMAGE PLATFORM DRIVER diff --git a/scripts/boot6-gen-runscm.sh b/scripts/boot6-gen-runscm.sh @@ -0,0 +1,84 @@ +#!/bin/sh +## boot6-gen-runscm.sh — emit run.scm driving tcc3 to build seed-kernel. +## +## tcc3 -c each translation unit (kernel.S, kernel.c, mem.c) → .o, then +## tcc3 links + emits a flat arm64 boot Image directly (no `ld -T +## kernel.lds`, no objcopy). The link line is three tcc flags: +## +## -nostdlib -static freestanding link, no startfiles +## -Wl,-Ttext=0x40080000 base address (arm64 RAM_BASE + +## text_offset; QEMU's `-kernel` loads +## us here) +## -Wl,--oformat=binary flat-bytes output (objcopy -O binary +## equivalent). Required because QEMU's +## `-kernel <ELF>` path doesn't run the +## arm64 boot wrapper that puts DTB in +## x0 — only the flat-Image path does. +## Detection is by `ARM\x64` magic at +## file offset 0x38, which is in +## kernel.S's Image header at the top of +## `.text`. +## +## Everything else the kernel needs from the linker is supplied by +## conventions tcc3 already honors: +## +## _start hard-coded entry symbol; kernel.S's arm64 Image +## header is named `_start` (not `_head`) so this works +## out of the box. +## __bss_start auto-defined at the start of merged .bss via the +## `bss-start-symbol` simple-patch. +## _end auto-defined by stock tcc at the end of merged .bss +## (tcc_add_linker_symbols). kernel.S uses it to +## bracket the bss-zero loop and to compute the +## Image-header `image_size` field; kernel.c uses it +## to place the kernel heap above the loaded image. +## +## Usage: boot6-gen-runscm.sh <arch> <out.scm> + +set -eu +[ "$#" -eq 2 ] || { echo "usage: $0 <arch> <out.scm>" >&2; exit 2; } +ARCH=$1; OUT=$2 +[ "$ARCH" = aarch64 ] || { echo "boot6-gen: only aarch64 supported" >&2; exit 2; } + +# Kernel CFLAGS — freestanding, static, no host startfiles. We omit gcc's +# -mgeneral-regs-only (no tcc equivalent); kernel.S enables CPACR_EL1.FPEN +# in stext before the first `bl kmain`, so tcc's SIMD callee-saves in +# function prologues don't trap. +KCFLAGS='"-nostdlib" "-ffreestanding" "-static"' + +cat > "$OUT" <<EOF +;; boot6 run.scm — build seed-kernel ELF with tcc3. +;; Generated by scripts/boot6-gen-runscm.sh; reads use in/, writes out/. + +(define (must r tag) + (if (and (car r) (= 0 (cdr r))) + r + (begin + (write-string stderr "boot6: step failed: ") + (write-string stderr tag) + (write-string stderr "\n") + (exit 1)))) + +(write-string stdout "boot6: tcc3 -c kernel.S\n") +(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/kernel-asm.o" "in/kernel.S") + "kernel.S -> kernel-asm.o") + +(write-string stdout "boot6: tcc3 -c kernel.c\n") +(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/kernel.o" "in/kernel.c") + "kernel.c -> kernel.o") + +(write-string stdout "boot6: tcc3 -c mem.c\n") +(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/mem.o" "in/mem.c") + "mem.c -> mem.o") + +(write-string stdout "boot6: tcc3 link kernel.elf (flat Image)\n") +(must (run "in/tcc3" "-nostdlib" "-static" + "-Wl,-Ttext=0x40080000" + "-Wl,--oformat=binary" + "-o" "out/kernel.elf" + "out/kernel-asm.o" "out/kernel.o" "out/mem.o") + "link kernel.elf") + +(write-string stdout "boot6: ALL-OK\n") +(exit 0) +EOF diff --git a/scripts/boot6.sh b/scripts/boot6.sh @@ -0,0 +1,100 @@ +#!/bin/sh +## boot6.sh — build the seed-kernel ELF with boot4's tcc3. +## +## Drives tcc3 to compile + link the seed kernel directly into a +## QEMU-bootable arm64 ELF: no `ld -T kernel.lds`, no objcopy. The link +## step uses the minimal flag set the kernel actually needs (see +## scripts/boot6-gen-runscm.sh for the rationale of each flag), notably +## --section-bracket=.bss:__bss_start:__bss_end which replaces the +## kernel.lds bracket assignments — the only genuine link-time service +## the kernel still requires. +## +## ─── Inputs ────────────────────────────────────────────────────────── +## build/$ARCH/boot4/tcc3 — boot4's verified self-host tcc +## (compiler + linker) +## build/$ARCH/boot2/scheme1 — driver runtime +## seed-kernel/kernel.S — boot stub, vector table, asm thunks, +## trailing 64 KB stack reserved as +## plain `.bss` (kstack_top is the end +## label of that reservation) +## seed-kernel/kernel.c — DTB parse, MMU bring-up, syscalls, +## virtio-blk, tmpfs, ELF loader +## tcc-cc/mem.c — memcpy/memset/memmove/memcmp +## +## ─── Tools ──────────────────────────────────────────────────────────── +## In container: scratch + busybox (boot2-empty:$ARCH). +## scheme1 evaluates run.scm against the flat staging +## root (cwd=/work for podman, cwd=/ for the seed +## kernel). Same run.scm drives both. +## +## ─── Outputs ───────────────────────────────────────────────────────── +## build/$ARCH/boot6/kernel.elf — final kernel ELF; QEMU loads its +## PT_LOAD segment at 0x40080000 and +## jumps to _head (the arm64 Image +## header), same boot path as the +## gcc-built flat Image. +## +## Usage: scripts/boot6.sh <arch> +## <arch>: aarch64 only. boot6 has no amd64/riscv64 analogue — the seed +## kernel is arm64-specific. + +set -eu + +usage() { echo "usage: $0 <aarch64>" >&2; exit 2; } +[ "$#" -eq 1 ] || usage +ARCH=$1 +[ "$ARCH" = aarch64 ] || { echo "[boot6] only aarch64 supported (got $ARCH)" >&2; exit 2; } + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT" + +DRIVER=${DRIVER:-podman} +PLATFORM=linux/arm64 +IMAGE=boot2-empty:$ARCH +BOOT2=build/$ARCH/boot2 +BOOT4=build/$ARCH/boot4 +OUT=build/$ARCH/boot6 +STAGE=build/$ARCH/.boot6-stage + +# ── prerequisites ───────────────────────────────────────────────────── +[ -x "$BOOT4/tcc3" ] || { echo "[boot6 $ARCH] missing $BOOT4/tcc3 (run scripts/boot4.sh $ARCH)" >&2; exit 1; } +[ -x "$BOOT2/scheme1" ] || { echo "[boot6 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; } +for f in seed-kernel/kernel.S seed-kernel/kernel.c tcc-cc/mem.c; do + [ -f "$f" ] || { echo "[boot6 $ARCH] missing $f" >&2; exit 1; } +done + +# ── ensure container image exists (podman driver only) ──────────────── +if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then + echo "[boot6 $ARCH] building $IMAGE" + podman build --no-cache --platform "$PLATFORM" -t "$IMAGE" \ + -f scripts/Containerfile.empty scripts/ +fi +if [ "$DRIVER" = seed ]; then + KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/kernel.elf + EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh + [ -f "$KERNEL_IMAGE" ] || { echo "[boot6] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } + export KERNEL_IMAGE EXTRACT +fi +export IMAGE PLATFORM DRIVER + +# ── stage inputs and run scheme1 + run.scm under $DRIVER ────────────── +. scripts/lib-runscm.sh +runscm_init "$STAGE" "$OUT" + +RUNSCM=$STAGE/run.scm +scripts/boot6-gen-runscm.sh "$ARCH" "$RUNSCM" +echo "[boot6 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines" + +runscm_scheme1 "$BOOT2/scheme1" +runscm_prelude scheme1/prelude.scm +runscm_runscm "$RUNSCM" + +runscm_input tcc3 "$BOOT4/tcc3" +runscm_input kernel.S seed-kernel/kernel.S +runscm_input kernel.c seed-kernel/kernel.c +runscm_input mem.c tcc-cc/mem.c + +runscm_export kernel.elf +runscm_run 1200 + +echo "[boot6 $ARCH/$DRIVER] OK -> $OUT/kernel.elf ($(wc -c <"$OUT/kernel.elf") bytes)" diff --git a/scripts/seed-accept-boot34.sh b/scripts/seed-accept-boot34.sh @@ -4,9 +4,10 @@ ## bootN/'s podman-built artefacts. ## ## Prereqs (build first): -## - seed-kernel/build/Image (`make` in seed-kernel/) -## - build/aarch64/boot{0,1,2,3,4}/ (run scripts/bootN.sh aarch64 -## under DRIVER=podman to populate references) +## - build/aarch64/boot6/kernel.elf (run ./scripts/boot.sh aarch64 +## under DRIVER=podman to produce the tcc-built seed kernel) +## - build/aarch64/boot{0,1,2,3,4}/ (same boot.sh run populates these +## references) ## ## What it does: ## 1. Stash existing podman-built build/aarch64/boot{3,4}/ as ref/. @@ -25,8 +26,8 @@ ARCH=aarch64 ROOT=$(cd "$(dirname "$0")/.." && pwd) cd "$ROOT" -KERNEL=seed-kernel/build/Image -[ -f "$KERNEL" ] || { echo "missing $KERNEL — make in seed-kernel/" >&2; exit 1; } +KERNEL=build/$ARCH/boot6/kernel.elf +[ -f "$KERNEL" ] || { echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } [ -x build/$ARCH/boot3/tcc0 ] || { echo "build/$ARCH/boot3/tcc0 missing — run scripts/boot3.sh aarch64" >&2; exit 1; } REF=build/$ARCH/.seed-ref diff --git a/scripts/seed-accept-boot5.sh b/scripts/seed-accept-boot5.sh @@ -4,9 +4,10 @@ ## artefacts. Mirrors scripts/seed-accept-boot34.sh. ## ## Prereqs (build first): -## - seed-kernel/build/Image (`make` in seed-kernel/) -## - build/aarch64/boot{0..5}/ (run scripts/bootN.sh aarch64 -## under DRIVER=podman to populate references) +## - build/aarch64/boot6/kernel.elf (run ./scripts/boot.sh aarch64 +## under DRIVER=podman to produce the tcc-built seed kernel) +## - build/aarch64/boot{0..5}/ (same boot.sh run populates these +## references) ## ## What it does: ## 1. Stash existing podman-built build/aarch64/boot5/ as ref/. @@ -23,8 +24,8 @@ ARCH=aarch64 ROOT=$(cd "$(dirname "$0")/.." && pwd) cd "$ROOT" -KERNEL=seed-kernel/build/Image -[ -f "$KERNEL" ] || { echo "missing $KERNEL — make in seed-kernel/" >&2; exit 1; } +KERNEL=build/$ARCH/boot6/kernel.elf +[ -f "$KERNEL" ] || { echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } [ -d build/$ARCH/boot5 ] || { echo "build/$ARCH/boot5 missing — run scripts/boot5.sh aarch64" >&2; exit 1; } for f in libc.a crt1.o crti.o crtn.o hello; do [ -e build/$ARCH/boot5/$f ] || { echo "build/$ARCH/boot5/$f missing — run scripts/boot5.sh aarch64" >&2; exit 1; } diff --git a/scripts/seed-accept.sh b/scripts/seed-accept.sh @@ -23,13 +23,13 @@ ARCH=aarch64 ROOT=$(cd "$(dirname "$0")/.." && pwd) cd "$ROOT" -KERNEL=seed-kernel/build/Image +KERNEL=build/$ARCH/boot6/kernel.elf EXTRACT=seed-kernel/scripts/extract-blk.sh SCHEME1=build/$ARCH/boot2/scheme1 CATM=build/$ARCH/boot2/catm PRELUDE=scheme1/prelude.scm -[ -f "$KERNEL" ] || { echo "missing $KERNEL — run 'make' in seed-kernel/" >&2; exit 1; } +[ -f "$KERNEL" ] || { echo "missing $KERNEL — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; } [ -x "$SCHEME1" ] || { echo "missing $SCHEME1 — run boot2 first" >&2; exit 1; } [ -x "$CATM" ] || { echo "missing $CATM — run boot2 first" >&2; exit 1; } diff --git a/scripts/simple-patches/tcc-0.9.26/bss-start-symbol.after b/scripts/simple-patches/tcc-0.9.26/bss-start-symbol.after @@ -0,0 +1,13 @@ + /* `__bss_start` mirror of the auto-defined `_end` (= end of .bss). + * Together they let a kernel-style image zero its .bss without an + * ld-script section bracket. Stock tcc 0.9.26 only synthesizes + * `_end`, so freestanding asm that wants a start-of-bss anchor + * either needs an ld script or this patch. */ + set_elf_sym(symtab_section, + 0, 0, + ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, + bss_section->sh_num, "__bss_start"); + set_elf_sym(symtab_section, + bss_section->data_offset, 0, + ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, + bss_section->sh_num, "_end"); diff --git a/scripts/simple-patches/tcc-0.9.26/bss-start-symbol.before b/scripts/simple-patches/tcc-0.9.26/bss-start-symbol.before @@ -0,0 +1,4 @@ + set_elf_sym(symtab_section, + bss_section->data_offset, 0, + ELFW(ST_INFO)(STB_GLOBAL, STT_NOTYPE), 0, + bss_section->sh_num, "_end"); diff --git a/scripts/stage1-flatten.sh b/scripts/stage1-flatten.sh @@ -149,6 +149,11 @@ apply_our_patch lex-char-unsigned "$SRC/tccpp.c" apply_our_patch lp64-long-constant "$SRC/tccpp.c" apply_our_patch elfinterp-stub "$SRC/tccelf.c" +# Auto-define `__bss_start` alongside tcc's existing `_end` symbol so a +# freestanding image (kernel.S) can zero its .bss with start/end anchors +# without an ld script. Mirrors the live-bootstrap convention. +apply_our_patch bss-start-symbol "$SRC/tccelf.c" + # x86_64 static-link PLT32 collapse: under BOOTSTRAP we force # static_link=1 with no .dynamic / no PT_INTERP, so the runtime linker # never fills the PLT's GOT slots. Upstream tcc 0.9.26 only collapses diff --git a/seed-kernel/kernel.S b/seed-kernel/kernel.S @@ -2,15 +2,27 @@ * plus C-callable thunks for ops that can't be expressed in plain C * (sysreg msr/mrs, barriers, cache/TLB ops, PSCI conduits, cpu pause). */ -.section .head.text, "ax" -.globl _head -_head: +.section .text, "ax" +.globl _start +_start: /* arm64 Image header (Documentation/arm64/booting.rst). - * code0 must be a valid instruction (a branch, in our case). */ + * code0 must be a valid instruction (a branch, in our case). + * + * The header lives at the top of `.text` (not a separate + * `.head.text` section) because we link kernel.S first, so its + * `.text` content lands at file offset 0 — QEMU's arm64 boot + * protocol detects this image by `ARM\x64` magic at offset 0x38, + * which is the `.ascii "ARM\x64"` line below. No ld-script + * section ordering required. + * + * Entry symbol is `_start` (not `_head`) so a script-less linker + * — tcc3 in particular — picks us up via its hard-coded default. + * Image-size end marker is `_end`, which both gnu ld and tcc3 + * auto-define at the end of `.bss`. */ b stext .long 0 .quad 0x80000 /* text_offset (preferred load offset within RAM) */ - .quad _image_end - _head /* image_size */ + .quad _end - _start /* image_size */ .quad 0xa /* flags: 4K pages, anywhere in physmem, LE */ .quad 0 .quad 0 @@ -67,9 +79,11 @@ in_el1: msr cpacr_el1, x9 isb - /* Zero BSS. */ + /* Zero BSS. `__bss_start` and `_end` are auto-defined by both + * gnu ld (via the kernel.lds bracket) and tcc3 (via the + * `bss-start-symbol` simple-patch + tcc's existing `_end` def). */ ldr x1, =__bss_start - ldr x2, =__bss_end + ldr x2, =_end 1: cmp x1, x2 b.ge 2f str xzr, [x1], #8 @@ -453,3 +467,17 @@ arm64_psci_call: .Lpsci_smc: smc #0 ret + + +/* ─── Kernel stack ──────────────────────────────────────────────────────── + * 64 KB reserved as plain `.bss`. Keeping the stack inside `.bss` (rather + * than a custom `.stack` section) means the only link-time service the + * kernel needs is bracketing the merged `.bss` with __bss_start/__bss_end + * — no custom output-section ordering, no link-script reservations. The + * stext bss-zero loop runs before any stack write (it only touches x1/x2), + * so zeroing our own stack region first is safe. */ +.section .bss, "aw", %nobits +.balign 16 +.skip 0x10000 +.globl kstack_top +kstack_top: diff --git a/seed-kernel/kernel.lds b/seed-kernel/kernel.lds @@ -6,15 +6,17 @@ * which is PC-relative), so the link base mostly affects symbol values. */ -ENTRY(_head) +ENTRY(_start) SECTIONS { . = 0x40080000; - .head.text : { - KEEP(*(.head.text)) - } - + /* kernel.S lives entirely in `.text` (the arm64 Image header sits + * at its top), and it is linked first, so its contributions land at + * file offset 0 — exactly where QEMU's arm64 boot protocol expects + * the `ARM\x64` magic at byte 0x38. No `.head.text` indirection + * needed; a script-less linker (tcc3) reaches the same layout via + * its default input-order section concatenation. */ .text : ALIGN(8) { *(.text .text.*) } @@ -27,23 +29,20 @@ SECTIONS { *(.data .data.*) } + /* `.bss` includes the 64KB kernel stack, which kernel.S reserves as + * plain `.bss` content ending at `kstack_top`. kernel.S references + * `__bss_start` and `_end` to bracket the bss-zero loop; both names + * are de-facto linker conventions (gnu ld defines them via this + * script, and tcc3 synthesizes them automatically via the + * bss-start-symbol simple-patch + its built-in `_end` def). */ .bss : ALIGN(16) { __bss_start = .; *(.bss .bss.*) *(COMMON) . = ALIGN(16); - __bss_end = .; - } - - /* 64KB kernel stack */ - .stack : ALIGN(16) { - kstack_bottom = .; - . += 0x10000; - kstack_top = .; } _end = .; - _image_end = .; /DISCARD/ : { *(.note.*) *(.comment) *(.eh_frame)