设置裸机 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 位指令等以充分利用其所有功能。
请原谅这个有点宽泛的问题。我想知道如何创建针对裸机 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 位指令等以充分利用其所有功能。