From 2f1010b02ee75bca16d8edacef8750a7abdb45da Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Fri, 17 Jul 2026 10:25:30 +0200 Subject: [PATCH] fix: stream ELF program loading instead of buffering whole files ElfLoad read the entire binary into one kernel heap allocation before copying segments out. The heap grows through ReallocConsecutive, so a 40 MB cc1plus required 10k physically contiguous pages - effectively impossible after boot with a 368 MB ramdisk module resident, and the failed spawn surfaced as posix_spawnp ENOENT in the gcc driver. The loader now reads the ELF header and program header table (bounded at 64 entries), then copies each PT_LOAD page and the PT_TLS template directly from the VFS into freshly mapped process pages. Peak kernel memory per load drops from fileSize to one page regardless of binary size. Boot-smoke verified: all userspace loads through this path. Co-Authored-By: Claude Fable 5 --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Sched/ElfLoader.cpp | 112 +++++++++++++++++---------------- 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 694c0f8..8b446bd 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 22 +#define MONTAUK_BUILD_NUMBER 23 diff --git a/kernel/src/Sched/ElfLoader.cpp b/kernel/src/Sched/ElfLoader.cpp index 7c10857..0c56840 100644 --- a/kernel/src/Sched/ElfLoader.cpp +++ b/kernel/src/Sched/ElfLoader.cpp @@ -433,33 +433,58 @@ namespace Sched { return 0; } - // Read entire file into a heap buffer - uint8_t* fileData = (uint8_t*)Memory::g_heap->Request(fileSize); - if (fileData == nullptr) { - Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to allocate " << fileSize << " bytes for file"; + // Stream the load: headers first, then each segment page copied + // straight from the file into freshly mapped process pages. The + // previous whole-file heap buffer needed fileSize bytes of + // physically contiguous kernel heap, which a 40 MB compiler + // binary cannot rely on after boot. + Elf64Header hdr{}; + if (Fs::Vfs::ReadBackendFile(file, (uint8_t*)&hdr, 0, sizeof(hdr)) + != (int)sizeof(hdr) || !ValidateElfHeader(&hdr)) { Fs::Vfs::CloseBackendFile(file); return 0; } - Fs::Vfs::ReadBackendFile(file, fileData, 0, fileSize); - Fs::Vfs::CloseBackendFile(file); - - // Prevent the optimizer from reordering the VfsRead store past the - // header validation reads that follow. - asm volatile("" ::: "memory"); - - // Validate ELF header - Elf64Header* hdr = (Elf64Header*)fileData; - if (!ValidateElfHeader(hdr)) { - Memory::g_heap->Free(fileData); + if (hdr.e_phnum == 0 || hdr.e_phnum > 64 || + hdr.e_phentsize != sizeof(Elf64ProgramHeader)) { + Kt::KernelLogStream(Kt::ERROR, "ELF") + << "Unsupported program header table (" << (uint64_t)hdr.e_phnum + << " entries of " << (uint64_t)hdr.e_phentsize << " bytes)"; + Fs::Vfs::CloseBackendFile(file); return 0; } - // Process program headers + Elf64ProgramHeader phdrs[64]; + if (Fs::Vfs::ReadBackendFile(file, (uint8_t*)phdrs, hdr.e_phoff, + (uint64_t)hdr.e_phnum * sizeof(Elf64ProgramHeader)) + != (int)((uint64_t)hdr.e_phnum * sizeof(Elf64ProgramHeader))) { + Fs::Vfs::CloseBackendFile(file); + return 0; + } + + // Copy the file bytes that overlap [pageStart, pageStart+0x1000) + // for a segment whose file image spans [vaddr, vaddr+filesz) at + // file offset fileOff. + auto copyPageFromFile = [&](uint64_t physAddr, uint64_t pageStart, + uint64_t vaddr, uint64_t fileOff, + uint64_t filesz) -> bool { + uint64_t pageEnd = pageStart + 0x1000; + uint64_t dataEnd = vaddr + filesz; + uint64_t copyStart = (pageStart > vaddr) ? pageStart : vaddr; + uint64_t copyEnd = (pageEnd < dataEnd) ? pageEnd : dataEnd; + if (copyStart >= copyEnd) { + return true; + } + uint8_t* dst = (uint8_t*)Memory::HHDM(physAddr) + (copyStart - pageStart); + uint64_t srcOff = fileOff + (copyStart - vaddr); + uint64_t len = copyEnd - copyStart; + return Fs::Vfs::ReadBackendFile(file, dst, srcOff, len) == (int)len; + }; + Elf64ProgramHeader* tlsPhdr = nullptr; uint64_t maxVaddrEnd = 0; - for (uint16_t i = 0; i < hdr->e_phnum; i++) { - Elf64ProgramHeader* phdr = (Elf64ProgramHeader*)(fileData + hdr->e_phoff + i * hdr->e_phentsize); + for (uint16_t i = 0; i < hdr.e_phnum; i++) { + Elf64ProgramHeader* phdr = &phdrs[i]; if (phdr->p_type == PT_TLS && phdr->p_memsz > 0) { tlsPhdr = phdr; @@ -487,7 +512,7 @@ namespace Sched { void* page = Memory::g_pfa->AllocateZeroed(); if (page == nullptr) { Kt::KernelLogStream(Kt::ERROR, "ELF") << "Out of physical pages"; - Memory::g_heap->Free(fileData); + Fs::Vfs::CloseBackendFile(file); return 0; } @@ -497,28 +522,15 @@ namespace Sched { // Map into the process's PML4 with User bit set if (!Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, virtAddr)) { Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to map page"; - Memory::g_heap->Free(fileData); + Fs::Vfs::CloseBackendFile(file); return 0; } - // Copy file data that overlaps this page (via HHDM) - uint64_t pageStart = virtAddr; - uint64_t pageEnd = virtAddr + 0x1000; - - uint64_t segFileStart = phdr->p_vaddr; - uint64_t segFileEnd = phdr->p_vaddr + phdr->p_filesz; - - uint64_t copyStart = (pageStart > segFileStart) ? pageStart : segFileStart; - uint64_t copyEnd = (pageEnd < segFileEnd) ? pageEnd : segFileEnd; - - if (copyStart < copyEnd) { - uint64_t dstOffset = copyStart - pageStart; - uint64_t srcOffset = copyStart - phdr->p_vaddr + phdr->p_offset; - uint64_t copySize = copyEnd - copyStart; - - uint8_t* dst = (uint8_t*)Memory::HHDM(physAddr) + dstOffset; - uint8_t* src = fileData + srcOffset; - memcpy(dst, src, copySize); + if (!copyPageFromFile(physAddr, virtAddr, phdr->p_vaddr, + phdr->p_offset, phdr->p_filesz)) { + Kt::KernelLogStream(Kt::ERROR, "ELF") << "Segment read failed"; + Fs::Vfs::CloseBackendFile(file); + return 0; } } } @@ -550,21 +562,15 @@ namespace Sched { } // Copy the .tdata template bytes that land in this page. - uint64_t pageStart = virtAddr; - uint64_t pageEnd = virtAddr + 0x1000; - uint64_t dataStart = base; - uint64_t dataEnd = base + tlsPhdr->p_filesz; - uint64_t copyStart = (pageStart > dataStart) ? pageStart : dataStart; - uint64_t copyEnd = (pageEnd < dataEnd) ? pageEnd : dataEnd; - if (copyStart < copyEnd) { - memcpy((uint8_t*)Memory::HHDM(physAddr) + (copyStart - pageStart), - fileData + tlsPhdr->p_offset + (copyStart - dataStart), - copyEnd - copyStart); + if (!copyPageFromFile(physAddr, virtAddr, base, + tlsPhdr->p_offset, tlsPhdr->p_filesz)) { + ok = false; + break; } // Write the self-pointer if TP falls in this page. - if (tp >= pageStart && tp + 8 <= pageEnd) { - *(uint64_t*)((uint8_t*)Memory::HHDM(physAddr) + (tp - pageStart)) = tp; + if (tp >= virtAddr && tp + 8 <= virtAddr + 0x1000) { + *(uint64_t*)((uint8_t*)Memory::HHDM(physAddr) + (tp - virtAddr)) = tp; } } @@ -576,13 +582,13 @@ namespace Sched { outTls->align = align; } else { Kt::KernelLogStream(Kt::ERROR, "ELF") - << "PT_TLS setup failed (out of pages?), " + << "PT_TLS setup failed (out of pages?), " << "process will run without TLS"; } } - uint64_t entryPoint = hdr->e_entry; - Memory::g_heap->Free(fileData); + uint64_t entryPoint = hdr.e_entry; + Fs::Vfs::CloseBackendFile(file); return entryPoint; }