Program Loading: Phases
Same rules: host tests and sabotage cases land with each phase, criteria are stated in terms the phase controls, and any boot count is a ratio against the same measurement on the parent commit - see boot_repeatability.md for why a bare count cannot be a verdict here.
X-P0 — Say what happens
Objective. A failed exec explains itself before anything is restructured.
Steps
- One counter per refusal reason: not found, short read, bad header, no interpreter, out of memory, image too large.
- One log line per refusal, with the path and the step. Today several refusals
return
-1silently and the caller reports "cannot load". - Gate assertion: the deliberate refusals a boot performs are the only ones.
Done when. Every return -1 in the exec path is preceded by exactly one
line naming the path and the reason.
X-P1 — The parse comes out
Objective. Everything that is a data transformation is portable and tested.
Files created — kernel/exec/image.c, include/vibeos/image.h,
tests/kernel/loader_tests.c
Steps, in order
- Segment layout: given headers and a bias, produce the list of (vaddr, len, prot, file offset) a loader must satisfy. Pure function, host-tested, including the cases that have bitten: holes between segments, a segment whose memory size exceeds its file size, a segment ending mid-page.
- The startup block builder: argv, envp and the auxiliary vector, with
AT_ENTRYstaying the program's own entry when an interpreter starts - the rule a dynamic program depends on and which is currently a comment. - Point the existing loader at both; delete the inline versions.
Out of scope. Reading the file, mapping anything.
Tests
- Host: nine layout cases, plus a startup block decoded back and compared.
- Sabotage:
scripts/dev/cases/exec-image.txt- lose the BSS tail; overlap two segments; put the interpreter's entry inAT_ENTRY.
X-P2 — Load by mapping, and the staging buffers go
This is the phase that repairs the defect, and it is memory-plan P4 step 3.
Files created — kernel/exec/load.c
Steps, in order
- A segment becomes a region backed by the file:
vibeos_vma_insertwithVIBEOS_BACKING_FILE, the file's id, and the segment's offset. The region layer already carries all three and advances the offset correctly on a split - that was tested at memory P3 precisely for this. - The fault handler resolves a file-backed region through
vibeos_cache_get, which already exists and is already asserted by the boot gate at a hit ratio. execvestops reading whole files. The ELF headers are still read eagerly- they are needed to decide anything - but the segments are not.
- Delete
g_exec_elfandg_interp_elf, and the 6 MiB they hold.
Out of scope. Writable file mappings and write-back, which stay with the memory plan.
Tests
- Host: a fault against a file-backed region returns the right page; a region split mid-file still faults to the right offset.
- Gate: the staging buffers are gone from
meminfo; a re-exec of the same program is served from the cache; no disk read happens underg_exec_lock. - Sabotage:
scripts/dev/cases/exec-load.txt- fault in from the wrong offset; keep the staging buffer alive; read a segment eagerly.
Done when. g_exec_elf and g_interp_elf do not exist; the boot's exec
path performs no read with interrupts masked.
X-P3 — All-or-nothing
Objective. A failure at any step leaves the caller running its old image.
Steps, in order
- Fault injection at each allocation and read point, driven by a boot-time seed like the memory stress service.
- The unwinding made structural: the new address space and region list are built beside the old ones and swapped in one step.
- The outgoing address space released through the single shared ownership test.
Done when. The injection service completes every seed with the parent still running its original program; counters return to their starting values.
X-P4 — Acceptance
- Concurrent exec. Two processes exec different programs at once, repeatedly. Today they share two global buffers.
- A truncated program is refused, not parsed - the failure that once made a short file look complete.
- Re-exec is cheap: the second exec of a program reads nothing from disk.
- The interpreter substitution is one function, marked as a stand-in, with a test that fails if a second hard-coded path appears. (Deleted with its check by docs/abi/ A4, once the loader could be found by path.)