MBR

From Electriki
Revision as of 12:46, 20 October 2015 by Khelben (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

The Master Boot Record (or MBR) is a special sector on an x86 computer that contains instructions to continue loading the operating system. Usually it contains additional data such as partition tables and disk geometry, however this is very much dependant on the operating system(s) installed on the disk. Usually the MBR is found on the very first sector of a disk.

On an x86 computer, such as 8086, 286, 386 etc, the BIOS performs its initialisation routines, then sequentially loads the first sector (usually 512 bytes) of each disk into memory at linear address 00007C00. The BIOS then checks the word at 07CFE, if the word is 0xAA55 the BIOS transfers control to the loaded segment, otherwise it continues to load the next disk in the sequence. x86 computers store information in Little-endian format, IE the least significant byte (LSB) is stored first, so the boot signature is stored on disk sequentially as 0x55 0xAA.

The sequence in which the BIOS loads each disk is usually configurable from the BIOS setup program. Modern BIOSes can also attempt to boot from USB block devices (such as USB flash drive, pens etc), or network protocols.

When the BIOS passes control to the bootloader, only a few things about the machine state can be assumed:

  1. The machine is running in Real Mode (Actually a tiny fraction of older machines boot in protected mode, but the consensus seems to be to not support these)
  2. The boot loader is loaded into linear address 07C00. However it is important to note that whilst most BIOSs load the boot loader into 0000:7C00, some (noteably some compaq machines) use a different segment/offset combination to reach the same effective address. As such when writing a boot loader it is safest to assume that CS:IP is unknown at the time of entry.
  3. The DL register contains the BIOS drive number from which the bootloader was loaded from. This is usually 00h or 01h for the first or second floppy disk, 80h or 81h for the first or second hard disk, Option ROMs or network boot would normally be result in a drive identified as 0x7F or 0xFF
  4. ES:DI Should point to PnP data if present, however many operating systems do not rely on this and use fallback methods to retrieve PnP data.

Once control is passed to the MBR, it can do pretty much anything it wants, However, to maintain compatibility with other operating systems it usually operates as a chain loader to locate the active partition and boot the local boot record (the first 512-byte segment of the partition).

below is a basic boot loader example in NASM assembly language, it doesn't do much other than load a few Kb off the disk and is intended to be run on a virtual machine such as QEMU (It may run on others but that's all I've tested it on so far).

<syntaxhighlight lang="ASM">

===============================================================================
Boot loader program
Written by Mark Fortune 2015
All rights, including copyright reserved
Copyright (c) Mark Fortune 2015
Repeat after me - The source code IS the documentation
-------------------------------------------------------------------------------
Licence and warranty
--------------------
This source file, and any binary file or files produced from it are strictly
for non-profit educational purposes only. The source code is freely
distributable provided it is done so in full, unmodified, and with full
aknowledgement to the author or authors, and/or copyright holder(s).
This software/source comes with no absolutely no warranty implied or otherwise
The software is to be used AT YOUR OWN RISK, the author cannot be held
responsible to any damage, including but not limited to data loss, hardware
damage caused through the use of this software, financial losses or loss of time
THERE IS NO GUARANTEE THAT THIS SOFTWARE WILL WORK AS EXPECTED, OR AT ALL ON
ANY GIVEN MACHINE CONFIGURATION
BECAUSE OF THE NATURE OF THIS SOFTWARE (AS AN EXPERIMENTAL BOOT LOADER) IT
SHOULD BE CONSIDERED THAT USE OF THE RESULTING BINARY FILE(S) _WILL_ RESULT
IN SEVERE DATA LOSS IF USED. AS SUCH IT IS STRONGLY RECOMMENDED THAT ANY
RESULTING BINARY FILE(S) ONLY BE RUN ON A SANDBOXED VIRTUAL MACHINE THAT
DOES NOT HAVE ACCESS TO ANY CRITICAL DATA
UNDER NO CIRCUMSTANCES MUST ANYONE USE THE RESULTING BINARY FILE(S) TO REPLACE
A CURRENT MASTER BOOT RECORD OR LOCAL BOOT RECORD, IT LIKELY WILL NOT WORK
AND COULD DESTROY ANY DATA YOU CURRENTLY HAVE ON YOUR SYSTEM. AT THE VERY LEAST
IT WILL DESTROY THE PARTITION TABLE OF ANY DISK IT IS INSTALLED ON
-------------------------------------------------------------------------------
Any questions or comments should be directed to 8khelben8@8bsnet.co.uk8
remove all of the '8's from the email address
I will not respond to silly questions
I will not respond to questions like "I put this on my machine and now it wont
boot", i've already told you not to do that
===============================================================================
Assembler source intended for compilation with NASM, x86 machines
There's probably some opcodes in here that don't work on early CPU's -
I counted 3 errors about invalid instructions using the CPU 8086 directive,
they're probably easy to fix but meh.
The function of this boot loader in its present state is to load a 64k second-
stage loader or kernel into memory and execute it. At the moment this is simply
the next 64k on the disk after this program
I might add some bells and whistles later, but for now it's adequate for my
purposes
===============================================================================
Equates (Constants)
-------------------------------------------------------------------------------
Although it's extremely unlikely that we'll run on a system with < 512K RAM,
we'll set our important segments to areas of memory within the first 512K
If this is to be loaded onto the fabled unicorn turd with < 512K ram,
adjustment of the SEG_RELOCATE and SEG_STACK will be necessary
At the moment it's enough that we set these so they're out of the way of
any loaded code (which will be loaded into 07C0
)

SEG_BOOT EQU 0x07C0 ; Initial boot segment (CS) SEG_RELOCATE EQU 0x6000 ; Code segment after relocation BOOT_SIZE EQU 0x0200 ; Size of boot loader in bytes (512b) SEG_VIDEO EQU 0xB800 ; VGA Text ram SEG_STACK EQU 0x7000 ; Where to place our stack OFS_INDICATOR EQU 0x0F00 ; VRAM diagnostics indicator offset


Memory Map on boot
-------------------------------------------------------------------------------
Segment Linear Linear
Base End
===============================================================================
100000 100000 10FFFF High memory (A20)
F000 F0000 FFFFF BIOS
E000 E0000 EFFFF Expansion ROMs
D000 D0000 DFFFF ?????
C000 C0000 CFFFF EMS
B000 B0000 BFFFF Video RAM (EGA/CGA)
A000 A0000 AFFFF Video RAM (VGA)
9000 90000 9FFFF
8000 80000 8FFFF
----------- Limit on 512K machines ------------------------
7000 70000 7FFFF --- Our stack goes here ---
6000 60000 6FFFF --- Our relocated code goes here ---
5000 50000 5FFFF
4000 40000 4FFFF
3000 30000 3FFFF
2000 20000 2FFFF
1000 10000 1FFFF
0000 00000 0FFFF Varies, see below
SEG_0000 breakdown
-------------------------------------------------------------------------------
Base seg linear linear Description
start end
------------------------------------------------------
07C0 07C00 Boot loader
0050 00500 007BFF -- Available --
0040 00400 004FF BIOS data area
0000 00000 003FF Interrupt table
-------------------------------------------------------------------------------
Conventions of this code
AX is used to pass many function parameters and return values, so it should
be assumed that it is never preserved when calling a function
With all other registers, unless they specifically return a variable they will
probably be preserved across functions

[ORG 0] ; File origin. Note NASM and MASM treat this differently


BIOS_ENTRY_POINT:

The Bios will transfer control to our boot loader here
The label is not necessary, but it helps to illustrate where our loader starts
We can assume that the boot loader has been loaded into 7C00h, but we don't
know if this is 0000
7C00, 07C0:0000, or anywhere in between since there's no
strict rules on how the bios sets up the machine, so we do a far call to our
boot loader entry point to set the code segment (CS) to a known value.
You can set the boot segment to any valid value to reach linear address 0x7C00+
I prefer to use segment 07C0 since this aligns the start of the code on a
segment boundary. The code should still work for other values, although the
ORG directive will need to be modified to reflect the different file offset -
For example, if you choose to use 0000 as your boot segment, change [ORG 0] to
[ORG 0x7C00] (I haven't tested this so feel free to correct me if i'm wrong),
otherwise all your variable data and function entry points will be at the wrong
addresses.

JMP SEG_BOOT:ENTRYPOINT ; Far jump to code section

===============================================================================
Data section
-------------------------------------------------------------------------------
We lump any initialised variables or data here since this section is skipped
in the execution flow due to the previous jump instruction

STR_LOADING db `\r\nInitialising system...\0` STR_BOOTDRIVE db `\r\nBoot drive is \0` STR_DISKERROR db `\r\nError reading disk\0`

CHAR_HEX db `0123456789ABCDEF` ; Used for Hex translations

SCREEN_POS db 0 BOOT_DRIVE db 0

HDD_SECTORS dw 0 HDD_HEADS db 0 HDD_CYLINDERS db 0

===============================================================================
Support functions
-------------------------------------------------------------------------------


-------------------------------------------------------------------------------

PRINTCHAR:

-------------------------------------------------------------------------------

; Used by all the print(x) functions. Prints the ascii character ; specified in AL PUSH BX ; Preserve affected registers PUSH CX MOV AH, 0x0E ; Teletype function MOV BX, 7 ; Character attributes MOV CX, 1 ; 1 character int 0x10 ; Video service interrupt POP CX ; Restore affected registers POP BX RET

-------------------------------------------------------------------------------

PRINTF:

-------------------------------------------------------------------------------

; Prints a null terminated string to the screen using the bios teletype ; function on service 10h. ; [DS:SI] points to next character in the string to be printed ; LODSB automatically increments SI (But make sure the direction flag ; is cleared before calling this)

LODSB ; Load next character into AL CMP AL, 0 ; Null? JE ENDPRINTF ; Exit if NULL CALL PRINTCHAR ; Print the character JMP PRINTF ; Repeat for next character ENDPRINTF: RET ; Return to caller

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------

PRINTHEXBYTE:

-------------------------------------------------------------------------------

; prints a byte in hex ; AL Contains the number to print PUSH BX ; Preserve affected registers

AND AX, 0x00FF ; Discard high byte of AL

PUSH AX ; Store AX for later XOR BX, BX ; Set BX = 0 MOV BL, AL ; Copy AL into BL SHR BL, 4 ; Reading high nibble

MOV AL, [CHAR_HEX + BX] ; Move value in hex lookup table into AL CALL PRINTCHAR ; Print the character

POP BX ; POP AX's old value into BX

AND BL, 0x0F ; Mask off high nibble MOV AL, [CHAR_HEX + BX] ; Move value in hex lookup table into AL CALL PRINTCHAR ; Print ze charachter

POP BX ; restore BX RET ; Return to caller

-------------------------------------------------------------------------------


===============================================================================
Main program entry point
-------------------------------------------------------------------------------

ENTRYPOINT: ; We should be here after the far jump from the start of the code ; The code segment should be set to SEG_BOOT (0x07C0)

; Initialise CPU registers to known state

; A secondary boot process indicator is displayed in the lower left of the screen ; (assuming 80x25 text mode) ; 0 - Loader present in memory and running at 07C0: ; 1 - Machine state normalised ; 2 - Preparing to relocate boot loader to SEG_RELOCATE ; 3 - Boot loader copied to SEG_RELOCATE, preparing far jump ; 4 - Far jump complete, now running from SEG_RELOCATE ; 5 - Preparing to load second stage boot loader ; 6 - Loading finished ; 7 - Transfering control to second stage boot loader

MOV BYTE [FS:OFS_INDICATOR], '0'; Update status

CLI ; Clear Interrupts CLD ; Clear Direction flag

; Initialise stack somewhere safe MOV AX, SEG_STACK MOV SS, AX MOV SP, 0xFFFF

; Set FS to video text buffer for direct video access MOV AX, SEG_VIDEO MOV FS, AX

; Set data segment (same as code segment) MOV AX, CS ; Copy code segment address MOV DS, AX ; Needed for printf

MOV BYTE [FS:OFS_INDICATOR], '1'; Update status

; Save any bios information provided through registers MOV [BOOT_DRIVE], DL ; Store boot drive ; The bios also transfers some information about the PnP bios in some ; registers. I'll not bother about that here since most boot loaders ; don't bother preserving it. (also CBA)

; Print 'initialising' message MOV SI, STR_LOADING CALL PRINTF

MOV BYTE [FS:OFS_INDICATOR], '2'; Update status

;------------------------------------------------------------------------ ; Our boot loader currently occupies the memory we want to load the next ; stage boot loader into, so we need to move the boot loader out of the ; way. ;------------------------------------------------------------------------

; Set source location [DS:SI] MOV AX, SEG_BOOT MOV DS, AX MOV SI, 0

; Set destination [ES:DI] MOV AX, SEG_RELOCATE MOV ES, AX MOV DI, 0

MOV CX, BOOT_SIZE ; Set size of data to move (512 bytes) REP MOVSB ; Move data

MOV BYTE [FS:OFS_INDICATOR], '3'; Update status

; I'll demonstrate another way to perform a far call here, by pushing ; the address to the stack and performing a far return ; I actually did this initially to try to fix a bug which turned out to ; be completely unrelated to this, but i'll leave it in to demonstrate ; other ways to control program flow.

PUSH SEG_RELOCATE ; Push segment to stack PUSH LOADER ; Push offset to stack RETF ; Far Return

; Effectively this is the same as: ; JMP SEG_RELOCATE:LOADER ; Far jump to program loader

LOADER: ;------------------------------------------------------------------------ ; Once we reach here we're running from the relocated program memory ; specified by SEG_RELOCATE. 07C0 is now free for us to load our second- ; stage boot loader or kernel into ;------------------------------------------------------------------------ MOV BYTE [FS:OFS_INDICATOR], '4'; Update status MOV AX, CS MOV DS, AX ; Update data segment

MOV BYTE [FS:OFS_INDICATOR], '5'; Update status

; Display a message to show which drive we're booting from MOV SI, STR_BOOTDRIVE ; Display message CALL PRINTF MOV AL, [BOOT_DRIVE] ; 00h: fda, 01h: fdb, 80h: hda, 81h, hdb CALL PRINTHEXBYTE ; Print drive number MOV AL, 'h' ; Append a 'h' to denote hexadecimal CALL PRINTCHAR

; This next bit is very quick and dirty, i'm almost ashamed of it but ; all I want to do at the moment is load the second-stage bootloader ; or kernel into memory at 07C0: ; so that's exactly what it does, loads the next 64K into ram and ; transfers execution to it ; Sector read operation loads data into ES:BX

MOV AX,SEG_BOOT ; Set the destination segment MOV ES,AX ; Set ES MOV BX, 0 ; Set BX

MOV AH, 0x02 ; Service select: read sectors MOV AL, 128 ; 128 sectors = 64k ; MOV CX, 2 ; Cylinder 0, sector 2 MOV DH, 0 ; Head 0 MOV DL, [BOOT_DRIVE] ; Drive number INT 0x13

; If successful, our second stage boot loader or kernel will be loaded ; into 07C0:0000. If an error occured the carry flag should be set

MOV BYTE [FS:OFS_INDICATOR], '6'; update status JC DISK_READ_ERROR ; test for error MOV BYTE [FS:OFS_INDICATOR], '7'; No error, update status JMP SEG_BOOT:0x0000 ; Jump to loaded code DISK_READ_ERROR: ; Error occured MOV SI, STR_DISKERROR ; Display error message CALL PRINTF HALT: ; If we reach here, halt the system JMP SHORT HALT

Partition table entries at 0x1BEH
-------------------------------------------------------------------------------
The partition table contains 4 records of 16 bytes each, starting at 0x01BEh
In it's current state, this code just ignores it but it's defined here for
prosperity
Later versions might do something with it, such as loading the active partition

TIMES 446-($-$$) DB 0

PART_1 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 0x1BE PART_2 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 0x1CE PART_3 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 0x1DE PART_4 db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 0x1EE

Boot signature at 0x1FE
-------------------------------------------------------------------------------

TIMES 510-($-$$) DB 0 ; Align boot signature. Actually the boot signature comes ; directly after the partition table so this alignment ; shouldn't strictly be necessary, but since we're ; treating the partition table as an optional luxury at ; the moment, we'll put it here in case we don't want the ; partition table DW 0xAA55 ; Boot signature

-------------------------------------------------------------------------------

</syntaxhighlight>