Technical Information about the i386 Boot Loader
Segment Selectors in the GDT
All of the segments defined by the boot loader allow use of the entire
32-bit address space from 0-4GB. In the following descriptions,
kernel means ring 0 and user means ring 3.
- 0x08 - kernel 32-bit code segment
- 0x10 - kernel 32-bit data segment
- 0x18 - kernel 32-bit stack segment (not necessary)
- 0x20 - kernel 16-bit code segment (used for return to real mode)
- 0x28 - kernel 16-bit stack segment (used for return to real mode)
- 0x33 - user 32-bit code segment (RPL of 3)
- 0x3b - user 32-bit data segment (RPL of 3)
- 0x43 - user 32-bit stack segment (RPL of 3)
Returning to DOS
To return cleanly to DOS, you need to make sure that:
- the lower 1MB of memory isn't torn up;
- the GDTR is restored (or at least, the first 6 entries (48
or 0x30 bytes) are what they were when the kernel gained control;
- PIC1 is set at 08h, PIC2 at 70h, with all interrupts unmasked.
You don't need to worry about the last two items, unless you've
changed the GDT or the PIC registers (you can't do this without
some serious effort, so don't worry about it until we get there).
However, staying out of the lower 1MB of memory is a bit tougher,
even with NULL references taken care of.
The easiest way to return to DOS is simply to leave the _start()
function. However, this may not be possible in an exception
handler before you have context switching. So, an alternative
method (and also a very messy one) is to save the frame pointer
(%ebp) in the _start() function to a global variable, and then
restore that value plus 4 to the stack register (%esp),
immediately followed by a return. This looks like this:
- To save %ebp (must be in the _start function):
asm ("mov %%ebp, %0":"=m" (oldEBP));
- To return to the bootloader:
asm ("mov %0, %%esp; ret": :"r" (oldEBP + 4));
where oldEBP is a global variable defined as int.
You can get a copy of the Executable and Linking Format (ELF)
specification here.
Source Code
Check out the
assembly source code for the boot loader.