Build System
Aegis OS build system architecture: toolchain, Make targets, rootfs construction, and ISO packaging
Build System
Aegis uses a monolithic GNU Make build system with a single top-level Makefile that orchestrates kernel compilation, userspace program builds, rootfs image construction, and ISO packaging. The build is designed for cross-compilation on a Linux host targeting x86_64 bare-metal.
v1 note: The build system is v1 and evolving alongside the OS. Contributions are welcome – file issues or propose changes at exec/aegis.
Toolchain Requirements
| Tool | Binary | Purpose |
|---|---|---|
| Cross GCC | x86_64-elf-gcc |
Kernel C compilation (freestanding) |
| Cross LD | x86_64-elf-ld |
Kernel linking |
| NASM | nasm |
x86_64 assembly (boot stub, ISR stubs, context switch, syscall entry) |
| Cargo (nightly) | cargo +nightly |
Rust capability library (kernel/cap/) |
| musl libc | Built from source | Dynamic C library for userspace programs |
| objcopy | x86_64-elf-objcopy |
Binary blob embedding (initrd) |
| GRUB | grub-mkrescue, grub-mkimage |
ISO construction and EFI image generation |
| debugfs | /sbin/debugfs |
ext2 rootfs image manipulation |
| sgdisk | /usr/sbin/sgdisk |
GPT disk image partitioning |
Compiler Flags
The kernel is compiled with a strict freestanding configuration:
CFLAGS = \
-ffreestanding -nostdlib -nostdinc \
-isystem $(GCC_INCLUDE) \
-mcmodel=kernel \
-fno-pie -fno-pic \
-mno-red-zone -mno-mmx -mno-sse -mno-sse2 \
-fno-stack-protector \
-fno-omit-frame-pointer \
-g \
-Wall -Wextra -Werror \
-I<kernel subdirectories>
Key flag rationale:
-nostdinc+-isystem $(GCC_INCLUDE): Excludes all system headers, then adds back only GCC’s own freestanding headers (stdint.h,stddef.h, etc.)-mcmodel=kernel: Generates code assuming execution in the higher-half virtual address space (above0xFFFFFFFF80000000)-mno-red-zone: Required for x86_64 kernels; interrupt handlers use stack space below RSP that the red zone would otherwise reserve-mno-mmx -mno-sse -mno-sse2: No floating-point or SIMD in kernel code-fno-stack-protector: No stack canary (no runtime support for__stack_chk_fail)-fno-omit-frame-pointer: Preserves frame pointers for reliable stack traces-Werror: All warnings are build failures
Assembly is compiled with NASM in ELF64 mode:
ASFLAGS = -f elf64
Kernel Source Organization
The kernel source is divided into subsystem directories, each producing its own set of object files:
kernel/
├── arch/x86_64/ # Architecture: boot, GDT, IDT, PIC, PIT, LAPIC, IOAPIC,
│ # SMP, TLB, syscall entry, PCIe, ACPI, serial, VGA, keyboard,
│ # mouse, SMAP/SMEP
├── core/ # main.c, printk, random, capability policy
├── cap/ # Rust capability validation library (Cargo project)
├── mm/ # Physical/virtual memory management, KVA, VMA
├── sched/ # Scheduler, wait queues
├── proc/ # Process management, ELF loader
├── syscall/ # Syscall dispatch + per-category handlers (io, memory,
│ # process, exec, identity, cap, time, file, dir, meta,
│ # signal, socket, random, disk, futex)
├── fs/ # VFS, ext2, initrd, ramfs, procfs, memfd, console, pipe,
│ # block device, GPT, poll
├── tty/ # TTY, PTY
├── signal/ # Signal delivery
├── drivers/ # NVMe, xHCI, USB HID, USB mouse, virtio-net, RTL8169,
│ # framebuffer, ramdisk
└── net/ # Ethernet, IP, UDP, TCP, sockets, UNIX sockets, epoll
Assembly sources in kernel/arch/x86_64/:
| File | Purpose |
|---|---|
boot.asm |
Multiboot2 entry, 32-bit trampoline, GDT setup, jump to 64-bit |
isr.asm |
Interrupt service routine stubs (pushes error code, calls C handler) |
ctx_switch.asm |
Context switch: saves/restores register state between tasks |
syscall_entry.asm |
SYSCALL/SYSRET entry point, user-kernel transition |
ap_trampoline.asm |
SMP application processor startup trampoline (real mode to long mode) |
Primary Make Targets
Build Targets
| Target | Command | Description |
|---|---|---|
all |
make |
Builds build/aegis.elf (kernel binary only) |
iso |
make iso |
Full ISO: kernel + rootfs + ESP + GRUB bootloader |
test-iso |
make test-iso |
Text-mode ISO for automated testing (uses grub-test.cfg) |
disk |
make disk |
GPT disk image wrapping the rootfs (for NVMe testing) |
rootfs |
make rootfs |
ext2 rootfs image only |
build-musl |
make build-musl |
Build musl libc as a shared library |
curl_bin |
make curl_bin |
Build BearSSL + curl (external dependency) |
Run Targets
| Target | Command | Description |
|---|---|---|
run |
make run |
Boot ISO in QEMU with q35, std VGA, serial on stdio |
run-fb |
make run-fb |
Boot ISO in QEMU with q35, virtio-vga (framebuffer) |
Both run targets automatically attach NVMe if build/disk.img exists, add xHCI + USB keyboard, and configure the isa-debug-exit device for programmatic shutdown.
Debug Targets
| Target | Command | Description |
|---|---|---|
gdb |
make gdb |
Launch QEMU with GDB server on :1234, then attach GDB |
sym |
make sym ADDR=0x... |
Resolve a kernel address to source file + line via addr2line |
Test Targets
| Target | Command | Description |
|---|---|---|
test |
make test |
Build ISO, run the Rust integration test suite (tests/) |
test-q35 |
make test-q35 |
Build ISO + disk, run tests with AEGIS_PRESET=q35 |
install-test |
make install-test |
Run installer test via Vortex stack |
Clean
make clean
Removes the build/ directory, cleans all user program sub-Makefiles, and runs cargo clean on the Rust capability library.
Kernel Link Process
The kernel is linked with a custom linker script at tools/linker.ld:
OUTPUT_FORMAT("elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
PHYS_BASE = 0x100000;
KERN_VMA = 0xFFFFFFFF80000000;
Memory Layout
Physical Address Space:
┌──────────────────────────┐ 0x100000 (1 MB)
│ .multiboot │ ← Must be in first 8 KB (Multiboot2 spec)
│ .text.boot │ ← 32-bit boot stub, runs at physical addresses
├──────────────────────────┤
│ │
│ (identity mapped 1:1 │
│ during early boot) │
│ │
└──────────────────────────┘
Virtual Address Space (higher half):
┌──────────────────────────┐ 0xFFFFFFFF80000000 + offset
│ .text │ ← Kernel code (AT physical)
│ .rodata │ ← Read-only data, embedded blobs
│ .data │ ← Initialized data
│ .bss │ ← Zero-initialized data
│ _kernel_end │ ← End marker (virtual address)
└──────────────────────────┘
The .multiboot and .text.boot sections run at physical addresses (VMA = LMA). All remaining sections are relocated to the higher half: VMA starts at KERN_VMA + offset, while LMA remains physical. The boot stub sets up page tables to map both the identity region (for the trampoline) and the higher-half region before jumping to main().
Final Link Command
x86_64-elf-ld -T tools/linker.ld -nostdlib -o build/aegis.elf \
<all object files> \
kernel/cap/target/x86_64-unknown-none/release/libcap.a
The Rust capability library (libcap.a) is linked directly into the kernel ELF alongside the C object files.
Initrd / Binary Blob Embedding
Aegis embeds a minimal set of programs directly into the kernel ELF as binary blobs in .rodata. This provides the initial userspace programs available before the rootfs is mounted.
Blob Build Process
Source ELF → build/blobs/<name>.bin → objcopy → build/blobs/<name>.o → linked into kernel
- Copy: The source ELF is copied to
build/blobs/<name>.bin - Convert:
objcopyconverts the raw binary into an ELF object with the data placed in.rodata(read-only) - Link: The object file is linked into the kernel
Embedded Blobs
| Blob | Source | Notes |
|---|---|---|
login |
user/bin/login/login.elf |
Login manager (dynamically linked) |
vigil |
user/bin/vigil/vigil |
Init system (dynamically linked) |
shell |
user/bin/shell/shell.elf |
Minimal shell (statically linked) |
init |
user/bin/vigil/vigil |
Alias for vigil (init process) |
echo |
Built from user/bin/echo/main.c |
Static build via musl-gcc -static |
cat |
Built from user/bin/cat/main.c |
Static build via musl-gcc -static |
ls |
Built from user/bin/ls/main.c |
Static build via musl-gcc -static |
The echo, cat, and ls blobs are built as minimal static binaries (musl-gcc -static -O2 -s) directly from their single-file sources, bypassing the normal dynamic-linking build path.
Userspace Program Builds
musl libc
All dynamically-linked userspace programs depend on musl libc 1.2.5, built from source by tools/build-musl.sh:
bash tools/build-musl.sh
Output goes to build/musl-dynamic/:
usr/lib/libc.so– shared librarylib/ld-musl-x86_64.so.1– dynamic linker (symlink tolibc.so)usr/bin/musl-gcc– GCC wrapper with patched specs file
The script auto-detects stale builds (wrong architecture) and rebuilds as needed.
Simple User Programs
Programs with no extra library dependencies are declared in a single list:
SIMPLE_USER_PROGS = \
ls cat echo pwd uname clear true false wc grep sort \
mkdir touch rm cp mv whoami ln chmod chown readlink \
shutdown reboot login stsh httpd nettest polltest
A macro generates a Make rule for each: user/bin/<name>/<name>.elf depends on musl and is built by running make -C user/bin/<name>.
Programs with Extra Dependencies
Programs requiring additional libraries have explicit dependency declarations:
| Program | Dependencies |
|---|---|
lumen (compositor) |
libglyph, libcitadel, musl |
bastion (display manager) |
libglyph, libauth, musl |
installer |
libinstall, musl |
gui-installer |
libglyph, libinstall, musl |
External Builds
| Program | Build Script | Dependencies |
|---|---|---|
| BearSSL | tools/build-bearssl.sh |
None |
| curl | tools/build-curl.sh |
BearSSL |
| Rune (text editor) | tools/build-rune.sh |
Rust toolchain |
Rust Capability Library
The capability validation subsystem is written in Rust and compiled as a static library:
cargo +nightly build --release \
--target x86_64-unknown-none \
--manifest-path kernel/cap/Cargo.toml
Output: kernel/cap/target/x86_64-unknown-none/release/libcap.a
The x86_64-unknown-none target produces a freestanding binary with no standard library, suitable for linking into the kernel. The Rust code provides capability validation functions called from C via FFI.
Rootfs Image Construction
The rootfs is an ext2 filesystem image built by tools/build-rootfs.sh. The process has three phases:
1. Create Empty Image
dd if=/dev/zero of=build/rootfs.img bs=512 count=120832
mke2fs -t ext2 -F -L aegis-root build/rootfs.img
This creates a ~59 MB ext2 filesystem (120,832 sectors of 512 bytes).
2. Copy Skeleton Directory
The rootfs/ directory in the source tree is replicated into the image verbatim. This provides the base directory structure and configuration files (e.g., /etc/vigil/ service definitions, /etc/passwd, etc.).
3. Install Binaries from Manifest
The rootfs.manifest file is the single source of truth for what goes into the rootfs:
# Format: SOURCE DEST
build/musl-dynamic/usr/lib/libc.so /lib/libc.so
build/musl-dynamic/usr/lib/libc.so /lib/ld-musl-x86_64.so.1
user/bin/shell/shell.elf /bin/sh
user/bin/vigil/vigil /bin/vigil
...
Rules:
- Everything under
/bin/and/lib/is automaticallychmod 0755 - Parent directories are created on demand
- Missing source files generate a warning but do not fail the build
Assets
The rootfs build also installs optional assets:
- Wallpaper/logos: Converted from PNG to raw format via Python scripts, installed to
/usr/share/ - TTF fonts: Copied from
assets/*.ttfto/usr/share/fonts/ - Kernel binary: Copied to
/boot/aegis.elffor installed-system boot - CA certificates:
tools/cacert.peminstalled to/etc/ssl/certs/ca-certificates.crt
ISO Construction
The ISO is a GRUB-bootable El Torito image:
build/isodir/
├── boot/
│ ├── aegis.elf ← Kernel binary
│ ├── rootfs.img ← ext2 root filesystem
│ ├── esp.img ← EFI System Partition (for UEFI boot)
│ └── grub/
│ ├── grub.cfg ← GRUB configuration
│ ├── font.pf2 ← JetBrains Mono (optional)
│ └── wallpaper.png (optional)
GRUB Configuration
tools/grub.cfg provides three boot modes:
| Menu Entry | Kernel Arguments | Description |
|---|---|---|
| Aegis (graphical) | boot=graphical quiet |
Full GUI: Bastion greeter + Lumen desktop |
| Aegis (text) | boot=text quiet |
Text-mode: direct TTY login |
| Aegis (debug) | boot=text |
Text-mode with verbose kernel output |
The kernel is loaded via Multiboot2 with two modules:
rootfs.img– root filesystemesp.img– EFI System Partition (used by the installer)
Test ISO
make test-iso builds a separate ISO using tools/grub-test.cfg:
- Single menu entry:
boot=text quiet - Zero-second timeout (instant boot)
- Used by automated integration tests that need CLI access
EFI System Partition
The ESP image (build/esp.img) is a FAT16 filesystem containing:
EFI/BOOT/BOOTX64.EFI– GRUB EFI binary (built viagrub-mkimage)EFI/BOOT/grub.cfg– Installed-system GRUB configEFI/BOOT/unicode.pf2– Unicode font- Optional: background image, wallpaper, custom font
This ESP is embedded in the ISO and also written to disk during installation for UEFI boot support.
Disk Image
The disk image is a GPT-partitioned raw image for NVMe testing:
make disk
┌─────────────────────────────────────┐
│ GPT Header │
├─────────────────────────────────────┤ Sector 34
│ Partition 1: aegis-root │
│ Type: A3618F24-0C76-4B3D-0001-... │
│ Sectors 34–122879 │
│ Contains: ext2 rootfs │
├─────────────────────────────────────┤ Sector 122880
│ Partition 2: aegis-swap │
│ Type: A3618F24-0C76-4B3D-0002-... │
│ Remaining space │
├─────────────────────────────────────┤
│ GPT Footer │
└─────────────────────────────────────┘
The rootfs partition uses a custom Aegis-specific GPT type GUID. The ext2 rootfs image is dd‘d into partition 1 at sector offset 2048.
Build Dependencies Graph
┌─────────────┐
│ make iso │
└──────┬──────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│aegis.elf │ │rootfs.img│ │ esp.img │
└────┬─────┘ └────┬─────┘ └──────────┘
│ │
┌───────┼───────┐ │
▼ ▼ ▼ ▼
┌──────┐┌──────┐┌────┐┌──────────────────────┐
│Kernel││ ASM ││Rust││ rootfs.manifest │
│ .c ││stubs ││cap ││ + rootfs/ skeleton │
│files ││ ││lib ││ + all user programs │
└──────┘└──────┘└────┘└──────────┬───────────┘
│
┌────────┼────────┐
▼ ▼ ▼
┌────────┐┌──────┐┌────────┐
│ Simple ││ Libs ││External│
│ progs ││glyph ││ curl │
│(musl) ││auth ││bearssl │
└───┬────┘└──┬───┘└────────┘
│ │
▼ ▼
┌──────────────────┐
│ musl libc │
│ (build-musl.sh) │
└──────────────────┘
Adding a New Userspace Program
- Create
user/bin/<name>/with aMakefileand source files - If the program has no extra library dependencies, add it to
SIMPLE_USER_PROGSin the top-level Makefile - If it has library dependencies, add an explicit rule (see
lumenorbastionas examples) - Add the output binary to
rootfs.manifest:user/bin/<name>/<name>.elf /bin/<name> - Run
make iso– the manifest-driven rootfs builder handles the rest