Mellivora OS is a bare-metal, from-scratch 32-bit protected-mode operating system written entirely in NASM assembly language. It targets BIOS-bootable x86 hardware and runs under QEMU or on compatible real hardware.
This guide covers everything you need to build, run, and test the OS.
| Tool | Version | Purpose |
|---|---|---|
| NASM | 2.15+ | Netwide Assembler — assembles all .asm sources |
| GNU Make | 4.0+ | Build orchestration |
| QEMU | 6.0+ | qemu-system-x86_64 — i486-compatible emulator for testing |
| Python 3 | 3.6+ | Runs populate.py to populate the filesystem |
| dd | any | Disk image construction (standard on Linux/macOS) |
sudo apt update
sudo apt install nasm qemu-system-x86 make python3sudo dnf install nasm qemu-system-x86 make python3sudo pacman -S nasm qemu-full make pythonbrew install nasm qemu make python3Note: On macOS,
ddandhdiutilare pre-installed. You may need to usegmakeinstead ofmakeif the systemmakeis too old.
To build mellivora.iso on Linux, install one of the following:
# Debian/Ubuntu
sudo apt install xorriso
# Fedora
sudo dnf install xorriso
# Arch
sudo pacman -S xorrisoUse WSL2 (Windows Subsystem for Linux) with an Ubuntu distribution, then follow the Debian/Ubuntu instructions above. Native Windows builds are not supported.
git clone https://github.com/James-HoneyBadger/Mellivora_OS.git
cd Mellivora_OS
make fullThis single command:
- Assembles the boot sector (
boot.asm→boot.bin, 512 bytes) - Assembles the Stage 2 loader (
stage2.asm→stage2.bin, ≤16 KB) - Assembles the kernel (
kernel.asm→kernel.bin) - Creates a 2 GB raw disk image (
mellivora.img) - Writes boot sector, Stage 2, and kernel to the image
- Assembles all user-space assembly programs in
programs/into flat binaries - Runs
populate.pyto create subdirectories and write the current file set into HBFS (231 programs + 31 samples + docs)
| Target | Command | Description |
|---|---|---|
| OS image | make or make all |
Build boot + Stage 2 + kernel, create disk image |
| Programs | make programs |
Assemble all programs in programs/ |
| Populate | make populate |
Write files and programs into the disk image |
| Full build | make full |
All of the above in order |
| Bootable ISO | make iso |
Create mellivora.iso with install docs and user guide included |
| Clean | make clean |
Remove all generated files (.bin, .lst, .img, .iso) |
| Sizes | make sizes |
Show component sizes |
| Run | make run |
Launch in QEMU |
| Debug | make debug |
Launch in QEMU with monitor + debug logging |
After a successful build:
mellivora.img 2 GB bootable raw disk image
mellivora.iso Bootable ISO media with docs and install guide
boot.bin 512-byte MBR boot sector
stage2.bin Stage 2 loader (≤16 KB)
kernel.bin 32-bit protected-mode kernel
programs/*.bin Compiled user programs (current assembly program set)
*.lst Assembly listing files (useful for debugging)
make isoThis creates mellivora.iso, which:
- boots directly in BIOS/legacy-compatible VMs,
- includes
mellivora.imgas the El Torito hard-disk boot image, - bundles the full
INSTALL.mdandUSER_GUIDE.mddocumentation inside the ISO.
The ISO staging tree includes:
boot/mellivora.img
README.txt
docs/INSTALL.md
docs/USER_GUIDE.md
NASM will produce warnings like:
kernel.asm:NNNN: warning: uninitialized space declared in .text section: zeroing
These are normal and harmless. They occur because Mellivora uses flat binary format
(-f bin) and declares BSS variables with resb/resd — NASM notes that it's zeroing
that space in the output binary.
make runmake iso
qemu-system-i386 -m 128 -cdrom mellivora.iso -boot d -no-reboot -no-shutdownThis is the recommended way to test the distributable install media exactly as users will receive it.
This launches QEMU with:
| Setting | Value |
|---|---|
| CPU | i486-compatible x86 emulation |
| RAM | 128 MB |
| Disk | mellivora.img as raw IDE drive |
| Boot | Hard disk (drive C) |
| Behavior | No auto-reboot, no auto-shutdown |
make debugAdds QEMU Monitor on stdio and interrupt/reset logging. Useful monitor commands:
| Command | Description |
|---|---|
info registers |
Show all CPU registers |
info mem |
Show memory mappings |
xp /16xw 0x100000 |
Examine 16 dwords at kernel base |
quit |
Exit QEMU |
qemu-system-i386 -m 128 \
-drive file=mellivora.img,format=raw,if=ide,cache=writethrough \
-boot c -no-reboot -no-shutdownUseful additional options:
| Option | Description |
|---|---|
-m 256 |
Increase RAM to 256 MB |
-serial stdio |
Route serial output (COM1) to your terminal |
-audiodev id=snd,driver=sdl -machine pcspk-audiodev=snd |
Enable PC speaker audio |
-S -s |
Start paused + enable GDB server on port 1234 |
The 2 GB raw disk image has this layout:
LBA Range Size Content
─────────────────────────────────────────────────────────
LBA 0 512 B Stage 1 boot sector (MBR)
LBA 1–32 16 KB Stage 2 loader
LBA 33+ variable 32-bit kernel (sector count generated from `kernel.bin` size)
LBA 4096 512 B HBFS superblock
LBA 4097–4224 64 KB Block allocation bitmap
LBA 4225–4480 128 KB Root directory (32 blocks, 455 entries)
LBA 4481+ ~2 GB Data blocks (4 KB each)
The populate.py script creates 5 subdirectories and places the curated runtime file set (231 programs + 31 samples + docs):
/
├── bin/ Utility programs (hello, edit, grep, sort, tcc, ...)
├── games/ Games (snake, tetris, galaga, pacman, mine, ...)
├── Burrows/ Burrows desktop applications
├── samples/ C, Perl, and BASIC source files (hello.c, fib.c, hello.pl, hello.bas, ...)
├── docs/ 5 text files (readme, license, notes, todo, poem)
└── script.bat Example batch script
⚠ WARNING: Writing to a real disk will destroy all data on that disk. Only do this on a dedicated test machine or USB drive.
- i486-or-newer x86 CPU with BIOS legacy boot support
- IDE or SATA disk / USB drive with BIOS legacy boot
- At least 1 MB RAM (128 MB recommended)
- PS/2 keyboard (USB works if BIOS provides PS/2 emulation)
- VGA-compatible display
# Identify your target device
lsblk
# Write the image (TRIPLE-CHECK the device name!)
sudo dd if=mellivora.img of=/dev/sdX bs=1M status=progress
sync- Write
mellivora.imgto a USB drive withddor writemellivora.isowith a USB imaging tool such as balenaEtcher - Enter BIOS setup (usually F2, DEL, or F12)
- Enable "Legacy Boot" or "CSM" mode
- Set USB drive or optical media as the first boot device
- Save and reboot
Note: UEFI-only systems (no CSM) will not boot Mellivora — it uses a traditional MBR boot sector and BIOS-style boot flow.
- Create a new x86 VM in legacy BIOS mode
- Attach
mellivora.isoas the VM's optical drive - Give the VM at least 128 MB RAM
- Boot from the ISO
- For a persistent install, attach a virtual disk and write
boot/mellivora.imgfrom the host onto that disk
Mellivora_OS/
├── boot.asm Stage 1 MBR boot sector (16-bit real mode)
├── stage2.asm Stage 2 loader (A20, E820, protected mode switch)
├── kernel.asm Kernel entry and include graph (main file + 26 include modules)
├── Makefile Build system
├── populate.py HBFS image populator with subdirectory support
├── CHANGELOG.md Version history (current: v13.0.0)
├── README.md Project overview
│
├── programs/ User-space assembly programs (~231 total)
│ ├── syscalls.inc Shared constants and helpers
│ ├── hello.asm ... through ...
│ └── wc.asm
│
├── samples/ C, Perl, and BASIC source files
│ ├── hello.c ... through ...
│ ├── hello.pl ...
│ └── hello.bas
│
└── docs/ Full documentation suite
├── INSTALL.md This file
├── USER_GUIDE.md Shell command reference
├── PROGRAMMING_GUIDE.md Writing programs for Mellivora
├── TECHNICAL_REFERENCE.md Architecture and internals
└── TUTORIAL.md Beginner walkthrough
- Ensure
mellivora.imgexists and is not empty:ls -la mellivora.img - Rebuild:
make clean && make full
The kernel is built with -O0 (optimization disabled) to prevent NASM's multi-pass
optimizer from oscillating on near/far jump encodings. If you see "label changed during
code generation", ensure -O0 is present in the kernel build rule in the Makefile.
Run make full (not just make) — this includes the populate step that writes programs
to the filesystem.
QEMU requires explicit audio configuration:
qemu-system-i386 -m 128 \
-drive file=mellivora.img,format=raw,if=ide -boot c \
-audiodev id=snd,driver=sdl -machine pcspk-audiodev=sndKernel sector count is generated automatically from kernel.bin into kernel_sectors.inc.
Check current size with ls -la kernel.bin; Stage 2 reads the generated sector count at boot.
qemu-system-i386 -m 128 \
-drive file=mellivora.img,format=raw,if=ide -boot c \
-serial stdio