Even when using GRUB, you cannot just write int main() and expect that to run. The most basic setup to get your ELF format kernel to be booted by GRUB consists of three files:
| loader.s | assembler (gas) code for initial setup and calling the C code; |
|---|---|
| kernel.c | C kernel proper; |
| linker.ld | a linker script (for GNU LD) putting the two together. |
(It is assumed that you use a GCC Cross-Compiler for this.)
Alternate and derivative barebones
- Barebones in Free Pascal can be found
at towp's page, in a larger 'quickstart' archive. - If you prefer a HigherHalfKernel, there is a HigherHalfBareBones for you to follow.
loader.s
This piece of code is taking over control from the Multiboot bootloader, and jumps into the kernel proper.
# loader.s
.global _loader # making entry point visible to linker
# setting up the Multiboot header - see GRUB docs for details
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
.set MAGIC, 0x1BADB002 # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum required
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
# reserve initial kernel stack space
.set STACKSIZE, 0x4000 # that is, 16k.
.comm stack, STACKSIZE, 32 # reserve 16k stack on a quadword boundary
_loader: mov $(stack + STACKSIZE), %esp # set up the stack
push %eax # Multiboot magic number
push %ebx # Multiboot data structure
call _main # call kernel proper
hlt # halt machine should kernel return
Assemble this with as -o loader.o loader.s.
Here is the NASM version of this example, which is otherwise a little hard to find....
-- BruceJohnston
kernel.c
This is not exactly your average int main(). Most noteably, you do not have any library stuff available. As soon as you write so much as #include <, you have probably made the first mistake. Welcome to kernel land.
void _main( void* mbd, unsigned int magic )
{
// write your kernel here
}
Compile this with gcc -o kernel.o -c kernel.c -Wall -Werror -nostdlib -nostartfiles -nodefaultlibs. (-Wall -Werror are not exactly required, but using them will certainly help you later on. They might seem to be a pest, but remember: The compiler is your friend!)
linker.ld
This is easy:
ENTRY (_loader)
SECTIONS
{
. = 0x00100000;
.text :
{
*(.text)
}
.rodata ALIGN (0x1000) :
{
*(.rodata)
}
.data ALIGN (0x1000) :
{
*(.data)
}
.bss :
{
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
Link with ld -T linker.ld loader.o kernel.o. The linker script reserves stack space in the .bss section, aligns the sections on page boundaries... you are now set to write your own kernel.
Questions
Why the multiboot header? Wouldn't a pure ELF file be loadable by GRUB anyway?
GRUB is capable of loading a variety of formats. However, in this tutorial we are creating a
Multiboot compliant kernel that could be loaded by any other compliant bootloader. To achieve this, the multiboot header is mandatory.
Is the AOUT kludge required for my kernel ?
The AOUT kludge is not necessary for kernels in ELF format: a multiboot-compliant loader will recognize an ELF executable as such and use the program header to load things in their proper place. You can provide an AOUT kludge with your ELF kernel, in which case the headers of the ELF file are ignored. With any other format, such as AOUT, COFF or PE kernels, the AOUT kludge it is required, however.
Can the multiboot header be anywhere in the kernel file, or does it have to be in a specific offset?
The multiboot header must be in the first 8kb of the kernel file for GRUB to find it. You can ensure that this is the case by putting the header in its own source code file and passing that as the first object file to LD.
Will GRUB wipe the BSS section before loading the kernel?
Yes. For ELF kernels, the .bss section is automatically identified and cleared (despite the
Multiboot specification being a bit vague about it). For other formats, if you ask it politely to do so, that is if you use the 'address override' information from the multiboot header (flag #16) and give a non-zero value to the bss_end_addr field. Note that using "address override" with an ELF kernel will disable the default behaviour and do what is described by the "address override" header instead.
What is the state of registers / memory / etc. when GRUB calls my kernel?
GRUB is an implementation of the
Multiboot specification. Anything not specified there is "undefined behaviour", which should ring a bell (not only) with C/C++ programmers... Better check the
Machine State section of multiboot documentation, and assume nothing else.
I get unresolved references because of missing / superfluous underscores in the object files !?
As an immediate resolve, you can use -fno-leading-underscores or -fleading-underscores to get the object file variant you need; in the long run, you might want to set up a GCC Cross-Compiler with the correct default behaviour.
I get Error: junk at end of line, first unrecognized character is ',' ...
Chances are the GNU as you use is not targeted at ELF - it chokes on the .comm stack, STACKSIZE, 32 line, as three arguments to .comm are only supported for ELF targets. Try setting up a GCC Cross-Compiler.
I still get Error 13: Invalid or unsupported executable format from GRUB. What's going on ?
Chances are the multiboot header is missing from the final executable.
If you are using some other format than ELF (such as PE), you should specify the AOUT kludge in the multiboot header. The mbchk program (coming with GRUB) and "objdump -h" should give you more hints about what is going on.
It may also happen if you use an ELF object file instead of an executable (e.g. you have an ELF file with unresolved symbols or unfixable relocations). Try to link your ELF file to a binary executable to get more accurate error messages.
