Grub loadable 64 bit kernel (multiboot2)

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
yasar11732
Member
Member
Posts: 28
Joined: Thu Sep 27, 2018 5:10 pm
Libera.chat IRC: yasar
Location: Turkey
Contact:

Grub loadable 64 bit kernel (multiboot2)

Post by yasar11732 »

Hi,

I am trying to create an 64 bit elf image, that I can load with grub. However, "grub-file --is-x86-multiboot2" command returns error. Here is my boot.S file;

Code: Select all

.set MULTIBOOT2_HEADER_MAGIC, 0xe85250d6
.set GRUB_MULTIBOOT_ARCHITECTURE_I386, 0
.set MULTIBOOT_HEADER_TAG_END, 0

.section .boot,"ax"
.code32

.align 8
multiboot_header:
        .long   MULTIBOOT2_HEADER_MAGIC
        .long   GRUB_MULTIBOOT_ARCHITECTURE_I386
        .long   multiboot_header_end - multiboot_header
        .long   -(MULTIBOOT2_HEADER_MAGIC + GRUB_MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))

        .short MULTIBOOT_HEADER_TAG_END
        .short 0
        .long 8
multiboot_header_end:

.globl _start
_start:        
loop:   hlt
        jmp     loop
It is pretty much copy/paste from multiboot2 example code. I trimmed it as much as possible to get simplest working thing first.

Here is my linker file;

Code: Select all


ENTRY(_start)

SECTIONS {
    . = 1M;

    .boot ALIGN (4): 
    {
        *(.boot)
    }
}
It is also as simple as it gets. Here is my compilation steps;
x86_64-elf-gcc -MD -c boot.S -o boot.o -g -O2 -ffreestanding -mno-red-zone -Iinclude
x86_64-elf-gcc -T linker.ld -o kernel.elf -g -O2 -ffreestanding -mno-red-zone -nostdlib -lgcc boot.o
grub-file --is-x86-multiboot2 kernel.elf
As far as I can tell, this is supposed to create a valid elf image. According to objdump, my .boot section looks like this;

Code: Select all

kernel/kernel.elf:     file format elf64-x86-64

Contents of section .boot:
 100000 d65052e8 00000000 18000000 12afad17  .PR.............
 100010 00000000 08000000 f4ebfd             ...........     
Everthing seems normal to me here too. Finally, "readelf --sections" give me this output;

Code: Select all

There are 11 section headers, starting at offset 0x100308:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .boot             PROGBITS         0000000000100000  00100000
       000000000000001b  0000000000000000  AX       0     0     8
  [ 2] .debug_line       PROGBITS         0000000000000000  0010001b
       000000000000004a  0000000000000000           0     0     1
  [ 3] .debug_line_str   PROGBITS         0000000000000000  00100065
       0000000000000022  0000000000000001  MS       0     0     1
  [ 4] .debug_info       PROGBITS         0000000000000000  00100087
       0000000000000028  0000000000000000           0     0     1
  [ 5] .debug_abbrev     PROGBITS         0000000000000000  001000af
       0000000000000014  0000000000000000           0     0     1
  [ 6] .debug_aranges    PROGBITS         0000000000000000  001000d0
       0000000000000030  0000000000000000           0     0     16
  [ 7] .debug_str        PROGBITS         0000000000000000  00100100
       000000000000002e  0000000000000001  MS       0     0     1
  [ 8] .symtab           SYMTAB           0000000000000000  00100130
       00000000000000d8  0000000000000018           9     8     8
  [ 9] .strtab           STRTAB           0000000000000000  00100208
       000000000000008c  0000000000000000           0     0     1
  [10] .shstrtab         STRTAB           0000000000000000  00100294
       0000000000000071  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
Again, as far as I can tell, this seems normal to me. ".boot" is the first section to be loaded.

I don't know what I am doing wrong here, so any help is appreciated. Thanks in advance,

Best Regards,
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Grub loadable 64 bit kernel (multiboot2)

Post by nullplan »

According to the section headers, your .boot section is 1MB into the file. The GRUB2 header however has to occur within the first 8kB (or was it 4?). Anyway, You need to account for the headers in the linker script. My linker script looks like this:

Code: Select all

ENTRY(_kstart)
OUTPUT_FORMAT("elf64-x86-64")

PHDRS {
    headers PT_PHDR PHDRS;
    text PT_LOAD FILEHDR PHDRS;
    data PT_LOAD;
}

SECTIONS {
    . = 0xffffffff80000000 + SIZEOF_HEADERS;
    .text : {
        *(.text)
        *(.text.*)
    } :text
    .rodata : {
        *(.rodata)
        *(.rodata.*)
    }

    /* Normally, the overlap between text and data section is handled by having
     * two different pages for the last bits of text and the first bits of data.
     * That way, if the last bits of text are overwritten, it won't affect the
     * text that is actually used. Unfortunately, for the kernel this is not
     * possible. The whole file is loaded into memory en bloc, so the same page
     * would be mapped twice. Therefore, a write access to the writable page
     * would end up being visible in the non-writable side of things. Therefore,
     * we must actually page-align here.
     */
    . = ALIGN(2M);
    .data : {
        *(.data)
        *(.data.*)
    } :data
    .bss : {
        *(.bss)
        *(COMMON)
        *(.bss.*)
    }
}
Carpe diem!
yasar11732
Member
Member
Posts: 28
Joined: Thu Sep 27, 2018 5:10 pm
Libera.chat IRC: yasar
Location: Turkey
Contact:

Re: Grub loadable 64 bit kernel (multiboot2)

Post by yasar11732 »

Thanks for the reply, can you give more detail about this part;

Code: Select all

PHDRS {
    headers PT_PHDR PHDRS;
    text PT_LOAD FILEHDR PHDRS;
    data PT_LOAD;
}
From the context, I get that it does something with elf headers, but I don't understand it exactly.
yasar11732
Member
Member
Posts: 28
Joined: Thu Sep 27, 2018 5:10 pm
Libera.chat IRC: yasar
Location: Turkey
Contact:

Re: Grub loadable 64 bit kernel (multiboot2)

Post by yasar11732 »

I tried to make it work with PHDRS directive with no success. But I was able to fix it with "-z max-page-size=0x1000" flag at linking stage, as suggested in the wiki.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Grub loadable 64 bit kernel (multiboot2)

Post by nullplan »

yasar11732 wrote:Thanks for the reply, can you give more detail about this part;
You know, you can look at the documentation. Anyway, this directive declares what program headers I expect the output to contain. I use it, because current versions of GNU ld have some pretentions to being secure software, and like to introduce extraneous segments for "ROP protection". Uh-huh. But the only ROP protection that actually works long-term is fixing buffer overflow bugs.

Anyway, my directive declares three program headers: one PT_PHDR entry for the headers (not quite sure if I need that, but it is quite small and all the normal ELF files have it, so why not?), and two loadable segments, of which the first contains the headers. If I didn't write that, the first segment would not contain the headers, and consequently they would not be mapped. But my kernel requires the headers to map itself once more (after the bootloader does it the first time). I could do that with linker symbols, but I think doing it with the header is more elegant.

That is also why I set the initial position counter to -2GB + SIZEOF_HEADERS, so the start of the file (including the headers) is mapped to -2GB. This makes it very easy to locate at run-time.

And then in the SECTIONS directive, I can tell the linker to use a specific segment by writing a colon and the name of a segment on the same line as the closing brace of a section definition. The same segment will be used for all following sections until overwritten again.
yasar11732 wrote: But I was able to fix it with "-z max-page-size=0x1000" flag at linking stage, as suggested in the wiki.
Yes, that is also a very sensible flag, and if I knew how to put it into a linker script, I would. I suspect you have now managed to reduce the offset of the .boot section from 1MB to 4kB, which is enough for GRUB. But I wanted to have as little bloat as possible, so I increased the position counter by SIZEOF_HEADERS, allowing the linker to fit the headers and the code onto the same page.
Carpe diem!
Post Reply