The boot sector is compiled and inserted on to the test hard drive image. My manual BPB compiled with UASM and use dd to manually install on my 50 Meg DOS vdi. I cannot remember why I used UASM instead of wasm or jwasm, but maybe it will come to me.
Overall Purpose
Only does LBA reads – int 13 0x41 check error if no extensions. Assumes a modern system has more than 1 meg RAM so no TOM check. The basic function this BPB:
- Loads watload.bin to 0xC000
- Overlay BIOS parameter block (BPB) data to watload.bin
- Far jump to watload.bin
This is the big start and the following is a memory map I threw together to help track of what memory areas I was using. The example is a simple old 32bit system boot which starts in real mode (16bit). The following represents the boot block found by the system as the machine initializes and loaded to 0x0000:0x7C00. Once the BIOS initializes, it jumps to 0x0000:0x7C00 and it is all you at that point, no safety nets.
; --------
; | |
; |watload | at 0xC000 (0x800 bytes)
; +--------+
; | |
; | Buffer | Use start 0x7E00 for Root Dir and FAT load buffer
; |--------| 0000:7E00 (0:BP+200)
; |BOOT SEC| contains BPB
; |ORIGIN |
; |--------| 0000:7C00 (0:BP)
; |VARS | only known is 1st data sector (start of cluster 2)
; |--------| 0000:7BFC (DS:[BP-4])
; |STACK | minimal 256 bytes (1/2 sector)
; |- - - - |
; |KERNEL | kernel loaded here (max 58 sectors, 29KB)
; |LOADED | also used as FAT buffer
; |--------| 0070:0000 (0:0700)
; |KERNEL |
; |LOADED |
; |--------| 0000:0600
; |DOS DA | DOS Data Area,
; | | Cluster list temp from FAT read
; |********| 0000:0500
; |BDA | BIOS Data Area
; +--------+ 0000:0400
; |IVT | Interrupt Vector Table
; +--------+ 0000:0000
It’s All You
This is the most rewarding thing about learning the low level stuff, it is all you from this point. When it works, there is nothing like. When it doesn’t work, there is nothing like it. In the next snippet, I set up some address for use and add daptable.inc. Note the nop to keep the short jmp.
;
.8086 ; enable assembler warnings to limit instruction set
include daptable.inc
BASE equ 0x7C00 ; boot sector originally at 0x0:BASE
BUFFER_SEG equ 0x07E0
BUFFER_OFF equ 0x0000 ; buffer at 0x7E00 for Root and FAT load
BIOS_SEG equ 0x0C00 ; watload.bin load at 0xC000
CLUSTERLIST equ 0x0500 ; location to store bios cluster list
;-----------------------------------------------------------------------
; Entry point after MBR hand off. Stock MBR is located at 0000:0x0600
_TEXT segment use16 'code'
org BASE
BS_jmpBoot:
jmp short start
nop
BIOS parameter block (BPB)
So, this is the BPB and some form of BPB, for whatever filesystem in use, will be written when the media is formatted. In my case, I made my test drive image, FAT, and pulled the BPB for use in my boot sector.
BPB_Start:
BS_OEMName db 'IBM 7.0' ; OEM label
BPB_BytsPerSec dw 0x200 ; Number of bytes per sector (512) Must be one of 512, 1024, 2048, 4096.
BPB_SecPerClus db 0x4 ; Number of sectors per cluster Must be one of 1, 2, 4, 8, 16, 32, 64, 128.
BPB_RsvdSecCnt dw 0x1 ; reserved sectors, in 12/16 usually 1 for BPB, FAT32 uses 32
BPB_NumFATs db 0x2 ; number of FATs,
BPB_RootEntCnt dw 0x200 ; root directory entries, 0 for FAT32. 512 is recommended for FAT16.
BPB_TotSec16 dw 0x0 ; 16-bit total count of sectors on the volume, if 0 see BPB_TotSec32
BPB_Media db 0xF8 ; is no longer usually used, F8 HD FA Ram Disk
BPB_FATSz16 dw 0x64 ; sectors per 1 FAT copy
BPB_SecPerTrk dw 0x3F ; sectors per track
BPB_NumHeads dw 0x10 ; number of heads
BPB_HiddSec dd 0x3F ; hidden sectors
BPB_TotSec32 dd 0x18D71 ; big total sectors BPB_TotSec32 * BPB_BytsPerSec = HD size
BS_DrvNum db 0x80 ; boot unit
BS_Reserved1 db 0 ; Reserved (used by Windows NT). FAT always 0
BS_BootSig db 0x29 ; 0x29 indicates next 3 fields in the boot sector present
BS_VolID dd 0x30E1671C ; volume serial number
BS_VolLab db 'PCDOS_DEV '; volume label
BS_FilSysType db 'FAT16 ' ; filesystem id
fat_start dd ? ; first FAT sector
data_start dd ? ; first Data sector
bios_cluster dw ? ; bios cluster from root dir
Disk Address Packet (DAP)
The DAP is used for LBA reads and was included early. This link: daptable.inc shows the DAP with comments.
; DAP : Disk Address Packet
DAP DAP_TABLE <0x10,0x0,0x0,0x7E00,0x0,0x0,0x0>
BPB Code
This is where the previous short jump leads us to, the start of the code.
********************************************************************
; * Start of BPB code
; ********************************************************************
start:
cli
cld
mov BS_DrvNum, dl ; save BIOS drive number
xor ax, ax ; segment registers 0x0000
mov ds, ax
mov es, ax
mov ss, ax
mov bp, BASE ; setup stack
mov sp, BASE
sti
; Volume Structure:
; | | | |
; BPB | FAT | FAT | Root Dir | Data
; | | Copy | |
; | | | |
; fat_start dir_start data_start
; fat_start = BPB_HiddSec + BPB_RsvdSecCnt
mov si, word ptr BPB_HiddSec
mov di, word ptr BPB_HiddSec+2
add si, word ptr BPB_RsvdSecCnt
adc word ptr fat_start+2, di ; DI:SI = first FAT sector
mov word ptr fat_start, si
; dir_start = (BPB_NumFATs * BPB_FATSz16) + fat_start
mov al, BPB_NumFATs
cbw
mul BPB_FATSz16 ; DX:AX = total number of FAT sectors
add si, ax
adc di, dx ; DI:SI = first root directory sector
mov word ptr DAP.DAP_sector_low, si
mov word ptr DAP.DAP_sector_low+2, di ; root dir start in DAP will be
; first read in
; RootDirSectors = (BPB_RootEntCnt * 32) / BPB_BytsPerSec;
mov ax, 32
xor dx, dx
mul word ptr BPB_RootEntCnt
div word ptr BPB_BytsPerSec ; Divide (dx:ax,sectsize) to (ax,dx)
mov DAP.DAP_num_sectors, ax ; number of root dir sectors to DAP
; where the data starts
add si, ax
mov word ptr data_start, si
adc word ptr data_start+2, di ; DI:SI = first data sector
; First, read the root directory into buffer.
; IBM/MS INT 13 Extensions - INSTALLATION CHECK
; will not work without LBA extensions
mov ah,041h
mov bx,055aah
mov dl, [BS_DrvNum] ; BIOS drive, 0=A:, 80=C:
int 0x13
jnc root_read
mov ax, 0x0E31
jmp print_error ; Error 1 - No bios extensions
; yep, damn - int 13 0x42 is supported, read in full root dir at
; 0x7E00 - big read
root_read:
call readdrive ; read in root directory
jnc get_biosname
mov ax, 0x0E32
jmp print_error ; Error 2 - Root directory read
; read through root dir directory for watload.bin
get_biosname:
lea si, filename ;Starting address of first buffer
lea di, buffer ;Starting address of first buffer
push si
push di
mov ax, BPB_RootEntCnt ;Count FAT 16 directory entries max
next_dir:
mov cx, 0x0B ;Scanning 11 bytes (CX is used by REPE)
repe cmpsb ; ...and compare it.
je bios_found
pop di
add di, 0x20
pop si
push si
push di
cmp byte ptr [es:di], 0x00
jz no_bios ; ax counts max dir entries, but if first
dec ax ; char di is zero, no more dir entries
jne next_dir ; so bail early
; get here if entry not found 0x00, first free entry or for some
; reason you read through all entries not found or 0xE5 and
; ax counts down to zero
no_bios:
mov ax, 0x0E33
jmp print_error ; Error 3 - watload.bin
bios_found:
pop di ; offset of found dir entry
pop si ; empty stack
; watload directory entry found, pull cluster, store in data
mov ax, [di+0x1A] ; first cluster
mov [bios_cluster], ax
; reuse DAP structure - setup DAP for FAT read. Read in full FAT
; at 0x7E00
lea bx, buffer
mov ax, [BPB_FATSz16]
mov DAP.DAP_num_sectors, ax
mov ax, word ptr [fat_start]
mov dx, word ptr [fat_start+2]
mov word ptr DAP.DAP_sector_low, ax
mov word ptr DAP.DAP_sector_low+2, dx
call readdrive ; read FAT into memory at 0x7E00
jnc read_fat
mov ax, 0x0E35
jmp print_error ; Error 5 - FAT read error
read_fat:
; set up ds:di to the FAT buffer
mov si, BUFFER_OFF
mov ax, BUFFER_SEG
mov ds, ax
mov ax, [ds:si]
sub ax, 0xFFF8
je good_fat_table ; first word should be 0xFFF8 or
mov ax, 0x0E36
jmp print_error ; Error 6 - FAT table bad
; At this point, the entire FAT is loaded at 0x7E00 and ds:si are set
; to that seg:off. Assume es - 0, set es:di 0000:0500
good_fat_table:
lea di, [CLUSTERLIST]
mov ax, bios_cluster ; cluster number from root dir
next_clust:
stosw ; store cluster number, inc next after store
mov si, ax
add si, si ; cluster * 2
mov ax, [ds:si]
; In some docs the end-of-clusterchain marker is listed as FFF8, but
; in others FFFF (FAT16). From what I have seen FFFF is the correct
; marker and FFF8 is only at cluster 1 position.
cmp ax, 0xFFFF
jne next_clust
xor ax, ax ; mark end of temp cluster list 0x0000
stosw
; set ds back to 0x0000 so my next int 13 0x42 reads the DAP at the
; correct address
mov ds, ax
; **************************************************************
; **** Note: buffer at 0x7E00 no longer needed ****
; **************************************************************
; The issue is that the stock IBMBIOS loads at 0x0700 and a full
; load will over write
mov DAP.DAP_buffer_off, ax ; load to DAP off -- 0x0000
mov ax, BIOS_SEG ; load segment
mov DAP.DAP_buffer_seg, ax ; load to DAP seg -- 0x0D00
mov al, [BPB_SecPerClus] ; each read will be size of cluster
cbw
mov DAP.DAP_num_sectors, ax ; load in DAP
; LBA_sector = ((cluster_number - 2) * BPB_SecPerClus) + data_start
; Start read at LBA_sector for BPB_SecPerClus
; then read next cluster number and repeat until loaded
lea di, [CLUSTERLIST]
mov ax, word ptr [data_start] ; low word data_start
mov word ptr DAP.DAP_sector_low, ax
mov ax, word ptr [data_start+2] ; high word data_start
mov word ptr DAP.DAP_sector_low+2, ax
xor dx, dx
mov ax, [es:di]
sub ax, 0x0002 ; subtract 2, comp for FAT position
mul [BPB_SecPerClus] ; dx:ax
add word ptr [DAP.DAP_sector_low], ax
add word ptr [DAP.DAP_sector_low+2], dx
call readdrive
mov cx, word ptr [fat_start]
mov dx, word ptr [fat_start+2]
mov si, word ptr data_start
mov di, word ptr data_start+2
; far jump to the start of watload.bin
jmp far ptr loader_start
; Print string pointed to by DS:SI using
; BIOS TTY output via int 10h/AH=0eh
print_error:
int 0x10
jmp $
readdrive:
mov ah, 0x42
mov dl, BS_DrvNum
lea si, DAP
int 0x13
ret
filename db "WATLOAD BIN"
; Fill free space between code/data and signature with zero
db ((0x200 - 2) - ($ - BS_jmpBoot)) dup(0)
; True, the MBR checks at 0x7DFE after BPB loaded for signature before
; jumping to start. -MKG
signature dw 0xAA55
; This will be 0x7E00 - where FAT16 RootDir will load and reuse for
; FAT read - for FAT I set ds:di == 0x7E00
buffer:
_TEXT ends
loader segment use16 'code' at 0x0000
org 0xC000
loader_start label near
loader ends