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.