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 later developed another language,
Modula-2 (and it's own successor,
Oberon), 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.
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
{
/////////////////////////////////////////////////////////
// //
// 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 = .;
}
http://users.skynet.be/towp/freepascal_barebone_os.rar for the full archive.
Related threads
how to call pascal from assembler
http://www.mega-tokyo.com/forum/index.php?board=1;action=display;threadid=9743
