-
Notifications
You must be signed in to change notification settings - Fork 0
building and running
From a clean checkout to a booting OS in two commands — for any of the five stages.
Every stage of MyOS-Simple is a self-contained directory with its own Makefile.
There is no top-level build; you cd into a stage and build it there. All five
stages share the same verbs (make, make run, make clean, make help), so
once you can build one you can build them all.
The build is a freestanding 32-bit (i386) toolchain plus an emulator. On a
64-bit host you also need the 32-bit multilib for GCC — without it the kernel
compile fails (see troubleshooting.md).
Debian / Ubuntu:
sudo apt update
sudo apt install nasm gcc gcc-multilib binutils make qemu-system-x86Fedora:
sudo dnf install nasm gcc glibc-devel.i686 libgcc.i686 binutils make qemu-system-x86Arch:
sudo pacman -S nasm gcc lib32-glibc binutils make qemu-system-x86What each piece does:
-
nasm — assembles the boot sector (
-f bin, a flat binary) and the 32-bit kernel entry stub (-f elf32). -
gcc — compiles the C kernel as freestanding 32-bit code (
-m32). -
ld (binutils) — links the kernel to a fixed load address via
linker.ld. - make — drives each stage's build.
-
coreutils —
catconcatenates boot sector + kernel;truncatepads the image to a whole number of sectors. - qemu-system-x86_64 — boots the raw disk image.
💡 Tidbit: The emulator binary is
qemu-system-x86_64even though the OS runs in 32-bit protected mode. The 64-bit QEMU happily executes 32-bit guests; the name refers to the host target QEMU was built for, not the guest CPU.
The tree is verified known-good against these versions. Older or newer usually work too — these are just the exact ones used to validate the build:
| Component | Version |
|---|---|
| nasm | 2.16.01 |
| gcc | 13.3.0 (with -m32 multilib) |
| ld | 2.42 (GNU Binutils) |
| qemu | 8.2.2 (qemu-system-x86_64) |
| make | 4.3 |
Check yours:
nasm --version && gcc --version | head -1 && ld --version | head -1
qemu-system-x86_64 --version | head -1 && make --version | head -1Pick a stage directory and use the shared verbs:
cd helloworld-os-asm # or any other stage directory
make # build the bootable image(s)
make run # boot the primary image in QEMU
make clean # remove build artifacts
make help # list available targetsThe five stage directories are:
| Stage | Directory | What make run boots |
|---|---|---|
| 1 | helloworld-os-asm |
Monochrome boot-sector "Hello World" |
| 2 | helloworld-os-c |
First C kernel in protected mode |
| 3 | os-c-with-shell |
The interactive command shell |
| 4 | helloworld-os-c-v2 |
Shell + clock + processes + calculator |
| 5 | helloworld-os-c-v3 |
The stabilized release |
Stages 2–5 each add two more verbs:
make run-simple # boot the pure-assembly "simple" image
make debug # boot under QEMU with a GDB stub (-s -S) on :1234make debug is the entry point for debugging-with-gdb.md.
Stage 1 (helloworld-os-asm) is pure assembly with no C kernel, so it has no
run-simple or debug target. Instead it adds a color/keyboard variant:
cd helloworld-os-asm
make run # monochrome variant (helloworld.img)
make run-color # color + keyboard variant (helloworld_color.img)
make rebuild # clean and rebuild both imagesTo rebuild the whole tree from clean — useful after pulling new changes:
for d in helloworld-os-asm helloworld-os-c os-c-with-shell \
helloworld-os-c-v2 helloworld-os-c-v3; do
make -C "$d" clean && make -C "$d"
donemake -C <dir> runs make in that directory without changing your shell's
working directory, so you can launch this from the repository root.
For the C stages the default target builds two images: the C image and a
pure-assembly "simple" image. The C image is assembled from three pieces. Using
stage 2 (helloworld-os-c/Makefile) as the reference:
-
Boot sector —
nasm -f bin boot.asm -o boot.bin(helloworld-os-c/Makefile:26). A flat 512-byte binary ending in the0xAA55signature. -
Kernel entry stub —
nasm -f elf32 kernel_entry.asm -o kernel_entry.o(Makefile:30). The tiny assembly shim that calls the C entry point. -
C kernel —
gcc -m32 -ffreestanding -nostdlib ... -c kernel.c -o kernel.o(Makefile:9-11, 34). Freestanding, no C runtime. -
Link —
ld -m elf_i386 -T linker.ld -o kernel.bin kernel_entry.o kernel.o(Makefile:38). The linker script fixes the load address. -
Assemble image —
cat boot.bin kernel.bin > helloworld-c.imgthentruncate -s 10240 helloworld-c.img(Makefile:42-44). The boot sector sits first, the kernel follows, and the image is padded to a whole number of sectors.
Stages 3–5 follow the same recipe but link more object files. Stage 3 links
shell.o (os-c-with-shell/Makefile:38); stages 4 and 5 also link process.o
and rtc.o (helloworld-os-c-v2/Makefile:45-46).
💡 Tidbit: The C stages 4 and 5
truncate -s 20480(40 sectors) instead of 10240 (helloworld-os-c-v2/Makefile:52), because their kernels are large enough to need it. See Why image size matters.
⚠️ Caveat: The kernel is linked withOUTPUT_FORMAT(binary)(seehelloworld-os-c/linker.ld:11), so the output is a flat binary with no symbols. That is what makes the image directly bootable, but it also means GDB sees no function names by default — see debugging-with-gdb.md for how to get C symbols back.
make run is just a thin wrapper. The underlying command is always:
qemu-system-x86_64 -drive format=raw,file=helloworld-c.imgSubstitute whichever .img you want to boot. Running it yourself is handy when
you want to add QEMU flags, e.g. -monitor stdio to drop into the QEMU monitor:
qemu-system-x86_64 -drive format=raw,file=helloworld-c.img -monitor stdioTo quit QEMU, close the window, or press Ctrl-A then X in a text console.
The bootloader reads a fixed number of sectors from disk into memory — it
does not parse a filesystem or read until EOF. Each stage's boot.asm hard-codes
the count in mov dh, N:
| Stage | Sectors loaded | Source |
|---|---|---|
| 2 | 16 | helloworld-os-c/boot.asm:35 |
| 3 | 15 | os-c-with-shell/boot.asm:34 |
| 4 | 39 | helloworld-os-c-v2/boot.asm:35 |
| 5 | 39 | helloworld-os-c-v3/boot.asm:35 |
Two consequences:
- The image file must be at least boot sector + N sectors long, which is why
the Makefile pads it with
truncate. The BIOS reads whole 512-byte sectors, so the file must be a whole number of them. - If you grow the kernel past N sectors it will be silently truncated and the machine will crash. Growing the kernel safely is covered in writing-your-own-stage.md.
- debugging-with-gdb.md — step through the boot sector and kernel
- troubleshooting.md — when the build or boot fails
- writing-your-own-stage.md — extend the OS yourself
- ../reference/toolchain-and-build.md — every flag and target
- ../concepts/boot-process.md — power-on to C entry
-
../concepts/freestanding-c.md — why
-ffreestanding -nostdlib - ../Home.md — wiki home
Stages
- 1 · Assembly boot
- 2 · C protected mode
- 3 · Interactive shell
- 4 · Clock / processes / calc
- 5 · Stabilized release
Concepts — boot
Concepts — protected mode
Concepts — hardware
Concepts — OS services
Reference
- Memory map
- I/O ports
- GDT descriptor format
- Scancode tables
- Command reference
- Toolchain & build
- Glossary
Guides