我应该制作自己的 OS 内核 ELF 还是原始二进制文件?

Should I make my own OS kernel ELF or raw binary?

我已经开始了我的 OS 开发之旅。人们通常会大喊使用原始二进制而不是 ELF(或其他结构化格式)是自定义 OS 中应用程序的常见错误。我可以支持,因为 ELF 提供了额外的好处(存储元信息的地方,例如符号 table、.debug 和 .line)。但是,让我们考虑一下内核二进制文件本身。它是否应该结构化(如 ELF),如果是,为什么?否则写一个 ELF 加载器并在 stage1 加载器之后立即挤压它似乎是一种浪费。

AFAIK Linux 内核是一个 ELF 文件,但我不知道为什么。

a.out 可能是 raw/flat 二进制文件和 ELF 之间更好的折衷方案。在 a.out 中,您可以像在 ELF 中一样将代码与数据与未初始化的数据分开,并且可以进行重定位(和符号,如果您愿意的话),如果不显式地重新发明部分,您就不会在 raw/flat 二进制文件中获得这些那个。不过,a.out比ELF要简单的多,几乎和raw/flat一样简单。非常容易解析和加载(除非你像我在编译器的 DPMI 存根中那样用 16 位汇编代码来做:)。

我争论过我是否应该深入研究一个导致自以为是的答案的广泛问题。我通常会投票结束这样的问题,但在这种情况下,我将提供可能对其他人有益的回应。如果你问 为什么? 我正在为这个问题做这个 - Whosebug 上的历史表明,这个问题经常作为更具体的 OS 开发问题的一部分被间接问到。


内核ELF的一些优点?

  • 可以在object
  • 中嵌入调试信息
  • ELF 加载器可以在内存中设置图像,自动将 BSS 部分归零等
  • 未初始化或零初始化的全局数据不占用图像内部空间。
  • 兼容多引导的引导加载程序(如 GRUB)可以加载正确设计的 ELF 可执行文件
  • 可以设计成可重定位。

缺点?

  • ELF headers 被放置在可执行文件的开头,这可能会干扰预期的目标环境,可执行文件将 运行 在(如引导加载程序)
  • 对于小程序,ELF headers 对于某些用途(引导加载程序)来说可能太大
  • 需要代码(最小 ELF 加载程序)bootstrap 将可执行文件放入内存并开始执行它。

我们为什么不使用 ELF 作为最终引导扇区映像 (MBR)?

主要原因是ELF格式在代码前放置了header信息。旧版 BIOSes(非 EFI)无法理解它并开始将 header 信息作为代码执行。


能否使用 ELF 映像调试 16 位引导加载程序?

这取决于环境和调试器。通过 GDBQEMU 中进行远程调试,这是很有可能的。您可以在 NASM/GAS 等汇编程序中生成 16 位实模式可执行文件作为 ELF object (使用 Dwarf 调试信息),link 将其转换为最终的 ELF 可执行文件,然后使用 objcopy 之类的程序剥离 ELF headers 生成最终的平面二进制文件。

如果您无论如何都将其剥离为平面二进制文件,为什么还要为引导加载程序生成 ELF objects?

虽然精简的二进制文件将 运行 在目标环境中,但具有远程调试功能的环境如 QEMU 可以使用本地 ELF 二进制文件来解析变量名,标签、常量,并允许导航原始源(不仅仅是原始程序集)。

您能否提供此技术的 16 位调试示例?

是的,以前出现过此类问题。我提供的答案显示了如何使用 GDB 的远程调试服务和 QEMU 中的远程调试器执行此操作。可以在 Whosebug answer 中找到一个这样的例子。该示例是一个示例 16 位引导加载程序,可以使用 GDB 进行调试。 GDB 的 16 位调试是有问题的,因为它不理解 16 位代码中的 segment:offset 对。 link 提供给脚本,在这方面有帮助,以及示例 QEMU 用法。


与多重引导加载程序一起使用时,ELF 可执行文件是否有优势?

是的!像 GRUB 这样的符合多重引导的引导加载程序的一大优势是它理解 ELF 图像。如果您正在编写一个保护模式内核,并且您为您的内核使用了一个正确构建的 Multiboot 兼容可执行文件——您可以节省设置保护模式环境、A20 Gate 启用、获取内存映射的苦差事(在 x86 系统上) ,并初始化启动视频模式mode。

QEMU 可以直接启动一个 Multiboot 兼容的 ELF 内核可执行文件吗?

是的,通过使用 -kernel 选项的正确命令行是可能的。 OS Dev Wiki 有例子。

您可以使用带调试信息的 ELF 二进制文件调试 32 位保护模式吗?

是的,这比在实模式下对 16 位引导加载程序 运行ning 更简单。我在 Whosebug answer 中提供了此技术的示例。尽管该技术适用于使用 ISO 映像的 QEMU,您也可以使用 -kernel 选项直接使用多重启动内核加载 QEMU .


为什么 Linux 的现代版本的内核使用 ELF 格式?

在 Linux 开发的古代,Linux 有自己的引导加载程序、设置保护模式、启用 A20 门等。这个过程在不同的体系结构中是不同的。 Linux 内核开发人员选择将这项工作留给第 3 方引导加载程序。

在现代桌面系统上,您会发现 GRUB 用作 Muliboot 加载器; ELILO可以用过的;在某些嵌入式系统上,U-Boot 成为首选的引导加载程序。 Multiboot 规范源于需要引导 Linux 内核,但它独立于 OS。互联网上的许多玩具内核示例都被编码为用作 ELF 可执行文件,以便它们可以利用 Multiboot 兼容的引导加载程序必须提供的功能。

有关 Multiboot 规范的更多信息,请参阅 GRUB Documentation

我个人推荐 flat binary,因为它真的很容易加载。只需将文件复制到 ram 中,跳转到代码中,就大功告成了。 (如果您正在制作自己的引导加载程序)