Rolling Your Own Bootloader: Difference between revisions

no edit summary
[unchecked revision][unchecked revision]
(Rough import from mediawiki)
 
No edit summary
Line 6:
=== Disclaimer ===
 
Okay. We have [[GRUB]], we have BareBones[[Bare bones]] and [BareBonesC[C PlusPlus bare bones|C++ bare bones]] but still people complain we don't have a page explaining how the bootloader can be coded.
 
I won't try to give you full code that works because if that was what you were looking for, you'd be using one of the ToolBootLoader[[:Category:Bootloaders|premade bootloaders]] instead. This page plans to tell you what is needed and what could be wished in a bootloader, and optionnallyoptionally points at parts of the FAQ that can help you achieving the goals.
 
Whether or not you'll use your own bootloader or reuse an existing tool is completely up to you. If you get the feeling you don't understand a thing, make sure you read our page about BootSequencethe [[Boot sequence]] first ;)
 
Hope you'll enjoy it %%% --PypeClicker
Line 18:
The bootloader ultimately has to bring the kernel (and all the kernel needs to bootstrap) in memory, switch to an environment that the kernel will like and then transfer control to the kernel.
 
As the scope of this FAQarticle is pmodeprotected mode C kernels, iI'll assume that "an environment the kernel will like" means ProtectedMode[[Protected Mode]], with kernel and additional components being stored at their 'favouritefavorite', compile-time known locations, with a wide-enough stack ready and BSS section cleared.
 
=== What you could wish to add ===
 
Since the bootloader runs in RealMode[[Real Mode]], it has easier access to BIOS resources and functions. Therefore it's a good place to perform [memory map detection|[How doDo I determineDetermine theThe amountAmount ofOf RAM?|memory map detection]], [[Getting VBE Mode Info|detecting available video modes|GettingVbeModeInfo]], loading additionnaladditional files etc. The bootloader will collect this information and present it in a way the kernel will be able to understand
 
== Loading ... Please wait ... ==
Line 28:
=== Where will you load your kernel ? ===
 
That's one of the first questions you need to answer. Being in RealMode[[Real Mode]], the easiest is to stay below the 1MB barrier -- which means you practically have 512KB of memory to load things. You may wish the kernel to be loaded at a well-known position -- say 0x10000 physical (es=0x1000, bx=0 when calling INT13h).
 
;__tip__ tip: If your kernel is bigger (or is expected to grow bigger) than this, you'll probably prefer to have the kernel above the 1MB barrier, which means you need to [activate [[A20 gateLine|Why cant I access all my memory?A20]] gate and switch to UnrealMode[[Unreal Mode]] to load the kernel (with A20 alone, you cannot have more than 64K above 1MB).
;__caveat__ caveat: Note that BIOS will still be unable to write to memory above 1MB, so you need to read stuff in a buffer below 1MB and _then_''then'' manually copy those stuff above 1MB.
 
=== How will you find your kernel ? ===
 
The bits of your kernel are somewhere on some disk (presumably the booting disk, but this is not mandatory). Question is: where on the disk ? Is it a regular file on a [[FAT|FAT-formatted floppy|FAT12 document]] ? is it a collection of consecutive sectors in the "reserved area" of the FAT12 floppy (in which case you may need a dedicated tool to format the disk and install the kernel on it) ? Or is the floppy simply left unformatted and kernel pasted directly with a [[Disk Images|disk image tool|Working with Disk Images]].
 
All the above options are possible. Maybe the one iI'd choose myself would be to reserve enough space on a FAT12 floppy to store the _list''list of sectors_sectors'' used by the kernel file. The "advantage" of being fully-FAT12 is that you don't need to re-write the bootsector everytimeevery time you rewrite the kernel.
 
=== What else could you need to load ? ===
 
That mainly depends on what's in your kernel. Linux, for instance, requires an additionnaladditional 'initrd' file that will contain the 'initialization process' (as user level). If your kernel is modular and if [Filesystems[File Systems]] are understood by some modules, you need to load the modules along with the kernel. Same goes for 'microkernel services' like disk/files/memory services, etc.
 
=== What if i get beyond the 512 bytes of the boot sector ? ===
 
Use [[GRUB]] ;) -- nah, kidding ... just make sure the first 512 bytes are able to load the rest of your loader and you're safe. Some do this with a separate "second stage" loader, others by really inserting a '512-bytes' break in their ASM code, making sure the rest of the loader is put after the bootsector (that is, starting at 0x7e00 ;)
 
=== What if iI wish to offer the user the option to boot several OSes ? ===
 
The easiest way to boot another OS is a mechanism called _chainloading_''chainloading''. Windows stores something akin to a second-stage bootloader in the boot sector of the _partition_''partition'' it was installed in. When installing Linux, writing e.g. LILO or GRUB to the _partition_''partition'' boot sector instead of the MBR is also an option. Now, the thing your MBR bootsector can do is to _relocate_''relocate'' itself (copying from 0x07c0:0x0000 to, traditionally, 0x0060:0x0000), parse the partition table, display some kind of menu and let the user chose which partition to boot from. Then, your (relocated) MBR bootsector would load that _partition_''partition'' boot sector to 0x07c0:0x0000, and jump there. The partition boot sector would be none the wiser that there already was a bootsector loaded _before_''before'', and could actually load yet _another_''another'' bootsector - which is why it's called _chainloading_''chainloading''.
 
You see that with diplayingdisplaying a menu in some intelligible way and accepting keystrokes, such a multi-option bootloader can get quite complex rather quickly. We didn't even touch the subject of booting from extended partitions, which would require sequentially reading and parsing multiple extended partition tables before printing the menu.
 
Taken to the extreme, bootmanagersboot managers like that can become as complex as a simple OS (much like GRUB is, which offers reading from various filesystems, booting Multiboot kernels, chainloading, loading initrd ramdisks etc. etc. - such internals will not be addressed here.
 
=== How do i actually load bytes ===
 
UsingBios[[BIOS]] interrupt 13h. Get info about it at the [RBIL[Ralf Brown's Interrupt List]], make sure you know floppies may fail one or two times, that you cannot read more than a track at once, and you're done.
 
If you need a guidance, feel free to check [lowlevel.asm|http://clicker.cvs.sourceforge.net/clicker/c32-lxsdk/kernel/src/sosflppy/lowlevel.asm?view=log lowlevel.asm]
 
 
Note also that most [Filesystems[File Systems]] involve some conversion between allocation units (blocks/clusters) and physical "Cylinder:Head:Sector" values. Those conversions are simple once you know the _sectors''sectors-per-track_track'' and _heads_''heads'' counts. Check out [[OSRC]] for additionnaladditional info.
 
<pre>
<verbatim>
> Does anyone have a formula for converting DOS Sectors to
> Physical Sectors (Head, Cylinder, Sector) such as used in
Line 75:
BIOS_Head_num = (DOS_sector_num DIV Sectors_per_track) MOD Total_heads
BIOS_Track_num = (DOS_sector_num DIV Sectors_per_track) DIV Total_heads
</verbatimpre>
 
If you're loading above 1MB, you should proceed in 2 steps: first using BIOS to load in the "conventionnalconventional" area, and then performing a =<tt>rep movsd=</tt> to place the data where they ultimately should go.
 
== Loaded. Gathering Informations ==
 
The next step consist of collecting as much information as you can/need: [amount of installed RAM| [How doDo I determineDetermine theThe Amount Of RAM|amount of installed RAM?]], available [[Getting VBE Mode Info|video modes|GettingVbeModeInfo]] and things alike are easier to do in real mode, so better do them while in RealMode[[Real Mode]] than trying to come back to real mode for a trip later.
 
A very simple solution here is to organize your informations as a flat table (ala [[BIOS|BIOS data area|UsingBios]]). An alternative could be to add those informations as a structured flow: you keep an index at a well-known address (or at some address you'll pass to the kernel when loaded) and that index gives for each "key" the address of the corresponding datastructuredata structure. E.g.
 
<pre>
<verbatim>
organization lookup code (eax == signature)
+------+------+ mov esi, well_known_index_address
Line 96:
mov eax,[esi]
ret
</verbatimpre>
 
== Ready. Entering ProtectedMode[[Protected Mode]] ... ==
 
To enter pmodeprotected mode you should first disable iterruptsinterrupts and set global descriptor table. After it set PE bit of CR0:
 
<pre>
<verbatim>
mov eax,cr0
or eax,1
mov cr0,eax
</verbatimpre>
 
After it set registers and do a far jump to kernel.
If data selector is 10h, code selector is 8 and kernel offset is 10000h do:
 
<pre>
<verbatim>
mov ax,10h
mov ds,ax
Line 119:
mov ss,ax
jmp 8:10000h
</verbatimpre>
 
Notes:
* that in this case, the GDT will be _temporary_''temporary''. Indeed, the loader has no idea of what the kernel wants to do with the GDT, so all it can do is providing a minimal and let the kernel reload GDTR with an appropriate GDT later.
* it's common for the loader to keep interrupts disabled (the kernel will enable them later when an IDT is properly set up)
* give yourself the time about thinking whether you'll enable paging now or not. Keep in mind that debugging paging initialization code without the help of exception handlers may quickly become a nightmare !
* it is possible to perform more initialization once pmodeprotected mode is enabled and before kernel is loaded. This will, however, require that you mix 16 bits and 32bits code in a single object file, which can quickly become a nightmare too...
50

edits