Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting up a bare metal x86 Ada toolchain

Tags:

bare-metal

ada

Please forgive the somewhat broad question. I'm wondering how to create an Ada toolchain targeting bare-metal x86. I've seen Lucretia's Ada Bare Bones tutorial on osdev.org, which provides some useful information about building a suitable runtime for bare-metal development. This aspect is quite straightforward, but I'm a little unsure about how to build a cross compiler for the platform, or if this is even necessary.

Am I correct in my assumption that creating a "freestanding" binary is done by compiling with the right kind of RTS? If I were to create/utilise a proper freestanding RTS, would it be suitable to use either the out-of-the-box AdaCore or FSF GNAT targeting x86? Any help understanding this would be greatly appreciated.

like image 453
ajxs Avatar asked Jun 23 '19 07:06

ajxs


1 Answers

First of all, please note that I'm note an expert in bare-metal programming, but as this is interesting, I'll give it a try. That being said, I don't think you need a cross compiler. A native platform compiler (e.g. GNAT CE 2019 for Linux x86-64) will just do.

To illustrate this, you might want to recreate the multiboot/hello_world example found here on GitHub in Ada. Here are the steps I took on my Debian machine with GNAT CE 2019 installed to get this working.

First of all I installed some necessary packages (QEMU, NASM and GNU xorriso) and cloned the repository mentioned above:

$ sudo apt-get install qemu nasm xorriso
$ git clone https://github.com/cirosantilli/x86-bare-metal-examples.git

Then, within the repository, I switched to the directory multiboot/hello-world, built the example as-is and executed the resulting image in QEMU to check if everything was setup correctly:

multiboot/hello-world $ make
multiboot/hello-world $ make run

The result was a QEMU window popping up that said hello world in the top-left corner. I proceeded by closing QEMU and run make clean to clean up.

I then removed main.c and replaced it by the Ada translation main.adb:

with System.Storage_Elements;

procedure Main is

   --  Suppress some checks to prevent undefined references during linking to
   --
   --    __gnat_rcheck_CE_Range_Check
   --    __gnat_rcheck_CE_Overflow_Check
   --
   --  These are Ada Runtime functions (see also GNAT's a-except.adb).

   pragma Suppress (Index_Check);
   pragma Suppress (Overflow_Check);


   --  See also:
   --    https://en.wikipedia.org/wiki/VGA-compatible_text_mode
   --    https://en.wikipedia.org/wiki/Color_Graphics_Adapter#Color_palette

   type Color is (BLACK, BRIGHT);

   for Color'Size use 4;
   for Color use (BLACK => 0, BRIGHT => 7);


   type Text_Buffer_Char is
      record
         Ch : Character;
         Fg : Color;
         Bg : Color;
      end record;   

   for Text_Buffer_Char use
      record
         Ch at 0 range 0 .. 7;
         Fg at 1 range 0 .. 3;
         Bg at 1 range 4 .. 7;
      end record;


   type Text_Buffer is
     array (Natural range <>) of Text_Buffer_Char;


   COLS : constant := 80;
   ROWS : constant := 24;   

   subtype Col is Natural range 0 .. COLS - 1;
   subtype Row is Natural range 0 .. ROWS - 1;


   Output : Text_Buffer (0 .. (COLS * ROWS) - 1);
   for Output'Address use System.Storage_Elements.To_Address (16#B8000#);


   --------------
   -- Put_Char --
   --------------

   procedure Put_Char (X : Col; Y : Row; Fg, Bg : Color; Ch : Character) is
   begin
      Output (Y * COLS + X) := (Ch, Fg, Bg);
   end Put_Char;

   ----------------
   -- Put_String --
   ----------------

   procedure Put_String (X : Col; Y : Row; Fg, Bg : Color; S : String) is
      C : Natural := 0;
   begin
      for I in S'Range loop
         Put_Char (X + C, Y, Fg, Bg, S (I));
         C := C + 1;
      end loop;
   end Put_String;

   -----------
   -- Clear --
   -----------

   procedure Clear (Bg : Color) is
   begin
      for X in Col'Range loop
         for Y in Row'Range loop
            Put_Char (X, Y, Bg, Bg, ' ');
         end loop;
      end loop;
   end Clear;


begin

   Clear (BLACK);
   Put_String (0, 0, BRIGHT, BLACK, "Ada says: Hello world!");

   --  Loop forever.
   while (True) loop
      null;
   end loop;

end Main;

Because we're running Ada, I had to change entry.asm and replaced the following lines to make sure that that the entry point of the Ada program instead of the C program was invoked. The entry point of the Ada program emitted by GNAT is _ada_main (see output of objdump -t main.o after compilation):

-- extern main
++ extern _ada_main

[...]

-- call main
++ call _ada_main

In the Makefile I replaced the following lines to properly compile and link the Ada program. Note that I compile to i386 (using the -m32 switch) and request the linker to emit an elf_i386 executable as the processor will not execute 64-bit instructions directly after startup:

-- ld -m elf_i386 -nostdlib -T linker.ld -o '$@' $^
++ ld -m elf_i386 -T linker.ld -o '$@' $^

[...]

-- main.o: main.c
-- <TAB>gcc -c -m32 -std=c99 -ffreestanding -fno-builtin -Os -o '$@' -Wall -Wextra '$<'
++ main.o: main.adb
++ <TAB>gcc -c -m32 -Os -o '$@' -Wall -Wextra '$<'

[...]

-- rm -f *.elf *.o iso/boot/*.elf *.img
++ rm -f *.ali *.elf *.o iso/boot/*.elf *.img

NOTE: Mind the tabs (indicated with <TAB>) before gcc. make is picky on this subject!

I then again subsequently invoked make and then make run to see a QEMU window pop up, but now showing the text:

Ada says: Hello world!

This Ada program executed bare-metal (in IA-32 Real Mode)! I then took the demonstration even further by converting main.img to a VirtualBox disk (VDI) using

VBoxManage convertfromraw main.img main.vdi --variant Fixed

and then created a simple VM (of type "other" and version "other/unknown") with main.vdi as its disk. I booted the VM and (once again) saw the text "Ada says: Hello world!" pop up.

Hence, given the result of above, I think that the compiler is not the main problem when programming x86 bare-metal. I rather think that the main challenges are:

  • Obtaining a proper Ada Runtime (e.g. zero footprint; ZFP) that does not link to any OS libraries (e.g. C standard library; libc). I don't know any, but some might exist out-of-the box. I'm not sure if the one on OSDev.org is complete to the level of a ZFP runtime. For simple programs as the one above, you can omit the runtime (as I did in this example) if you're willing to suppress checks (see comment in source code).

  • Getting the x86 processor all up and running (see here for a nice statement on this). The example above remains in 32-bit real mode (if I state correct), but you might want to proceed to protected mode, 64-bit instructions, etc. to benefit of all its power.

like image 53
DeeDee Avatar answered Dec 02 '22 15:12

DeeDee