Page 1 of 1

Is GRUB only able to load 32-bit multiboot kernels?

Posted: Wed Sep 28, 2016 1:49 pm
by rgmf
Hello everyone,

I am reading http://wiki.osdev.org/Bare_Bones and here you can read the following:

"You will not be able to correctly complete this tutorial with a x86_64-elf cross-compiler, as GRUB is only able to load 32-bit multiboot kernels."

What that is mean? What about 64 bit Linux kernel, for instance? You can using grub to boot 64 bits Linux distributions, so I do not understand this sentence.

Regards.

Re: Is GRUB only able to load 32-bit multiboot kernels?

Posted: Wed Sep 28, 2016 2:09 pm
by mmk
What that is mean? What about 64 bit Linux kernel, for instance? You can using grub to boot 64 bits Linux distributions, so I do not understand this sentence.
Linux does not follow the Multiboot standard.

By GRUB there is GRUB legacy and GRUB 2. I know that GRUB 2 can load a 64 bit ELF executable that has a Multiboot header, but it does not put the system into Long Mode.
Patched GRUB legacy to allow ELF64 loading should also work, although I have not personally tried it.

So you'll need a small bootstrap as part of that executable that does the switch and some initialization (setup a stack, etc.) before calling "kernel_main" or equivalent
main function.

Re: Is GRUB only able to load 32-bit multiboot kernels?

Posted: Wed Sep 28, 2016 3:17 pm
by rgmf
mmk wrote:
What that is mean? What about 64 bit Linux kernel, for instance? You can using grub to boot 64 bits Linux distributions, so I do not understand this sentence.
Linux does not follow the Multiboot standard.

By GRUB there is GRUB legacy and GRUB 2. I know that GRUB 2 can load a 64 bit ELF executable that has a Multiboot header, but it does not put the system into Long Mode.
Patched GRUB legacy to allow ELF64 loading should also work, although I have not personally tried it.

So you'll need a small bootstrap as part of that executable that does the switch and some initialization (setup a stack, etc.) before calling "kernel_main" or equivalent
main function.
I see... thanks a lot for your comment.

Re: Is GRUB only able to load 32-bit multiboot kernels?

Posted: Thu Sep 29, 2016 10:52 am
by Boris
Hi,
you have two options:
- Use a small 32 bit kernel that loads your actual 64 bit kernel from a multi boot module.
- Use objcopy to put your 64 bit sections in a 32 bit elf, and expect things to work due to sign extension.
I use $(OBJCOPY) -O elf32-i386 $< $ in my makefile to do that, where objcopy is from my cross x86_64 compiler binutils.

Re: Is GRUB only able to load 32-bit multiboot kernels?

Posted: Fri Sep 30, 2016 10:44 am
by rgmf
Boris wrote:Hi,
you have two options:
- Use a small 32 bit kernel that loads your actual 64 bit kernel from a multi boot module.
- Use objcopy to put your 64 bit sections in a 32 bit elf, and expect things to work due to sign extension.
I use $(OBJCOPY) -O elf32-i386 $< $ in my makefile to do that, where objcopy is from my cross x86_64 compiler binutils.
Thank you Boris, you are very kind ;)

Re: Is GRUB only able to load 32-bit multiboot kernels?

Posted: Fri Sep 30, 2016 4:40 pm
by StephanvanSchaik
There are about four options to consider with different orders of difficulty if you want to load a 64-bit kernel. However, if it is your first time writing a kernel, then I do recommend writing a 32-bit multiboot-compliant kernel as your first kernel. This is by far the easiest route as you can simply run the kernel using QEMU directly (using the -kernel option), which means that you get to focus on kernel development itself, which on its own requires some dedication. Of course, feel free to ignore this piece of advice, if you are dedicated enough and if you want to invest this time learning about all the bells and whistles involved, since you will learn a lot more from the other options :).

The easiest by far would be to use GRUB 2 to load a multiboot2-compliant ELF64 executable directly, which saves you some time and effort as you don't have to bother parsing and loading an ELF64 executable. However, since you'll end up in protected mode, you will still have to set up long mode (i.e. set up paging, set some bits in the appropriate registers and load a GDT). In this case I recommend writing the boot stub in Assembly, as a naive boot stub will only take you about 50 lines of code.

The more complicated part is that your boot stub will initially start out with physical addresses and that once you are in long mode, you will end up using virtual addresses (let's assume you want to load the kernel at 0xFFFF800000000000). This can be solved using a linker script. I'll provide a basic one here that can be used as an example, but make sure that you fully understand the linker script and how it will affect your kernel lay-out:

Code: Select all

OUTPUT_FORMAT(elf64-x86-64)
ENTRY(_start)

SECTIONS {
	. = KERNEL_LMA;
	
	.boot ALIGN(4K) : {
		*(.multiboot)
		source/arch/x86-64/boot.o (.text)
		source/arch/x86-64/boot.o (.data)
		source/arch/x86-64/boot.o (.bss)
		boot_end = .;
	}

	. += KERNEL_VMA;

	.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_VMA) {
		*(EXCLUDE_FILE(*source/arch/x86-64/boot.o) .text)
	}

	.rodata ALIGN (4K) : AT(ADDR(.rodata) - KERNEL_VMA) {
		*(.rodata)
	}
	
	.data ALIGN (4K) : AT(ADDR(.data) - KERNEL_VMA) {
		*(EXCLUDE_FILE(*source/arch/x86-64/boot.o) .data)
	}

	.bss ALIGN (4K) : AT(ADDR(.bss) - KERNEL_VMA) {
		*(EXCLUDE_FILE(*source/arch/x86-64/boot.o) .bss)
		*(COMMON)
	}

	end = .;
}
Alternatively, you can also use GRUB Legacy to load a multiboot-compliant ELF32 executable with a boot stub to set up 64-bit (since GRUB Legacy doesn't support loading ELF64 executables directly). This basically adds the requirement that you have to link your 64-bit kernel as an ELF32 executable. This will have the added complication that ELF32 only supports 32-bit physical and virtual addresses, but that shouldn't be much of a problem as GRUB will only be using the physical addresses to load your kernel and as the linked code simply ends up being 64-bit code in disguise (i.e. you can simply store both 32-bit and 64-bit code in your .text section, as long as the entry point is valid 32-bit code).

Since GRUB Legacy only supports loading ELF32 executables, the third option is to write a multiboot-compliant 32-bit kernel that performs the following actions for you:
  • Using the multiboot info structs, find where exactly GRUB has loaded the module representing your ELF64 executable.
  • Parse the ELF header to obtain the entry point and to access the program headers.
  • Parse the program headers to figure out at what physical and virtual addresses each "program" has to be loaded. Since you have to set up an initial page table before entering long mode, it makes sense to map the programs at the appropriate virtual addresses. Load the root of your page tables, the PML4 entry into the CR3-register.
  • Set up identity mapping for the first few megabytes (for the transition from physical to virtual addresses, otherwise you will get page faults).
  • Set the eight bit in the EFER MSR to enable long mode. You'll have entered 32-bit compatibility mode at this point.
  • Enable paging by setting the 31st bit in CR0 and optionally enable the 5th bit for physical address extensions (2 MiB pages). At this point, you are using virtual addresses.
  • Set up a GDT with a 64-bit code and data segment (DPL = 0), load it and have your CS, DS, ES and SS registers point to the appropriate descriptors. After performing a far jump, you'll be in 64-bit long mode.
  • Jump to the entry point of your 64-bit executable.
To simplify the allocation of your page tables, you can simply reserve some space in the .bss section of your loader or declare them as global variables in your programming language of choice (you can mostly do this in C). However, since you may not know in advance how many page tables you have to allocate, you may also set up a simple physical memory allocator for the boot process (e.g. you can use the multiboot memory map as a data structure to allocate memory from).

Ultimately, the most dificult route to wander, but probably the most fun one, is to write your own boot loader that loads your kernel using BIOS/UEFI, sets up protected mode (if you are in real mode), and performs the steps outlined in the third option at minimum. Do note that your kernel needs a lot of useful information that can only be easily collected by your boot loader, such as the E820 memory map, setting up VBE and loading additional modules for your kernel to use.

The OSDev wiki surely provides sufficient information on setting up long mode, setting up paging, and parsing ELF executables.


Yours sincerely,
Stephan.

Re: Is GRUB only able to load 32-bit multiboot kernels?

Posted: Mon Oct 03, 2016 12:30 pm
by rgmf
StephanvanSchaik wrote:There are about four options to consider with different orders of difficulty if you want to load a 64-bit kernel. However, if it is your first time writing a kernel, then I do recommend writing a 32-bit multiboot-compliant kernel as your first kernel. This is by far the easiest route as you can simply run the kernel using QEMU directly (using the -kernel option), which means that you get to focus on kernel development itself, which on its own requires some dedication. Of course, feel free to ignore this piece of advice, if you are dedicated enough and if you want to invest this time learning about all the bells and whistles involved, since you will learn a lot more from the other options :).

...

Yours sincerely,
Stephan.
Wow!!! Thank you for your detailed explanation. I will begin with this first option. Anyway, I saved all your post for future.

Regards.