A Python-native operating system development framework for building bootable x86 and x86_64 systems. Aetherix supports both legacy BIOS and modern UEFI firmware, generates machine code directly through its own native encoder, and provides high-level APIs alongside low-level instruction control.
Project description
Aetherix
A Python-native operating system development framework for building real, bootable x86 and x86_64 systems.
Aetherix generates machine code directly through its own native x86 encoder — no nasm, no ld, no external assembler toolchain. It supports two genuinely separate boot paths, legacy BIOS (16‑bit real mode → 32‑bit protected mode, MBR) and modern UEFI (PE32+, 64‑bit long mode, GPT/FAT32) — and gives you both a batteries-included high-level API and raw, byte-level instruction control when you need it.
from aetherix import Project
with Project("MyOS") as os:
@os.kernel_entry
def main(prog, drivers):
drivers.vga.clear(prog)
drivers.vga.print_string(prog, "Hello, world!")
prog.hlt()
os.build("myos.img")
qemu-system-i386 -drive file=myos.img,format=raw
Or write it to a USB stick: sudo dd if=myos.img of=/dev/sdX bs=4M status=progress conv=fsync
(VirtualBox and VMware also accept the raw .img/.bin directly as a virtual disk.)
Table of contents
- Why Aetherix
- Install
- Quick start
- Examples
- What you actually get
- What's not implemented (and why)
- UEFI support
- Architecture
- CLI
- Verifying the output yourself
- Documentation
- Contributing
- License
Why Aetherix
Most "write your own OS" tutorials hand you fragile shell scripts wired around nasm and a linker. Aetherix takes a different approach:
- No external assembler. Machine code is emitted directly by a small C encoder core (
native/encoder.c), driven from Python. Building an image needs nothing beyond a C compiler at install time. - Two real, independently-verified boot paths. A classic BIOS/MBR path and a genuinely separate UEFI/GPT/FAT32 path — not one bolted onto the other.
- Verification over vibes. Every primitive this project ships with has been checked byte-for-byte against a reference (Intel SDM /
objdump), and the higher-level behavior (disk loading, keyboard input, graphics compositing, UEFI protocol calls) is confirmed by actually executing built images under CPU emulation (Unicorn, QEMU) — not just by disassembling them. See Verifying the output yourself. - Honest about scope. Aetherix tells you what's implemented and what's a documented extension point (
HAL().scaffolded()) — it doesn't fake hardware support it doesn't have. - Two altitudes of API. High-level (
drivers.vga.print_string(...)) for getting an OS running fast, and low-level (prog.mov16(AX, 0x1234)) for full instruction-level control when you need it.
Install
pip install aetherix
Requires a C compiler (gcc/clang/MSVC) on the machine building the package — the native encoder core is compiled once, either at install time or on first import for dev checkouts. Nothing beyond that is required to build an image — Aetherix emits its own machine code. You do need an emulator (QEMU / VirtualBox / VMware) or real hardware to run what you build.
python -c "import aetherix; print(aetherix.__version__)"
For image/graphics support (aetherix.imaging, used to prepare loading screens, backgrounds, and logos for aetherix.drivers.graphics), also install Pillow — a build-time-only dependency, not needed by the OS you build:
pip install "aetherix[imaging]"
Quick start
from aetherix import Project
with Project("MyOS") as os:
@os.kernel_entry
def main(prog, drivers):
drivers.vga.clear(prog)
drivers.vga.print_string(prog, "Hello, world!")
prog.hlt()
os.build("myos.img")
qemu-system-i386 -drive file=myos.img,format=raw
That's the entire pipeline — bootloader generation, kernel assembly, protected-mode switch, and disk image writing all happen inside os.build(...). For the complete API (drivers, the HAL, embedding files, disk images, raw Program control, and UEFI), see API.md.
Examples
| Example | What it demonstrates |
|---|---|
hello_os.py |
Minimal: prints fixed messages, waits for one keypress, halts. |
aether_shell.py |
A genuinely interactive OS: a live keyboard-echoing shell with a runtime VGA cursor (moves, wraps at end-of-row, handles Enter/Backspace/Escape/Shift), a speaker beep on F1, and real power control (F2 reboot, F3 QEMU/Bochs shutdown). Verified end-to-end by scripting keystrokes through CPU emulation (tests/test_emulation.py), not just by disassembling the output. |
graphics_os.py |
VGA graphics (Mode 13h, 320×200, 256-color): a boot loading animation, a background image, and a composited logo. Uses aetherix.imaging to prepare ordinary PNGs at build time; ships with self-contained placeholder art so it runs with no external files. Verified pixel-for-pixel and palette-for-palette correct under CPU emulation. |
uefi_hello.py |
A completely separate boot path: a genuine UEFI application (PE32+, 64-bit long mode) on a GPT+FAT32 disk image, booted by real, unmodified OVMF firmware in real QEMU. See UEFI support. |
python aether_shell.py
qemu-system-i386 -drive file=build/aether_shell.img,format=raw
python graphics_os.py
qemu-system-i386 -drive file=build/graphics_os.img,format=raw
python uefi_hello.py
qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=build/uefi_hello.img,format=raw
graphics_os.py in action
Self-contained placeholder art (assets/generate_assets.py), so the example runs with no external files — swap in your own PNGs any time, aetherix.imaging handles resizing and color quantization for whatever you give it.
Boot loading animation (a real "video" — a sequence of frames shown in order with a delay between them):
Home screen — background and logo, prepared together with imaging.load_images_shared_palette so both render correctly against VGA Mode 13h's single, screen-wide 256-color palette:
Full example source
hello_os.py — minimal Aetherix example
"""
hello_os -- minimal Aetherix example.
Boots to real mode, sets up segments, prints a message via BIOS, loads a
32-bit kernel that switches to protected mode, clears the VGA text screen,
prints a message directly to video memory, waits for a keypress via the
PS/2 controller, then halts.
Run:
python examples/hello_os.py
Then boot the produced image in an emulator, e.g.:
qemu-system-i386 -drive file=hello_os.img,format=raw
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from aetherix import Project, regs
with Project("HelloOS", boot_message="HelloOS bootloader starting...") as os_:
@os_.kernel_entry
def main(prog, drivers):
drivers.vga.clear(prog, attr=drivers.vga.WHITE_ON_BLACK)
drivers.vga.print_string(prog, "Hello from Aetherix!", row=0, col=0,
attr=drivers.vga.GREEN_ON_BLACK)
drivers.vga.print_string(prog, "Press any key to continue...", row=2, col=0)
drivers.keyboard.wait_key_scancode(prog)
drivers.vga.print_string(prog, "Key received. System halted.", row=4, col=0,
attr=drivers.vga.YELLOW_ON_BLACK)
prog.hlt()
os_.add_file("readme.txt", "This file is embedded in the disk image via AVFS.\n")
out_dir = Path(__file__).resolve().parent.parent / "build"
out_dir.mkdir(parents=True, exist_ok=True)
out = os_.build(str(out_dir / "hello_os.img"))
print(f"Built: {out} ({out.stat().st_size} bytes)")
print(f"Kernel sectors: {(len(os_.kernel.assemble()) + 511) // 512}")
print("Boot it with: qemu-system-i386 -drive file=hello_os.img,format=raw")
aether_shell.py — a genuinely interactive example OS
"""
aether_shell -- a genuinely interactive example OS.
Unlike hello_os.py (which prints a few fixed, compile-time-known messages
and waits for one keypress), this boots into a live loop: it reads your
keyboard input in real time -- Shift and all -- and echoes it to the
screen using a runtime VGA cursor (aetherix.drivers.terminal) that
actually moves, wraps at the end of a row, and responds to
Enter/Backspace. There is no fixed script of what appears where; what
shows up on screen depends on what you type.
Controls:
(letters, digits, punctuation, space) echoed as typed
Shift uppercase letters, shifted symbols
Enter move to the next line
Backspace erase the previous character
F1 beep the PC speaker
F2 reboot (hardware reset)
F3 shutdown (QEMU/Bochs only --
see aetherix.drivers.power)
Esc goodbye message, then halt
Run:
python examples/aether_shell.py
Then boot the produced image in an emulator, e.g.:
qemu-system-i386 -drive file=aether_shell.img,format=raw
Or verify it under CPU emulation without needing QEMU installed:
pip install unicorn
python tests/emulate.py build/aether_shell.img
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from aetherix import Project, regs
with Project("AetherShell", boot_message="AetherShell starting...") as os_:
@os_.kernel_entry
def main(prog, drivers):
vga = drivers.vga
kb = drivers.keyboard
term = drivers.terminal
power = drivers.power
kb.init(prog) # zero the Shift-state flag
vga.clear(prog)
vga.print_string(prog, "AetherShell v0.2 -- a live Aetherix demo OS",
row=0, col=0, attr=vga.GREEN_ON_BLACK)
vga.print_string(prog, "Type (Shift works!). Enter=newline Backspace=erase",
row=1, col=0)
vga.print_string(prog, "F1=beep F2=reboot F3=shutdown Esc=halt",
row=2, col=0)
term.init(prog, start_row=4)
prog.label("shell_loop")
kb.wait_key_scancode(prog) # blocks; raw scancode (make OR break) ends up in AL
prog.cmp8(regs.AL, kb.SCANCODE_ESCAPE)
prog.jcc(regs.JZ, "shell_halt")
prog.cmp8(regs.AL, kb.SCANCODE_ENTER)
prog.jcc(regs.JZ, "shell_enter")
prog.cmp8(regs.AL, kb.SCANCODE_BACKSPACE)
prog.jcc(regs.JZ, "shell_backspace")
prog.cmp8(regs.AL, kb.SCANCODE_F1)
prog.jcc(regs.JZ, "shell_beep")
prog.cmp8(regs.AL, kb.SCANCODE_F2)
prog.jcc(regs.JZ, "shell_reboot")
prog.cmp8(regs.AL, kb.SCANCODE_F3)
prog.jcc(regs.JZ, "shell_shutdown")
# Shift press/release: update the flag keyboard.read_char also
# uses, then go straight back to waiting -- Shift never prints.
prog.cmp8(regs.AL, kb.SCANCODE_LSHIFT)
prog.jcc(regs.JZ, "shell_shift_on")
prog.cmp8(regs.AL, kb.SCANCODE_RSHIFT)
prog.jcc(regs.JZ, "shell_shift_on")
prog.cmp8(regs.AL, kb.SCANCODE_LSHIFT | kb.BREAK_BIT)
prog.jcc(regs.JZ, "shell_shift_off")
prog.cmp8(regs.AL, kb.SCANCODE_RSHIFT | kb.BREAK_BIT)
prog.jcc(regs.JZ, "shell_shift_off")
# Any other break code (bit 7 set) -- ignore, don't echo on release.
prog.test_al(kb.BREAK_BIT)
prog.jcc(regs.JNZ, "shell_loop")
# Dispatch any other recognized key, Shift-aware: compare the
# scancode against each known entry and, on a match, print
# whichever of its two (compile-time-known) characters matches
# the current runtime Shift state. Same technique
# keyboard.read_char uses internally, inlined here because this
# loop also needs to catch Enter/Backspace/F-keys in the same
# pass, which read_char deliberately doesn't return for.
all_codes = sorted(set(kb.SCANCODE_SET1_ASCII) | set(kb.SCANCODE_SET1_SHIFTED))
for code in all_codes:
skip_label = prog.unique_label("key_skip")
use_shifted_label = prog.unique_label("key_use_shifted")
unshifted_ch = kb.SCANCODE_SET1_ASCII.get(code, kb.SCANCODE_SET1_SHIFTED.get(code))
shifted_ch = kb.SCANCODE_SET1_SHIFTED.get(code, unshifted_ch)
prog.cmp8(regs.AL, code)
prog.jcc(regs.JNZ, skip_label)
prog.load8(regs.BL, kb.SHIFT_STATE_VAR)
prog.cmp8(regs.BL, 0)
prog.jcc(regs.JNZ, use_shifted_label)
term.putchar_imm(prog, unshifted_ch)
prog.jmp("shell_loop")
prog.label(use_shifted_label)
term.putchar_imm(prog, shifted_ch)
prog.jmp("shell_loop")
prog.label(skip_label)
prog.jmp("shell_loop") # unrecognized key -- ignore and keep waiting
prog.label("shell_shift_on")
prog.store8(kb.SHIFT_STATE_VAR, 1)
prog.jmp("shell_loop")
prog.label("shell_shift_off")
prog.store8(kb.SHIFT_STATE_VAR, 0)
prog.jmp("shell_loop")
prog.label("shell_enter")
term.newline(prog)
prog.jmp("shell_loop")
prog.label("shell_backspace")
term.backspace(prog)
prog.jmp("shell_loop")
prog.label("shell_beep")
drivers.speaker.beep(prog, frequency_hz=880, duration_iterations=1_500_000)
prog.jmp("shell_loop")
prog.label("shell_reboot")
vga.print_string(prog, "Rebooting...", row=24, col=0, attr=vga.YELLOW_ON_BLACK)
power.reboot(prog)
prog.label("shell_shutdown")
vga.print_string(prog, "Shutting down...", row=24, col=0, attr=vga.YELLOW_ON_BLACK)
power.shutdown(prog)
prog.label("shell_halt")
vga.print_string(prog, "Goodbye! System halted.", row=24, col=0, attr=vga.YELLOW_ON_BLACK)
prog.hlt()
out_dir = Path(__file__).resolve().parent.parent / "build"
out_dir.mkdir(parents=True, exist_ok=True)
out = os_.build(str(out_dir / "aether_shell.img"))
print(f"Built: {out} ({out.stat().st_size} bytes)")
print(f"Kernel sectors: {(len(os_.kernel.assemble()) + 511) // 512}")
print("Boot it with: qemu-system-i386 -drive file=aether_shell.img,format=raw")
graphics_os.py — boot animation, background, and composited logo
"""
graphics_os -- an example OS with a real boot animation, a background
image, and a home-screen logo, using VGA Mode 13h (320x200, 256-color).
Boot flow:
1. Loading screen: plays a short progress-bar animation (a real "video"
-- a sequence of frames shown in order with a delay between them).
2. Home screen: a full-screen background image with a logo composited
on top (two separate images, blitted to different screen regions).
3. Halts, leaving the home screen displayed.
Uses placeholder art generated by examples/assets/generate_assets.py --
run that first (or just run this script, which does it for you if the
assets are missing). Swap in your own PNGs any time; aetherix.imaging
handles resizing and color quantization for whatever you give it.
Run:
python examples/graphics_os.py
Then boot the produced image in an emulator, e.g.:
qemu-system-i386 -drive file=graphics_os.img,format=raw
Or verify it under CPU emulation without needing QEMU installed:
pip install unicorn
python tests/emulate.py build/graphics_os.img
(the emulator prints the VGA text buffer, which will be blank here since
this example only uses graphics mode -- see tests/test_emulation.py for
how the framebuffer/palette are checked instead.)
"""
import sys
import subprocess
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from aetherix import Project, imaging
from aetherix.drivers import graphics
ASSETS_DIR = Path(__file__).resolve().parent / "assets"
if not (ASSETS_DIR / "background.png").exists():
print("Generating placeholder assets...")
subprocess.run([sys.executable, str(ASSETS_DIR / "generate_assets.py")], check=True)
# -- Prepare assets at build time (resizing/quantizing happens here, not
# at boot -- the kernel only ever handles already-prepared bytes) --
loading_frames = [
imaging.load_image(ASSETS_DIR / f"loading_{i}.png") for i in range(6)
]
# background and logo will be on screen AT THE SAME TIME, so they must
# share one 256-color palette -- VGA Mode 13h has a single palette for the
# whole screen, not one per image (see imaging.load_images_shared_palette).
background, logo = imaging.load_images_shared_palette([
(ASSETS_DIR / "background.png", graphics.GFX_WIDTH, graphics.GFX_HEIGHT, True),
(ASSETS_DIR / "logo.png", 64, 64, False),
])
LOGO_X = (graphics.GFX_WIDTH - logo.width) // 2
LOGO_Y = (graphics.GFX_HEIGHT - logo.height) // 2
with Project("GraphicsOS", graphics_mode=graphics.MODE_13H) as os_:
@os_.kernel_entry
def main(prog, drivers):
# 1. Loading animation -- plays once, then falls through.
drivers.graphics.play_frames(prog, loading_frames, delay_iterations=3_000_000, loop=False)
# 2. Home screen -- background, then a logo composited on top.
drivers.graphics.show_image(prog, background, x=0, y=0)
drivers.graphics.show_image(prog, logo, x=LOGO_X, y=LOGO_Y)
prog.hlt()
out_dir = Path(__file__).resolve().parent.parent / "build"
out_dir.mkdir(parents=True, exist_ok=True)
out = os_.build(str(out_dir / "graphics_os.img"))
print(f"Built: {out} ({out.stat().st_size} bytes)")
print(f"Kernel sectors: {(len(os_.kernel.assemble()) + 511) // 512}")
print("Boot it with: qemu-system-i386 -drive file=graphics_os.img,format=raw")
assets/generate_assets.py — placeholder art used by graphics_os.py
"""
Generates placeholder art for examples/graphics_os.py using Pillow, so the
example is runnable with no external image files. Swap these PNGs for your
own artwork any time -- graphics_os.py just calls aetherix.imaging.load_image
on whatever's in this directory.
Run: python examples/assets/generate_assets.py
"""
from pathlib import Path
from PIL import Image, ImageDraw
OUT_DIR = Path(__file__).resolve().parent
def make_loading_frames(n=6, size=(320, 200)):
"""A simple progress-bar boot animation, frame by frame."""
frames = []
for i in range(n):
img = Image.new("RGB", size, (10, 12, 30))
draw = ImageDraw.Draw(img)
draw.text((110, 70), "Aetherix", fill=(120, 200, 255))
bar_w, bar_h = 200, 14
bx, by = (size[0] - bar_w) // 2, 110
draw.rectangle([bx, by, bx + bar_w, by + bar_h], outline=(80, 90, 120))
fill_w = int(bar_w * (i + 1) / n)
draw.rectangle([bx, by, bx + fill_w, by + bar_h], fill=(90, 200, 140))
draw.text((bx, by + 20), f"Loading... {int(100 * (i + 1) / n)}%", fill=(180, 190, 210))
frames.append(img)
img.save(OUT_DIR / f"loading_{i}.png")
return frames
def make_background(size=(320, 200)):
"""A simple vertical gradient desktop background."""
img = Image.new("RGB", size)
px = img.load()
top = (20, 30, 60)
bottom = (70, 40, 90)
for y in range(size[1]):
t = y / (size[1] - 1)
r = int(top[0] + (bottom[0] - top[0]) * t)
g = int(top[1] + (bottom[1] - top[1]) * t)
b = int(top[2] + (bottom[2] - top[2]) * t)
for x in range(size[0]):
px[x, y] = (r, g, b)
img.save(OUT_DIR / "background.png")
return img
def make_logo(size=(64, 64)):
"""A simple circular logo icon to overlay on the home screen."""
img = Image.new("RGB", size, (0, 0, 0))
draw = ImageDraw.Draw(img)
draw.ellipse([2, 2, size[0] - 2, size[1] - 2], fill=(90, 170, 255), outline=(230, 240, 255))
draw.text((size[0] // 2 - 4, size[1] // 2 - 6), "A", fill=(15, 20, 40))
img.save(OUT_DIR / "logo.png")
return img
if __name__ == "__main__":
OUT_DIR.mkdir(parents=True, exist_ok=True)
make_loading_frames()
make_background()
make_logo()
print(f"Generated placeholder assets in {OUT_DIR}")
uefi_hello.py — a minimal, genuinely working UEFI application
"""
uefi_hello -- a minimal, genuinely working UEFI application.
This is a completely different boot path from the other examples in this
directory (hello_os.py, aether_shell.py, graphics_os.py), which all boot
via BIOS/MBR. UEFI firmware boots a PE32+ executable directly in 64-bit
long mode -- no 16/32-bit stages, no BIOS interrupts, no boot sector.
Demonstrates: console output, cursor control, and keyboard input (all via
real UEFI protocol calls -- ConOut/ConIn), plus locating an arbitrary
protocol by GUID and calling a method on it that this package doesn't
wrap with a dedicated helper (aetherix.uefi.boot_services/protocol --
the general extension mechanism for any UEFI protocol you need that
isn't built in yet).
This builds:
- uefi_hello.efi -- the PE32+ application itself
- uefi_hello.img -- a complete GPT+FAT32 disk image containing it at
\\EFI\\BOOT\\BOOTX64.EFI, ready to boot in QEMU+OVMF
or write to a real USB stick
Run:
python examples/uefi_hello.py
Then boot it with QEMU + OVMF (get OVMF.fd from your Linux distro's
`ovmf`/`edk2-ovmf` package, or https://github.com/tianocore/edk2):
qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=build/uefi_hello.img,format=raw
Or verify it without QEMU/OVMF installed, via real x86-64 CPU emulation:
pip install unicorn
python tests/emulate_uefi.py build/uefi_hello.efi
Or write it to a real USB stick (BACK UP THE STICK FIRST -- this
overwrites the entire device):
sudo dd if=build/uefi_hello.img of=/dev/sdX bs=4M status=progress conv=fsync
Then boot from it on any UEFI PC with Secure Boot disabled (this app
isn't signed).
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from aetherix.uefi.app import UefiApp
from aetherix.uefi import console, keyboard, diskimage
from aetherix import regs
app = UefiApp()
@app.entry
def main(prog, sys_table):
console.clear_screen(prog, sys_table)
console.set_cursor_position(prog, sys_table, 0, 0)
console.println(prog, "Hello from Aetherix UEFI!", sys_table)
console.println(prog, "", sys_table)
console.println(prog, "This machine code was generated entirely by aetherix.uefi --", sys_table)
console.println(prog, "no nasm, no ld, no C compiler toolchain for the .efi itself.", sys_table)
console.println(prog, "", sys_table)
console.println(prog, "Press any key to continue...", sys_table)
keyboard.read_key(prog, sys_table, regs.R12, regs.R13)
console.set_cursor_position(prog, sys_table, 0, 8)
console.println(prog, "Key received. Returning control to firmware.", sys_table)
out_dir = Path(__file__).resolve().parent.parent / "build"
out_dir.mkdir(parents=True, exist_ok=True)
efi_path = app.build(str(out_dir / "uefi_hello.efi"))
print(f"Built: {efi_path} ({efi_path.stat().st_size} bytes)")
img_path = diskimage.write_disk_image(efi_path.read_bytes(), str(out_dir / "uefi_hello.img"))
print(f"Built: {img_path} ({img_path.stat().st_size} bytes)")
print("Boot it with: qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=uefi_hello.img,format=raw")
What you actually get (read before you assume too much)
Building a full production OS with GPU, USB, camera, printer, and network driver stacks is a multi-year effort even for large teams. Aetherix does not claim to hand you that. What it genuinely gives you:
- A verified-correct real-mode bootloader: segment setup, BIOS teletype output, A20 line enable, BIOS LBA disk loading with retry, and a far jump into your kernel.
- A verified-correct 16→32-bit transition: flat GDT construction,
lgdt,CR0.PEset, far jump into a 32-bit code segment — computed with exact, non-guessed offsets. - Working drivers for hardware that's actually reachable without an existing OS or bus/protocol stack underneath you:
- VGA text-mode framebuffer (clear screen, print strings).
- PS/2 keyboard — full Scancode Set 1 (letters, digits, punctuation, Tab), Shift-aware (uppercase, shifted symbols), plus a high-level
read_charhelper. Extended keys (arrows, Insert/Delete/Home/End/Page Up/Down) aren't covered yet. - PC speaker — PIT-driven beep with an actual audible duration.
- Power control —
restart(soft, jump back to kernel start),reboot(hard, keyboard-controller reset pulse — works on real hardware and every mainstream emulator), and an honestly-scopedshutdown/sleep_until_keypress(QEMU/Bochs magic port, not real ACPI). - Terminal — a runtime VGA cursor (not just compile-time-fixed text): moves, wraps at end-of-row, supports newline/backspace.
- Graphics — VGA Mode 13h (320×200, 256-color): palette upload and image blitting, so a kernel can show a loading screen, a background, and composited images, not just text.
- A minimal embedded read-only file archive format (AVFS) for bundling files into the disk image at build time.
- A Python API designed so both styles work: high-level (
drivers.vga.print_string(...)) and low-level (prog.mov16(AX, 0x1234), straight machine-code control).
What's not implemented (and why)
GPU (beyond VGA Mode 13h — no modern framebuffer/GOP/VBE/hardware acceleration/3D), USB, Ethernet/networking, printer, fan control, battery telemetry, and camera all need real bus enumeration or protocol stacks (PCI, a USB host controller driver, ACPI, etc.) that don't exist yet in this codebase. Rather than fake them, aetherix.drivers.hal.HAL lists them as named, documented extension points:
from aetherix import HAL
hal = HAL()
print(hal.implemented()) # ['vga', 'keyboard', 'speaker']
print(hal.scaffolded()) # ['gpu', 'usb', 'ethernet', 'printer', 'fan', 'battery', 'camera']
Accessing a scaffolded driver raises NotImplementedError with an explanation of what it needs, rather than silently doing nothing. There is also no file manager, task manager, calculator, or custom in-OS programming language yet, no Caps Lock, and no real ACPI shutdown/sleep. Building any of these is exactly the kind of contribution this project is scoped for — see CONTRIBUTING.md for a concrete, tractability-ordered list of realistic next drivers and opcodes.
UEFI support
Everything above (Project, BootSector, Kernel, aetherix.drivers) is the BIOS boot path. UEFI is a genuinely different architecture, not an extension of it: firmware loads a PE32+ executable directly into 64-bit long mode — no 16/32-bit stages, no BIOS interrupts, no MBR boot sector. It boots from a GPT-partitioned disk with a FAT32 EFI System Partition, not a flat binary. I/O goes through UEFI protocol function-pointer calls (Microsoft x64 calling convention), not int instructions. The two APIs cannot be mixed — pick one per OS you're building.
aetherix.uefi is a separate subsystem built for this from scratch:
- A minimal 64-bit long-mode instruction set (
Program(bits=64)) — REX-prefixed mov/add/sub/cmp, RIP-relativelea, indirect calls through a register, full RAX–R15 register range. - A PE32+ writer (
aetherix.uefi.pe) producing a genuinely validEFI_APPLICATIONimage (position-independent by construction, so no.relocsection is needed). UefiApp(aetherix.uefi.app) — generates the Microsoft x64 ABI prologue/epilogue around your entry function.- Console I/O (
aetherix.uefi.console,aetherix.uefi.keyboard) —ConOut/ConIncalls, UCS-2 encoded as the spec requires. - A generic protocol-call extension mechanism (
aetherix.uefi.protocol,aetherix.uefi.boot_services) —locate_protocol+protocol.calllet you drive any UEFI protocol (Graphics Output, Block I/O, File System, or anything else) by GUID and byte offset, with no changes needed to this package. - GPT + FAT32 (
aetherix.uefi.gpt,aetherix.uefi.fat32,aetherix.uefi.diskimage) — a real, from-scratch disk image builder: protective MBR, primary + backup GPT with correct CRC32 checksums, and a FAT32 ESP containing\EFI\BOOT\BOOTX64.EFI.
Scope: this targets UEFI applications — code that uses Boot Services and returns control to firmware, like a shell command. It is not a full custom OS loader (that means calling ExitBootServices, taking over the memory map, and setting up your own GDT/IDT/paging — see CONTRIBUTING.md if you want to build that on top of this).
from aetherix.uefi.app import UefiApp
from aetherix.uefi import console, diskimage
app = UefiApp()
@app.entry
def main(prog, sys_table):
console.clear_screen(prog, sys_table)
console.println(prog, "Hello from Aetherix UEFI!", sys_table)
efi_path = app.build("build/uefi_hello.efi")
diskimage.write_disk_image(efi_path.read_bytes(), "build/uefi_hello.img")
qemu-system-x86_64 -bios /path/to/OVMF.fd -drive file=build/uefi_hello.img,format=raw
Verified at every layer, independently, not just by this codebase's own logic checking itself:
| Layer | How it's verified |
|---|---|
| PE file | file recognizes it as PE32+ executable (EFI application) x86-64 |
| Machine code | objdump -M intel -m i386:x86-64 |
| Real execution | A Unicorn (UC_MODE_64) harness simulating EFI_SYSTEM_TABLE/ConOut/ConIn/BootServices (tests/emulate_uefi.py, tests/test_uefi.py) |
| GPT | gdisk -l disk.img — "No problems found" |
| FAT32 | fsck.vfat and mtools (mdir/mcopy), including byte-for-byte file extraction |
| End-to-end | Real, unmodified OVMF firmware in real QEMU boots the image and prints the exact expected text |
Full API details (console output, keyboard input, the protocol extension mechanism, disk image building) are in API.md, section 18.
Architecture
aetherix/
asm.py # two-pass label/jump resolver over the native encoder (16/32/64-bit)
regs.py # register / condition-code constants
context.py # Project: the batteries-included entry point (BIOS path)
imaging.py # build-time image loading/quantization (needs Pillow)
boot/realmode.py # BootSector builder (512-byte MBR-style boot sector)
kernel/
pmode.py # flat GDT construction
builder.py # Kernel: 16-bit trampoline + GDT + 32-bit body
drivers/
vga.py, keyboard.py, speaker.py, terminal.py, graphics.py, power.py # implemented
hal.py # driver registry + extension points
fs/simplefs.py # AVFS embedded file archive
image/diskimage.py # boot sector + kernel + AVFS -> flat .img/.bin
uefi/ # separate boot path -- see "UEFI support" above
app.py # UefiApp: MS x64 ABI prologue/epilogue + PE assembly
console.py # ConOut protocol calls (output, cursor control)
keyboard.py # ConIn protocol calls (key input)
protocol.py # generic protocol-method-call extension mechanism
boot_services.py # LocateProtocol (find a protocol interface by GUID)
guid.py # EFI_GUID mixed-endian encoding (shared by gpt.py too)
pe.py # PE32+ writer
gpt.py, fat32.py, diskimage.py # GPT + FAT32 ESP disk image builder
native/
encoder.c # the actual x86 instruction encoder (C, no deps)
Every layer is independently usable — build a raw Program and hand-assemble arbitrary real/protected-mode code without ever touching Project, or add a new driver module and register it with HAL.
CLI
aetherix new MyOS # scaffold a starter project script
aetherix info # show implemented vs. scaffolded hardware support
Verifying the output yourself
Don't take byte correctness on faith — disassemble it:
head -c 512 myos.img > boot.bin
objdump -D -b binary -m i8086 boot.bin # boot sector, 16-bit
dd if=myos.img of=kernel.bin bs=512 skip=1
objdump -D -b binary -m i386 kernel.bin # (after the trampoline offset)
Or go further and actually execute it under CPU emulation — this is how real bugs (like the multi-chunk LBA disk load issue, see CHANGELOG.md) were caught, since disassembly alone proves the bytes are plausible x86, not that the boot sequence completes:
pip install unicorn
python tests/emulate.py myos.img
This prints the real-mode boot message, the disk reads that occurred (LBA, sector count, destination), and the final VGA text buffer contents — i.e. what a real screen would show, without needing QEMU/VirtualBox. tests/test_emulation.py runs this automatically as a regression test whenever unicorn is installed. The UEFI path has its own equivalent (tests/emulate_uefi.py) — see UEFI support.
Documentation
- API.md — the complete usage guide: every layer from
Projectdown to rawPrograminstructions, theHAL, embedding files (AVFS), disk images, writing a custom bootloader/kernel, the full UEFI subsystem, a common-errors table, and a full worked example. - CONTRIBUTING.md — how to add a native encoder opcode or a new driver, code style (including the RSP/R12 and RBP/R13 ModRM gotchas), and a concrete, tractability-ordered roadmap of realistic next contributions.
- CHANGELOG.md — project history, including real bugs found and fixed during development and how each was verified.
Contributing
The most valuable contributions right now are new drivers and new instruction-encoder opcodes, following the existing pattern in aetherix/drivers/vga.py/keyboard.py/speaker.py and native/encoder.c. See CONTRIBUTING.md for the full guide, code style, and a roadmap ordered roughly by tractability — from small (serial port, extended keyboard scancodes) to substantial (a real writable filesystem, ACPI parsing, a full custom UEFI OS loader).
License
AGPL-3.0-or-later.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aetherix-0.1.0.tar.gz.
File metadata
- Download URL: aetherix-0.1.0.tar.gz
- Upload date:
- Size: 215.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0624632cb572bc1ad35bcc3e87bc2b92bb08e0f5b0aaaaef1b1d64487377de53
|
|
| MD5 |
3c4826ca93c665200ac2ac017a3a77f1
|
|
| BLAKE2b-256 |
46d1e51a4f016663241bbf70369df72cc456bba6be1697b4de56f786942a6e9d
|
File details
Details for the file aetherix-0.1.0-py3-none-any.whl.
File metadata
- Download URL: aetherix-0.1.0-py3-none-any.whl
- Upload date:
- Size: 198.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c7b0e45ed31aab53ab544838169c225d5d475f48a79b577b6f518d6bf99b704
|
|
| MD5 |
34e57bfd879f8b9a60c9e1ddf8871f4a
|
|
| BLAKE2b-256 |
98aa808e13ea88ce11008c5514fa4c605b4145ec07100781775285d17590efb7
|