Skip to content

Instantly share code, notes, and snippets.

@mudassaralichouhan
Last active November 28, 2025 10:28
Show Gist options
  • Select an option

  • Save mudassaralichouhan/ccb306618fcc2ccef335b33fbc640404 to your computer and use it in GitHub Desktop.

Select an option

Save mudassaralichouhan/ccb306618fcc2ccef335b33fbc640404 to your computer and use it in GitHub Desktop.

Absolutely! Let’s create a complete step-by-step workflow to build and run a minimal C kernel with a Multiboot header inside a Docker container on your M1 Mac, and then boot it in QEMU. I’ll assume your container is x86_64 (--platform linux/amd64) Ubuntu.


Step 0: Start a Docker container

docker run --platform linux/amd64 -it --name os-builder -v ~/osdev:/root/osdev ubuntu:26.04 bash
  • Mount ~/osdev on host → /root/osdev in container
  • You will build everything inside /root/osdev

Step 1: Install required packages

Inside container:

apt update
apt install -y build-essential gcc-multilib gdb-multiarch nasm xorriso grub-pc-bin qemu-system-x86
  • gcc-multilib → 32-bit compilation
  • nasm → assembly bootloader
  • grub-pc-bin + xorriso → create bootable ISO
  • qemu-system-x86 → test kernel

Step 2: Prepare directory

cd /root/osdev
mkdir -p kernel_test
cd kernel_test

Step 3: Create files

multiboot_header.S

; Multiboot header for GRUB
section .multiboot
align 4
dd 0x1BADB002        ; magic
dd 0x00010003        ; flags
dd -(0x1BADB002 + 0x00010003)  ; checksum

boot.S

BITS 32
GLOBAL _start
EXTERN kmain

_start:
    call kmain
hang:
    jmp hang

kernel.c

void kmain(void) {
    char *video = (char*)0xB8000;
    const char *msg = "Hello OS World!";
    for(int i=0; msg[i]; i++){
        video[i*2] = msg[i];
        video[i*2+1] = 0x07;
    }
    while(1);
}

Step 4: Compile and link

# Assemble header and bootloader
nasm -f elf32 multiboot_header.S -o multiboot_header.o
nasm -f elf32 boot.S -o boot.o

# Compile kernel
gcc -m32 -ffreestanding -c kernel.c -o kernel.o

# Link everything into ELF binary (Multiboot)
ld -m elf_i386 -Ttext 0x1000 -nostdlib -o kernel.bin multiboot_header.o boot.o kernel.o

Step 5: Prepare ISO folder

mkdir -p iso/boot/grub
cp kernel.bin iso/boot/kernel.bin

cat > iso/boot/grub/grub.cfg <<EOF
menuentry "MyOS" {
    multiboot /boot/kernel.bin
}
EOF

Step 6: Generate bootable ISO

grub-mkrescue -o myos.iso iso
  • Output: myos.iso in /root/osdev/kernel_test/

Step 7: Boot in QEMU

Option A: inside container (headless):

qemu-system-i386 -cdrom myos.iso -m 512M -nographic

Option B: on host Mac (recommended for GUI):

docker cp os-builder:/root/osdev/kernel_test/myos.iso ~/osdev/myos.iso
qemu-system-i386 -cdrom ~/osdev/myos.iso -m 512M
  • You should see “Hello OS World!” printed on the screen

Workflow Summary

  1. Start x86_64 Docker container
  2. Install build tools + GRUB + QEMU
  3. Create multiboot_header.S, boot.S, kernel.c
  4. Assemble/compile/link → kernel.bin
  5. Prepare ISO with GRUB
  6. grub-mkrescuemyos.iso
  7. Boot with QEMU

If you want, I can provide a single bash script that does all these steps automatically inside the container, so you only need to run it once and the ISO boots immediately.

Do you want me to do that?

Method 1:

boot.s

; boot.S - 32-bit assembly bootloader with Multiboot header
BITS 32
GLOBAL _start
EXTERN kmain

; -------------------------
; Multiboot header (32-bit)
; -------------------------
align 4
multiboot_header:
    dd 0x1BADB002       ; magic number
    dd 0x0              ; flags (0 = minimal)
    dd -(0x1BADB002 + 0x0) ; checksum

; -------------------------
; Entry point
; -------------------------
_start:
    call kmain
hang:
    jmp hang

kernal.c

#define COM1 0x3F8

static inline unsigned char inb(unsigned short port) {
    unsigned char ret;
    __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
    return ret;
}

static inline void outb(unsigned short port, unsigned char val) {
    __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}

void write_serial(char c) {
    while (!(inb(COM1 + 5) & 0x20)); // wait until transmit ready
    outb(COM1, c);
}

void kmain(void) {
    const char *msg = "Hello OS!\n";
    for (int i = 0; msg[i]; i++) write_serial(msg[i]);
    while(1); // halt
}
nasm -f elf32 boot.S -o boot.o
gcc -m32 -ffreestanding -c kernel.c -o kernel.o
ld -m elf_i386 -Ttext 0x1000 -nostdlib -o kernel.bin boot.o kernel.o
mkdir -p iso/boot/grub
cp kernel.bin iso/boot/kernel.bin

cat > iso/boot/grub/grub.cfg <<EOF
menuentry "MyOS" {
    multiboot /boot/kernel.bin
}
EOF

grub-mkrescue -o myos.iso iso
qemu-system-i386 -cdrom myos.iso -m 512M -nographic

Mehtod 2:

nasm -f bin boot.S -o boot.bin
gcc -m32 -ffreestanding -c kernel.c -o kernel.o
ld -m elf_i386 -Ttext 0x1000 -nostdlib -e kmain -o kernel.elf kernel.o
objcopy -O binary kernel.elf kernel.raw
cat kernel.raw >> boot.bin
qemu-system-i386 -fda boot.bin -m 512M -nographic
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment