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 (above 0xFFFFFFFF80000000)
  • -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.

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().

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
  1. Copy: The source ELF is copied to build/blobs/<name>.bin
  2. Convert: objcopy converts the raw binary into an ELF object with the data placed in .rodata (read-only)
  3. 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 library
  • lib/ld-musl-x86_64.so.1 – dynamic linker (symlink to libc.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 automatically chmod 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/*.ttf to /usr/share/fonts/
  • Kernel binary: Copied to /boot/aegis.elf for installed-system boot
  • CA certificates: tools/cacert.pem installed 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 filesystem
  • esp.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 via grub-mkimage)
  • EFI/BOOT/grub.cfg – Installed-system GRUB config
  • EFI/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

  1. Create user/bin/<name>/ with a Makefile and source files
  2. If the program has no extra library dependencies, add it to SIMPLE_USER_PROGS in the top-level Makefile
  3. If it has library dependencies, add an explicit rule (see lumen or bastion as examples)
  4. Add the output binary to rootfs.manifest:
    user/bin/<name>/<name>.elf    /bin/<name>
    
  5. Run make iso – the manifest-driven rootfs builder handles the rest