Linux Compatibility Progress
Status: In Progress (static, position-independent and dynamically linked Linux binaries all run end to end) Last review: 2026-08-27
Read this first: there are two things called "Linux compatibility"
They are unrelated, and confusing them is how this area went untracked.
- The kernel's Linux syscall layer (
kernel/arch/x86_64/arch_hw.c, theLSYS_*dispatch). This is the real one. It is what runs a static musl program and a static BusyBox that this project did not build, and it is gated on every boot. - The portable compatibility scaffold (
user/compat/). A runtime object with per-target enable flags, translated/denied counters, and one translation function per foreign OS. The Linux translator maps two syscall numbers. It is a shape, not a subsystem.
Everything that works today is (1). The kernel does not call (2) to run a Linux binary; it enables the Linux target in the scaffold at boot and reports its counters, which is why the serial log shows small numbers next to a working BusyBox.
Implemented
- 67 Linux syscalls in the
LSYS_*dispatch, covering process lifecycle (fork,vfork,execve,wait4,exit_group), files and descriptors, pipes, signals (rt_sigaction,rt_sigprocmask,rt_sigreturn,kill,tgkill,tkill), memory (mmap,brk), sockets, and the startup interrogation a C runtime performs beforemain. - A System V startup stack with a populated auxiliary vector, including
AT_RANDOM- absent, it is a null pointer dereferenced while seeding the stack canary, beforemainruns. - Copy-on-write
fork, signal delivery through the libc's ownSA_RESTORERtrampoline, and pipelines in BusyBox's shell. - An unimplemented syscall is reported by number on the serial line rather than silently returning zero, so a program failing for that reason says so.
- Dynamically linked programs run. A position-independent executable is
placed at a bias chosen by the loader, and one naming a
PT_INTERPhas that interpreter mapped into the same address space and entered instead of the program. The interpreter relocates itself fromAT_BASEand jumps toAT_ENTRY, which stays the program's own entry. Gated:DYN_OKandPIE_OKboth fail the boot if they stop appearing. - The interpreter path is translated. A musl binary asks for
/lib/ld-musl-x86_64.so.1, and the boot volume is FAT, which has neither that directory nor a name that long; the loader sits beside the other programs and the one path is substituted in the kernel. That is a stand-in for a filesystem layout, not a design.
Known sharp edges
Recorded because each cost hours and none is visible from the code:
- Arguments typed
intarrive zero-extended:mov $-100, %edidelivers0x00000000ffffff9c, so comparing all 64 bits againstAT_FDCWDnever matches. Read them throughVIBEOS_ARG_INT(). - Linux
sigset_tbits are numbered from zero - bit 0 is signal 1. This kernel numbers them by signal, and converts only at the boundary. - A wait status is not an exit code: exit code in the high byte, signal in the low seven bits.
/proc/self/exemust be absolute. glibc does not merely prefer it; it asserts and aborts during startup.
Pending
-
Dynamic executables were the largest gap here and are now closed; what is written under Implemented above covers them. What remains unproven is breadth: one program and one interpreter have been run, not a library with dependencies of its own.
-
Threads work, and are gated.
clone(CLONE_VM|CLONE_THREAD)creates a task sharing the caller's address space with its own kernel stack and TLS base;futexwaits and wakes for real; exit clears the word a joiner sleeps on - before any context switch, because the word lives in the dying task's address space - and only tears that space down when the last thread of the group leaves. Four threads share a counter under a contended mutex, land on exactly 8000, and each sees its own thread-local. Sixteen boots out of sixteen with that running. -
getrandomis deliberatelyENOSYS.AT_RANDOMis supplied; a real entropy source is a separate promise and has not been made. -
Process groups and a controlling terminal, so
ioctl(TCGETS)can be something other thanENOTTYandCtrl-Ccan target a job. -
The scaffold in
user/compat/linux/translates two syscall numbers and is not on the path any real binary takes. Either it grows into the portable front end for the kernel layer, or it should be deleted; leaving it looking like the implementation is the current cost.
Next checkpoint
- Decide the scaffold's fate, then start M13:
PT_INTERPand a loader, which is what turns "static binaries" into "binaries".