Bare bones
From OSDev Wiki
In this tutorial we will compile a simple C kernel and boot it.
Contents |
Overview
Even when using GRUB, some setup is required before entering an int main() type function. The most basic setup to get an ELF format kernel to be booted by GRUB consists of three files:
- loader.s - assembler "glue" between bootloader and kernel
- kernel.c - your actual kernel routines
- linker.ld - for linking the above files
The second part of this tutorial briefly describes how to boot the compiled kernel.
loader.s
loader.s takes over over control from the Multiboot bootloader, and jumps into the kernel proper.
NASM
global _loader ; making entry point visible to linker extern _kmain ; _kmain is defined elsewhere ; setting up the Multiboot header - see GRUB docs for details MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries MEMINFO equ 1<<1 ; provide memory map FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header CHECKSUM equ -(MAGIC + FLAGS) ; checksum required section .text align 4 MultiBootHeader: dd MAGIC dd FLAGS dd CHECKSUM ; reserve initial kernel stack space STACKSIZE equ 0x4000 ; that's 16k. _loader: mov esp, stack+STACKSIZE ; set up the stack push eax ; pass Multiboot magic number push ebx ; pass Multiboot info structure call _kmain ; call kernel proper hlt ; halt machine should kernel return section .bss align 32 stack: resb STACKSIZE ; reserve 16k stack on a quadword boundary
Assemble using:
nasm -f elf -o loader.o loader.s
GAS
.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 _kmain # call kernel proper hlt # halt machine should kernel retur
Assemble using:
as -o loader.o loader.s
kernel.c
This is not exactly your average int main() (which is one of the reasons why you should not call it like that). Most notably, 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 kmain( void* mbd, unsigned int magic )
{
/* Write your kernel here. Example: */
unsigned char *videoram = (unsigned char *) 0xb8000;
videoram[0] = 65; /* character 'A' */
videoram[1] = 0x07; /* forground, background color. */
}
Compile using:
gcc -o kernel.o -c kernel.c -Wall -Wextra -Werror -nostdlib -nostartfiles -nodefaultlibs.
Note: the flags -Wall -Wextra -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
ENTRY (_loader)
SECTIONS{
. = 0x00100000;
.text :{
*(.text)
}
.rodata ALIGN (0x1000) : {
*(.rodata)
}
.data ALIGN (0x1000) : {
*(.data)
}
.bss : {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
Link using:
ld -T linker.ld -o kernel.bin loader.o kernel.o
The file kernel.bin is now your kernel (all other files are no longer needed).
In the next section of this tutorial, we will actually boot this kernel!
Booting the kernel
In a few minutes, you will see your kernel in action.
Preparing bochs and grub
In short: bochs is a virtual computer (so you won't damage your real computer), and grub is a boot loader.
For bochs, prepare a configuration file named bochsrc.txt in your working directory. It should contain
boot: floppy floppya: 1_44="floppy.img", status=inserted
which means that we will boot from a virtual floppy (actually a file). We will create this floppy file below.
From the GRUB project download the binaries. For a first success, only the files stage1 and stage2 are needed. They are in a package named grub-0.97-pc386.
Also create a file named pad with exactly 750 bytes (content does not matter; more precisely, the 750 is the result of 102400 - len(stage1)- len(stage2)).
Creating the floppy image
Create the floppy image as follows:
cat stage1 stage2 pad kernel.bin > floppy.img # linux, netbsd, ... cp stage1+stage2+pad+kernel.bin > floppy.img # Windows, DOS
As a result, the kernel begins exactly in block #200 (remember, a block equals 512 bytes). Without padding, the kernel would start in the middle of a block, making it impossible to boot.
Starting bochs
This step is equivalent to turning on a computer with a bootable floppy inserted.
Important: first check the size of your kernel.bin in blocks. This is the length in bytes, divided by 512 and rounded up to the next integer. You will need this number. Let us assume that the kernel size is 18 blocks.
Type bochs. It will display a "monitor" with some messages from GRUB, ending in a command promt. Type:
kernel 200+18 boot
The first command means that the kernel starts at block #200 (see above) and its size is 18 blocks. This just loads the kernel into the memory, but does not start it. The boot command really starts the 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 behavior 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 behavior", 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 behavior.
- 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 ...
- 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.
