Esta pagina se ve mejor con JavaScript habilitado

MercuryOS Cross-compiler y BinUtils

 ·  🎃 kr0m

Compilar código fuente de 32bits en un sistema de 64bits accarea muchos problemas, hasta ahora nos ha sido indiferente ya que casi todo el código era ASM, pero seguir desarrollando así es una fuente de problemas en el futuro. Si utilizamos el compilador de nuestro sistema, este hará muchas suposiciones específicas a nuestro SO/Arquitectura, además utilizará las librerias y cabeceras propias. Para evitar todo esto vamos a instalar un cross-compiler de 32bits nativo, se acabaron las opciones como -m32 en nuestro gcc, además compilaremos también unas binutils de 32bits.

Antes de comenzar es recomendable que leas estos artículos anteriores:


Al compilar un kernel y no un programa user-space debemos compilar con ciertas opciones:

  • ffreestanding: Ciertas funciones serán implementadas por nosotros mismo, memset, memcpy, memcmp y memmove
  • fno-exceptions, -fno-rtti (C++): Deshabilitamos opciones de C++ que no funcionan out-of-the-box en kernel-land
  • nostdlib: Es el equivalente a -nostartfiles -nodefaultlibs, no iniciamos ficheros(crt0.o, crti.o, crtn.o), ya que solo se utilizan en programas user-space, tampoco queremos las librerías por defecto como libc
  • lgcc: Al habilitar nostdlib hemos eliminado también la librería gcc pero esta es necesaria para el compilador así que la habilitamos, debe ser el último parámetro de compilación
    • GCC generates calls to routines in this library automatically, whenever it needs to perform some operation that is too complicated to emit inline code for

No debemos utilizar nunca el linker manualmente a no ser que sea estrictamente necesario y si lo hacemos debemos utilizar el que hemos compilado en nuestras cross-tools. El mismo consejo es aplicable para el resto de tools: readelf, objcopy, objdump.

El compilador generará código y buscará ciertas librerías basándose en el TargetTriplet, este describe la plataforma sobre la que correrá el código. Podemos ver el TargetTriplet con el comando:

gcc -dumpmachine

x86_64-portbld-freebsd12.1

El compilador que trae FreeBSD generará código para este TargetTriplet, pero nosotros necesitamos generarlo para yourOsTargetArch-yourChosenFormat-none, el none del final indica que no debe utilizar ninguna librería del sistema, queremos que nuestro kernel sea lo mas standalone posible.

Mas tarde cuando tengamos librerías del sistema(/lib, /include) compilaremos los programas de user-space con otro compilador para que enlace de forma dinámica los binarios con dichas librerías, el compilador será: yourOsTargetArch-yourChosenFormat-yourOs

De este modo tendremos dos compiladores:

  • yourOsTargetArch-yourChosenFormat-none: Compilador del kernel
  • yourOsTargetArch-yourChosenFormat-yourOs: Compilador de los programas userspace

Instalamos texinfo, una dependencia necesaria para compilar nuestro gcc.

pkg install texinfo

El resto de pasos se pueden realizar como usuario regular, nos bajamos la última versión de BinUtils y GCC :

mkdir -p crossCompiler/binutils
mkdir crossCompiler/gcc
cd crossCompiler/binutils
fetch https://ftp.gnu.org/gnu/binutils/binutils-2.34.tar.xz
tar -Jxvf binutils-2.34.tar.xz
cd ..
cd gcc
fetch https://ftp.gnu.org/gnu/gcc/gcc-10.1.0/gcc-10.1.0.tar.xz
tar -Jxvf gcc-10.1.0.tar.xz

Todo el crossCompiler se va instalar bajo $HOME/opt/cross de este modo no crearemos conflictos con el entorno de compilación de nuestro SO:

mkdir -p $HOME/opt/cross
export PREFIX="$HOME/opt/cross"
export TARGET=i686-elf
export PATH="$PREFIX/bin:$PATH"
cd $HOME/crossCompiler/binutils
mkdir build-binutils
cd build-binutils
../binutils-2.34/configure –target=$TARGET –prefix="$PREFIX" –with-sysroot –disable-nls –disable-werror
gmake -j8
gmake install

Podemos ver algunas opciones de configuración interesantes:

  • --disable-nls: Le indica a binutils que no incluya soporte nativo de lenguajes, es opcional pero reduce las dependencias de compilación, solo soportaremos Inglés :)
  • --with-sysroot: Le indica a binutils que utilice un sysroot vacío

Con el comando siguiente comprobamos que el compilador de ASM esté disponible en el path:

which – $TARGET-as || echo $TARGET-as is not in the PATH
/home/kr0m/opt/cross/bin/i686-elf-as

Compilamos gcc:

cd $HOME/crossCompiler/gcc
mkdir build-gcc
cd build-gcc
../gcc-10.1.0/configure –target=$TARGET –prefix="$PREFIX" –disable-nls –enable-languages=c,c++ –without-headers
gmake -j8 all-gcc
gmake -j8 all-target-libgcc
gmake install-gcc
gmake install-target-libgcc

NOTA: gcc depende de libgcc una librería de bajo nivel que el compilador espera que esté disponible cuando compila código.

Las opciones con las que se ha compilado gcc son:

  • --disable-nls: Le indica a binutils que no incluya soporte nativo de lenguajes, es opcional pero reduce las dependencias de compilación, solo soportaremos Inglés :)
  • --without-headers: Le indicamos a gcc que no debe confiar en que haya ninguna librería de C en el sistema
  • --enable-languages: Le indicamos los lenguajes de programación que debe soportar

Podemos obtener la versión del compilador con:

/home/kr0m/opt/cross/bin/i686-elf-gcc -v

Using built-in specs.  
COLLECT_GCC=/home/kr0m/opt/cross/bin/i686-elf-gcc  
COLLECT_LTO_WRAPPER=/usr/home/kr0m/opt/cross/bin/../libexec/gcc/i686-elf/10.1.0/lto-wrapper  
Target: i686-elf  
Configured with: ../gcc-10.1.0/configure --target=i686-elf --prefix=/home/kr0m/opt/cross --disable-nls --enable-languages=c,c++ --without-headers  
Thread model: single  
Supported LTO compression algorithms: zlib zstd  
gcc version 10.1.0 (GCC) 

Consultamos el TargetTriplet del compilador:

/home/kr0m/opt/cross/bin/i686-elf-gcc -dumpmachine

i686-elf

Hacemos que el compilador y las binutils estén disponibles en el path:

export PATH="$HOME/opt/cross/bin:$PATH"
vi .bashrc

export PATH="$HOME/opt/cross/bin:$PATH"

Este compilador es freestanding lo que implica que no utilizará librerías como libc tan solo un subset muy básico:float.h, iso646.h, limits.h, stdalign.h, stdarg.h, stdbool.h, stddef.h, stdint.h y stdnoreturn.h, todo son simplemente definiciones de tipos sin ficheros .c

Nuestro fichero Makefile también se verá afectado:

vi Makefile

#$@ : Fichero a generar
#$^ : Todas las dependencias
#$< : Primera dependencia

# 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
    qemu-system-i386 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"
# gdb -ex "set architecture i386" -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
    qemu-system-i386 os-image -s -S

# Used for debugging purposes
kernel/kernel.elf: boot/kernel_entry.o ${OBJ}
    i686-elf-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

boot/boot_sect.bin : boot/boot_sect.asm
    nasm -f bin $< -o $@

# 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 and drivers
kernel/kernel.bin : kernel/kernel_entry.o ${OBJ}
    i686-elf-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 -ggdb -m32 -ffreestanding -c $< -o $@
    i686-elf-gcc -ggdb -ffreestanding -fno-exceptions -nostdlib -c $< -o $@ -lgcc

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

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

clean :
    rm -fr *.bin *.dis *.o os-image
    rm -fr kernel/*.o kernel/*.elf boot/*.bin drivers/*.o
Si te ha gustado el artículo puedes invitarme a un RedBull aquí