Ada Bare Bones: Difference between revisions
[unchecked revision] | [unchecked revision] |
No edit summary |
|||
Line 60: | Line 60: | ||
<source lang="ada"> |
<source lang="ada"> |
||
pragma Discard_Names; |
pragma Discard_Names; |
||
pragma Restrictions (No_Enumeration_Maps); |
|||
pragma Normalize_Scalars; |
pragma Normalize_Scalars; |
||
pragma Restrictions ( |
pragma Restrictions (No_Exception_Handlers); |
||
pragma Restrictions (No_Finalization); |
pragma Restrictions (No_Finalization); |
||
pragma Restrictions (No_Tasking); |
|||
pragma Restrictions (Max_Tasks => 0); |
|||
pragma Restrictions (No_Protected_Types); |
pragma Restrictions (No_Protected_Types); |
||
pragma Restrictions (No_Delay); |
pragma Restrictions (No_Delay); |
||
-- pragma Restrictions (No_Floating_Point); |
|||
pragma Restrictions (No_Recursion); |
pragma Restrictions (No_Recursion); |
||
pragma Restrictions (No_Allocators); |
pragma Restrictions (No_Allocators); |
||
Line 155: | Line 154: | ||
===Compiling the runtime=== |
===Compiling the runtime=== |
||
Create a file called gnat.gpr and copy the following into it: |
Create a file called gnat.gpr in the root directory and copy the following into it: |
||
<pre> |
<pre> |
||
Line 208: | Line 207: | ||
===startup.s=== |
===startup.s=== |
||
This is PC specific so place this in the src/pc directory. |
|||
====GAS==== |
====GAS==== |
||
Line 255: | Line 256: | ||
===Console=== |
===Console=== |
||
The following 2 files give you access to the VGA console at 80x25 characters. |
The following 2 files give you access to the VGA console at 80x25 characters. As they are PC specific, they go into the src/pc directory. |
||
====console.ads==== |
====console.ads==== |
||
<source lang="ada"> |
<source lang="ada"> |
||
with System; |
|||
-- TODO |
|||
package Console is |
|||
pragma Preelaborate (Console); |
|||
type Background_Colour is |
|||
(Black, |
|||
Blue, |
|||
Green, |
|||
Cyan, |
|||
Red, |
|||
Magenta, |
|||
Brown, |
|||
Light_Grey); |
|||
for Background_Colour use |
|||
(Black => 16#0#, |
|||
Blue => 16#1#, |
|||
Green => 16#2#, |
|||
Cyan => 16#3#, |
|||
Red => 16#4#, |
|||
Magenta => 16#5#, |
|||
Brown => 16#6#, |
|||
Light_Grey => 16#7#); |
|||
for Background_Colour'Size use 4; |
|||
type Foreground_Colour is |
|||
(Black, |
|||
Blue, |
|||
Green, |
|||
Cyan, |
|||
Red, |
|||
Magenta, |
|||
Brown, |
|||
Light_Grey, |
|||
Dark_Grey, |
|||
Light_Blue, |
|||
Light_Green, |
|||
Light_Cyan, |
|||
Light_Red, |
|||
Light_Magenta, |
|||
Yellow, |
|||
White); |
|||
for Foreground_Colour use |
|||
(Black => 16#0#, |
|||
Blue => 16#1#, |
|||
Green => 16#2#, |
|||
Cyan => 16#3#, |
|||
Red => 16#4#, |
|||
Magenta => 16#5#, |
|||
Brown => 16#6#, |
|||
Light_Grey => 16#7#, |
|||
Dark_Grey => 16#8#, |
|||
Light_Blue => 16#9#, |
|||
Light_Green => 16#A#, |
|||
Light_Cyan => 16#B#, |
|||
Light_Red => 16#C#, |
|||
Light_Magenta => 16#D#, |
|||
Yellow => 16#E#, |
|||
White => 16#F#); |
|||
for Foreground_Colour'Size use 4; |
|||
type Cell_Colour is |
|||
record |
|||
Foreground : Foreground_Colour; |
|||
Background : Background_Colour; |
|||
end record; |
|||
for Cell_Colour use |
|||
record |
|||
Foreground at 0 range 0 .. 3; |
|||
Background at 0 range 4 .. 7; |
|||
end record; |
|||
for Cell_Colour'Size use 8; |
|||
type Cell is |
|||
record |
|||
Char : Character; |
|||
Colour : Cell_Colour; |
|||
end record; |
|||
for Cell'Size use 16; |
|||
Screen_Width : constant Natural := 80; |
|||
Screen_Height : constant Natural := 25; |
|||
subtype Screen_Width_Range is Natural range 1 .. Screen_Width; |
|||
subtype Screen_Height_Range is Natural range 1 .. Screen_Height; |
|||
type Row is array (Screen_Width_Range) of Cell; |
|||
type Screen is array (Screen_Height_Range) of Row; |
|||
Video_Memory : Screen; |
|||
for Video_Memory'Address use System'To_Address (16#000B_8000#); |
|||
pragma Import (Ada, Video_Memory); |
|||
procedure Put |
|||
(Char : in Character; |
|||
X : in Screen_Width_Range; |
|||
Y : in Screen_Height_Range; |
|||
Foreground : in Foreground_Colour := White; |
|||
Background : in Background_Colour := Black); |
|||
procedure Put |
|||
(Str : in String; |
|||
X : in Screen_Width_Range; |
|||
Y : in Screen_Height_Range; |
|||
Foreground : in Foreground_Colour := White; |
|||
Background : in Background_Colour := Black); |
|||
procedure Clear (Background : in Background_Colour := Black); |
|||
end Console; |
|||
</source> |
</source> |
||
Line 266: | Line 384: | ||
<source lang="ada"> |
<source lang="ada"> |
||
package body Console is |
|||
-- TODO |
|||
procedure Put |
|||
(Char : in Character; |
|||
X : in Screen_Width_Range; |
|||
Y : in Screen_Height_Range; |
|||
Foreground : in Foreground_Colour := White; |
|||
Background : in Background_Colour := Black) is |
|||
begin |
|||
Video_Memory (Y)(X).Char := Char; |
|||
Video_Memory (Y)(X).Colour.Foreground := Foreground; |
|||
Video_Memory (Y)(X).Colour.Background := Background; |
|||
end Put; |
|||
procedure Put |
|||
(Str : in String; |
|||
X : in Screen_Width_Range; |
|||
Y : in Screen_Height_Range; |
|||
Foreground : in Foreground_Colour := White; |
|||
Background : in Background_Colour := Black) is |
|||
begin |
|||
for Index in Str'First .. Str'Last loop |
|||
Put (Str (Index), |
|||
X + Screen_Width_Range (Index) - 1, |
|||
Y, |
|||
Foreground, |
|||
Background); |
|||
end loop; |
|||
end Put; |
|||
procedure Clear (Background : in Background_Colour := Black) is |
|||
begin |
|||
for X in Screen_Width_Range'First .. Screen_Width_Range'Last loop |
|||
for Y in Screen_Height_Range'First .. Screen_Height_Range'Last loop |
|||
Put (' ', X, Y, Background => Background); |
|||
end loop; |
|||
end loop; |
|||
end Clear; |
|||
end Console; |
|||
</source> |
</source> |
||
=== |
===bare_bones.adb=== |
||
This is platform independent and therefore goes into the src directory. |
|||
<source lang="ada"> |
<source lang="ada"> |
||
with Console; use Console; |
|||
-- TODO |
|||
procedure Bare_Bones is |
|||
begin |
|||
Clear; |
|||
Put ("Hello, bare bones in Ada.", |
|||
Screen_Width_Range'First, |
|||
Screen_Height_Range'First); |
|||
end Bare_Bones; |
|||
pragma No_Return (Bare_Bones); |
|||
</source> |
</source> |
||
===linker.ld=== |
===linker.ld=== |
||
This is a PC specific script so goes into the src/pc directory. |
|||
<pre> |
<pre> |
||
OUTPUT_FORMAT(elf32-i386) |
|||
STARTUP(startup.o) |
|||
ENTRY (startup) |
ENTRY (startup) |
||
Line 283: | Line 455: | ||
. = 0x00100000; |
. = 0x00100000; |
||
.text |
.text :{ |
||
code = .; _code = .; __code = .; |
|||
{ |
|||
*(.text) |
*(.text) |
||
*(.rodata) |
|||
} |
} |
||
.rodata ALIGN (0x1000) : |
.rodata ALIGN (0x1000) : { |
||
*(.rodata) |
|||
*(.rodata*) |
|||
} |
} |
||
.data ALIGN (0x1000) : |
.data ALIGN (0x1000) : { |
||
data = .; _data = .; __data = .; |
|||
{ |
|||
*(.data) |
*(.data) |
||
} |
} |
||
.bss : |
.bss : { |
||
{ |
|||
sbss = .; |
sbss = .; |
||
bss = .; _bss = .; __bss = .; |
|||
*(COMMON) |
*(COMMON) |
||
*(.bss) |
*(.bss) |
||
ebss = .; |
ebss = .; |
||
} |
} |
||
end = .; _end = .; __end = .; |
|||
} |
} |
||
</pre> |
</pre> |
||
===makefile=== |
===makefile=== |
||
Place this file in the root directory. |
|||
<pre> |
|||
ARCH = i386 |
|||
RTS_DIR = `pwd`/rts/boards/$(ARCH) |
|||
ifeq ($(ARCH),i386) |
|||
GNATMAKE = gnatmake |
|||
AS = as |
|||
ASFLAGS = --32 -march=i386 |
|||
OBJS = obj/startup.o obj/multiboot.o obj/console.o |
|||
BOARD = pc |
|||
.PHONY: obj/multiboot.o obj/console.o |
|||
endif |
|||
all: bare_bones |
|||
bare_bones: $(OBJS) src/bare_bones.adb |
|||
$(GNATMAKE) --RTS=$(RTS_DIR) -XBoard=$(BOARD) -Pbare_bones.gpr |
|||
obj/startup.o: src/$(BOARD)/startup.s |
|||
$(AS) $(ASFLAGS) src/$(BOARD)/startup.s -o obj/startup.o |
|||
.PHONY: clean |
|||
clean: |
|||
-rm obj/* *~ bare_bones |
|||
</pre> |
|||
===bare_bones.gpr=== |
===bare_bones.gpr=== |
||
Place this file in the root directory. |
|||
<pre> |
|||
project Bare_Bones is |
|||
type Arch_Name is ("i386", "arm"); |
|||
type Board_Name is ("pc", "rpi"); |
|||
Arch : Arch_Name := "i386"; |
|||
Board : Board_Name := external ("Board"); |
|||
-- TODO: Add in a case statement that adds an arch dir to source. |
|||
case Board is |
|||
when "pc" => |
|||
for Source_Dirs use ("src", "src/pc"); |
|||
when "rpi" => |
|||
for Source_Dirs use ("src", "src/rpi"); |
|||
end case; |
|||
for Object_Dir use "obj"; |
|||
for Exec_Dir use "."; |
|||
for Main use ("bare_bones.adb"); |
|||
package Builder is |
|||
Basic_Switches := ("-gnat2005", "-g", "-x", "-a", "-gnatg", |
|||
"-gnatec=../gnat.adc", "-gnaty-I", "-gnaty+d"); |
|||
case Board is |
|||
when "pc" => |
|||
for Default_Switches ("Ada") use Basic_Switches & |
|||
("-m32", "-march=i386"); |
|||
when "rpi" => |
|||
for Default_Switches ("Ada") use Basic_Switches & |
|||
("-march=armv6zk", "-mfpu=vfp", "-mfloat-abi=hard", "-marm", |
|||
"-mcpu=arm1176jzf-s", "-mtune=arm1176jzf-s"); |
|||
end case; |
|||
end Builder; |
|||
package Compiler is |
|||
case Board is |
|||
when "pc" => |
|||
for Default_Switches ("Ada") use |
|||
("-O0", "-g", "-ggdb", "-ffunction-sections", "-fdata-sections"); |
|||
when "rpi" => |
|||
for Default_Switches ("Ada") use |
|||
("-O0", "-g", "-ggdb", "-ffunction-sections", "-fdata-sections"); |
|||
end case; |
|||
end Compiler; |
|||
-- To reduce size of final binary. |
|||
package Linker is |
|||
for Default_Switches ("Ada") use |
|||
("-Wl,--gc-sections", "-static", "-nostartfiles", "-nodefaultlibs", |
|||
"-T../src/" & Board & "/linker.ld", "-v"); |
|||
end Linker; |
|||
end Bare_Bones; |
|||
</pre> |
|||
==Testing== |
|||
Make sure you have built the RTS above before this next stage otherwise you won'g have a kernel. |
|||
<source lang="bash"> |
|||
make |
|||
qemu -kernel bare_bones |
|||
</source> |
|||
On the QEMU window, it should clear the screen, the the cursor won't move so it will be in the middle of the screen, in the top-left corner will be the message "Hello, bare bones in Ada." |
|||
[[Category:Bare bones tutorials]] |
[[Category:Bare bones tutorials]] |
Revision as of 19:24, 13 June 2012
Difficulty level |
---|
![]() Medium |
Kernel Designs |
---|
Models |
Other Concepts |
In this tutorial we will compile a simple Ada kernel and boot it.
WAIT! Have you read Getting Started, Beginner Mistakes, and some of the related OS theory?
Preface
This tutorial is based on my multiboot kernel which I developed some time ago and placed on my site [1] and will also be the basis for my own kernel TAMP.
One of the first things people ask on the Ada IRC channel on Freenode is "Can Ada be used for OS development?" to which the answer is a resounding yes. But there are 2 problems:
- The people asking this question are new to Ada, and
- GNAT is not the easiest compiler to build.
Therefore these users don't understand what it takes to get the compiler into a useable state.
As you may have seen from other bare bones tutorials on this site, they state that you must have a compiler built which can handle ELF files, the usual way is by building GCC which targets i386-elf or some other similar architecture. The problem here is that GNAT will not build for these targets out of the box without messing with it's makefile. You have to do this as the makefile builds the RTS and then the gnat tools (gnatmake, gnatbind, et al).
So, for this tutorial, we will use the system GNAT compiler to build for i386 and later I will show how to build an arm-elf compiler and tools. I am on Debian testing 64-bit with GNAT 4.6.
GNAT and the Ada runtime system (RTS)
For this kernel we will be configuring a zero footprint RTS profile. This basically means, we have a compiler, tools and not much else.
Directory structure
We need a place to structure this kernel,
mkdir -p bare_bones/src/pc
cd bare_bones
mkdir -p rts/boards/i386/adalib
mkdir -p rts/boards/i386/adainclude
mkdir -p rts/src
mkdir -p obj
RTS files to copy
You will need to copy the following files from your compiler's RTS directory into rts/src and then create symbolic links from them to rts/boards/<arch>/adainclude where your arch is i386 or arm, etc.
N.B: You need to modify the location where these files are copied from, I've just used the location from my machine, which is most likely different to yours.
for f in "ada.ads" "a-unccon.ads" "a-uncdea.ads" "gnat.ads" "g-souinf.ads" "interfac.ads" "s-atacco.adb" "s-atacco.ads" "s-maccod.ads" "s-stoele.adb" "s-stoele.ads"
do
cp /usr/lib/gcc/x86_64-linux-gnu/4.6/adainclude/$f rts/src/
ln -s `pwd`/rts/src/$f `pwd`/rts/boards/i386/adainclude/$f
done
Files
gnat.adc
This file in the root directory of the build tells GNAT there are some configuration pragmas to apply to the build. These pragmas can also be placed at the start of your custom sytem.ads (see below), but we'll place them here for now.
What these do is to tell GNAT how much of the RTS we can use in our kernel, which is not a lot really.
pragma Discard_Names;
pragma Restrictions (No_Enumeration_Maps);
pragma Normalize_Scalars;
pragma Restrictions (No_Exception_Handlers);
pragma Restrictions (No_Finalization);
pragma Restrictions (No_Tasking);
pragma Restrictions (No_Protected_Types);
pragma Restrictions (No_Delay);
pragma Restrictions (No_Recursion);
pragma Restrictions (No_Allocators);
pragma Restrictions (No_Dispatch);
pragma Restrictions (No_Implicit_Dynamic_Code);
pragma Restrictions (No_Secondary_Stack);
Discard_Names
In Ada, the compiler generates strings for various data types, e.g. enumerations, these strings can then be used in I/O.
type Fruit is (Orange, Banana, Apple);
-- Ada defines the following strings, "Orange", "Banana" and "Apple" in an array.
-- These strings can be accessed using the 'Image attribute, as in
Put (Fruit'Image (Orange));
-- Prints "Orange" to the console.
Normalize_Scalars
Forces all scalars to be initialised, se the latest GNAT RM:Normalize_Scalars for more information.
No_Exception_Propagation
No_Finalization
Max_Tasks
No_Protected_Types
No_Delay
No_Recursion
No_Allocators
No_Dispatch
No_Implicit_Dynamic_Code
No_Secondary_Stack
system.ads
Every Ada program must have access to the System package, this essentially tells the compiler what kind of hardware we are building for, therefore there will be 1 system.ads file per architecture your kernel supports.
Copy a system.ads from GCC that matches the target you are working with, in our case this is gcc-<version>/gcc/ada/system-linux-x86.ads, name it system.ads and place it into rts/boards/i386/ we need to edit this a bit.
We don't need to change anything from the top as all the integer sizes should be correct. Go to the private part of the spec and change the following values:
- Command_Line_Args => False
- Configurable_Run_Time => True
- Exit_Status_Supported => False
- Stack_Check_Probes => False
- Suppress_Standard_Library => True
- ZCX_By_Default => False
- GCC_ZCX_Support => False
For more information on these options, see gcc-<version>/gcc/ada/targparm.ads.
Last chance handler
When you start to write and compile Ada using this custom environment, the compiler will automatically place calls from the runtime into your final binary (this is what the compiler normally does, but we've restricted it a lot). One of these calls is to Last_Chance_Handler so create 2 new files and place into rts/boards/<arch>/adainclude, as follows.
last_chance_handler.ads
with System;
procedure Last_Chance_Handler
(Source_Location : System.Address; Line : Integer);
pragma Export (C, Last_Chance_Handler, "__gnat_last_chance_handler");
last_chance_handler.adb
procedure Last_Chance_Handler
(Source_Location : System.Address; Line : Integer) is
pragma Unreferenced (Source_Location, Line);
begin
-- TODO: Add in code to dump the info to serial/screen which
-- is obviously board specific.
loop
null;
end loop;
end Last_Chance_Handler;
As you can see, the meat of the handler is actualy a null loop at the moment, this is something you need to complete for your OS kernel and also, per platform.
Compiling the runtime
Create a file called gnat.gpr in the root directory and copy the following into it:
library project gnat is type Arch_Name is ("i386", "arm"); type Board_Name is ("pc", "rpi"); Arch : Arch_Name := "i386"; Board : Board_Name := external ("Board"); case Board is when "pc" => Arch := "i386"; when "rpi" => Arch := "arm"; end case; for Source_Dirs use ("rts/boards/" & Arch & "/adainclude"); for Object_Dir use "obj"; --"rts/boards/" & Arch & "/adalib"; package Builder is Basic_Switches := ("-gnat2005", "-g", "-x", "-a", "-gnatg"); case Board is when "pc" => for Default_Switches ("Ada") use Basic_Switches & ("-m32", "-march=i386"); when "rpi" => for Default_Switches ("Ada") use Basic_Switches & ("-march=armv6zk", "-mfpu=vfp", "-mfloat-abi=hard", "-marm", "-mcpu=arm1176jzf-s", "-mtune=arm1176jzf-s"); end case; end Builder; package Compiler is for Default_Switches ("Ada") use ("-O2", "-ffunction-sections", "-fdata-sections"); end Compiler; for Library_Kind use "static"; for Library_Name use "gnat-4.6"; for Library_Dir use "rts/boards/" & Arch & "/adalib"; end gnat;
Now compile with the following command:
gnatmake -XBoard=pc -Pgnat.gpr
Inside rts/boards/i386/adainclude/ you should have the RTS sources symbolically linked along with the custom last_chance_hander and system files. Inside rts/boards/i386/adalib/ you should have the libgnat-4.6.a and also *.ali matching the source which are required by GNAT.
startup.s
This is PC specific so place this in the src/pc directory.
GAS
.global startup # making entry point visible to linker
# setting up the Multiboot header - see GRUB docs for details
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
.set MAGIC, 0x1BADB002 # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum required
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
# reserve initial kernel stack space
.set STACKSIZE, 0x4000 # that is, 16k.
# On my binutils the following line didn't work as the .lcomm instruction takes 2 parameters.
#.lcomm stack, STACKSIZE, 32 # reserve 16k stack on a doubleword boundary
.lcomm stack, STACKSIZE # reserve 16k stack
.comm mbd, 4 # we will use this in kmain
.comm magic, 4 # we will use this in kmain
startup:
movl $(stack + STACKSIZE), %esp # set up the stack
movl %eax, magic # Multiboot magic number
movl %ebx, mbd # Multiboot data structure
call main # call main created by gnatbind
cli
hang:
hlt # halt machine should kernel return
jmp hang
multiboot.ads
-- TODO
Console
The following 2 files give you access to the VGA console at 80x25 characters. As they are PC specific, they go into the src/pc directory.
console.ads
with System;
package Console is
pragma Preelaborate (Console);
type Background_Colour is
(Black,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
Light_Grey);
for Background_Colour use
(Black => 16#0#,
Blue => 16#1#,
Green => 16#2#,
Cyan => 16#3#,
Red => 16#4#,
Magenta => 16#5#,
Brown => 16#6#,
Light_Grey => 16#7#);
for Background_Colour'Size use 4;
type Foreground_Colour is
(Black,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
Light_Grey,
Dark_Grey,
Light_Blue,
Light_Green,
Light_Cyan,
Light_Red,
Light_Magenta,
Yellow,
White);
for Foreground_Colour use
(Black => 16#0#,
Blue => 16#1#,
Green => 16#2#,
Cyan => 16#3#,
Red => 16#4#,
Magenta => 16#5#,
Brown => 16#6#,
Light_Grey => 16#7#,
Dark_Grey => 16#8#,
Light_Blue => 16#9#,
Light_Green => 16#A#,
Light_Cyan => 16#B#,
Light_Red => 16#C#,
Light_Magenta => 16#D#,
Yellow => 16#E#,
White => 16#F#);
for Foreground_Colour'Size use 4;
type Cell_Colour is
record
Foreground : Foreground_Colour;
Background : Background_Colour;
end record;
for Cell_Colour use
record
Foreground at 0 range 0 .. 3;
Background at 0 range 4 .. 7;
end record;
for Cell_Colour'Size use 8;
type Cell is
record
Char : Character;
Colour : Cell_Colour;
end record;
for Cell'Size use 16;
Screen_Width : constant Natural := 80;
Screen_Height : constant Natural := 25;
subtype Screen_Width_Range is Natural range 1 .. Screen_Width;
subtype Screen_Height_Range is Natural range 1 .. Screen_Height;
type Row is array (Screen_Width_Range) of Cell;
type Screen is array (Screen_Height_Range) of Row;
Video_Memory : Screen;
for Video_Memory'Address use System'To_Address (16#000B_8000#);
pragma Import (Ada, Video_Memory);
procedure Put
(Char : in Character;
X : in Screen_Width_Range;
Y : in Screen_Height_Range;
Foreground : in Foreground_Colour := White;
Background : in Background_Colour := Black);
procedure Put
(Str : in String;
X : in Screen_Width_Range;
Y : in Screen_Height_Range;
Foreground : in Foreground_Colour := White;
Background : in Background_Colour := Black);
procedure Clear (Background : in Background_Colour := Black);
end Console;
console.adb
package body Console is
procedure Put
(Char : in Character;
X : in Screen_Width_Range;
Y : in Screen_Height_Range;
Foreground : in Foreground_Colour := White;
Background : in Background_Colour := Black) is
begin
Video_Memory (Y)(X).Char := Char;
Video_Memory (Y)(X).Colour.Foreground := Foreground;
Video_Memory (Y)(X).Colour.Background := Background;
end Put;
procedure Put
(Str : in String;
X : in Screen_Width_Range;
Y : in Screen_Height_Range;
Foreground : in Foreground_Colour := White;
Background : in Background_Colour := Black) is
begin
for Index in Str'First .. Str'Last loop
Put (Str (Index),
X + Screen_Width_Range (Index) - 1,
Y,
Foreground,
Background);
end loop;
end Put;
procedure Clear (Background : in Background_Colour := Black) is
begin
for X in Screen_Width_Range'First .. Screen_Width_Range'Last loop
for Y in Screen_Height_Range'First .. Screen_Height_Range'Last loop
Put (' ', X, Y, Background => Background);
end loop;
end loop;
end Clear;
end Console;
bare_bones.adb
This is platform independent and therefore goes into the src directory.
with Console; use Console;
procedure Bare_Bones is
begin
Clear;
Put ("Hello, bare bones in Ada.",
Screen_Width_Range'First,
Screen_Height_Range'First);
end Bare_Bones;
pragma No_Return (Bare_Bones);
linker.ld
This is a PC specific script so goes into the src/pc directory.
OUTPUT_FORMAT(elf32-i386) STARTUP(startup.o) ENTRY (startup) SECTIONS { . = 0x00100000; .text :{ code = .; _code = .; __code = .; *(.text) *(.rodata) } .rodata ALIGN (0x1000) : { *(.rodata) } .data ALIGN (0x1000) : { data = .; _data = .; __data = .; *(.data) } .bss : { sbss = .; bss = .; _bss = .; __bss = .; *(COMMON) *(.bss) ebss = .; } end = .; _end = .; __end = .; }
makefile
Place this file in the root directory.
ARCH = i386 RTS_DIR = `pwd`/rts/boards/$(ARCH) ifeq ($(ARCH),i386) GNATMAKE = gnatmake AS = as ASFLAGS = --32 -march=i386 OBJS = obj/startup.o obj/multiboot.o obj/console.o BOARD = pc .PHONY: obj/multiboot.o obj/console.o endif all: bare_bones bare_bones: $(OBJS) src/bare_bones.adb $(GNATMAKE) --RTS=$(RTS_DIR) -XBoard=$(BOARD) -Pbare_bones.gpr obj/startup.o: src/$(BOARD)/startup.s $(AS) $(ASFLAGS) src/$(BOARD)/startup.s -o obj/startup.o .PHONY: clean clean: -rm obj/* *~ bare_bones
bare_bones.gpr
Place this file in the root directory.
project Bare_Bones is type Arch_Name is ("i386", "arm"); type Board_Name is ("pc", "rpi"); Arch : Arch_Name := "i386"; Board : Board_Name := external ("Board"); -- TODO: Add in a case statement that adds an arch dir to source. case Board is when "pc" => for Source_Dirs use ("src", "src/pc"); when "rpi" => for Source_Dirs use ("src", "src/rpi"); end case; for Object_Dir use "obj"; for Exec_Dir use "."; for Main use ("bare_bones.adb"); package Builder is Basic_Switches := ("-gnat2005", "-g", "-x", "-a", "-gnatg", "-gnatec=../gnat.adc", "-gnaty-I", "-gnaty+d"); case Board is when "pc" => for Default_Switches ("Ada") use Basic_Switches & ("-m32", "-march=i386"); when "rpi" => for Default_Switches ("Ada") use Basic_Switches & ("-march=armv6zk", "-mfpu=vfp", "-mfloat-abi=hard", "-marm", "-mcpu=arm1176jzf-s", "-mtune=arm1176jzf-s"); end case; end Builder; package Compiler is case Board is when "pc" => for Default_Switches ("Ada") use ("-O0", "-g", "-ggdb", "-ffunction-sections", "-fdata-sections"); when "rpi" => for Default_Switches ("Ada") use ("-O0", "-g", "-ggdb", "-ffunction-sections", "-fdata-sections"); end case; end Compiler; -- To reduce size of final binary. package Linker is for Default_Switches ("Ada") use ("-Wl,--gc-sections", "-static", "-nostartfiles", "-nodefaultlibs", "-T../src/" & Board & "/linker.ld", "-v"); end Linker; end Bare_Bones;
Testing
Make sure you have built the RTS above before this next stage otherwise you won'g have a kernel.
make
qemu -kernel bare_bones
On the QEMU window, it should clear the screen, the the cursor won't move so it will be in the middle of the screen, in the top-left corner will be the message "Hello, bare bones in Ada."