From fc4c563ca14ddec3de3aeef5a861f9707eaae389 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 21 Aug 2026 17:18:09 +0200 Subject: docs: ADR 0006 (executable binaries) + implementation plan + glossary terms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR 0006: PE32+ loader in rsnix, versioned exports-table ABI constructed per invocation, task-per-binary, single dd-able system image with /bin/cli and /bin/gui, shell-owned grammar with injected line streams, per-terminal input discipline as prerequisite phase, frozen built-in core, 0.6.0 at first exec. Plan: five phases (image → input discipline → loader/exec → streams + migration → GUI binaries), each with a QEMU verification gate. Glossary: Binary, Loader, Exports table, System image; kind = launch surface. --- docs/adr/0006-executable-binaries-and-bin-tree.md | 115 ++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/adr/0006-executable-binaries-and-bin-tree.md (limited to 'docs/adr/0006-executable-binaries-and-bin-tree.md') diff --git a/docs/adr/0006-executable-binaries-and-bin-tree.md b/docs/adr/0006-executable-binaries-and-bin-tree.md new file mode 100644 index 0000000..17109c4 --- /dev/null +++ b/docs/adr/0006-executable-binaries-and-bin-tree.md @@ -0,0 +1,115 @@ +# 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. -- cgit v1.2.3