Hasta ahora cada vez que realizabamos alguna modificación en el bootloader o en nuestro SO teníamos que recompilar y linkar los ficheros a mano, a lo largo de la historia programadores anteriores ya se vieron en tal tesitura así que crearon una herramienta llamada make, mediante make podremos indicar dependencias entre ficheros y comandos a ejecutar en caso de que alguno de ellos haya sido modificado.
Antes de comenzar es recomendable que leas estos artículos anteriores:
- Boot Sector
- Interrupciones
- Memoria
- Pila
- IF-ELSE
- Funciones
- Segmentación de memoria
- Lectura de datos desde disco
- Entrando a modo protegido 32bits
- Compilación, linkado, gestión de la pila y variables en C
- Punteros
- Kernel
- EntryPoint
Como vamos a utilizar el make de GNU tendremos que instalar gmake:
Un ejemplo básico podría ser este:
kernel.o : kernel.c
cc -m32 -ffreestanding -c kernel.c -o kernel.o
NOTA: Cuidado con los espacios y tabulaciones porque Make requiere que todo sean tabulaciones reales.
Podemos compilar el kernel con el comando:
cc -m32 -ffreestanding -c kernel.c -o kernel.o
La belleza de Make reside en que solo ejecutará la orden si alguna de las dependencias ha sido modificada, si volvemos a ejecutar make nos indicará que ya tenemos la última versión y no es necesario recompilar:
gmake: 'kernel.o' is up to date.
Un Makefile mas avanzados podría ser este, al ejecutar make kernel.bin si alguna de las dependencias requiere ser recompilada lo hará de forma automática:
# Build the kernel binary
kernel.bin : kernel_entry.o kernel.o
ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary
# Build the kernel object file
kernel.o : kernel.c
cc -m32 -ffreestanding -c kernel.c -o kernel.o
# Build the kernel entry object file .
kernel_entry.o : kernel_entry.asm
nasm kernel_entry.asm -f elf -o kernel_entry.o
Make soporta varias variables especiales, de este modo el mantenimiento del script será mas sencillo conforme crezca el proyecto:
- $^: Indica todas las dependencias
- $<: Indica la primera dependencia
- $@: Indica el fichero a generar
A continucación la versión final de mi Makefile:
# Run Qemu to simulate booting of our code .
run : all
qemu-system-x86_64 os-image
# Compile all elements
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_sect.bin 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.bin : kernel_entry.o kernel.o
ld -o kernel.bin -Ttext 0x1000 $^ --oformat binary
# Build our kernel object file .
kernel.o : kernel.c
cc -m32 -ffreestanding -c $< -o $@
# Build our kernel entry object file .
kernel_entry.o : kernel_entry.asm
nasm $< -f elf -o $@
# Assemble the boot sector to raw machine code
# The -I options tells nasm where to find our useful assembly
# routines that we include in boot_sect . asm
boot_sect.bin : boot_sect.asm
nasm -f bin $< -o $@
# Clear away all generated files .
clean :
rm -fr *.bin *.dis *.o os-image *.map
# Disassemble our kernel - might be useful for debugging .
kernel dis : kernel.bin
ndisasm -b 32 $< > $@
Hacemos limpieza y comprobamos que todo el proceso funcione:
rm -fr *.bin *.dis *.o os-image *.map
Compilamos todo lo necesario, generamos la imagen de disco y la cargamos en Qemu, todo esto en un solo comando.
Excelente todo habrá funcionado como la seda y tendremos corriendo la última versión de nuestro bootloader/kernel en Qemu, cada vez que queramos modificar algo tan solo debemos darle a make run y nuestro script se encargará de recompilar todo lo necesario.