# ADR 0006 — Executable binaries: PE loader, exports-table ABI, the /bin tree, and the system image ## Status Accepted. ## Context Nothing can be executed inside raisernics that was not compiled into the kernel image: every command is a built-in function inside the `shell` crate, dispatched by a match statement. The kernel has no program loader, no exec concept, and (per design) no address-space separation — all tasks share one identity-mapped space. We want commands to become real, loadable binaries so the system can gain software without recompiling the kernel, so GUI apps and CLI tools are structurally the same kind of thing, and so a launcher can enumerate launchable apps by listing a directory. ## Decision - **Format: PE32+.** Binaries are built with the existing `x86_64-unknown-uefi` toolchain into standalone PE images. The loader allocates pages, copies segments, applies `.reloc` base relocations at load time, and jumps to the entry point. No page-table changes — loading is alloc-copy-relocate-jump in the one shared address space. - **Loader lives in `rsnix`** as a kernel facility (alongside `fat`, `ahci`). - **ABI: versioned exports table passed at entry** — not syscalls, not well-known addresses. The kernel hands the binary a table of function pointers (filesystem, memory, window/event, streams) with a version field; both sides share the type definitions in-workspace, so the contract is compile-time checked and old on-disk binaries fail loudly on a version mismatch. - **The caller constructs the table per invocation.** Output injection: `put_line` points either at the terminal or at the shell's filter pipeline head. Input injection: `get_line` reads from wherever the shell resolved the grammar's input side (keyboard, file, TIL-truncated source). Redirection is dependency injection, not global state. - **Task per binary.** Every binary runs as its own task. CLI binaries are waited on by the calling terminal's shell (completion via message); GUI binaries are fire-and-forget. A faulting binary kills only its own task, honouring ADR 0003's graceful-death guarantee. - **System image.** The build produces one dd-able FAT32 image containing bootloader, kernel, `\bin\cli\*`, `\bin\gui\*`, `\etc\*`, and seed content. Imaging it onto a disk yields a complete installed system. The volume mounts through the ordinary `/disk//` mechanism with full read-write; overwriting a command on the image persists across reboots. - **Resolution rule.** For name N: run `\bin\\N` from the first mounted volume that has it; else the built-in N; else "unknown command". No volume is privileged; no boot-volume identity exists. - **Kind = launch surface.** `/bin/cli` holds programs that run inside a terminal session; `/bin/gui` holds programs launchable with no terminal (they own their windows). A future launcher enumerates `/bin/gui`. - **The shell owns 100% of the grammar (ADR 0002).** Binaries never see USE/TIL/filters/sinks — they receive clean argv and injected line streams. Any binary gets stdin/stdout for free; which commands accept input keywords stays a policy choice. - **^C and stop flow through the stream functions.** `put_line -> false` means stop (head kill or pager ^C); `get_line` returns Eof (^D) or Abort (^C) distinctly. The `-p` pager blocks inside the stream call in the command task's context. No signals, no abort flags. - **Per-terminal input discipline is a prerequisite phase.** Keyboard routing becomes total: IRQ ring → WM pump → focused window's queue, always. Each terminal's shell owns canonical mode from its own queue; the home terminal becomes an ordinary focusable window and the `FOCUSED == CONSOLE` sentinel retires. Binaries consume assembled lines through a bounded per-invocation channel fed by the owning shell. - **Frozen built-in core**: `help`, `clear`, `echo`, `poweroff`/`exit`, `halt`, `rescan` stay built-ins permanently — needed before disks exist or existing to fix them. Everything else migrates (`cat` last; it exercises every grammar corner). - **Version 0.6.0 lands when exec first works** (loader + ABI + one binary end-to-end). ## Alternatives considered - **Flat raw binary at a fixed address** — trivial loader, but fixed addresses forbid concurrent resident binaries (fatal under preemptive tasks). - **ELF** — familiar but fights the toolchain for zero benefit here. - **Syscall gate (`int` + numbers)** — more OS-flavored, but stdout would become "the current command's sink" global state — the exact bug class the ACTIVE-terminal incident came from — and buys nothing without address spaces. - **Static linking of rsnix into binaries** — forbidden for stateful modules: each binary would get private copies of mount tables and node pools. - **Synchronous execution in the caller's task** — simplest, but a faulting binary halts task 0 and the machine. - **ramfs-seeded /bin** — rejected in favour of the system image: rw through FAT write-back persists across reboots, and the image doubles as the distribution artifact. - **Boot-volume identity / root-path unification** (`\etc` visible at `/etc`) — deferred; needs VFS bind/alias machinery. - **A third "TUI" binary kind for rsdosedit** — rejected; `edit` is classified CLI (it lives in a terminal session) and migrates only after the CLI ABI grows raw-mode surface access. Until then it stays a built-in. ## Consequences - Positive: raisernics gains an exec story without abandoning the single-address-space philosophy; commands become hot-swappable at runtime (`cat x WRITING /bin/cli/ls`) and persistent across reboots; CLI and GUI programs share one spawn path, loader, and ABI. - Positive: the grammar becomes enforceable by construction — binaries cannot break ADR 0002 because they never see it. - Negative: build orchestration grows a stage (build `/bin` crates → stage artifacts → build image); cargo does not chain these natively. - Negative: streaming output crosses a task boundary; `-t` flush and ^C latency gain one scheduling round trip. - Negative: a compute-bound binary that never calls the streams cannot be ^C'd (policy, same blind spot `stress` has today). - Deferred: raw-mode CLI ABI extension (for `edit`), FAT shadowing of `/bin`, boot-volume identity, launcher.