Primary entry point for AI agents (Claude Code, Codex, Cursor, Copilot, etc.) Read this first before making any changes. This file is kept up to date with the actual state of the codebase.
- Kernel language: Aether (
.ae), compiled withaether --target kernel - Boot chain: NASM flat binaries (stage1.ae, stage2.ae, boot.ae) — compiled with
aether --target boot - Standalone binaries: Aether (
.ae), compiled withaether --target binary - Compiler: Installed at
/Users/cyberdeth/.local/bin/aether— alwaysmake install-localafter changes - Stdlib:
/Users/cyberdeth/.local/lib/aether/libaether.aelib(universal ELF64) - Build:
make→build/aether.img - Run:
make run(headless) ormake run-graphic - Test:
pkill -f qemu; qemu-system-x86_64 ... -serial file:/tmp/qemu_test.txt; cat /tmp/qemu_test.txt - Source:
/Volumes/Backup/Development/Project_Aether/os/ - Branch:
master(active development)
os/
├── src/
│ ├── boot/ # Boot chain (flat binaries)
│ │ ├── stage1.ae # 512-byte MBR (INT 13h, loads stage2)
│ │ ├── stage2.ae # ATA PIO kernel loader (reads sectors to 0x1000000)
│ │ └── boot.ae # Long mode entry (GDT, PAE, page tables, calls kernel main)
│ ├── kernel/ # Kernel proper
│ │ ├── main.ae # Kernel entry — pure Aether wiring (BSS zero, serial init,
│ │ │ # syscall page setup, binary index scan, ELF load, exec)
│ │ ├── serial.ae # COM1 serial I/O (port I/O asm — in/out)
│ │ ├── ata.ae # ATA PIO disk read (port I/O asm — in/out/rep insw)
│ │ └── elfloader.ae # ELF64 loader with tiny asm helpers (readU8/16/32/64,
│ │ # copyMemory, zeroMemory, callEntry)
│ ├── bin/ # Standalone userland binaries
│ │ ├── libsys.ae # Legacy userspace runtime (syscall wrappers)
│ │ ├── shell.ae # Shell binary (imports libaether)
│ │ └── ... (help, ls, echo, etc.)
│ └── lib/
│ └── libsys.ae # OS-specific syscall wrappers
├── tools/
│ ├── kernel.ld # Kernel linker script (0x1004000)
│ ├── build_image.py # Disk image builder (patches AETHBINX marker)
│ └── pad_and_combine.py # Boot + kernel combiner
├── Makefile
└── AGENTS.md # THIS FILE
| Region | Address | Size | Purpose |
|---|---|---|---|
| Stage1 MBR | 0x7C00 | 512 bytes | Boot sector |
| Stage2 loader | 0x7E00 | 16KB | ATA PIO kernel loader |
| Page tables / GDT | 0x6000–0xA000 | 16KB | PML4, PDP, PD, GDT |
| Stack | 0xC000 | 4KB | Initial stack |
| Module registry | 0x4000 | 4KB | Kernel module interface |
| Syscall page | 0x5000 | 4KB | Function table for /bin/ binaries (slot 1 = puts) |
| Kernel link | 0x1004000 | — | Kernel ELF link address |
| Binary exec space | 0x2000000 | ~64KB | /bin/ ELF loads here |
| Available RAM | 0x11E6000–0x10000000 | ~226MB | For page allocator |
BIOS → stage1.ae (MBR, 512B, loads stage2 via INT 13h)
→ stage2.ae (ATA PIO, reads kernel sectors to 0x1000000)
→ boot.ae (GDT, PAE, long mode, page tables, calls 0x1004000)
→ kernel main() (serial init → read binary index → load shell → call shell)
- Boot code calls 0x1004000 — the kernel's
main()function is at this address (defined first in source, enforced by@entry(0x1004000)) clibefore kernel call — no IDT is set up, any interrupt causes triple fault- Boot preamble — boot.ae is padded to 16KB (0x4000) before kernel.bin starts
Initialization sequence:
- Zero BSS (using
__bss_start/__bss_endlinker symbols) serial_init()— COM1 serial port (115200 8N1)- Print
OK\nto prove boot - Set up syscall page slot 1 (
[0x5008]=serial_puts) - Read binary index from disk (ATA, 2 sectors)
- Scan index for "shell" by name
- Read shell ELF from disk into 0x2000000
loadElf()— parse program headers, copy segments, zero BSScallEntry()— jump to shell's entry point with rdi/rsi=0 (no argv)
Key design rules:
- Zero asm blocks in main.ae — all hardware access in sub-files
- Main.ae is pure Aether logic — wiring, control flow, data parsing
- Asm helpers use local variables for return values (compiler may zero rax after raw asm)
serial_init()— COM1 init (asmin/out)serial_putc(c)— Write single charserial_puts(s)— Write null-terminated string
ata_read_sectors(lba, count, buf)— Read sectors via ATA PIO- rcx save/restore —
rep inswuses cx (must save sector counter around rep) - Returns sector count read (0 on error)
readU8/16/32/64(addr)— Raw memory reads (onemoveach)copyMemory(dst, src, count)—rep movsbzeroMemory(addr, count)—rep stosbcallEntry(addr)— Jump to loaded binary (zeros rdi/rsi for argc/argv convention)loadElf(buf)— Parse ELF headers, iterate program headers, copy PT_LOAD segments, zero BSS
Binaries use binary target (aether --target binary):
- Entry at 0x2000000 with
_startwrapper that handles argc/argv convention callEntry()zeros rdi/rsi so_start.main_no_argpath is taken- Binaries import libaether from
~/.local/lib/aether/libaether.aelib print()is a compiler built-in: on freestanding targets it calls through[0x5008](kernel's puts)
The compiler at /Users/cyberdeth/.local/bin/aether has these OS-relevant features:
| Feature | Description |
|---|---|
--target kernel |
Generates kernel ELF with linker script |
--target binary |
Generates userland ELF at 0x2000000 |
@entry(N) |
Marks function as entry point at address N |
sys func name() at(N) |
Generates indirect call through 0x5000+N |
print() built-in |
Host: write syscall. Freestanding: call [0x5008] |
.aelib format |
Always ELF64 (universal, not host-specific) |
| Asm rax preservation | Asm blocks at end of functions no longer get rax zeroed |
Always run make install-local in the compiler repo after any change, then rebuild the OS.
Written by build_image.py at the sector immediately after the kernel:
Header: count:u32
Entries: [start_sector:u32, size:u32, name:32bytes] × count
The index sector number is patched into the kernel at build time via the AETHBINX marker (8 bytes) followed by the sector number (8 bytes, dq). Kernel reads bin_index_sector_val via asm, reads 2 sectors into sector_buf BSS, then scans for the "shell" entry.
-
clibefore kernel call: boot.ae must emitclibefore calling kernel main — no IDT means any interrupt causes triple fault. -
Asm blocks must NOT contain
ret: The compiler epilogue handlesleave; ret. Always usejmp .donepattern. -
rcxclobber:rep inswusescxas word count. Always push/poprcxaroundrep inswif rcx holds data. -
Compiler zeroes rax after asm: Fixed — asm blocks at function end now preserve rax. But implicit in the compiler's epilogue is
mov rsp, rbp; pop rbp; ret— so rax must be set by the asm block. -
String
+does concat: Detected at codegen time when either operand is a string. Num+num is arithmetic. -
__aether_itoa clobbers rcx: Save rcx before calling itoa.
-
NASM scale factors: Only 1, 2, 4, 8 allowed. Use
imulfor other multipliers. -
kernel.bin = boot.bin (padded to 16KB) + aether.bin: The kernel.bin includes the 16KB boot preamble.
-
AETHBINX marker must be in
.datasection: The marker and following dq value are patched by build_image.py. If the marker isn't found, the sector number stays as 0xFFFFFFFFFFFFFFFF. -
Binary index can span 2 sectors: With 17+ entries (684+ bytes), read 2 sectors. BSS
sector_bufis 2048 bytes.
# Compiler change
cd /Volumes/Backup/Development/Project_Aether/compiler
make && make install-local && make test-host
# OS build
cd /Volumes/Backup/Development/Project_Aether/os
rm -rf build && make
# Test
pkill -f qemu
qemu-system-x86_64 -m 256M -smp 2 -nographic -no-reboot -M pc \
-drive file=build/aether.img,format=raw -serial file:/tmp/qemu_test.txt -display none &
sleep 8
cat /tmp/qemu_test.txt
pkill -f qemuExpected output: ...K>OK loading... loaded Aether>
| Phase | Status | Description |
|---|---|---|
| 1 | 🟢 COMPLETE | Boot chain, serial I/O, ATA PIO, ELF loader, binary exec |
| 2 | 🔵 IN PROGRESS | Shell, commands, standalone binaries |
| 3 | 🔴 NOT STARTED | Filesystem — AetherFS disk-backed FS module |