Be protected !

Fri 24 July 2015 by Saruta

Grub/Multiboot

Grub is compatible with multiboot which is a boot standard for 32-bits OS. This standard needs two things:

  • A 32-bits executable OS
  • A multiboot header in the 8192 first bytes of the OS.

There are actually two versions of multiboot, the first one was made in 1995 by Bryan Ford ad Erich Stefan Boleyn. The second one corrected some issue of the first one. I choose the first multiboot version just because it's the one that is the most commonly used.

Grub runs in 32-bits protected mode, if the OS is not multiboot compliant, then grub goes back in real mode, otherwise, it stays in protected mode.

Three double-words are required on a basic use:

  • A magic number (0x1badb002)
  • A flags number, 0x7 means page aligns, memory info and video mode information.
  • And a checksum, (CHECKSUM+MAGIC+FLAGS) = 0, usually CHECKSUM=-(MAGIC+FLAGS)

At boot, eax contains the magic number, and ebx contains the multiboot information structure.

#define MAGIC 0x1badb002
#define FLAGS 0x7

.section .multiboot
  .align 4
  .long MAGIC
  .long FLAGS
  .long -(MAGIC + FLAGS)

.global kernel_entry
  push %ebx
  push %eax
  call main

Here is the multiboot information structure:

struct multiboot_info {
  uint32_t flags; // not header flags !
  uint32_t mem_lower; // available memory from bios
  uint32_t mem_upper;
  uint32_t boot_device; 
  uint32_t cmdline;
  uint32_t mods_count; // boot modules
  uint32_t mods_addr;
  union {
    multiboot_aout_symbol_table_t aout_sym;
    multiboot_elf_section_header_table_t elf_sec;
  } u;
  uint32_t mmap_length; // memory mapping buffer
  uint32_t mmap_addr;
  uint32_t drives_len; // drive info buffer 
  uint32_t drives_addr;
}

Memory segmentation

There are three address types in x86 :

  • Logical Address
  • Linear Address
  • Physical Address

In a kernel, the logical address is the most commonly used. I am not using pagination in AtomOS, so it means that linear address is the same as physical address.

segmentation

The global description table register (lgdtr) points to the base of a table, the global description table (gdt). The current selector (cs when you execute code) contains the index in the table. Your segment descriptor contains a base address and a limit (size of the segment). Then, to obtain your physical address, you just have to add the base address and the logical address.

lgdt pointer_to_gdt

Reload segment selectors

segmentselector

A simple macro to make selectors :

#define MAKE_SELECTOR(index, ti, rpl) \
  ((index <<3) | (((ti)?1:0) << 2) | (rpl & 0x3))
  • index: is the gdt index
  • ti: 0 for gdt, 1 for ldt (another table)
  • rpl: ring, 3 for user, 0 for kernel

You load it in assembly :

movw _selector_, %ax
movw %ax, %ds
movw %ax, %es

Same thing with fs and gs.

To actualize code segment selector, it is a bit different. The simplest way is to do a long jump:

ljmp _code_selector_, $1f 
1:

Protected mode

To get your kernel in protected mode, you just have to turn on the first bit of cr0:

movl %cr0, %eax
orl $0x1, %eax
movl %eax, %cr0

Links:

  • https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
  • http://a.michelizza.free.fr/pmwiki.php?n=TutoOS.Grub

bitbucket link


Comments