Grub error: no multiboot header found, when too much code

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
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Grub error: no multiboot header found, when too much code

Post by j4cobgarby »

My kernel boots up from grub, but when I write the body of this one function, I get the following error when booting: "error: no multiboot header found". I've determined that it's not because of the specifics of the function that I'm writing, because I changed it to the exact same as another function, and it still happens.

My best guess is that, somehow, the kernel code is pushing the multiboot header section back in the kernel image, causing grub to not be able to find it. However I can't work out why this would be happening. Here's my linker file:

Code: Select all

ENTRY(_start)

SECTIONS
{
    /* start putting code at 1MB, apparently this is a
     * normal place for kernel code to be loaded from*/
    . = 0x100000;

    /* multiboot header has to be early on in the image so
     * that grub finds it */
    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.multiboot_header)   /* first the multiboot header */
        *(.text)        /* now the actual entry code */
    }

    /* now the uninitialised data */
    .bss BLOCK(4K) : ALIGN(4K)
    {
        *(.bss) /* just the stacks */
    }
}
My multiboot header is defined in a section named .multiboot_header.

Does anyone have any idea why this might happen?
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Grub error: no multiboot header found, when too much cod

Post by nullplan »

You are over-aligning the .text output section. Since you specify 4K, then after the ELF headers, the code has to be put at offset 4K, which puts the multiboot header just at the place where GRUB stops looking for it. Drop that alignment and it should work.
Carpe diem!
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Re: Grub error: no multiboot header found, when too much cod

Post by j4cobgarby »

nullplan wrote:You are over-aligning the .text output section. Since you specify 4K, then after the ELF headers, the code has to be put at offset 4K, which puts the multiboot header just at the place where GRUB stops looking for it. Drop that alignment and it should work.
Oh haha, that seems so obvious now, thanks, I'll try that. How come it ever worked then, why did this cause it to stop working just when the text section got too big?
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Re: Grub error: no multiboot header found, when too much cod

Post by j4cobgarby »

nullplan wrote:You are over-aligning the .text output section. Since you specify 4K, then after the ELF headers, the code has to be put at offset 4K, which puts the multiboot header just at the place where GRUB stops looking for it. Drop that alignment and it should work.
Okay so I changed my linker script to the following:

Code: Select all

ENTRY(_start)

SECTIONS
{
    /* start putting code at 1MB, apparently this is a
     * normal place for kernel code to be loaded from*/
    . = 0x100000;

    /* multiboot header has to be early on in the image so
     * that grub finds it */
    .text : {
        *(.multiboot_header)   /* first the multiboot header */
        *(.text)        /* now the actual entry code */
    }

    /* now the uninitialised data */
    .bss : {
        *(.bss) /* just the stacks */
    }
}
But I get the same error.
testjz
Posts: 23
Joined: Thu Aug 20, 2020 6:11 am

Re: Grub error: no multiboot header found, when too much cod

Post by testjz »

Since this is my first post, let me preface by saying that some years ago I wrote a minimal kernel that booted using GRUB. I probably still remember some specifics, so I can try to solve this problem.

First, I noticed that the relevant part of the OP's linker script is similar to the Bare Bones one. The OP's part is as follows:

Code: Select all

    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.multiboot_header)   /* first the multiboot header */
        *(.text)        /* now the actual entry code */
    }
while the Bare Bones one is:

Code: Select all

    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.multiboot)
        *(.text)
    }
Furthermore, while I was typing this post, the OP replied twice. In the first reply, they said the linker script was working previously, presumably without any modifications in the meanwhile. In the second one, they said it still doesn't work, presumably with the suggested modifications done. So I think the problem should be somewhere else.

I'm thinking of the possibility that the problem is with the definition of the multiboot header in the assembly file. Do you by chance define any data, align or skip, etc inside the .multiboot_header section, but before the definition of the multiboot header itself?
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Grub error: no multiboot header found, when too much cod

Post by bzt »

Hi,

I'm not entirely sure what you're doing. Normally listing the sections should be sufficient to set the code order in the final executable. But some optimizations might rearrange them, so you must tell the linker that your section is not movable, keep it as-is:

Code: Select all

.text : {
    KEEP(*(.multiboot_header))   /* first the multiboot header */
    *(.text)        /* now the actual entry code */
}
Check the result with "objdump" or "nm | sort". The multiboot structure should be kept as the first with this.

Cheers,
bzt
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: Grub error: no multiboot header found, when too much cod

Post by kzinti »

That explanation about alignment doesn't seem right to me. I align my .text section to 4K and it works just fine:

Code: Select all

SECTIONS
{
    . = 1M;

    ImageBase = .;

    .text ALIGN(4K) :
    {
        *(.multiboot*)  /* Make sure the multiboot headers are at the beginning of the image */
        *(.text*)
    } :phdr_text
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Re: Grub error: no multiboot header found, when too much cod

Post by j4cobgarby »

bzt wrote:Hi,

I'm not entirely sure what you're doing. Normally listing the sections should be sufficient to set the code order in the final executable. But some optimizations might rearrange them, so you must tell the linker that your section is not movable, keep it as-is:

Code: Select all

.text : {
    KEEP(*(.multiboot_header))   /* first the multiboot header */
    *(.text)        /* now the actual entry code */
}
Check the result with "objdump" or "nm | sort". The multiboot structure should be kept as the first with this.

Cheers,
bzt
What should I be looking for in the objdump output? It's saying that .multiboot_header is at 0x101038, but surely this shouldn't be the case, according to my linker file.
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: Grub error: no multiboot header found, when too much cod

Post by kzinti »

Are you linking with that linker file?

Is the header in a section named .multiboot_header?

There is something else going on here that you are not showing us...
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Re: Grub error: no multiboot header found, when too much cod

Post by j4cobgarby »

kzinti wrote:Are you linking with that linker file?

Is the header in a section named .multiboot_header?

There is something else going on here that you are not showing us...
Okay, I'll try to provide as much information as possible. You can have a look at all of the code at https://github.com/j4cobgarby/octOs.

In the file src/kernel/kio.c, if you remove the entire body of the function kio_getcurspos, it doesn't have the problem of not being able to find the multiboot header. (This isn't the actual body of the function, I just put a load of those lines in to ensure that it causes the problem).

The makefile calls gcc with the linker script, which is linker.ld.

You can run the operating system with bochs by just typing make, and then bochs.

Thanks for any help.
testjz
Posts: 23
Joined: Thu Aug 20, 2020 6:11 am

Re: Grub error: no multiboot header found, when too much cod

Post by testjz »

I cloned your repo, I built the source, and I ran the following readelf commands:

Code: Select all

readelf -S iso/boot/octos.bin # List the sections in iso/boot/octos.bin
readelf -s iso/boot/octos.bin # List the symbols in iso/boot/octos.bin
I noticed that .multiboot_header for some reason is after .text (and it shouldn't even be a separate header, but it indeed should be inside .text):

Code: Select all

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00100000 001000 001035 00  AX  0   0 4096
  [ 2] .multiboot_header PROGBITS        00101038 002038 000030 00   A  0   0  4
  ...
And it is at offset 0x2038, which is past the first 8 KiB where GRUB searches. So this explains the error message you are getting.

Then I looked further in your code, and I actually found the bug. It is a typo in multiboot.asm. You wrote the following at line 10:

Code: Select all

section .multiboot_header:
It should instead be:

Code: Select all

section .multiboot_header
However, there are several recommended practices that you don't seem to follow. Because it's late around here, I'll most likely do a follow-up post tomorrow or maybe some other day.
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Re: Grub error: no multiboot header found, when too much cod

Post by j4cobgarby »

testje wrote:I cloned your repo, I built the source, and I ran the following readelf commands:

Code: Select all

readelf -S iso/boot/octos.bin # List the sections in iso/boot/octos.bin
readelf -s iso/boot/octos.bin # List the symbols in iso/boot/octos.bin
I noticed that .multiboot_header for some reason is after .text (and it shouldn't even be a separate header, but it indeed should be inside .text):

Code: Select all

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00100000 001000 001035 00  AX  0   0 4096
  [ 2] .multiboot_header PROGBITS        00101038 002038 000030 00   A  0   0  4
  ...
And it is at offset 0x2038, which is past the first 8 KiB where GRUB searches. So this explains the error message you are getting.

Then I looked further in your code, and I actually found the bug. It is a typo in multiboot.asm. You wrote the following at line 10:

Code: Select all

section .multiboot_header:
It should instead be:

Code: Select all

section .multiboot_header
However, there are several recommended practices that you don't seem to follow. Because it's late around here, I'll most likely do a follow-up post tomorrow or maybe some other day.
Thank you very much! That did indeed fix it :) I can't believe I didn't see that
testjz
Posts: 23
Joined: Thu Aug 20, 2020 6:11 am

Re: Grub error: no multiboot header found, when too much cod

Post by testjz »

Don't worry, typos can always happen! :-)

Unfortunately, I didn't have enough time to complete my follow-up post until now, and it got much longer than I anticipated. So here we go...

First, I noticed that your source tree structure looks to me a bit suboptimal. Maybe you did some things this way by design, I don't know, but I'd recommend adopting a more maintainable structure anyway. Specifically, you'd have at the top-level one directory per "subproject", i.e. your kernel, your drivers (if and only if you are writing a microkernel), your standard library (I guess this is your "userspace" directory), your base utilities (in the future), your base services (again in the future), etc. More complex utilities and services can be granted a separate top-level directory each. The "static" misc files, which don't need any processing (building) and are just used "as is" in the sysroot, should be put in a separate top-level directory (e.g. "static" or "misc" or just the top-level directory itself), and then copied to the sysroot when building. Inside each subproject, you should have "include" for headers, and the source should be put inside either "src" or the subproject directory itself. I don't think it's a good idea to put "include" inside "src", as it's a bit unconventional. Each subproject should also have its own Makefile. The architecture-specific files (all assembly files, your linker script, and possibly some C files) should be put inside a subdirectory of "src", with one such directory per supported architecture and possibly, later in the future, additional directories for code that is shared between two architectures, but not the rest (for example i386 and x86_64, but not ARM, ARM64, RV32, RV64, MIPS or others). For example, you would have something like this (names enclosed in <> are placeholder names):

Code: Select all

LICENSE

Makefile

README

TODO

bochsrc

docs
|
+-  ...

kernel
|
+-  Makefile
|
+-  include
|   |
|   +-  ...
|
+-  src
    |
    +-  arch
    |   |
    |   +-  i386
    |   |   |
    |   |   +-  linker.ld
    |   |   |
    |   |   +-  ...
    |   |
    |   +-  ...
    |
    +-  ...

<libsys> (or whatever you name your standard library, e.g. for *nix it's usually "libc")
|
+-  Makefile
|
+-  include
|   |
|   +-  ...
|
+-  src
    |
    +-  arch
    |   |
    |   +-  i386
    |   |   |
    |   |   +-  ...
    |   |
    |   +-  ...
    |
    +-  ...

static
|
+-  boot
    |
    +-  grub
        |
        +-  grub.cfg

<utilbase> (or whatever you name your base utilities, e.g. for *nix it's usually "*utils")
Some other notes concerning filenames: Makefile filenames (no pun intented) should preferably start with a capital 'M'. A README filename should preferably be all capitals.

Another issue I found is your build system. You provide no way to clean the source tree. I have to note that I accidentally deleted "/boot/grub/grub.cfg" the first time I manually did the cleaning. I'd recommend the top-level Makefile to contain the targets "all" (which builds the sysroot using "make all" and "make install" of the Makefiles of each subproject), "clean" (which cleans the build files using "make clean" of the Makefiles of each subproject, and also cleans the sysroot), "iso" (which builds the .iso file from the sysroot), and maybe "bximage" (if you fix the command). In each subproject, the Makefile should have the "all" target (which builds inside that directory), the "clean" target (which cleans the build files but not the installed files) and the "install" target (which installs to $DESTDIR). The top-level Makefile should set $DESTDIR to tell the lower-level Makefiles to install to the sysroot.

One more tip: For building multiple C and/or assembly files in a subproject, define an $OBJECTS variable to contain the object file filenames and use a wildcard rule. Like this:

Code: Select all

DESTDIR ?=

OBJECTS := \
<object 1> \
<object 2> \
... \
<object n> \

.PHONY: all clean install
.SUFFIXES: .asm .c .o

all: octos.bin

clean:
	# rm files generated during building

install:
	# cp kernel to $(DESTDIR)/boot

octos.bin: $(OBJECTS)
	$(CC) -T linker.ld -o octos.bin -ffreestanding -nostdlib $(OBJECTS) -lgcc

.asm.o:
	nasm -f elf32 -o $@ $<

.c.o:
	$(CC) $(CFLAGS) -c -o $@ $<
Edit: For architecture-specific files, you should define more such variables, e.g. $OBJECTS_I386 or $OBJECTS_X86_64. Depending on the target platform specified, you should choose one of these variables at build time (it could be some Makefile conditionals, or an external script, or something else; I don't know of a standard way for this). Then, that variable should be appended to the prerequisites and to the inputs of the command of the target of your final output, in this case "octos.bin". Like this:

Code: Select all

octos.bin: $(OBJECTS) $(OBJECTS_ARCH_SELECTED)
	$(CC) -T linker.ld -o octos.bin -ffreestanding -nostdlib $(OBJECTS) $(OBJECTS_ARCH_SELECTED) -lgcc
One more thing, you can also pass -MD to $(CC), so it generates Makefile rules based on the include directives in each individual file. If the compiled file is named "main.c", the generated file will be named "main.d". As for the assembly files, I haven't looked into how nasm does this, but you don't really need to use includes in assembly. Just assemble them as separate units (with the help of the wildcard rules). Then at the bottom of your Makefile include those generated files like this:

Code: Select all

-include $(OBJECTS:.o=.d)
Don't forget to remove them during cleaning!

One third issue I found is that you don't name/place your files and functions based on what they do. For example, "src/kernel/kio.c" is effectively a VGA text mode driver (which has no input and is only one kind of possible output). Many of the files in your "src/drivers" directory aren't really drivers. For example, it's not called a driver the thing that setups the GDT or the TSS; this is an integral part of the core kernel. Your "multiboot.asm" is also part of the kernel, and usually you'd put this inside "boot.asm", along with the other pre-main() code, though it's absolutely fine to do it as a separate file assembled separately. You put your pre-main() code in "main.asm" and not "boot.asm", which is a misnomer since the main() function is defined elsewhere. You will probably for now remember these things (and probably others I didn't point out), but you might not remember them if you take a break of 3 months or work on other stuff for a similar period, and then return to it. I'd recommend you take code maintainability more seriously from the start to avoid headaches later. :-)

As for your VGA text mode driver, since I provided it as an example, I'd recommend for now that you'd use said code as a backend to a more generic kernel logging mechanism (possibly with other backends in place, e.g. serial, possibly in the future a log file, possibly in the long future a network resource, etc). Later, you could use it as a shell output, or you could stop supporting text mode at all and move to graphical modes instead.

I imagine you are overwhelmed by this post, as it's indeed a lot of information. I haven't looked at the implementations of each feature separately (e.g. GDT, IDT, TSS, ATA, PMM, etc), but I think that code maintainability matters more in the long run than bugs in feature implementations (though they do matter too). I'll leave the bugs to you. :-)
j4cobgarby
Member
Member
Posts: 64
Joined: Fri Jan 26, 2018 11:43 am

Re: Grub error: no multiboot header found, when too much cod

Post by j4cobgarby »

testje wrote:Don't worry, typos can always happen! :-)

Unfortunately, I didn't have enough time to complete my follow-up post until now, and it got much longer than I anticipated. So here we go...

First, I noticed that your source tree structure looks to me a bit suboptimal. Maybe you did some things this way by design, I don't know, but I'd recommend adopting a more maintainable structure anyway. Specifically, you'd have at the top-level one directory per "subproject", i.e. your kernel, your drivers (if and only if you are writing a microkernel), your standard library (I guess this is your "userspace" directory), your base utilities (in the future), your base services (again in the future), etc. More complex utilities and services can be granted a separate top-level directory each. The "static" misc files, which don't need any processing (building) and are just used "as is" in the sysroot, should be put in a separate top-level directory (e.g. "static" or "misc" or just the top-level directory itself), and then copied to the sysroot when building. Inside each subproject, you should have "include" for headers, and the source should be put inside either "src" or the subproject directory itself. I don't think it's a good idea to put "include" inside "src", as it's a bit unconventional. Each subproject should also have its own Makefile. The architecture-specific files (all assembly files, your linker script, and possibly some C files) should be put inside a subdirectory of "src", with one such directory per supported architecture and possibly, later in the future, additional directories for code that is shared between two architectures, but not the rest (for example i386 and x86_64, but not ARM, ARM64, RV32, RV64, MIPS or others). For example, you would have something like this (names enclosed in <> are placeholder names):

Code: Select all

LICENSE

Makefile

README

TODO

bochsrc

docs
|
+-  ...

kernel
|
+-  Makefile
|
+-  include
|   |
|   +-  ...
|
+-  src
    |
    +-  arch
    |   |
    |   +-  i386
    |   |   |
    |   |   +-  linker.ld
    |   |   |
    |   |   +-  ...
    |   |
    |   +-  ...
    |
    +-  ...

<libsys> (or whatever you name your standard library, e.g. for *nix it's usually "libc")
|
+-  Makefile
|
+-  include
|   |
|   +-  ...
|
+-  src
    |
    +-  arch
    |   |
    |   +-  i386
    |   |   |
    |   |   +-  ...
    |   |
    |   +-  ...
    |
    +-  ...

static
|
+-  boot
    |
    +-  grub
        |
        +-  grub.cfg

<utilbase> (or whatever you name your base utilities, e.g. for *nix it's usually "*utils")
Some other notes concerning filenames: Makefile filenames (no pun intented) should preferably start with a capital 'M'. A README filename should preferably be all capitals.

Another issue I found is your build system. You provide no way to clean the source tree. I have to note that I accidentally deleted "/boot/grub/grub.cfg" the first time I manually did the cleaning. I'd recommend the top-level Makefile to contain the targets "all" (which builds the sysroot using "make all" and "make install" of the Makefiles of each subproject), "clean" (which cleans the build files using "make clean" of the Makefiles of each subproject, and also cleans the sysroot), "iso" (which builds the .iso file from the sysroot), and maybe "bximage" (if you fix the command). In each subproject, the Makefile should have the "all" target (which builds inside that directory), the "clean" target (which cleans the build files but not the installed files) and the "install" target (which installs to $DESTDIR). The top-level Makefile should set $DESTDIR to tell the lower-level Makefiles to install to the sysroot.

One more tip: For building multiple C and/or assembly files in a subproject, define an $OBJECTS variable to contain the object file filenames and use a wildcard rule. Like this:

Code: Select all

DESTDIR ?=

OBJECTS := \
<object 1> \
<object 2> \
... \
<object n> \

.PHONY: all clean install
.SUFFIXES: .asm .c .o

all: octos.bin

clean:
	# rm files generated during building

install:
	# cp kernel to $(DESTDIR)/boot

octos.bin: $(OBJECTS)
	$(CC) -T linker.ld -o octos.bin -ffreestanding -nostdlib $(OBJECTS) -lgcc

.asm.o:
	nasm -f elf32 -o $@ $<

.c.o:
	$(CC) $(CFLAGS) -c -o $@ $<
Edit: For architecture-specific files, you should define more such variables, e.g. $OBJECTS_I386 or $OBJECTS_X86_64. Depending on the target platform specified, you should choose one of these variables at build time (it could be some Makefile conditionals, or an external script, or something else; I don't know of a standard way for this). Then, that variable should be appended to the prerequisites and to the inputs of the command of the target of your final output, in this case "octos.bin". Like this:

Code: Select all

octos.bin: $(OBJECTS) $(OBJECTS_ARCH_SELECTED)
	$(CC) -T linker.ld -o octos.bin -ffreestanding -nostdlib $(OBJECTS) $(OBJECTS_ARCH_SELECTED) -lgcc
One more thing, you can also pass -MD to $(CC), so it generates Makefile rules based on the include directives in each individual file. If the compiled file is named "main.c", the generated file will be named "main.d". As for the assembly files, I haven't looked into how nasm does this, but you don't really need to use includes in assembly. Just assemble them as separate units (with the help of the wildcard rules). Then at the bottom of your Makefile include those generated files like this:

Code: Select all

-include $(OBJECTS:.o=.d)
Don't forget to remove them during cleaning!

One third issue I found is that you don't name/place your files and functions based on what they do. For example, "src/kernel/kio.c" is effectively a VGA text mode driver (which has no input and is only one kind of possible output). Many of the files in your "src/drivers" directory aren't really drivers. For example, it's not called a driver the thing that setups the GDT or the TSS; this is an integral part of the core kernel. Your "multiboot.asm" is also part of the kernel, and usually you'd put this inside "boot.asm", along with the other pre-main() code, though it's absolutely fine to do it as a separate file assembled separately. You put your pre-main() code in "main.asm" and not "boot.asm", which is a misnomer since the main() function is defined elsewhere. You will probably for now remember these things (and probably others I didn't point out), but you might not remember them if you take a break of 3 months or work on other stuff for a similar period, and then return to it. I'd recommend you take code maintainability more seriously from the start to avoid headaches later. :-)

As for your VGA text mode driver, since I provided it as an example, I'd recommend for now that you'd use said code as a backend to a more generic kernel logging mechanism (possibly with other backends in place, e.g. serial, possibly in the future a log file, possibly in the long future a network resource, etc). Later, you could use it as a shell output, or you could stop supporting text mode at all and move to graphical modes instead.

I imagine you are overwhelmed by this post, as it's indeed a lot of information. I haven't looked at the implementations of each feature separately (e.g. GDT, IDT, TSS, ATA, PMM, etc), but I think that code maintainability matters more in the long run than bugs in feature implementations (though they do matter too). I'll leave the bugs to you. :-)
Thank you so much for this, it was really useful to read, I've started reorganising my code now. Sorry that I took so long to get back to you.
testjz
Posts: 23
Joined: Thu Aug 20, 2020 6:11 am

Re: Grub error: no multiboot header found, when too much cod

Post by testjz »

It's alright, no need to be sorry! :-)

Now that I looked at my reply again, one thing I forgot to tell you is that $CFLAGS should also contain -Wextra and possibly -Wshadow (you will decide), apart from -Wall. Contrary to what -Wall implies, not all warnings are enabled, which is even more strange considering RMS who originally wrote GCC is a linguistical pedant :-). -Wextra enables a larger number of useful warnings. -Wshadow warns you when you do a declaration or definition inside a scope that is (indirectly) within another scope where a declaration or definition with the same identifier was already done. For example:

Code: Select all

int test = 0;
...
if (condition) {
    char* test = get_test_string();
    ...
}
I recommend you also look through the other warning options of the compiler.
Post Reply