Pascal

From OSDev.wiki
Jump to navigation Jump to search

Is it because it's taught in many schools ? Several people seems to prefer using Pascal to C for their hobby OS, and, even if it asks a bit more setup than C, it seems it can be done ...

Historical note: The original Standard Pascal was in many ways a different language from the Object Pascal that most people today are familiar with, being much simpler but also more limited. Dr. Wirth himself did not intend Pascal for systems programming, and his ongoing language evolution were the languages Modula-2 and Oberon. Both languages are related to Pascal in a similar way as C++, C# and Java are related to C. In contrast to the C language family, in the course from Pascal to Oberon the language definition got a lot more compact, but the language itself got more powerful. Oberon-2 supports all concepts of object-oriented programming. The successors of Pascal were developed to address the weaknesses of Pascal in this regard. However, with the widespread adoption of the Object Pascal extensions (e.g.. unit, bitwise operators), many of these weaknesses (most specifically the lack of support for separate compilation) were eliminated. The reputation of Pascal as a toy language has unfairly persisted in many places however.

Oberon as a successor of Pascal has been used extensively to develop and research Operating Systems (see Native Oberon and BlueBottle).

Pascal had been used in early Apple Macs as implementation language.

Interfacing Pascal with Assembler.

Just like when Doing a kernel in C++, Pascal compilers mangle functions name to make them convey more informations (such as arguments and return types). That means if you just write

unit KernelMain;

interface

implementation

procedure kernel_main;
begin
    ...
end;

end.

You may end up with "THREADVARLIST_P$KERNEL_MAIN" rather than just "kernel_main" as a function name. If you're using FreePascal, the tool objdump can show you the symbol table of the .o file generated by the compiler, which will give you the "real" name of the function.

Alternatively, you could use compiler extra statements to enforce a "public name" to your function:

unit KernelMain;

interface

implementation

procedure kernel_main; [public, alias: 'KERNEL_MAIN'];
begin
    ...
end;

end.

Finally, simply declaring a program like you would when writing Pascal code for any normal platform will create a main routine named PASCALMAIN.

program Kernel;

uses Console,Stuff,Etc;

var
 stuff: type;

begin
{Your kernel here.}
end.

Note, too, that C and PASCAL doesn't share the same calling convention. Most notably, arguments in PASCAL are pushed from left to right while C push them from right to left. If this gets you in trouble, you can use cdecl modifier to force the compiler considering that your PASCAL procedure works like a C function (that should mainly be useful to interface pascal code with C code). Moreover, in PASCAL, the callee function is responsible from stack cleaning, while this is typically the job of the caller in C/C++ environment.

Pascal BareBones

credit flies to De Deyn Kim for the freepascal, public domain version of BareBones.

stub.asm

Assemble with nasm -f elf stub.asm -o stub.o

;/////////////////////////////////////////////////////////
;//                                                     //
;//               Freepascal barebone OS                //
;//                      stub.asm                       //
;//                                                     //
;/////////////////////////////////////////////////////////
;//
;//     By:             De Deyn Kim <kimdedeyn@skynet.be>
;//     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

compile with ppc32 -a -Aas -n -O3 -Op3 -Si -Sc -Sg -Xd -Tlinux -Rintel kernel.pas or fpc -Aelf -n -O3 -Op3 -Si -Sc -Sg -Xd -Rintel -Tlinux kernel.pas

{
/////////////////////////////////////////////////////////
//                                                     //
//               Freepascal barebone OS                //
//                      kernel.pas                     //
//                                                     //
/////////////////////////////////////////////////////////
//
//      By:             De Deyn Kim <kimdedeyn@skynet.be>
//      License:        Public domain
//
}

unit kernel;

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, 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 <kimdedeyn@skynet.be>
//      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

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;
          {The two variables below *can* be declared as a single qword variable, if your compiler supports qwords.}
          mem_lower: DWORD;
          mem_upper: DWORD;
          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;
          {Again, you can declare these as a qword.}
          base_addr_low: DWORD;
          base_addr_high: DWORD;
          {And once again, these can be made into one qword variable.}
          length_low: DWORD;
          length_high: DWORD;
          mtype: DWORD;
        end;

implementation

end.

linker script

OUTPUT_FORMAT("elf32-i386")
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 = .;
}