kit

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

Kernel build and image pipeline

kit can build freestanding kernels from C and assembly and turn the linked result into the flat load images and initramfs archives a bare-metal or QEMU-driven boot needs. This is a thin policy layer on top of the existing toolchain: the same compile, link, and object machinery used for hosted programs, plus three things a kernel author specifically needs — a richer linker-script subset, an image emitter (kit image / objcopy -O binary), and a cpio packager (kit cpio).

kit deliberately stops at the artifact boundary. It does not generate startup code, set up page tables, stacks, TLS, or privilege-mode entry, and it does not implement any boot protocol. Kernel startup and boot-protocol compliance are the author's responsibility; kit produces a deterministic ELF, a byte-exact flat image, and a well-formed archive, and nothing about how they are loaded or run.

Related: DRIVER.md (the multi-call binary and tool registry), LINK.md (symbol resolution, layout, relocation, linker scripts), OBJ.md (the object/image model the emitter reads), and RUNTIME.md (the freestanding headers, compiler-rt helpers, and the TLS contract — note there is no crt0).

Freestanding targets and the build shape

The supported freestanding ELF targets are:

A *-none-elf triple (EI_OSABI STANDALONE) resolves to a non-PIE ELF target by default and puts the driver and linker into freestanding mode, which turns on the strict validation described below.

There is no separate kit kernel command. The compile/link front doors are the ordinary build verbs (see DRIVER.md):

Both accept the freestanding and link flags a kernel needs, and build-exe accepts the common direct linker flags (not only the -Wl, escape hatch). A representative kernel link:

kit build-exe -target x86_64-none-elf \
  -ffreestanding -nostdlib -nostartfiles -static -no-pie \
  -mcmodel=kernel -mno-red-zone \
  -ffunction-sections -fdata-sections \
  -T kernel.ld -e _start \
  -Wl,--gc-sections \
  --map kernel.map --symbols kernel.sym \
  -o kernel.elf \
  boot.S kernel.c mm.c

The flags that matter for kernel code, and that kit honors rather than merely accepts:

-Ttext / -Tdata / -Tbss and --section-start=.name=addr place a freestanding image at a fixed load address from the command line, and work through both build-exe and ld.

Strict freestanding validation

A freestanding executable link (triggered by a *-none-elf input, or forced by the same freestanding_strict trigger from either build-exe or ld) rejects, by default, anything that implies a dynamic loader: DSO inputs, dynamic interpreter paths, dynamic sections and PLT/GOT imports, unresolved symbols, and cross-input target / object-format mismatches. --no-undefined makes unresolved references an error explicitly; --allow-undefined is the escape hatch.

Linker-script subset

Kernel layouts need a structured GNU-ld-compatible script subset larger than the default SECTIONS-only form. The script is parsed by a hand-written recursive-descent parser (src/link/link_script.c) into a structured KitLinkScript; the linker accepts only that structured form, and ld parses -T text into it. Unsupported directives are rejected with a diagnostic rather than silently ignored. See LINK.md for how scripted layout replaces the default permission-bucket placement.

The supported constructs:

KEEP(...) roots continue to interact correctly with --gc-sections.

Link-side side outputs

The linker produces deterministic side files, available from both ld and build-exe, so a kernel build can audit its own layout:

These are linker-owned, not image-owned: the image metadata sidecar (below) is a report about the image transform, not a substitute for --map. Supporting policy flags include --defsym name=expr (can satisfy an otherwise-undefined reference), --orphan-handling=place|warn|error|discard, and --fatal-warnings.

Image emission

Two front doors turn a linked ELF into a flat load image, both backed by one lower-level emitter (include/kit/image.h, implemented in src/obj/image.c):

The emitter consumes already-opened object/image state plus the original input bytes (see OBJ.md for the linked-image view it reads). It never reads the filesystem itself, chooses a boot protocol, or invokes an emulator.

Formats

Segment-based emission reads loadable ranges from the program headers, sorts by the selected address kind, detects overlaps, fills or rejects holes per policy, and preserves bytes exactly as they would be loaded. The main flag groups:

Flat-kernel Image header (arm64 / riscv64)

QEMU's -kernel path on arm64 and riscv consumes the flat Linux Image format: a raw loadable binary prefixed with a fixed 64-byte header the loader reads to place and size the image. The first 32 bytes are common to both arches: code0 / code1 (the entry branch; "MZ" low half when EFI), a u64 text_offset (load offset from a 2 MiB-aligned base), a u64 image_size (the in-memory footprint including BSS), and a u64 flags (bit 0 endianness; on arm64 bits 1-2 page size, bit 3 placement). The tails differ: arm64 carries magic = "ARM\x64" at offset 56; riscv64 carries a version u32 (currently 0x2) at 32 and magic2 = "RSC\x05" at 56.

kit supports this two ways:

Because flags encodes boot semantics, header synthesis is an explicit opt-in, never a default, and the boot-semantic fields are surfaced as explicit options (--image-text-offset, --image-endian, --image-page-size [arm64 only, 4k/16k/64k]) rather than invented — they error if given without --image-header. kit fills the magic/version and image_size deterministically and chooses no boot policy.

kit cpio — initramfs archives

The Linux kernel unpacks its initramfs from a cpio -H newc archive (magic 070701, or 070702 for the CRC variant), optionally compressed; the early-microcode convention is just an uncompressed cpio concatenated ahead of the compressed main archive. This is an archive format, not a boot protocol — a sibling of ar — so kit cpio lives in the byte-utility tool family rather than in the image emitter. The newc codec is driver-local (driver/cmd/cpio.c, mirroring tar.c's stateless shape) since only this one tool consumes it; the driver has no -Isrc. kit packages and inspects the archive; it does not build, mount, or boot it.

kit cpio -o -F initramfs.cpio  -z   init etc/   # create, gzip-compressed
kit cpio -t -F initramfs.cpio.gz                # list (auto-detects gzip)
kit cpio -i -F initramfs.cpio                   # extract

What it supports:

The tool is gated in driver/main.c behind KIT_TOOL_CPIO_ENABLED, alongside the other archive utilities.