MBR (Master Boot Record)
When a computer boots, the first program run is the BIOS, which stands for Basic Input/Output System. BIOS performs integrity checks of hardware, such as ensuring memory cards are properly slotted and readable, motherboard connection, etc.
BIOS are typically very standard and provide facilities or auxiliary functions for the Bootloader. The BIOS will check for a bootable device by looking in the first sector (sector 0) and checking byte 510 and 511 (the 511th and 512th byte, respectively) for the boot signature: 0x55AA.
On little Endian systems, this will be 0xAA55
If the boot signature is found, the BIOS will load the MBR (Master Boot Record) to address 0x7C00. The MBR is responsible for loading the second part of the bootloader.
When the MBR is loaded, the computer is running in Real-Mode, which is a 16-bit mode that uses segmented memory (not paging) and provides no memory protection. The objective is to switch to protected mode, which is 32 or 64-bit and provides memory protection.
Segment Registers
Code Segment -> CS
Data Segment -> CS
Stack Segment -> SS
Extra Segment -> ES
Example MBR
Below is a simple bootloader. I will cover a few important sections, however I'm assuming familiarity with assembly.
ORG Form
In segmented memory, addresses are calculated using the following formula:
Address * 16 + offset , where address is the address of one of the segment registers.
The multiplier of 16 is used because real-mode is restricted to 16-bit address, necessitating alignment with 16. The offset, in this example, is ORG.
Admittedly, this isn't the best way to boot; starting at 0x0000 is better, but I'll discuss that later.
Code & Data Segment
Next, you'll see two variables created: CODE_SEG and DATA_SEG. These represent the segments of memory that will have an entry in the Global Descriptor Table. Read the linked post to learn more about that. For now, just know that, in addition to the defined (User) code and data segment, a kernel code and data segment is also defined.
These segments define the boundaries for Kernel space and User space in memory and the GDT (analogous to the IVT/IDT) describes this layout to the CPU.
FAT Partitioning
If the media in which the MBR resides is not partitioned, loading is simple enough. However, if it is partitioned (e.g. Hard Disk), the beginning of the media will contain MBR or other partition information in what's known as a Volume Boot Record. The BIOS parameter block is used to store this information, and is 33 bytes.
times 33 db 0
The code above ensures 33 bytes are reserved, just in case the bootloader is stored on a Hard Drive. Remember, the goal of the MBR is to function on as many different media types and for as many different manufacturers as possible.
If you omit those bytes because you're only planning to boot from non-partitioned media, you run the risk of your MBR code being overwritten in the event that it's ever used on partitioned media.
Masking Interrupts
The code below masks interrupts and sets the segment registers to 0x00; except for the Stack Segment register, which is set ot 0x7C00. Why? Earlier, the formula for calculating memory addresses in segmented memory was introduced as: address * 16 + offset. If for whatever reason, the value in one of the segment addresses is not zero, the system may compute an erroneous address using that formula. Imagine if instead of 0x00 * 16 + 0x7C00 was used to start the MBR, 0x7D * 16 + 0x7C00 was used.
This is, in part, why it's best to start the bootloader with ORG 0x0000 instead of 0x7C00.
Global Descriptor Table
This deserves a more thorough explanation, which I will provide in the GDT post.
Boot Signature
The MBR ends with a calculation that guarantees the size of our MBR. An MBR is 512 bytes, and byte 510 and 511 (the 511th and 512th byte, respectively) hold the boot signature. The formula 510 - ($ - $$) subtracts the start address (denoted by the _start label) from the current address ($). That result is then subtracted from 510, to get the total size of the program. If it is not equal to 510 (the size needed so that the boot signature makes up the 511th and 512th byte), zero padded bytes will be used to fill the remaining space.
times 510 - ($ - $$)
Finally, the boot signature is added.
dw 0xAA55
This boot signature is written in Little-endian byte order to match the target system.
Last updated
Was this helpful?