The FreeBSD cross-compile emulation lane (Step 8) booted real FreeBSD 14.x and
every cross-compiled binary — even a bare WriteLn — SIGSEGV'd. truss pinned it:
mmap(0, 65536, RW, MAP_PRIVATE|0x20, -1, 0) ERR#9 'Bad file descriptor'
The shared allocator (runtime.mem) hardcodes the LINUX MAP_ANONYMOUS bit (0x20);
FreeBSD's MAP_ANON is 0x1000. Without a recognised anon flag FreeBSD treated the
first heap arena as a file mapping on fd -1, returned EBADF, and the allocator
dereferenced the -errno result -> SIGSEGV. This is exactly the runtime-only,
struct/syscall-flag class of bug host-side static ELF checks cannot see, and the
emulation lane caught it on its first run.
Fix: the FreeBSD mmap leaf translates the flag — if the Linux MAP_ANONYMOUS bit
(0x20) is set, clear it and set FreeBSD MAP_ANON (0x1000). This keeps runtime.mem
OS-agnostic (one canonical, Linux-shaped flag set) and confines the divergence to
the per-OS syscall leaf, matching the Strategy-B link-time-swap design. A flag
already carrying 0x1000 (e.g. runtime.start.static.freebsd's TLS mmap) passes
through untouched.
Also harden SetupTLS: read the program-header table directly from this binary's
own ELF header at the fixed non-PIE load base ($400000) instead of walking the
kernel auxv for AT_PHDR. A non-PIE ET_EXEC always maps its header there, so this
is exact and self-contained (FreeBSD does supply AT_PHDR, but relying on it
coupled startup to the auxv layout for no benefit).
Verified on real FreeBSD 14.3: hello prints 'Hello' (exit 0); fileio round-trips
a file — write/read/stat/unlink — printing file-io-ok/exists-ok/done (exit 0),
validating the struct-stat offsets, the file syscall leaf AND the allocator on a
real kernel. Linux unaffected (FreeBSD units are standalone): all four fixpoints
green, full suite 4029 tests under QBE-built and native-built runner. The
freebsd-crosscheck emulation job (fileio fixture) is the standing regression
guard for the mmap path.