11 – Developing an Operating System – Tutorial – Episode 5 – Reduce size, Linker Script and Standard GCC – OSDEV

This post is dedicated to osdev.org community and #osdev IRC channel at freenode servers.

If you have followed my tutorial so far, you might have noticed that our kernel is almost 128 MB in size.

We have multiple issues with our compilation process which we must fix before we go further. Let’s list down the issues and why do we need to fix it and what benefits we are going to get?

  1. Use Linker Script.
  2. Use latest GCC Compiler.
  3. Issues with printf.
  4. Issues with memory addressing.

We are right now using GCC 4.9, which is very old and also we have custom built GCC 4.9 to support i386-elf format. Also GCC 4.9 does not have certain attributes which we will require to use in later part. We also dont want to custom build GCC anymore. We should be able to use GCC out of the box. So First we will uninstall GCC 4.9 and Binutils, and install GCC straight out of packages.

sudo rm -rf /usr/local/i386elfgcc/
sudo apt remove gcc-4.9
sudo apt install gcc

Above commands will uninstall gcc 4.9 and install latest version of gcc.. For me gcc 9.3.0 is installed.

If we try to compile our code, it will not compile it straight away, we need to make adjustment in our compiler switches to make it work, but before that we also need to write a linker script. The main purpose of the linker script is to describe how the sections in the input files should be mapped into the output file, and to control the memory layout of the output file. Meaning we will be telling compiler how we want our code to be compiled, so that it executes the way we want.. not just from code / logic perspective but also in terms of where and how it will load variables, data, etc… in a memory. You can read more about Linker Scripts here – http://www.scoberlin.de/content/media/http/informatik/gcc_docs/ld_3.html

Below is the objdump of our kernel.elf file. Our objdump shows that our kernel is loaded at correct address but it is spreading through 0x8000000, also it is linked with libc.so library, which is Linux library. We do not want our OS to be dependent on any other OS library.. because it will be disaster later on.

You can produce objdump of kernel by executing following command:

objdump -phxd kernel.elf > kernel.objdump
LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
     filesz 0x000002e8 memsz 0x000002e8 flags r--
LOAD off    0x00001000 vaddr 0x08049000 paddr 0x08049000 align 2**12
     filesz 0x00000494 memsz 0x00000494 flags r-x
LOAD off    0x00002000 vaddr 0x0804a000 paddr 0x0804a000 align 2**12
     filesz 0x00000244 memsz 0x00000244 flags r--
LOAD off    0x00002f08 vaddr 0x0804bf08 paddr 0x0804bf08 align 2**12
     filesz 0x00000110 memsz 0x00000118 flags rw-

We will begin by writing a very simple linker script, create a new file named linker.ld

OUTPUT_FORMAT(elf32-i386)
ENTRY(start)
SECTIONS
{
    . = 0x01000; /*0x001000*/
    .text BLOCK(2K) : ALIGN(2K)
    {
        *(.text)
    }

    /*Read-only data*/
    .rodata BLOCK(2K) : ALIGN(2K)
    {
        *(.rodata)
    }

    /*Read-write data (initialized)*/
    .data BLOCK(2K) : ALIGN(2K)
    {
        *(.data)
    }

    /*Read-write data (uninitialized) and stack*/
    .bss BLOCK(2K) : ALIGN(2K)
    {
        *(.bss)
    }
}

We also need to make corrections in our compile.bat file:

echo off
echo "clean all binaries"
del *.bin
del *.o
del *.elf
del *.objdump
del *.log

echo "compile boot.asm"
fasm boot.asm

echo "compile kernel.c"
wsl gcc -g -m32 -ffreestanding -c *.c drivers/*.c -nostartfiles -nostdlib

echo "link all c object files"
wsl ld -m elf_i386 -nostdlib -T linker.ld *.o -o kernel.elf

echo "compile loader.asm"
fasm loader.asm

echo "Link loader and kernel"
wsl ld -m elf_i386 -nostdlib -T linker.ld loader.o kernel.elf -o kernel_full.elf

echo "objdump loader file"
objdump -phxd loader.o > loader.objdump

echo "objdump kernel file"
objdump -phxd kernel_full.elf > kernel.objdump

echo "Producing elf file"
wsl objcopy kernel_full.elf -O binary kernel.bin

echo "Creating image...."
type boot.bin kernel.bin > os_image.bin

echo "Launching QEMU"
qemu-system-x86_64 os_image.bin
rem bochs -f bochsconfig.txt
rem bochsdbg -f bochsconfig.conf

Make changes in boot.asm file to read more sectors from the disk.

load_kernel:
     mov  bx, MSG_LOAD_KERNEL
     call print
     call print_nl

     mov  bx, KERNEL_OFFSET ;read from disk and store in 0x1000
     mov  dh, 7 ;read 7 sectors from HDD or bootable disk
     mov  dl, [BOOT_DRIVE]
     call disk_load
     ret

Change our kernel.c’s main method name to kmain method, and make some more changes in structure of a code (Not necessary).

#include "drivers/screen.h"

void kmain(void) {
	char str[] = "Welcome to Learn OS. ";
	char str1[] = "This message has been printed using printf.";

    clear();
    printf(str);
    printf(str1);
}

And finally update loader.asm

org 0x1000
format ELF ;instruct assembler to produce ELF (Executable and Linkable Format) file.

extrn kmain ;tell assembler that main is the external function so ignore the assembler / compiler if main is not found in code.

start:
section '.text'
    call kmain ;call external main function.
    jmp $

Once we have made above changes, our code is now ready to get compiled. Also our kernel size has reduced drastically to be just under 12 KB.

Code is available at: https://github.com/dhavalhirdhav/LearnOS

In the next blog, we will see how we can further go ahead and load GDT table from kernel as well as IDT table, we will also learn how to write drivers and update our basic VGA driver. Also we are going to look at issues our boot loader and kernel has and how we plan to resolve them.

About the Author