Ada Bare Bones: Difference between revisions
[unchecked revision] | [unchecked revision] |
Content deleted Content added
No edit summary |
m Bot: Replace deprecated source tag with syntaxhighlight |
||
(35 intermediate revisions by 11 users not shown) | |||
Line 1:
''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|GNAT Cross-Compiler}}
This tutorial requires the use of a [[GCC_Cross-Compiler|cross-compiler]] with Ada support targeting the <tt>i686-elf</tt> architecture. Theoretically, any Ada compiler capable of producing bare-metal ELF binaries for this architecture is suitable. The recommended compiler is [https://www.gnu.org/software/gnat/ 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 [[GNAT Cross-Compiler|this article]].
==== GPRbuild ====
This tutorial will require the use of the [https://docs.adacore.com/gprbuild-docs/html/gprbuild_ug.html 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: [https://www.adacore.com/download] or can be built from source files obtained from AdaCore's [https://github.com/AdaCore/gprbuild github profile]. Older versions of GNAT distributed by the Free Software Foundation were able to build GNAT project files using the <tt>gnatmake</tt> 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 [[GNAT_Cross-Compiler#GPRbuild_Integration|here]].
== Ada run-time library ==
{{Main|Ada Runtime Library}}
The Ada run-time library is responsible for the implementation of the standard library as defined in the [http://ada-auth.org/standards/12rm/html/RM-TOC.html 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: <tt>adalib</tt> and <tt>adainclude</tt>. <tt>adainclude</tt> contains the runtime package specifications and source files, which are used analogously to include files in C. <tt>adalib</tt> contains the compiled binary artifacts of the runtime library.
The following code demonstrates setting up a directory structure for the RTS:
<syntaxhighlight lang="bash">
mkdir -p bare_bones/src/pc
cd bare_bones
Line 34 ⟶ 39:
mkdir -p rts/src
mkdir -p obj
</syntaxhighlight>
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.
<syntaxhighlight lang="bash">
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" \
"s-stoele.ads"
do
cp
ln -s
done
</syntaxhighlight>
==Files==
Line 54 ⟶ 62:
===gnat.adc===
During compilation the GNAT compiler will search for a file named <tt>gnat.adc</tt> within the current working directory. This file is expected to contain one or more configuration pragmas that will be applied to the current compilation. These pragma directives instruct the compiler what features of the runtime should be restricted. This is of particular importance to kernel development since many of Ada's features are not supported by a freestanding, bare-metal environment.
These pragma directives can alternatively be placed at the start of the runtime library's <tt>system.ads</tt> file (see below), however convention dictates using the <tt>gnat.adc</tt> file for this purpose.
Additionally, it is possible to apply additional global configuration files by appending the following line to the <tt>Builder</tt> package in a GNAT project file:
<
package Builder is
-- ...
for Global_Configuration_Pragmas use "kernel.adc";
end Builder;
</syntaxhighlight>
It is also possible to instruct the compiler to apply additional files containing configuration pragmas to the current compilation using the switch <tt>-gnatec=path</tt> on the command line. These configuration pragmas are applied in addition to those found in <tt>gnat.adc</tt>, if it is present.
More information on configuration files can be found in the GNAT documentation: [https://gcc.gnu.org/onlinedocs/gnat_ugn/Handling-of-Configuration-Pragmas.html]
The GNAT Reference Manual and the Ada Reference Manual provide information on the various configuration pragmas.
See below for a list of restriction pragmas useful for a bare bones kernel and runtime:
<syntaxhighlight lang="ada">
pragma Discard_Names;
pragma Restrictions (No_Enumeration_Maps);
Line 74 ⟶ 95:
pragma Restrictions (No_Implicit_Dynamic_Code);
pragma Restrictions (No_Secondary_Stack);
</syntaxhighlight>
'''Note:''' ''Do not use <tt>pragma No_Run_Time</tt>. It is obsolete.''
Below is an explanation of these configuration pragmas:
{| {{wikitable}}
! Restriction
! Purpose
|-
| Discard_Names || The Ada compiler automatically generates strings containing the names of enumerated data types, among others. These strings can be used in I/O.
<
type Fruit is (Orange, Banana, Apple);
-- Ada defines the following strings, "Orange", "Banana" and "Apple" in an array.
Line 95 ⟶ 114:
Put (Fruit'Image (Orange));
-- Prints "Orange" to the console.
</syntaxhighlight>
This
|-
| Normalize_Scalars || Forces all scalars to be initialised. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/Pragma-Normalize_005fScalars.html#Pragma-Normalize_005fScalars GNAT RM:Normalize_Scalars] for more information.
|-
| No_Exception_Propagation || This directive forces the compiler to disallow any attempt to raise an exception over a subprogram boundary. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fException_005fPropagation.html#No_005fException_005fPropagation GNAT RM:No_Exception_Propagation] for more information.
Note: The [http://docs.adacore.com/gnat-hie-docs/html/gnathie_ug_3.html#SEC8 GNAT High Integrity Edition] documentation states the following:
<blockquote>
Exception declarations and raise statements are still permitted under this restriction. A raise statement is compiled into a call of <tt>__gnat_last_chance_handler</tt>.
</blockquote>
All exceptions that are not handled with an explicit exception handler within its subprogram will be caught with the <tt>Last_Chance_Handler</tt> subprogram. This will cause a warning to be issued at compile time.
|-
| No_Exception_Registration || Ensures no stream operations are performed on types declared in Ada.Exceptions. See [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fException_005fRegistration.html#No_005fException_005fRegistration GNAT RM:No_Exception_Registration] for more information.
|-
| No_Finalization
| Restricts the use of controlled types. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fFinalization.html#No_005fFinalization GNAT RM:No_Finalization] for more information.
|-
| No_Tasking
| This directive restricts all features related to tasking, including the use of protected objects. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fTasking.html#No_005fTasking GNAT RM:No_Tasking] for more information.
|-
| No_Protected_Types
| This reinforces the above restriction. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fProtected_005fTypes.html#No_005fProtected_005fTypes GNAT RM:No_Protected_Types] for more information.
|-
| No_Delay
| Restricts the use of <tt>delay</tt> statements or the calendar package. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fDelay.html#No_005fDelay GNAT RM:No_Delay] for more information.
|-
| No_Recursion
| Restricts the use of recursion. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fRecursion.html#No_005fRecursion GNAT RM:No_Recursion] for more information.
|-
| No_Allocators
| Restricts the use of dynamic memory. This is essential for a bare-metal application, since there is no underlying facility for allocation of dynamic memory. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fAllocators.html#No_005fAllocators GNAT RM:No_Allocators] for more information.
|-
| No_Dispatch
| Disallows calling a subprogram using Ada's object-orientated mechanism. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fDispatch.html#No_005fDispatch GNAT RM:No_Dispatch] for more information.
|-
| No_Implicit_Dynamic_Code
| Disallows nested subprograms or any other features that generate trampolines on the stack. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fImplicit_005fDynamic_005fCode.html#No_005fImplicit_005fDynamic_005fCode GNAT RM:No_Implicit_Dynamic_Code] for more information.
|-
| No_Secondary_Stack
| Ada uses a ''secondary stack'' to return unconstrained types, such as arbitrary length strings or variant records. This directive instructs the compiler that there is no secondary stack present, and to restrict the use of code that requires one. Refer to [http://gcc.gnu.org/onlinedocs/gcc-4.7.0/gnat_rm/No_005fSecondary_005fStack.html#No_005fSecondary_005fStack GNAT RM:No_Secondary_Stack] for more information.
|}
Passing the <tt>-r</tt> flag to the binder instructs it to emit a list of further restrictions that are possible to apply to the project.
<syntaxhighlight lang="ada">
package Binder is
for Default_Switches ("Ada") use ("-r");
end Binder;
</syntaxhighlight>
===system.ads===
Line 289 ⟶ 171:
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:
Line 305 ⟶ 187:
Also, add the following line in the private part of the package:
<
private
Run_Time_Name : constant String := "Bare Bones Run Time";
</syntaxhighlight>
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.
Line 315 ⟶ 197:
===Last chance handler===
The Ada runtime requires the presence of a <tt>Last_Chance_Handler</tt> subprogram. This subprogram is used as a handler for any exceptions that are not explicitly handled within their subprogram. These calls to the <tt>Last_Chance_Handler</tt> procedure in the case of unhandled exceptions will be automatically generated by the compiler.
In order to facilitate this the last chance handler procedure must be defined somewhere within the program. Typically this is defined within the runtime. The last chance handler procedure may have any name, however the compiler will search for a procedure with external linkage with the name <tt>__gnat_last_chance_handler</tt>.
Create the following files in the <tt>rts/boards/${arch}/adainclude</tt>:
====last_chance_handler.ads====
<
with System;
Line 325 ⟶ 211:
(Source_Location : System.Address; Line : Integer);
pragma Export (C, Last_Chance_Handler, "__gnat_last_chance_handler");
</syntaxhighlight>
====last_chance_handler.adb====
<
procedure Last_Chance_Handler
(Source_Location : System.Address; Line : Integer) is
pragma Unreferenced (Source_Location, Line);
begin
-- TODO: Add in board-specific code to dump
loop
null;
end loop;
end Last_Chance_Handler;
</syntaxhighlight>
The contents of the <tt>Last_Chance_Handler</tt> procedure will need to be tailored to the specific platform of the kernel. Typically this procedure will dump information regarding the exception to output media such as a [[Serial_Ports|serial port]].
===Compiling the runtime===
Create a file called <tt>gnat.gpr</tt> in the root directory
<
library project gnat is
type Arch_Name is ("i386", "arm");
Line 389 ⟶ 274:
for Library_Dir use "rts/boards/" & Arch & "/adalib";
end gnat;
</syntaxhighlight>
Now compile with the following command:
<
</syntaxhighlight>
Inside <tt>rts/boards/i386/adainclude/
''Note: Please note that the above lib might need it's name changed as some OSes might have built libgnat with a version number different to the one used here. The system libnat version may be <tt>libgnat-4.4.a</tt> and GNAT will expect to find that, so change the name in the GPR file accordingly.''
===startup.s===
This is the main system bootstrapping code. This version is PC specific, so place this in the <tt>src/pc</tt> directory.
''Note: This source file is written using GAS syntax.''
<
.global startup #
#
.set ALIGN, 1<<0 #
.set MEMINFO, 1<<1 #
.set FLAGS, ALIGN | MEMINFO #
.set MAGIC, 0x1BADB002 # 'magic number' lets your bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) #
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
#
.set STACKSIZE, 0x4000 #
.lcomm stack, STACKSIZE # Reserve 16k stack on a32-bit boundary
.comm
startup:
movl $(stack + STACKSIZE), %esp #
# 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 only affects the interrupt flag for the processor on which it is
# executed.
hang:
hlt #
# 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
</syntaxhighlight>
===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, [https://github.com/Lucretia/bare_bones/blob/master/src/pc/multiboot.ads multiboot.ads] and [https://github.com/Lucretia/bare_bones/blob/master/src/pc/multiboot.adb multiboot.adb].
===Console===
Line 455 ⟶ 357:
====console.ads====
<
with System;
Line 574 ⟶ 476:
procedure Clear (Background : in Background_Colour := Black);
end Console;
</syntaxhighlight>
====console.adb====
<
package body Console is
procedure Put
Line 617 ⟶ 519:
end Clear;
end Console;
</syntaxhighlight>
===bare_bones.adb===
Line 623 ⟶ 525:
This is platform independent and therefore goes into the src directory.
<
with Console; use Console;
Line 635 ⟶ 537:
end Bare_Bones;
pragma No_Return (Bare_Bones);
</syntaxhighlight>
===linker.ld===
Line 683 ⟶ 585:
Place this file in the root directory.
<
ARCH = i386
RTS_DIR = `pwd`/rts/boards/$(ARCH)
ifeq ($(ARCH),i386)
GPRBUILD = gprbuild
AS = as
ASFLAGS = --32 -march=i386
Line 702 ⟶ 604:
bare_bones: $(OBJS) src/bare_bones.adb
$(
obj/startup.o: src/$(BOARD)/startup.s
Line 711 ⟶ 613:
clean:
-rm obj/* *~ bare_bones
</syntaxhighlight>
===bare_bones.gpr===
Line 717 ⟶ 619:
Place this file in the root directory.
<
project Bare_Bones is
type Arch_Name is ("i386", "arm");
Line 772 ⟶ 674:
end Linker;
end Bare_Bones;
</syntaxhighlight>
==Raspberry Pi==
===Boot process===
As stated in [http://www.raspberrypi.org/phpBB3/viewtopic.php?f=63&t=6685], 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 [http://kernelnomicon.org/?p=92 FreeBSD] porting page, we can build u-boot for RPi.
<syntaxhighlight lang="bash">
git clone git://github.com/gonzoua/u-boot-pi.git
cd u-boot-pi
make rpi_b_config
</syntaxhighlight>
==Testing==
Line 778 ⟶ 709:
Make sure you have built the RTS above before this next stage otherwise you won't be able to compile the kernel.
<
make qemu
</syntaxhighlight>
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."
Line 790 ⟶ 719:
I have created a Git repository on [https://github.com/Lucretia/bare_bones 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.
==Next Steps==
A useful next step for further developing the bare bones kernel outlined in this article is implementing capability for using the <tt>'Image</tt> attributes on scalar types. This facilitates the printing of integers in string form, which is extremely useful for kernel debugging. A simple guide on how to accomplish this is detailed [[Ada_Runtime_Library#Image_attributes|here]].
[[Category:Bare bones tutorials]]
|