Skip to content

An open-source operating system developed from scratch! This project aims to provide a minimalistic yet functional operating system for educational and exploratory purposes.

DarmorGamz/DarmorOS

Repository files navigation

DarmorOS

https://wiki.osdev.org/GCC_Cross-Compiler

Bootloader

GRUB git clone https://git.savannah.gnu.org/git/grub.git

Building a Cross-Compiler

We build a toolset running on your host that can turn source code into object files for your target system.

It is dangerous and a very bad idea to install it into system directories

Add the installation prefix to the PATH of the current shell session

export PREFIX="$HOME/opt/cross"
export TARGET=i686-elf
export PATH="$PREFIX/bin:$PATH"

This compiles the binutils (assembler, disassembler, and various other useful stuff), runnable on your system but handling code in the format specified by $TARGET.

Binutils

wget https://ftp.gnu.org/gnu/binutils/binutils-2.41.tar.xz
tar -xf binutils-2.41.tar.xz
cd $HOME/DarmorOS
 
mkdir build-binutils
cd build-binutils
../binutils-2.41/configure --target=$TARGET --prefix="$PREFIX" --with-sysroot --disable-nls --disable-werror
make
make install

--disable-nls tells binutils not to include native language support. This is basically optional, but reduces dependencies and compile time.

--with-sysroot tells binutils to enable sysroot support in the cross-compiler by pointing it to a default empty directory. By default, the linker refuses to use sysroots for no good technical reason, while gcc is able to handle both cases at runtime. This will be useful later on.

Install tools

sudo apt install libgmp3-dev
sudo apt install libmpc-dev
sudo apt install libmpfr-dev
sudo apt install xorriso
sudo apt install qemu-system-x86
sudo apt install grub-pc-bin

GCC

git clone git://gcc.gnu.org/git/gcc.git build-gcc
cd $HOME/DarmorOS
 
# The $PREFIX/bin dir _must_ be in the PATH. We did that above.
which -- $TARGET-as || echo $TARGET-as is not in the PATH
 
mkdir build-gcc
cd build-gcc
../gcc-13.2.0/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --enable-languages=c,c++ --without-headers
make all-gcc DO THIS NEXT
make all-target-libgcc
make install-gcc
make install-target-libgcc

We build libgcc, a low-level support library that the compiler expects available at compile time. Linking against libgcc provides integer, floating point, decimal, stack unwinding (useful for exception handling) and other support functions. Note how we are not simply running make && make install as that would build way too much, not all components of gcc are ready to target your unfinished operating system.

--disable-nls is the same as for binutils above.

--without-headers tells GCC not to rely on any C library (standard or runtime) being present for the target.

--enable-languages tells GCC not to compile all the other language frontends it supports, but only C (and optionally C++).

Using the new Compiler

Now you have a "naked" cross-compiler. It does not have access to a C library or C runtime yet, so you cannot use most of the standard includes or create runnable binaries. But it is quite sufficient to compile the kernel you will be making shortly. Your toolset resides in $HOME/opt/cross (or what you set $PREFIX to). For example, you have a GCC executable installed as $HOME/opt/cross/bin/$TARGET-gcc, which creates programs for your TARGET.

You can now run your new compiler by invoking something like:

$HOME/opt/cross/bin/$TARGET-gcc --version

Note how this compiler is not able to compile normal C programs. The cross-compiler will spit errors whenever you want to #include any of the standard headers (except for a select few that actually are platform-independent, and generated by the compiler itself). This is quite correct - you don't have a standard library for the target system yet!

The C standard defines two different kinds of executing environments - "freestanding" and "hosted". While the definition might be rather fuzzy for the average application programmer, it is pretty clear-cut when you're doing OS development: A kernel is "freestanding", everything you do in user space is "hosted". A "freestanding" environment needs to provide only a subset of the C library: float.h, iso646.h, limits.h, stdalign.h, stdarg.h, stdbool.h, stddef.h, stdint.h and stdnoreturn.h (as of C11). All of these consist of typedef s and #define s "only", so you can implement them without a single .c file in sight.

Note that to have these compiler-provided includes work properly, you need to build your kernel with the -ffreestanding flag. Otherwise, they might attempt including your standard library's copy of the headers, which isn't gonna work if you don't have a standard library.

To use your new compiler simply by invoking $TARGET-gcc, add $HOME/opt/cross/bin to your $PATH by typing:

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

This command will add your new compiler to your PATH for this shell session. If you wish to use it permanently, add the PATH command to your ~/.profile configuration shell script or similar. Consult your shell documentation for more information.

You can now move on to complete the Bare Bones tutorial variant that lead you here and complete it using your new cross-compiler. If you built a new GCC version as your system compiler and used it to build the cross-compiler, you can now safely uninstall it unless you wish to continue using it.

OVERVIEW

boot.s - kernel entry point that sets up the processor environment
kernel.c - your actual kernel routines
linker.ld - for linking the above files

BOOTING THE OPERATING SYSTEM

To start the operating system, an existing piece of software will be needed to load it. This is called the bootloader and in this tutorial you will be using GRUB. Writing your own bootloader is an advanced subject, but it is commonly done. We'll later configure the bootloader, but the operating system needs to handle when the bootloader passes control to it. The kernel is passed a very minimal environment, in which the stack is not set up yet, virtual memory is not yet enabled, hardware is not initialized, and so on.

The first task you will deal with is how the bootloader starts the kernel. OSDevers are lucky because there exists a Multiboot Standard, which describes an easy interface between the bootloader and the operating system kernel. It works by putting a few magic values in some global variables (known as a multiboot header), which is searched for by the bootloader. When it sees these values, it recognizes the kernel as multiboot compatible and it knows how to load us, and it can even forward us important information such as memory maps, but you won't need that yet.

Since there is no stack yet and you need to make sure the global variables are set correctly, you will do this in assembly.

BOOTSTRAP ASSEMBLY

You can then assemble boot.s using:

i686-elf-as boot.s -o boot.o

IMPLEMENTING THE KERNEL

Compile using:

i686-elf-gcc -c kernel.c -o kernel.o -std=gnu99 -ffreestanding -O2 -Wall -Wextra

Note that the above code uses a few extensions and hence you build as the GNU version of C99.

LINKING THE KERNEL

You can now assemble boot.s and compile kernel.c. This produces two object files that each contain part of the kernel. To create the full and final kernel you will have to link these object files into the final kernel program, usable by the bootloader. When developing user-space programs, your toolchain ships with default scripts for linking such programs. However, these are unsuitable for kernel development and you need to provide your own customized linker script. Save the following in linker.ld:

You can then link your kernel using:

i686-elf-gcc -T linker.ld -o darmoros.bin -ffreestanding -O2 -nostdlib boot.o kernel.o -lgcc

VERIFYING MULTIBOOT

If you have GRUB installed, you can check whether a file has a valid Multiboot version 1 header, which is the case for your kernel. It's important that the Multiboot header is within the first 8 KiB of the actual program file at 4 byte alignment. This can potentially break later if you make a mistake in the boot assembly, the linker script, or anything else that might go wrong. If the header isn't valid, GRUB will give an error that it can't find a Multiboot header when you try to boot it. This code fragment will help you diagnose such cases:

grub-file --is-x86-multiboot darmoros.bin

Wrap command with conditional for output.

if grub-file --is-x86-multiboot darmoros.bin; then
  echo multiboot confirmed
else
  echo the file is not multiboot
fi

BOOTING THE KERNEL

BUILDING A BOOTABLE CDROM IMAGE

You can easily create a bootable CD-ROM image containing the GRUB bootloader and your kernel using the program grub-mkrescue. First you should create a file called grub.cfg containing the contents:

menuentry "darmoros" {
	multiboot /boot/darmoros.bin
}

You can now create a bootable image of your operating system by typing these commands:

mkdir -p isodir/boot/grub
cp darmoros.bin isodir/boot/darmoros.bin
cp grub.cfg isodir/boot/grub/grub.cfg
grub-mkrescue -o darmoros.iso isodir

TESTING THE OPERATING SYSTEM (QEMU)

Start the operating system with:

qemu-system-i386 -cdrom darmoros.iso

CREAT BUILD SCRIPTS

sudo chmod +x build.sh
sudo chmod +x clean.sh
sudo chmod +x config.sh
sudo chmod +x default-host.sh
sudo chmod +x headers.sh
sudo chmod +x target-triplet-to-arch.sh
sudo chmod +x iso.sh
sudo chmod +x qemu.sh
sudo chmod +x lazy-build-qemu.sh

About

An open-source operating system developed from scratch! This project aims to provide a minimalistic yet functional operating system for educational and exploratory purposes.

Topics

Resources

Stars

Watchers

Forks