设置裸机 x86 Ada 工具链

Setting up a bare metal x86 Ada toolchain

请原谅这个有点宽泛的问题。我想知道如何创建针对裸机 x86 的 Ada 工具链。 我看过 Lucretia 的 Ada Bare Bones tutorial on osdev.org,它提供了一些有关为裸机开发构建合适的运行时的有用信息。这方面非常简单,但我有点不确定如何为平台构建交叉编译器,或者是否有必要这样做。

我的假设是否正确,即创建 "freestanding" 二进制文件是通过使用正确类型的 RTS 进行编译来完成的? 如果我要 create/utilise 一个合适的独立 RTS,是否适合使用开箱即用的 AdaCore 或针对 x86 的 FSF GNAT?如果能帮助理解这一点,我们将不胜感激。

首先请注意,我是 bare-metal 编程方面的专家,但由于这很有趣,我会试一试。话虽如此,我认为您不需要交叉编译器。本机平台编译器(例如 Linux x86-64 的 GNAT CE 2019)就可以了。

为了说明这一点,您可能需要在 Ada 的 GitHub 上重新创建 multiboot/hello_world 示例。以下是我在安装了 GNAT CE 2019 的 Debian 机器上执行的步骤。

首先我安装了一些必要的包(QEMU、NASM 和 GNU xorriso)并克隆了上面提到的存储库:

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

然后,在存储库中,我切换到目录 multiboot/hello-world,构建示例 as-is 并在 QEMU 中执行生成的图像以检查是否一切设置正确:

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

结果是一个 QEMU window 弹出,在 top-left 角落显示 hello world。我通过关闭 QEMU 和 运行 make clean 来清理。

然后我删除了 main.c 并用 Ada 翻译替换它 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;

因为我们是 运行ning Ada,所以我必须更改 entry.asm 并替换以下行以确保入口点调用了 Ada 程序而不是 C 程序。 GNAT 发出的 Ada 程序的入口点是 _ada_main(编译后见 objdump -t main.o 的输出):

-- extern main
++ extern _ada_main

[...]

-- call main
++ call _ada_main

Makefile 中,我替换了以下行以正确编译和 link Ada 程序。请注意,我编译到 i386(使用 -m32 开关)并请求 linker 发出一个 elf_i386 可执行文件,因为处理器不会在启动后直接执行 64 位指令:

-- 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

注意:注意 gcc 之前的制表符(用 <TAB> 表示)。 make对这个问题很挑剔!

我随后再次调用 make 然后 make run 看到 QEMU window 弹出,但现在显示文本:

Ada says: Hello world!

这个 Ada 程序执行了 bare-metal(在 IA-32 实模式下)!然后,我通过使用

main.img 转换为 VirtualBox 磁盘 (VDI) 进一步进行了演示
VBoxManage convertfromraw main.img main.vdi --variant Fixed

然后创建了一个简单的 VM(类型 "other" 和版本 "other/unknown"),并将 main.vdi 作为其磁盘。我启动了 VM 并且(再次)看到文本 "Ada says: Hello world!" 弹出。

因此,鉴于以上结果,我认为编译器不是 x86 编程的主要问题 bare-metal。我认为主要的挑战是:

  • 获得不 link 任何 OS 库(例如 C 标准库;libc)的适当 Ada 运行时(例如零足迹;ZFP) .我不知道,但有些可能存在 out-of-the 框。我不确定 OSDev.org 上的那个是否完成到 ZFP 运行 时间的水平。对于上面的简单程序,如果您愿意取消检查(请参阅源代码中的注释),则可以省略 运行time(就像我在本例中所做的那样)。

  • 启动 x86 处理器并 运行ning(请参阅 here 以获得关于此的精彩声明)。上面的示例仍然处于 32 位实模式(如果我说的正确),但您可能希望继续保护模式、64 位指令等以充分利用其所有功能。