This page looks best with JavaScript enabled

MercuryOS Wildcards gmake

 ·  🎃 kr0m

As our OS grows, it becomes increasingly unmanageable to have all the files in a single directory. We must define a structure, a simple way to organize the code would be this: boot -> Any code related to the bootloader or subroutines used by it, kernel -> Any code related to the kernel, drivers -> Specific driver code.

Before starting, it is recommended that you read these previous articles:


We create the directory structure:

mkdir -p Project/boot/
mkdir -p Project/kernel/
mkdir -p Project/drivers/

We copy or move the necessary files:

cp kernel_entry.asm boot_sect.asm boot_sect_print.asm boot_sect_print_hex.asm boot_sect_disk.asm 32bit-gdt.asm 32bit-print.asm 32bit-switch.asm Project/boot
cp kernel.c kernel_entry.asm Project/kernel

Make allows the use of wildcards, making compilation scripts shorter and easier to maintain.

We can expand the names of files that match a pattern:

C_SOURCES = $(wildcard kernel/*.c drivers/*.c)

We can create a list from certain source files:

OBJ = ${C_SOURCES:.c=.o}

To use the list, simply reference it as ${OBJ}:

kernel.bin: kernel/kernel_entry.o ${OBJ}
    ld -o $@ -Ttext 0x1000 $^ --oformat binary

Now that we have moved the files, we need to modify the includes in boot_sect.asm

vi boot/boot_sect.asm

%include "boot/boot_sect_print.asm"
%include "boot/boot_sect_print_hex.asm"
%include "boot/boot_sect_disk.asm"
%include "boot/32bit-gdt.asm"
%include "boot/32bit-print.asm"
%include "boot/32bit-switch.asm"

The final Makefile would look like this:

vi Makefile

# Automatically generate lists of sources using wildcards.
C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
HEADERS = $(wildcard kernel/*.h drivers/*.h)

# TODO : Make sources dep on all header files .
# Convert the *.c filenames to *.o to give a list of object files to build
OBJ = ${C_SOURCES:.c=.o}

# Run Qemu to simulate booting of our code.
run : all
	qemu-system-x86_64 os-image

# Run Qemu to simulate booting of our code with debugging session.
# gdb -ex "set architecture i386:x86-64" -ex "set disassembly-flavor intel" -ex "target remote localhost:1234" -ex "symbol-file kernel/kernel.elf"
debug : all kernel/kernel.elf
	qemu-system-x86_64 os-image -s -S &

# Used for debugging purposes
kernel/kernel.elf: boot/kernel_entry.o ${OBJ}
	ld -o $@ -Ttext 0x1000 $^

# Defaul build target
all: os-image

# This is the actual disk image that the computer loads
# which is the combination of our compiled bootsector and kernel
os-image : boot/boot_sect.bin kernel/kernel.bin
	cat $^ > os-image

# This builds the binary of our kernel from two object files :
# - the kernel_entry, which jumps to main () in our kernel
# - the compiled C kernel
kernel/kernel.bin : kernel/kernel_entry.o ${OBJ}
	ld -o $@ -Ttext 0x1000 $^ --oformat binary

# Generic rule for compiling C code to an object file
# For simplicity , we C files depend on all header files.
%.o : %.c ${HEADERS}
	cc -g -m32 -ffreestanding -c $< -o $@

# Assemble the kernel_entry.
%.o : %.asm
	nasm -f elf $< -o $@

%.bin : %.asm
	nasm -f bin $< -o $@

clean :
	rm -fr *.bin *.dis *.o *.core os-image
	rm -fr kernel/*.o boot/*.bin drivers/*.o

NOTE: Once again, it is important to use real tabs and not spaces in Makefile files.

We check that the whole process works:

gmake clean
gmake run

If you liked the article, you can treat me to a RedBull here