FreePascal Information Logo Friend of FreePascal Compiler Title
Articles with Feedback, FPC News Library, PDF Collection, Mail Lists, Books, Newsgroups, IRC Open online discussion areas Research and Tutorials Tools, Compilers and Utilities Blurbs about us, advertising, etc.
Welcome to the FoFPC Research - Barebones OS

Barebones OS - nasm, fpc and ld

by: G.E. Ozz Nixon Jr.
Code by: De Deyn Kim
Published: March 2009
©opyright 2009 by Friends of FPC



      When building your own operating system it helps if someone sheds the light on this mysterious world ... building a kernel. After working on a few kernel projects I came across "Barebones OS" on OS Dev - if you follow the steps outlined below you can quickly see - building a kernel for GRUB is almost childs play. This may also give you the false impression that you will be able to build your OS in a few months. So, please understand - this will show you a concept - you will need to read more of our articles and research documents to start absorbing all of the nasty details this research document left out.

Tools you will need:
FPC of course
NASM assembler
ld with elf support
a working linux with GRUB (you will need to reboot it to start this OS!)

stub.asm
;/////////////////////////////////////////////////////////
;//                                                     //
;//               Freepascal barebone OS                //
;//                      stub.asm                       //
;//                                                     //
;/////////////////////////////////////////////////////////
;//
;//     By:             De Deyn Kim 
;//     License:        Public domain
;//

;
; Kernel stub
;

;
; We are in 32bits protected mode
;
[bits 32]

;
; Export entrypoint
;
[global kstart]

;
; Import kernel entrypoint
;
[extern kmain]

;
; Posible multiboot header flags
;
MULTIBOOT_MODULE_ALIGN        equ   1<<0
MULTIBOOT_MEMORY_MAP          equ   1<<1
MULTIBOOT_GRAPHICS_FIELDS     equ   1<<2
MULTIBOOT_ADDRESS_FIELDS      equ   1<<16

;
; Multiboot header defines
;
MULTIBOOT_HEADER_MAGIC        equ   0x1BADB002
MULTIBOOT_HEADER_FLAGS        equ   MULTIBOOT_MODULE_ALIGN | MULTIBOOT_MEMORY_MAP
MULTIBOOT_HEADER_CHECKSUM     equ   -(MULTIBOOT_HEADER_MAGIC+MULTIBOOT_HEADER_FLAGS)

;
; Kernel stack size
;
KERNEL_STACKSIZE                equ     0x4000

section .text

;
; Multiboot header
;
align 4
dd MULTIBOOT_HEADER_MAGIC
dd MULTIBOOT_HEADER_FLAGS
dd MULTIBOOT_HEADER_CHECKSUM

;
; Entrypoint
;
kstart:
        mov esp, KERNEL_STACK+KERNEL_STACKSIZE  ;Create kernel stack
        push eax                                ;Multiboot magic number
        push ebx                                ;Multiboot info
        call kmain                              ;Call kernel entrypoint
        cli                                     ;Clear interrupts
        hlt                                     ;Halt machine

section .bss

;
; Kernel stack location
;
align 32
KERNEL_STACK:
        resb KERNEL_STACKSIZE

kernel.pas
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                      kernel.pas                     //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim 
//      License:        Public domain
//
unit kernel; {note: unit not program!}

interface

uses
   multiboot,
   console;

procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: DWORD);
   stdcall;

implementation

procedure kmain(mbinfo: Pmultiboot_info_t; mbmagic: DWORD);
   stdcall; [public, alias: 'kmain'];

begin
   kclearscreen();
   kwritestr('Freepascal barebone OS booted!');
   xpos := 0;
   ypos += 1;

   if (mbmagic <> MULTIBOOT_BOOTLOADER_MAGIC) then begin
      kwritestr('Halting system...');
      kwritestr('A multiboot-compliant boot loader needed!');
      asm
         cli;
         hlt;
      end;
   end
   else begin
      kwritestr('Booted by a multiboot-compliant boot loader!');
      xpos := 0;
      ypos += 2;
      kwritestr('Multiboot information:');
      xpos := 0;
      ypos += 2;
      kwritestr('                       Lower memory  = ');
      kwriteint(mbinfo^.mem_lower);
      kwritestr('KB');
      xpos := 0;
      ypos += 1;
      kwritestr('                       Higher memory = ');
      kwriteint(mbinfo^.mem_upper);
      kwritestr('KB');
      xpos := 0;
      ypos += 1;
      kwritestr('                       Total memory  = ');
      kwriteint(((mbinfo^.mem_upper + 1000) div 1024) +1);
      kwritestr('MB');
   end;
   asm
   @loop:
      jmp @loop
   end;
end;

end.

console.pas
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                       console.pas                   //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim 
//      License:        Public domain
//
}

unit console;

interface

var
   xpos: Integer = 0;
   ypos: Integer = 0;

procedure kclearscreen();
procedure kwritechr(c: Char);
procedure kwritestr(s: PChar);
procedure kwriteint(i: Integer);
procedure kwritedword(i: DWORD);

implementation

var
   vidmem: PChar = PChar($b8000);

procedure kclearscreen(); [public, alias: 'kclearscreen'];
var
   i: Integer;

begin
   for i := 0 to 3999 do vidmem[i] := #0;
end;

procedure kwritechr(c: Char); [public, alias: 'kwritechr'];
var
   offset: Integer;

begin
   if (ypos > 24) then ypos := 0;
   if (xpos > 79) then xpos := 0;
   offset := (xpos shl 1) + (ypos * 160);
   vidmem[offset] := c;
   offset += 1;
   vidmem[offset] := #7;
   offset += 1;
   xpos := (offset mod 160);
   ypos := (offset - xpos) div 160;
   xpos := xpos shr 1;
end;

procedure kwritestr(s: PChar); [public, alias: 'kwritestr'];
var
   offset, i: Integer;

begin
   if (ypos > 24) then ypos := 0;
   if (xpos > 79) then xpos := 0;
   offset := (xpos shl 1) + (ypos * 160);
   i := 0;
   while (s[i] <> Char($0)) do begin
      vidmem[offset] := s[i];
      offset += 1;
      vidmem[offset] := #7;
      offset += 1;
      i += 1;
   end;
   xpos := (offset mod 160);
   ypos := (offset - xpos) div 160;
   xpos := xpos shr 1;
end;

procedure kwriteint(i: Integer); [public, alias: 'kwriteint'];
var
   buffer: array [0..11] of Char;
   str: PChar;
   digit: DWORD;
   minus: Boolean;

begin
   str := @buffer[11];
   str^ := #0;
   if (i < 0) then begin
      digit := -i;
      minus := True;
   end
   else begin
      digit := i;
      minus := False;
   end;
   repeat
      Dec(str);
      str^ := Char((digit mod 10) + Byte('0'));
      digit := digit div 10;
   until (digit = 0);
   if (minus) then begin
      Dec(str);
      str^ := '-';
   end;
   kwritestr(str);
end;

procedure kwritedword(i: DWORD); [public, alias: 'kwritedword'];
var
   buffer: array [0..11] of Char;
   str: PChar;
   digit: DWORD;

begin
   for digit := 0 to 10 do buffer[digit] := '0';
   str := @buffer[11];
   str^ := #0;
   digit := i;
   repeat
      Dec(str);
      str^ := Char((digit mod 10) + Byte('0'));
      digit := digit div 10;
   until (digit = 0);
   kwritestr(@Buffer[0]);
end;

end.

multiboot.pas
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                   multiboot.pas                     //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim 
//      License:        Public domain
//
unit multiboot;

interface

const
   KERNEL_STACKSIZE = $4000;
   MULTIBOOT_BOOTLOADER_MAGIC = $2BADB002;

type
   Pelf_section_header_table_t = ^elf_section_header_table_t;
   elf_section_header_table_t = packed record
      num: DWORD;
      size: DWORD;
      addr: DWORD;
      shndx: DWORD;
   end;
   Pmultiboot_info_t = ^multiboot_info_t;
   multiboot_info_t = packed record
      flags: DWORD;
      mem_lower: DWORD; { Amount of memory available below 1mb }
      mem_upper: DWORD; { Amount of memory available above 1mb }
      boot_device: DWORD;
      cmdline: DWORD;
      mods_count: DWORD;
      mods_addr: DWORD;
      elf_sec: elf_section_header_table_t;
      mmap_length: DWORD;
      mmap_addr: DWORD;
   end;
   Pmodule_t = ^module_t;
   module_t = packed record
      mod_start: DWORD;
      mod_end: DWORD;
      name: DWORD;
      reserved: DWORD;
   end;
   Pmemory_map_t = ^memory_map_t;
   memory_map_t = packed record
      size: DWORD;
      // You can declare these two as a single
      // qword if your compiler supports it
      base_addr_low: DWORD;
      base_addr_high: DWORD;
      // And again, these can be made into a
      // single qword variable
      length_low: DWORD;
      length_high: DWORD;
      mtype: DWORD;
   end;

implementation

end.

system.pas
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                     system.pas                      //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim 
//      License:        Public domain
//
unit system;

interface

type
   cardinal = 0..$FFFFFFFF;
   hresult = cardinal;
   dword = cardinal;
   integer = longint;
   pchar = ^char;

implementation

end.

linker.script
ENTRY(kstart)
SECTIONS
{
  .text  0x100000 :
  {
    text = .; _text = .; __text = .;
    *(.text)
    . = ALIGN(4096);
  }
  .data  :
  {
    data = .; _data = .; __data = .;
    *(.data)
    kimage_text = .;
    LONG(text);
    kimage_data = .;
    LONG(data);
    kimage_bss = .;
    LONG(bss);
    kimage_end = .;
    LONG(end);
    . = ALIGN(4096);
  }
  .bss  :
  {
    bss = .; _bss = .; __bss = .;
    *(.bss)
    . = ALIGN(4096);
  }
  end = .; _end = .; __end = .;
}

Compiling and Linking the modules
nasm -f elf stub.asm -o stub.o

fpc -Aelf -n -O3 -Op3 -Si -Sc -Sg -Xd -Rintel -Tlinux kernel.pas

ld -Tlinker.script -o kernel.obj stub.o kernel.o multiboot.o system.o console.o

      The above three steps compiled the stab.asm to a linkable object, compiled the kernel.pas file - producing kernel.ppu and kernel.o -- of course kernel.o is what we are interested in. The final step uses the ld linker to produce the output file kernel.obj which consists of the "elf" format stub.o, which calls kernel.o, which is dependent upon multiboot.o, system.o and console.o -- before I explain these files and other things that are happening - let's modify your grub file and see this actually boot up and work!

vim /boot/grub/menu.lst

append to the end of the file the following:
title		Barebones OS
root		(hd0,0)
kernel		/home/onixon/barebones/kernel.obj

      Make sure the path is to your kernel.obj file, and then save/quit. Reboot your VM and select the "Barebones OS" choice in the GRUB menu. You will see a screen like:

Barebones OS Screenshot in VMWare on my Mac OS X, inside an Ubuntu 8 VM
Barebones OS Screenshot in VMWare on Mac OS X inside an Ubuntu 8 VM

      So, if everything went well... you too will have a simple DOS looking VM that states how much memory is available to the kernel. Normally I would have posted this as an article, however, once you have the above working -- here comes the research. The above code (mainly the stub.asm and kernel.pas) makes it look like sub.asm is pushing eax (MULTIBOOT_HEADER_MAGIC) from stub.asm to kernel.pas' kmain... this is not possible. If it was we would get an error stating "A multiboot-compliant boot loader needed!"... because the MAGIC number in stub.asm is 0x1...2 and in multiboot.pas it is defined as 0x2...2.

      First step of course is to read-up on grub... doing a

grub --version

      Showed me I am running 0.97 (now called GRUB Legacy) -- so I did some reading and found that Grub displays information that kmain is erasing in the kclearscreen() call. So, I removed that line, changed ypos to start on line 10.. and recompiled then rebooted to the new kernel - to see ", <0x100000:0x1000:0x0>, <0x101000,0x1000:0x4000>, shtab=0x106168Starting up..." then the normal Barebones introduction. Then I came across "Multiboot Specification v0.6.95" which states the layout of the Multiboot loader as:

offset    type    fieldname          note
     0     u32    magic              required
     4     u32    flags              required
     8     u32    checksum           required (magic+flags)
    12     u32    header address     if flags[16] is set
    16     u32    load address       if flags[16] is set
    20     u32    load end address   if flags[16] is set
    24     u32    bss end address    if flags[16] is set
    28     u32    entry address      if flags[16] is set
    32     u32    mode type          if flags[2] is set
    36     u32    width              if flags[2] is set
    40     u32    height             if flags[2] is set
    44     u32    depth              if flags[2] is set

      Then I found what I was looking for...EAX contains 0x2BADB002 - denoting to the operating system that it was loaded by a multiboot-compliant boot loader (in this case GRUB). EBX contains the 32bit address of the multiboot information structure provided by the boot loader. The structure of this multiboot information is:

         +-------------------+
 0       | flags             |    (required)
         +-------------------+
 4       | mem_lower         |    (present if flags[0] is set)
 8       | mem_upper         |    (present if flags[0] is set)
         +-------------------+
 12      | boot_device       |    (present if flags[1] is set)
         +-------------------+
 16      | cmdline           |    (present if flags[2] is set)
         +-------------------+
 20      | mods_count        |    (present if flags[3] is set)
 24      | mods_addr         |    (present if flags[3] is set)
         +-------------------+
 28 - 40 | syms              |    (present if flags[4] or
         |                   |                flags[5] is set)
         +-------------------+
 44      | mmap_length       |    (present if flags[6] is set)
 48      | mmap_addr         |    (present if flags[6] is set)
         +-------------------+
 52      | drives_length     |    (present if flags[7] is set)
 56      | drives_addr       |    (present if flags[7] is set)
         +-------------------+
 60      | config_table      |    (present if flags[8] is set)
         +-------------------+
 64      | boot_loader_name  |    (present if flags[9] is set)
         +-------------------+
 68      | apm_table         |    (present if flags[10] is set)
         +-------------------+
 72      | vbe_control_info  |    (present if flags[11] is set)
 76      | vbe_mode_info     |
 80      | vbe_mode          |
 82      | vbe_interface_seg |
 84      | vbe_interface_off |
 86      | vbe_interface_len |
         +-------------------+



 Links and Products we find useful



ButtonGenerator.com
Valid XHTML 1.0 Transitional Internet Map
Programmer's Heaven
grat-i-fi-ca-tion - noun
the state of being gratified; great satisfaction.


"Friends of Free Pascal is, in my view, one of the best things to happen to FPC since the compiler first appeared"

Gary Funk
BoardWatch Magazine
Locations of visitors to this page world map hits counter
Copyright 2009 by 3F, LLC. All rights reserved. Worldwide.
Your request was processed by server #3 in 0.607980 secs.

sponsor
This sponsor helps us with our documentation