[← Home](Home.md) # 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. ## Install the toolchain 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](troubleshooting.md)). Debian / Ubuntu: ```sh sudo apt update sudo apt install nasm gcc gcc-multilib binutils make qemu-system-x86 ``` Fedora: ```sh sudo dnf install nasm gcc glibc-devel.i686 libgcc.i686 binutils make qemu-system-x86 ``` Arch: ```sh sudo pacman -S nasm gcc lib32-glibc binutils make qemu-system-x86 ``` What 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** — `cat` concatenates boot sector + kernel; `truncate` pads 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_64` even 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. ### Tested versions 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: ```sh nasm --version && gcc --version | head -1 && ld --version | head -1 qemu-system-x86_64 --version | head -1 && make --version | head -1 ``` ## Build and run a single stage Pick a stage directory and use the shared verbs: ```sh 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 targets ``` The 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 | ### Extra targets on the C stages Stages 2–5 each add two more verbs: ```sh make run-simple # boot the pure-assembly "simple" image make debug # boot under QEMU with a GDB stub (-s -S) on :1234 ``` `make debug` is the entry point for [debugging-with-gdb.md](debugging-with-gdb.md). ### Stage 1 is different 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**: ```sh 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 images ``` ## Build every stage in one pass To rebuild the whole tree from clean — useful after pulling new changes: ```sh 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" done ``` `make -C ` runs make in that directory without changing your shell's working directory, so you can launch this from the repository root. ## What `make` actually does 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: 1. **Boot sector** — `nasm -f bin boot.asm -o boot.bin` (`helloworld-os-c/Makefile:26`). A flat 512-byte binary ending in the `0xAA55` signature. 2. **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. 3. **C kernel** — `gcc -m32 -ffreestanding -nostdlib ... -c kernel.c -o kernel.o` (`Makefile:9-11, 34`). Freestanding, no C runtime. 4. **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. 5. **Assemble image** — `cat boot.bin kernel.bin > helloworld-c.img` then `truncate -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](#why-image-size-matters). > ⚠️ **Caveat:** The kernel is linked with `OUTPUT_FORMAT(binary)` (see > `helloworld-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](debugging-with-gdb.md) for how to get C symbols back. ## Run the image by hand `make run` is just a thin wrapper. The underlying command is always: ```sh qemu-system-x86_64 -drive format=raw,file=helloworld-c.img ``` Substitute 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: ```sh qemu-system-x86_64 -drive format=raw,file=helloworld-c.img -monitor stdio ``` To quit QEMU, close the window, or press **Ctrl-A** then **X** in a text console. ## Why image size matters 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](writing-your-own-stage.md). ## See also - [debugging-with-gdb.md](debugging-with-gdb.md) — step through the boot sector and kernel - [troubleshooting.md](troubleshooting.md) — when the build or boot fails - [writing-your-own-stage.md](writing-your-own-stage.md) — extend the OS yourself - [../reference/toolchain-and-build.md](toolchain-and-build.md) — every flag and target - [../concepts/boot-process.md](boot-process.md) — power-on to C entry - [../concepts/freestanding-c.md](freestanding-c.md) — why `-ffreestanding -nostdlib` - [../Home.md](Home.md) — wiki home