Win32 PE Loader
What is it?
The PE loader is what lets Impossible OS run Windows programs natively: it reads a 64-bit PE32+ executable, maps its sections into a new process, binds its imports and starts it in user mode. The kernel half already works for simple images: validation, fixed-base section mapping and import binding ship today. What is missing is the user-mode half that makes a compiled Windows program actually run: relocations, imports that can be called, and a Windows C runtime. This roadmap's nine sections are all still open; the kernel pieces shipped under the Binary Format System roadmap instead.
How does it work?
Today. A file reaches the loader through the ordinary exec path: SYS_EXEC runs task_exec(), which reads the file and hands it to exec_load_fmt(). That picks the loader by the file's magic bytes: 7F ELF for ELF, EIF! for EIF and MZ for PE (exec.c). There is no "PE first, ELF second" probing; each format is recognised by its own header.
flowchart LR
A[SYS_EXEC] --> B[task_exec]
B --> C[exec_load_fmt: match magic]
C -->|MZ| D[pe_validate]
D --> E[pe_load: map sections at ImageBase]
E --> F[bind imports to SSDT slots]
F --> G[register module and .pdata]
G --> H[ring-3 entry, RCX = PEB]
- Validation.
pe_validate()accepts only AMD64 (0x8664) images with a PE32+ optional header (0x20B); 32-bit images are rejected (pe.c, structures inpe.h). - Mapping.
pe_load()maps the headers and each section page by page at the image's ownImageBase, zero-fills uninitialised data and marks non-code sections no-execute. Cleanup on failure is incomplete:pe_rollback()unmaps a contiguous run of pages fromImageBase, so a section mapped past a gap in the RVAs can survive it, and a page whose mapping fails leaks its frame. Limits:ImageBaseat least 16 MiB, up to 96 sections and a 256 MiB mapped image. The file itself is capped lower:SYS_EXECstages it in kernel heap and refuses anything over 512 KiB (EXEC_KMALLOC_STAGE_MAX); the 16 MiBEXEC_MAX_IMAGE_SIZEbounds only what the format dispatcher accepts (exec.h). - Imports. The import directory is walked for up to 64 DLLs and 4,096 names each. A by-name import found in a built-in table is written into the IAT as that function's SSDT service number, not as an address; anything else, including every import by ordinal, is written as 0.
- Exception data. The
.pdatatable is recorded with the module so kernel exception dispatch can unwind through it, and the module is added to the PEB loader lists. - Launch. The new thread enters ring 3 with RCX pointing at the PEB.
System calls. The SYSCALL fast path is already set up: STAR, LSTAR and FMASK are programmed in syscall_fast.c with the entry stub in syscall_entry.asm, and the GDT already puts user data (0x18) just below user code (0x20), the order SYSRET needs (gdt.h). INT 0x80 and INT 0x2E still work too.
What are its interfaces?
| Interface | Status |
|---|---|
pe_validate(), pe_load(), PE32+ structures in pe.h |
Shipped |
Built-in import tables for kernel32.dll (14 names) and ntdll.dll (96 Nt* names) |
Shipped; see Win32 API Surface |
SYSCALL/SYSRET entry, STAR/LSTAR/FMASK |
Shipped |
| Base relocations, ASLR | Planned |
| TLS directory, delay-load and bound imports, ordinal imports | Not handled |
crt0_pe.asm, kernel32_stub.c, libcrt.a |
Planned |
How do I use it?
The loader is exercised by the kernel suite (make test-exec, which checks PE structure sizes, validation of good and bad images, section mapping, module registration and the ImageBase floor in test_exec.c) and by a hand-assembled PE that exits with status 0 (test_loader_pe.c). A normal compiled Windows program cannot run yet: its calls through the IAT would jump to a service number or to 0.
What is not implemented yet?
- Fix Ring-3 User-Mode Execution: the MSR setup and GDT order are done; PE programs using
SYSCALLdirectly is not yet proven - Windows x64 Syscall ABI
- PE Header Structures and PE Loader Core: shipped in
pe.handpe.c, except loading at a fallback base and complete rollback on failure - Base Relocations
- Import Resolution: callable stubs instead of service numbers, and a clean failure for a missing import
- PE Execution, EXE Format Precedence and the Minimal User-Mode Runtime
How does it compare with Windows 11 and Linux?
Windows 11 loads images in user mode with ntdll's Ldr* routines, rebases them for ASLR, resolves imports against real DLLs and enters the kernel through KiSystemCall64. Linux runs PE programs only through Wine, which loads them in user space on top of the ELF system. Impossible OS loads PE32+ in the kernel as a first-class format beside ELF and EIF; today it maps and binds images but cannot yet run a compiled Windows program.