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

I debated whether I should dive into a broad question that leads to opinionated answers. I would normally vote to close a question like this, but in this case I am going to offer an response that might be beneficial to others. If you ask Why? I am doing it for this question - history has shown on Stackoverflow that this question is often indirectly asked as part of a more specific OS development question.


Some advantages of ELF for a kernel?

  • Debug information can be embedded in the object
  • An ELF loader can set up the image in memory, zero out the BSS section automatically etc.
  • Uninitialized or zero initialized global data doesn't take up room inside the image.
  • Multiboot compliant bootloaders(like GRUB) can load properly designed ELF executables
  • Can be designed to be relocatable.

Disadvantages?

  • ELF headers are placed at the beginning of the executable which may interfere with intended target environment the executable will run in (like bootloaders)
  • For small programs the ELF headers can be too big for some uses (bootloaders)
  • Requires code (minimal ELF loader) to bootstrap an executable into memory and start executing it.

Why don't we use ELF for a final boot sector image (MBR)?

Main reason is that the ELF format places header information before the code. Legacy BIOSes (non EFI) won't understand it and start executing the header information as code.


Can you use ELF images for debugging a 16-bit bootloader?

It depends on the environment and debugger. With remote GDB debugging in QEMU this is very possible. You can generate a 16-bit real mode executable in an assembler like NASM/GAS etc as an ELF object (with Dwarf debug information), link it to a final ELF executable and then use a program like objcopy to strip off the ELF headers to generate the final flat binary.

Why bother generating ELF objects for a bootloader if you strip it down to a flat binary anyway?

Although a stripped down binary will run in the target environment, an environment with remote debugging capabilities like QEMU can use a local ELF binary to resolve variable names, labels, constants, and allow the original source to be navigated (not just raw assembly).

Can you provide an example of this technique for 16-bit debugging?

Yes, this type of issue has come up before. I have provided answers that show how to do this with the remote debugging services of GDB and the remote debugger in QEMU . One such example can be found in this StackOverflow answer. The example is a sample 16-bit bootloader that can be debugged with GDB. 16-bit debugging is problematic with GDB since it has no understanding of segment:offset pairs in 16-bit code. A link is provided to a script that helps in that regards, along with example QEMU usage.


Is there an advantage to ELF executable when used with a Multiboot loader?

Yes! One big advantage to Multiboot compliant bootloaders like GRUB is that it understands ELF images. If you are writing a protected mode kernel, and you use a properly constructed Multiboot compliant executable for your kernel - you can save on the drudgery (on x86 systems) of setting up the protected mode environment, the A20 Gate enabling, getting a memory map, and initializing the startup video mode mode.

Can QEMU launch a Multiboot compliant ELF kernel executable directly?

Yes, with proper command line using the -kernel option it is possible. OS Dev Wiki has an example.

Can you debug 32-bit protected mode using ELF binaries with debug info?

Yes, this is simpler than doing it for 16-bit bootloaders running in real mode. I offer an example of this technique in this StackOverflow answer. Although the technique is for QEMU using a ISO image, you can alternatively load up QEMU with your multiboot kernel directly using the -kernel option.


Why do modern versions of Linux use ELF format for the Kernel?

In the ancient days of Linux development, Linux had its own bootloader, set up protected mode, enabled the A20 gate etc. This process is different across architectures. A point came where the Linux kernel developers opted to leave that work up to a 3rd party bootloader.

On modern desktop systems you'll find GRUB used as a Muliboot loader; ELILO can be used; on some embedded systems U-Boot became a bootloader of choice. The Multiboot specification arose out of the need to boot a Linux kernel, but do it independent of the OS. Many toy kernel examples on the internet are coded to be used as ELF executables so that they can take advantage of what Multiboot compliant bootloaders have to offer.

More on the Multiboot specification can be found in the GRUB Documentation