User:Ajxs/ada bare bones
The aim of this tutorial is to produce a small Multiboot kernel written in the Ada programming language.
Preface
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.
Prerequisites
The following section details the prerequisite software necessary for successfully building the kernel.
Cross-Compiler
- Main article: GNAT Cross-Compiler
This tutorial requires the use of a cross-compiler with Ada support targeting the i686-elf architecture. Theoretically, any Ada compiler capable of producing bare-metal ELF binaries for this architecture is suitable. The recommended compiler is GNAT, which is part of the GNU Compiler Collection maintained by the Free Software Foundation. Historically it was difficult to create cross-compiler builds for GNAT, but recent versions of GCC have made this much simpler. For a tutorial on how to set up a GNAT cross-compiler, refer to this article.
GPRbuild
This tutorial will require the use of the GPRBuild utility to build GNAT project files. GPRbuild is a generic build tool designed for the construction of multi-language systems. It is included with AdaCore's distribution of their proprietary GNAT compiler: [1] or can be built from source files obtained from AdaCore's github profile. Older versions of GNAT distributed by the Free Software Foundation were able to build GNAT project files using the gnatmake utility, however newer versions do not support all the required features.
The cross-compiler mentioned above will need to be properly integrated with GPRbuild and its associated tools. This can be accomplished by following the guide detailed here.
Ada run-time library
- Main article: Ada Runtime Library
The Ada run-time library is responsible for the implementation of the standard library as defined in the Ada Language Reference Manual. Not all features outlined in the reference manual need to be implemented for every platform. Since the kernel will be running in a freestanding environment without the support of an underlying operating system, it will be built with what is known as a a zero-footprint run-time system (commonly abbreviated as 'ZFP'). A zero-footprint RTS will typically provide only the bare-minimum functionality for the target architecture.
The following steps will detail how to create an initial zero-footprint runtime library suitable for building the kernel.
The first step is to set up the directory structure to hold the runtime library. GNAT expects the runtime library to conform to the a set directory structure. It requires the presence of two directories: adalib and adainclude. adainclude contains the runtime package specifications and source files, which are used analogously to include files in C. adalib contains the compiled binary artifacts of the runtime library.
The following code demonstrates setting up a directory structure for the RTS:
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
The best way to begin creating a new run-time library is to base it upon the existing source files obtained from the system GNAT installation, modifying them where necessary.
The following code demonstrates copying the required files from the host system's GNAT installation's run-time library into rts/src and then creating symbolic links to rts/boards/${arch}/adainclude. Where ${arch} is i386, armv6, etc. The source location will need to be modified to reflect the location of the system compiler.
system_compiler_dir="/usr/lib/gcc/x86_64-linux-gnu/8"
rts_dir="${PWD}/rts"
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 "${system_compiler_dir}/adainclude/$f" "${rts_dir}/src/"
ln -s "${rts_dir}/src/$f" "${rts_dir}/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.
Note: Do not use pragma No_Run_Time as it is obsolete and has been for a number of years 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_Propagation);
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);
The final version of gnat.adc can be downloaded from GitHub.
By passing the -r flag to the binder (inside the bare_bones.gpr file), the binder will list further restrictions you can apply to enforce further checks.
package Binder is
for Default_Switches ("Ada") use ("-r");
end Binder;
Between the GNAT Reference Manual and the Ada 2005 Reference Manual, you can find out what the various pragmas are and what they do.
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.
This pragma tells the compiler not to generate these tables.
Normalize_Scalars
Forces all scalars to be initialised, see the latest GNAT RM:Normalize_Scalars for more information.
No_Exception_Propagation
This forces the compiler to disallow any attempt to raise an exception over a subprogram boundary. All exceptions are caught with the Last_Chance_Handler subprogram. See GNAT RM:No_Exception_Propagation for more information.
The GNAT High Integrity Edition documentation states the folowing:
Exception declarations and raise statements are still permitted under this restriction. A raise statement is compiled into a call of __gnat_last_chance_handler.
I have been unable, thus far, to raise my own exceptions, although I can declare one at library level. On placing a declaration inside console.ads:
TE : exception; -- A badly named exception.
and raising inside bare_bones.adb:
raise Console.TE;
exception
when Console.TE =>
Put ("TE caught", 1, 2);
and upon compiling, I get the following:
bare_bones.adb:17:04: construct not allowed in configurable run-time mode bare_bones.adb:17:04: file a-except.ads not found bare_bones.adb:17:04: entity "Ada.Exceptions.Raise_Exception" not available gnatmake: "/home/laguest/src/mine/bare_bones/src/bare_bones.adb" compilation error make: *** [disk/boot/bare_bones] Error 4
so the compiler is looking for the Ada.Exceptions package which defines the normal language standard exception handling, not the cut down version we are using here. Do the same with one of the language defined exceptions works fine. I will file a bug at FSF for this as I believe it to be incorrect.
I have had it confirmed via a bug I filed that the programmer is not allowed to declare their own exceptions and catch them. I have experimented by creating a version of the Ada.Exceptions (a-except.ad[sb]) package:
with System;
package Ada.Exceptions is
pragma Preelaborate (Exceptions);
type Exception_Id is private;
pragma Preelaborable_Initialization (Exception_Id);
Null_Id : constant Exception_Id;
procedure Raise_Exception
(E : Exception_Id;
Message : String := "");
pragma No_Return (Raise_Exception);
private
type Exception_Id is new Natural;
Null_Id : constant Exception_Id := Exception_Id'First;
end Ada.Exceptions;
with GNAT.Source_Info;
with Last_Chance_Handler;
package body Ada.Exceptions is
procedure Raise_Exception
(E : Exception_Id;
Message : String := "") is
pragma Unreferenced (E);
pragma Unreferenced (Message);
File : String := GNAT.Source_Info.File;
Line : Positive := GNAT.Source_Info.Line;
Source_Location : String := GNAT.Source_Info.Source_Location;
Enclosing_Entity : String := GNAT.Source_Info.Enclosing_Entity;
pragma Unreferenced (File, Line, Source_Location, Enclosing_Entity);
begin
Last_Chance_Handler (System.Null_Address, 0);
loop
null;
end loop;
end Raise_Exception;
end Ada.Exceptions;
This seemed to work until I tried to catch the exception inside bare_bones.adb at which point the compiler threw up a number of warnings, resulting in a compile failure:
bare_bones.adb:23:17: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:23:17: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:30:07: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:30:07: warning: Last_Chance_Handler will be called on exception bare_bones.adb:33:17: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:33:17: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:38:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:38:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:44:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:44:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:50:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:50:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:56:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:56:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:61:42: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:61:42: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:86:21: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:86:21: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:86:30: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:86:30: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:86:39: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:86:39: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:89:26: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:89:26: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:97:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:97:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:104:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:104:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:110:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:110:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:124:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:124:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:130:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:130:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:136:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:136:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:142:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:142:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:148:20: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:148:20: warning: "Constraint_Error" may call Last_Chance_Handler bare_bones.adb:151:04: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:151:04: warning: Last_Chance_Handler will be called on exception bare_bones.adb:160:04: warning: pragma Restrictions (No_Exception_Propagation) in effect bare_bones.adb:160:04: warning: this handler can never be entered, and has been removed bare_bones.adb:160:09: violation of restriction "No_Exception_Propagation" at ../gnat.adc:10 gnatmake: "/home/laguest/src/mine/bare_bones/src/bare_bones.adb" compilation error make: *** [disk/boot/bare_bones] Error 4
So the net effect of this is to not bother with this package and only use the language defined exceptions for bare metal programming.
No_Exception_Registration
Ensures no stream operations are performed on types declared in Ada.Exceptions, see GNAT RM:No_Exception_Registration for more information.
No_Finalization
Controlled types cannot be used, see GNAT RM:No_Finalization for more information.
No_Tasking
Turns off tasking, so you cannot define tasks or protected objects or do anything related to tasking, see GNAT RM:No_Tasking for more information.
No_Protected_Types
This is pretty much here for reinforcement of the above restriction. See GNAT RM:No_Protected_Types for more information.
No_Delay
You cannot use delay statements or the calendar package, see GNAT RM:No_Delay for more information.
No_Recursion
Should be self evident, see GNAT RM:No_Recursion for more information.
No_Allocators
You cannot use dynamic memory, see GNAT RM:No_Allocators for more information.
No_Dispatch
You cannot call a subprogram using Ada's object-orientated mechanism, see GNAT RM:No_Dispatch for more information.
No_Implicit_Dynamic_Code
You cannot use nested subprograms or any other features that generate trampolines on the stack, see GNAT RM:No_Implicit_Dynamic_Code for more information.
No_Secondary_Stack
Without a secondary stack, you cannot return unconstrained types, such as arbitrary length strings, or variant records, see GNAT RM:No_Secondary_Stack for more information.
What this also means is you cannot use the runtime features 'Image and 'Val on any types, this would be useful for sending debugging info to the console, i.e. means you don't have to write your own code for converting strings to/from numbers.
I believe that it would be a good idea to have a small secondary stack defined in the assembler startup code, but define your own System.Secondary_Stack (s-secsta.ad[sb]) package which provides the correct API. Inside this package in it's executable part, you could then import the secondary stack from the assembly code, this would then be executed on elaboration of the package at the start of the kernel's execution.
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/adainclude/ 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.
Also, add the following line in the private part of the package:
private
Run_Time_Name : constant String := "Bare Bones Run Time";
According to targparm.ads, it must be the first thing after the private keyword. It should also show up in error messages in parentheses, but I've not managed to get it to show up thus far. This is useful as it should show you which RTS you are currently using, just in case you configure your build incorrectly.
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",
"-gnatec=../gnat.adc");
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.
N.B: Please note that the above lib might need it's name changing as some OSes might have built libgnat with a version number different to the one I'm using. Yours might be libgnat-4.4.a and GNAT will expect to find that, so change the name in the GPR file accordingly.
startup.s
This is PC specific so place this in the src/pc directory.
GAS
.global startup # Make the startup entry point symbol visible to the linker
# Set 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 your bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # Checksum required
header: # Must be in the first 8kb of the kernel file
.align 4, 0x90 # Pad the location counter (in the current subsection) to a 4-byte
# storage boundary. The way alignment is specified can vary with
# host system architecture.
.long MAGIC
.long FLAGS
.long CHECKSUM
# Reserve initial kernel stack space
.set STACKSIZE, 0x4000 # 0x4000 being 16k.
.lcomm stack, STACKSIZE # Reserve 16k stack on a32-bit boundary
.comm mbd, 4 # Declare common symbol mbd, allocate it 4-bytes of
# uninitialized memory.
.comm magic, 4 # Declare common symbol magic, allocate it 4-bytes of
# uninitialized memory.
startup:
movl $(stack + STACKSIZE), %esp # Set up the stack
# The following saves the contents of the registers as they will likely be
# overwritten because main is not our actual entry point, Bare_Bones is. We
# will use these 2 symbols inside Bare_Bones.
movl %eax, magic # EAX indicates to the OS that it was loaded by a Multiboot-compliant boot
# loader
movl %ebx, mbd # EBX must contain the physical address of the Multiboot information
# structure
call main # Call main (created by gnatbind)
cli # Disable interrupts. then intentionally hang the system.
# CLI only affects the interrupt flag for the processor on which it is
# executed.
hang:
hlt # Because the HLT instruction halts until an interrupt occurs, the
# combination of a CLI followed by a HLT is used to intentionally hang
# the computer if the kernel returns.
# HLT is in a loop just in case a single HLT instruction fails to execute
# for some reason, (such as in the case of an NMI).
jmp hang
multiboot.ads
I won't show the source to this package as I have made it more Ada-like and it's quite large, so I will link to the current versions, multiboot.ads and multiboot.adb.
Console
Disclaimer: I wrote this package a long time ago and have reformatted it using my current Ada programming style. I have not gone too far into the code, so it may not be the best implementation of an 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) /* Tell the linker which startup code to use, we do this as there is no way to do this (well not easily) from the GNAT tools. */ 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;
Targetting GNAT to bare metal
NOTE As of GCC 4.9.4 the code to make GNAT build for bare metal targets has been put back in, the relevant patch for 4.9.2 can be found here
Then GNAT is built, it automatically builds:
- The compiler
- The runtime
- The tools (gnatmake, gnatlink, gnatls, etc.).
Unfortunately, the toolchain cannot be built for bare machine targets (i.e. <arch>-elf|coff|pe) straight out of the box like a C compiler can be.
To enable this, a number of files need to be modified. I will show what needs to be done to support arm-elf, ready for the next section on the Raspberry Pi. This work is based on work done previously [2].
gcc/ada/adaint.c
gcc/ada/adaint.h
gcc/ada/gcc-interface/Makefile.in
gcc/ada/gsocket.h
gcc/ada/mlib-tgt-specific-bare.adb
Can we get rid of this?
gcc/ada/s-oscons-tmplt.c
gcc/ada/system-bare-armel.ads
gnattools/configure[.ac]
Raspberry Pi
Boot process
As stated in [3], the RPi boot proces is as follows:
- Power on starts the stage 1 boot loader which is on the SoC, which loads the stage 2 boot loader (bootcode.bin) into L2 cache (thus turning it on).
- bootcode.bin enables SDRAM and loads the stage 3 boot loader (loader.bin).
- loader.bin loads and executes the VideoCore firmware (start.elf).
- start.elf loads config.txt, cmdline.txt and kernel.img.
The config.txt file can contain aline "kernel=<name>" where you can name the kernel image anything you like.
Ideally for development we would use an emulator or some kind of netbooting facility so we don't have to keep switching the SD Card from the Pi to the PC and vice versa, this would get tedious really fast.
U-Boot
Seems you need serial access to the board to do this, so I won't be atempting this yet.
By following the information starting on the FreeBSD porting page, we can build u-boot for RPi.
git clone git://github.com/gonzoua/u-boot-pi.git
cd u-boot-pi
make rpi_b_config
Testing
Make sure you have built the RTS above before this next stage otherwise you won't be able to compile the kernel.
make qemu
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."
Source access
I have created a Git repository on GitHub containing the source above so you don't have to do it by hand if you don't want to.
In fact there have ben a lot of changes since I started this project and it is a better idea to grab the source from GitHub as it will be more up to date. I will leave the documents above so you can see how it's evolved and also how it works, maybe a bit clearer that it is now.
Future
- Implement a secondary stack so we can use 'Image attributes.
- Implement a 64-bit version (non EFI).
- Implement a (U)EFI version.