Your Ad Here

Your Own Operating System


by: G.E. Ozz Nixon Jr.
Code by: De Deyn Kim
Published: March 2009
Copyright 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 child's 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:

 titleBarebones 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


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 |
         +-------------------+

G.E. Ozz Nixon Jr.

These are popular related words:
none.