MBR

From Electriki
Jump to: navigation, 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).

; ===============================================================================
; 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
 
; -------------------------------------------------------------------------------