Blog 106: GCC compiles C on Alpine/Kevlar — two ELF loader bugs squashed

Date: 2026-03-21 Milestone: M10 Alpine Linux

The Milestone

GCC 14.2.0 runs on Kevlar. gcc -o hello hello.c compiles and links successfully:

/ # gcc --version
gcc (Alpine 14.2.0) 14.2.0

/ # gcc -o /root/hello /root/hello.c
/ # echo $?
0

Two bugs prevented this — one in ELF loading, one in process management.


Bug 1: AT_PHDR wrong for non-PIE (ET_EXEC) binaries

Symptom: gcc --version crashed with SIGSEGV at address 0xa001e8950 (first attempt) then 0x40 (after partial fix). Every non-PIE dynamically- linked binary crashed.

Root cause: The kernel passed AT_PHDR pointing to a stack-mapped copy of the ELF header instead of the program headers in the loaded image. musl's dynamic linker computes load_bias = AT_PHDR - phdr[0].p_vaddr, so the wrong AT_PHDR produced a wildly incorrect load bias. For gcc (base 0x400000, e_phoff=0x40), AT_PHDR was 0x40 instead of 0x400040.

Fix: AT_PHDR = main_lo + main_base_offset + e_phoff

  • PIE (ET_DYN): main_lo=0, offset=basebase + e_phoff (unchanged)
  • Non-PIE (ET_EXEC): offset=0, main_lo=0x4000000x400040 (now correct)

This was a one-line fix in kernel/process/process.rs but affects every non-PIE binary on the system. All PIE binaries (curl, make, busybox, openrc) were unaffected because the PIE path already set AT_PHDR correctly.

Bug 2: clone() didn't add child to parent's children list

Symptom: gcc compiled but reported "failed to get exit status: No child process" — wait4() returned ECHILD.

Root cause: Process::clone_process() added the child to the process table and scheduler but forgot parent.children().push(child). The fork() path had this line; clone() didn't. Since musl's posix_spawn uses clone(CLONE_VM|CLONE_VFORK|SIGCHLD, ...), gcc's cc1/as/ld subprocesses were invisible to wait4().

Fix: Added parent.children().push(child.clone()) to the clone path, matching fork.


Alpine Image: build-base pre-installed

The Alpine ext4 image now includes build-base (gcc, binutils, make, musl-dev) pre-installed from Docker, with the disk increased to 512MB to accommodate the 245MB toolchain. This avoids the slow ~200MB download over emulated networking.


Known Issue: ext4 directory entry visibility

GCC-compiled binaries can't be executed immediately after compilation:

/ # gcc -o /root/hello /root/hello.c   # exit 0
/ # /root/hello                         # not found!

Freshly created files aren't visible to subsequent path lookups via a different VFS traversal. The ext4 create_file writes the directory entry to disk via write_block, but a new Ext2Dir instance reading the same directory doesn't find the entry. Under investigation — likely a block I/O coherence issue in the virtio-blk path.


Results

FeatureBeforeAfter
gcc --versionSIGSEGVgcc (Alpine 14.2.0) 14.2.0
gcc -o hello hello.cSIGSEGVexit 0
make --versionworkedGNU Make 4.4.1
Non-PIE ELF binariesall crashall work
clone() + wait4()ECHILDcorrect