搭建x86裸机Ada工具链

11
请原谅这个比较宽泛的问题。我想知道如何创建一个针对裸机x86的Ada工具链。 我看到了Lucretia在osdev.org上的Ada Bare Bones教程,提供了一些有关构建适合裸机开发的运行时的有用信息。这一方面非常简单,但我不太确定如何为该平台构建交叉编译器,或者是否需要这样做。
我的假设是,通过使用正确类型的运行时库进行编译可以创建"freestanding"二进制文件,这个假设正确吗? 如果我要创建/利用适合的自由运行时库,是否适合使用AdaCore或FSF GNAT针对x86的开箱即用版本?非常感谢任何帮助理解此问题的人。

1
你在开发哪个操作系统?我假设你在 c.l.a 上重新发布这个问题时使用的是 macOS,如果不是,那我就闭嘴了。 - Simon Wright
嗨,西蒙!很抱歉没有机会在c.l.a上回复你。我还没有测试你答案的一个方面。我的主机系统是x86 Linux! - ajxs
1个回答

11
首先请注意,我不是裸机编程方面的专家,但是因为这很有趣,我会尝试一下。话虽如此,我认为你不需要交叉编译器。本地平台编译器(例如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目录,按原样构建示例,并在QEMU中执行生成的映像以检查是否正确设置了一切:

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

结果是弹出一个QEMU窗口,左上角显示“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;

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

-- extern main
++ extern _ada_main

[...]

-- call main
++ call _ada_main


Makefile 中,我替换了以下行以正确编译和链接Ada程序。请注意,我使用 -m32 开关将其编译为 i386,并要求连接器生成一个 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窗口弹出,并显示文本:
Ada says: Hello world!

这个Ada程序是在IA-32实模式下运行的!然后我将演示进一步扩展,通过使用VirtualBox将main.img转换为虚拟硬盘(VDI)。

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

然后创建了一个简单的虚拟机(类型为“其他”和版本为“其他/未知”),并将main.vdi作为其磁盘。我启动了虚拟机,再次看到文本“Ada says: Hello world!”弹出。

因此,根据上述结果,我认为编译器不是在编写x86裸机程序时的主要问题。我认为主要的挑战是:

  • 获取适当的Ada运行时(例如零占用空间; ZFP),它不链接到任何操作系统库(例如C标准库; libc)。我不知道是否有这样的运行时存在,但某些可能已经可以直接使用。如果您愿意抑制检查(请参见源代码中的注释),则可以忽略运行时(如本示例中所做的那样)。

  • 让x86处理器全部运行起来(请参见此处的详细说明)。上面的示例仍处于32位实模式(如果我说得正确),但您可能希望进行保护模式、64位指令等操作,以发挥其全部功效。


1
非常感谢您抽出时间给出深思熟虑的回复。我认为这回答了我的问题。我的主要问题是是否能够使用Linux x86-64编译器针对裸机,以及运行时是否是关键挑战,这两个问题我认为在这里都得到了解答。再次感谢。当然,任何其他的评论和回答仍然受欢迎。 - ajxs
1
有趣! :-) - Ciro Santilli OurBigBook.com
2
您也可以查看LovelaceOS,这是一个尝试在ARM和x86上使用Ada编写操作系统的项目。请注意,它还远未完成 :) - Frédéric Praca
1
...或者Muen分离内核(SK),它是用SPARK 2014编写的。这个微内核也可以使用GNAT CE 2019编译,并且根据网站上的介绍,甚至允许您运行本地Ada主题(我从未尝试过,但听起来很有趣)。 - DeeDee
1
谢谢你提供的链接@FrédéricPraca,我会去看一下! 我在网上搜索时发现了Muen,当我调查将SPARK集成到我的工作中时,我肯定会用它作为参考。 我已经成功地设置了一个基本的ZFP运行时来扩展你的例子。我的下一个挑战是尝试适应使用GNAT CE。非常感谢你的出色回答。 - ajxs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接