Tag Archives: asm

Boot Sector

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:

  1. Loads watload.bin to 0xC000
  2. Overlay BIOS parameter block (BPB) data to watload.bin
  3. 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

WatBIOS

WatBIOS is loaded by watload and the entry point is at 0x10000.

The watinit.asm file contains the entry point and segments are defined in segments.inc and segment order is defined in the link file. The BIOSinitEntry segment must be the first segment due to containing the entry point code _cstart_. Entry is via a far jump from watinit.asm switching from real mode to protected mode. On entry, the interrupts must be disabled and stay disabled.

The entry code first sets up the protected mode descriptors and the stack. The watload the location of real mode data in di/si:

typedef struct realmode_data {
    uint16_t IDTAddr; // IDT location
    uint16_t GDTAddr; // GDT location
    uint16_t MEMMapAddr; // BIOS memmap location
    uint16_t INT11RET; // Return from INT 0x11
    uint16_t INT15C0Size; // INT15 C0 number of bytes following
    uint8_t INT15C0Data[0x0F]; // Actual returned data from INT15 C0
    uint8_t VESAMajor; // VESA version major
    uint8_t VESAMinor; // VESA version minor
    uint16_t VESATotalMem; // VESA memory chunks
} realmode_data;

Note: Because of the above rmdata.inc and rndata.h must be kept synchronized.

This data is stored and registers are cleared as is normal for a known starting value. Next, a jump into _TEXT is executed. As a default, and probably will not change, the IDT will start at 0x00. Now move the IVT to an out of the way location at 0x90000 and clear the old IVT area with 0x00. This module also sets BSS and stack parameters.

Finally, an IODelay is calculated calling FindIODelay. I am not sure if I need the IODelay, but at this point I have it. The code for IODelay is located in iodelay.asm and timer.inc which was pulled in from an old loader project. The defines used do not match those in other C-headers. This probably needs to be fixed in the future. A jump to the start of C-code in ldrinit.c is executed.

Execution picks up in ldrinit.c as C-code at main(). At this point ldrinit.c is used for calling functions and printing debug information. The module defines the global bootparams structure to store all system information as it is processed. There are two calls:

init_pics( )
detect_memory( )

Initial Memory Map

The initial probe for memory is performed in watload and stored at a default address of 0000:0900. This address is passed to watbios as a hand-off check and in case the location changes, symbol _MMPADDR. Three probes are performed, following an older Linux method, INT 0x15 88, e801, and e802.

BIOS Function: INT 0x15, AH = 0x88

This function may limit itself to reporting 15M (for legacy reasons) even if BIOS detects more memory than that. It may also report up to 64M. It only reports contiguous (usable) RAM. Stored in a 16bit (2 byte) value at _MMPADDR + 0. In watbios, value is retrieved to meminfo.ext_mem_k representing number of contiguous KB of usable RAM starting at 0x00100000. If contains signature then there was an error.

BIOS Function: INT 0x15, AX = 0xE801

BIOS Function: INT 0x15, EAX = 0xE820

Each e820 entry is 24 bytes stored sequentially, starting at _MMPADDR + 0x0F with the format:

  • 8 bytes – memory chunk start;
  • 8 bytes – memory chunk length;
  • 4 bytes – memory type
  • 4 bytes – four NOPs 0x90 as separator