Home arrow Articles arrow Booting with GRUB
Booting with GRUB Print
Thursday, 04 May 2006

GRUB stands for GRand Unified Bootloader and it's a multiboot compliant bootloader. This article will focus on its usage as a developer, not as a regular user. If you are looking for usage instructions, you can find more directions here.

Why could GRUB be interesting for you as an osdever? The most important advantages we identified are:

  • multiboot compliance
  • the hard drive can be shared between other OSs ( both multiboot or not )
  • it can switch to pmode and load the kernel to the final memory address location
  • it supports over 8 known filesystems for loading the key files

Since GRUB is mostly designed for 32-bit operating systems, we will focus only on them.

In order to be multiboot compliant, your kernel image must have incorporated a multiboot header within the first 8196 bytes of the file, usually in the beginning of the text segment after the executable header. The structure of the header is shown below:

For a detailed overview of all Multiboot header variables you can access the original GNU specifications here.

A minimal Multiboot compliant kernel

The aim of this chapter is to teach any C/ASM programmer how to code a very small kernel that can be booted with any Multiboot loader. The kernel is minimal, therefore just displaying some Multiboot specific information and then exits. The second aim is to show how to access these specific variables from the C side of your code, although defined in the ASM file.

First we will deal with boot.S, an ASM file compatible with GNU Assembler, containing the Multiboot header and the initialization procedure that eventually jumps to the kernel's main function (C code):

     #define ASM     1 
     #include <multiboot.h>

.text

.globl start, _start
start:
_start:
jmp multiboot_entry

/* Align 32 bits boundary. */
.align 4

/* Multiboot header. */
multiboot_header:
/* magic */
.long MULTIBOOT_HEADER_MAGIC
/* flags */
.long MULTIBOOT_HEADER_FLAGS
/* checksum */
.long -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
#ifndef __ELF__
/* header_addr */
.long multiboot_header
/* load_addr */
.long _start
/* load_end_addr */
.long _edata
/* bss_end_addr */
.long _end
/* entry_addr */
.long multiboot_entry
#endif /* ! __ELF__ */

multiboot_entry:
/* Initialize the stack pointer. */
movl $(stack + STACK_SIZE), %esp

/* Reset EFLAGS. */
pushl $0
popf

/* Push the pointer to the Multiboot information structure. */
pushl %ebx
/* Push the magic value. */
pushl %eax

/* Now enter the C main function... */
call EXT_C(cmain)

/* Halt. */
pushl $halt_message
call EXT_C(printf)

loop: hlt
jmp loop

halt_message:
.asciz "Halted."

/* Our stack area. */
.comm stack, STACK_SIZE

This code first initializes the stack pointer, clears the Multiboot flags, pushes multiboot information into the stack and enters the kernel. Now let's analyze the header file where we defined the structures, multiboot.h:

     /* The magic number for the Multiboot header. */
#define MULTIBOOT_HEADER_MAGIC 0x1BADB002

/* The flags for the Multiboot header. */
#ifdef __ELF__
# define MULTIBOOT_HEADER_FLAGS 0x00000003
#else
# define MULTIBOOT_HEADER_FLAGS 0x00010003
#endif

/* The magic number passed by a Multiboot-compliant boot loader. */
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002

/* The size of our stack (16KB). */
#define STACK_SIZE 0x4000

/* C symbol format. HAVE_ASM_USCORE is defined by configure. */
#ifdef HAVE_ASM_USCORE
# define EXT_C(sym) _ ## sym
#else
# define EXT_C(sym) sym
#endif

#ifndef ASM
/* Do not include here in boot.S. */

/* Types. */

/* The Multiboot header. */
typedef struct multiboot_header
{
unsigned long magic;
unsigned long flags;
unsigned long checksum;
unsigned long header_addr;
unsigned long load_addr;
unsigned long load_end_addr;
unsigned long bss_end_addr;
unsigned long entry_addr;
} multiboot_header_t;

/* The symbol table for a.out. */
typedef struct aout_symbol_table
{
unsigned long tabsize;
unsigned long strsize;
unsigned long addr;
unsigned long reserved;
} aout_symbol_table_t;

/* The section header table for ELF. */
typedef struct elf_section_header_table
{
unsigned long num;
unsigned long size;
unsigned long addr;
unsigned long shndx;
} elf_section_header_table_t;

/* The Multiboot information. */
typedef struct multiboot_info
{
unsigned long flags;
unsigned long mem_lower;
unsigned long mem_upper;
unsigned long boot_device;
unsigned long cmdline;
unsigned long mods_count;
unsigned long mods_addr;
union
{
aout_symbol_table_t aout_sym;
elf_section_header_table_t elf_sec;
} u;
unsigned long mmap_length;
unsigned long mmap_addr;
} multiboot_info_t;

/* The module structure. */
typedef struct module
{
unsigned long mod_start;
unsigned long mod_end;
unsigned long string;
unsigned long reserved;
} module_t;

/* The memory map. Be careful that the offset 0 is base_addr_low
but no size.
*/
typedef struct memory_map
{
unsigned long size;
unsigned long base_addr_low;
unsigned long base_addr_high;
unsigned long length_low;
unsigned long length_high;
unsigned long type;
} memory_map_t;
     #endif /* ! ASM */

The above code defines macros we are using to set the Multiboot header, and for the C code, defines the Multiboot strcutures, useful to understand the data provided to the kernel by the loader. The C code follows (kernel.c):

     #include <multiboot.h>

/* Macros. */

/* Check if the bit BIT in FLAGS is set. */
#define CHECK_FLAG(flags,bit) ((flags) & (1 << (bit)))

/* Some screen stuff. */
/* The number of columns. */
#define COLUMNS 80
/* The number of lines. */
#define LINES 24
/* The attribute of an character. */
#define ATTRIBUTE 7
/* The video memory address. */
#define VIDEO 0xB8000

/* Variables. */
/* Save the X position. */
static int xpos;
/* Save the Y position. */
static int ypos;
/* Point to the video memory. */
static volatile unsigned char *video;

/* Forward declarations. */
void cmain (unsigned long magic, unsigned long addr);
static void cls (void);
static void itoa (char *buf, int base, int d);
static void putchar (int c);
void printf (const char *format, ...);

/* Check if MAGIC is valid and print the Multiboot information structure
pointed by ADDR.
*/
void
cmain (unsigned long magic, unsigned long addr)
{
multiboot_info_t *mbi;

/* Clear the screen. */
cls ();

/* Am I booted by a Multiboot-compliant boot loader? */
if (magic != MULTIBOOT_BOOTLOADER_MAGIC)
{
printf ("Invalid magic number: 0x%x\n", (unsigned) magic);
return;
}

/* Set MBI to the address of the Multiboot information structure. */
mbi = (multiboot_info_t *) addr;

/* Print out the flags. */
printf ("flags = 0x%x\n", (unsigned) mbi->flags);

/* Are mem_* valid? */
if (CHECK_FLAG (mbi->flags, 0))
printf ("mem_lower = %uKB, mem_upper = %uKB\n",
(unsigned) mbi->mem_lower, (unsigned) mbi->mem_upper);

/* Is boot_device valid? */
if (CHECK_FLAG (mbi->flags, 1))
printf ("boot_device = 0x%x\n", (unsigned) mbi->boot_device);

/* Is the command line passed? */
if (CHECK_FLAG (mbi->flags, 2))
printf ("cmdline = %s\n", (char *) mbi->cmdline);

/* Are mods_* valid? */
if (CHECK_FLAG (mbi->flags, 3))
{
module_t *mod;
int i;

printf ("mods_count = %d, mods_addr = 0x%x\n",
(int) mbi->mods_count, (int) mbi->mods_addr);
for (i = 0, mod = (module_t *) mbi->mods_addr;
i < mbi->mods_count;
i++, mod++)
printf (" mod_start = 0x%x, mod_end = 0x%x, string = %s\n",
(unsigned) mod->mod_start,
(unsigned) mod->mod_end,
(char *) mod->string);
}

/* Bits 4 and 5 are mutually exclusive! */
if (CHECK_FLAG (mbi->flags, 4) && CHECK_FLAG (mbi->flags, 5))
{
printf ("Both bits 4 and 5 are set.\n");
return;
}

/* Is the symbol table of a.out valid? */
if (CHECK_FLAG (mbi->flags, 4))
{
aout_symbol_table_t *aout_sym = &(mbi->u.aout_sym);

printf ("aout_symbol_table: tabsize = 0x%0x, "
"strsize = 0x%x, addr = 0x%x\n",
(unsigned) aout_sym->tabsize,
(unsigned) aout_sym->strsize,
(unsigned) aout_sym->addr);
}

/* Is the section header table of ELF valid? */
if (CHECK_FLAG (mbi->flags, 5))
{
elf_section_header_table_t *elf_sec = &(mbi->u.elf_sec);

printf ("elf_sec: num = %u, size = 0x%x,"
" addr = 0x%x, shndx = 0x%x\n",
(unsigned) elf_sec->num, (unsigned) elf_sec->size,
(unsigned) elf_sec->addr, (unsigned) elf_sec->shndx);
}

/* Are mmap_* valid? */
if (CHECK_FLAG (mbi->flags, 6))
{
memory_map_t *mmap;

printf ("mmap_addr = 0x%x, mmap_length = 0x%x\n",
(unsigned) mbi->mmap_addr, (unsigned) mbi->mmap_length);
for (mmap = (memory_map_t *) mbi->mmap_addr;
(unsigned long) mmap < mbi->mmap_addr + mbi->mmap_length;
mmap = (memory_map_t *) ((unsigned long) mmap
+ mmap->size + sizeof (mmap->size)))
printf (" size = 0x%x, base_addr = 0x%x%x,"
" length = 0x%x%x, type = 0x%x\n",
(unsigned) mmap->size,
(unsigned) mmap->base_addr_high,
(unsigned) mmap->base_addr_low,
(unsigned) mmap->length_high,
(unsigned) mmap->length_low,
(unsigned) mmap->type);
}
}

/* Clear the screen and initialize VIDEO, XPOS and YPOS. */
static void
cls (void)
{
int i;

video = (unsigned char *) VIDEO;

for (i = 0; i < COLUMNS * LINES * 2; i++)
*(video + i) = 0;

xpos = 0;
ypos = 0;
}

/* Convert the integer D to a string and save the string in BUF. If
BASE is equal to 'd', interpret that D is decimal, and if BASE is
equal to 'x', interpret that D is hexadecimal.
*/
static void
itoa (char *buf, int base, int d)
{
char *p = buf;
char *p1, *p2;
unsigned long ud = d;
int divisor = 10;

/* If %d is specified and D is minus, put `-' in the head. */
if (base == 'd' && d < 0)
{
*p++ = '-';
buf++;
ud = -d;
}
else if (base == 'x')
divisor = 16;

/* Divide UD by DIVISOR until UD == 0. */
do
{
int remainder = ud % divisor;

*p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
}
while (ud /= divisor);

/* Terminate BUF. */
*p = 0;

/* Reverse BUF. */
p1 = buf;
p2 = p - 1;
while (p1 < p2)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2--;
}
}

/* Put the character C on the screen. */
static void
putchar (int c)
{
if (c == '\n' || c == '\r')
{
newline:
xpos = 0;
ypos++;
if (ypos >= LINES)
ypos = 0;
return;
}

*(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF;
*(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE;

xpos++;
if (xpos >= COLUMNS)
goto newline;
}

/* Format a string and print it on the screen, just like the libc
function printf.
*/
void
printf (const char *format, ...)
{
char **arg = (char **) &format;
int c;
char buf[20];

arg++;

while ((c = *format++) != 0)
{
if (c != '%')
putchar (c);
else
{
char *p;

c = *format++;
switch (c)
{
case 'd':
case 'u':
case 'x':
itoa (buf, c, *((int *) arg++));
p = buf;
goto string;
break;

case 's':
p = *arg++;
if (! p)
p = "(null)";

string:
while (*p)
putchar (*p++);
break;

default:
putchar (*((int *) arg++));
break;
}
}
}
}

Beside the video functions that are irelevant for this article, the above C code checks if the magic number returned by the loader, prints the Multiboot information it received and then passes back the execution to the ASM code which enters the halt state.

Loading the kernel

Assuming you have compiled the two source files and linked them, we can proced to the next step: loading the image. First setup grub on a floppy disk. If you don't know how to do this, read this document. Now copy the kernel image in the root of the floppy disk and reboot the machine.

After the machine's POST, GRUB will be loaded and you will see the GRUB prompt. Next step is to tell GRUB  which is the kernel image and to boot it:

kernel (fd0)/my_kernel_image
boot

And you'll see the magic! Please note that this is boot time GRUB configuration, the above two lines can be casted in a menu.lst file in grub's directory and can autoboot the kernel.

Further readings

First I want to mention the above code is originaly from the GNU foundation and can be found here. The next step would be to understand and use boot modules that are loadable directly by GRUB and not by the kernel itslef. This is very important for a modular micro-kernel.

The source files can be downloaded as gzipped tarball from here

Last Updated ( Sunday, 25 June 2006 )
Recent Forum Posts
File system implementation
12/11 23:13 - by daser
1.How good design and implementation file system? 2.how to program IDE and scsi disk? 3.How to rea
Writing OS from scratch
11/05 09:05 - by Gunalan
Hi all, In all the websites where ever i have visited on writing operating system, everybody has
Re:Booting kernel help for n
07/06 18:47 - by Anubis208
I found you can boot with GRUB if you have a multiboot header... ALIGN 4 mboot: ; Multiboot
Booting kernel help for noob
07/04 12:30 - by Anubis208
Could someone direct me to a tutorial on writing a bootsector and kernel on Windows using nasm and d
Re:64-bit kernel not passing
06/20 18:13 - by ChazZeromus
Can you give me the your code, data and stack segment descriptor? They should all be 64-bits no mat

A WebArticles site. Sponsored by Evoleto. Motorola V525 / Business Directory / Delaware Incorporation / Home Made Bazaar