Understanding System Bootstrap Process (2 of 3)
Table of Content
- Loading Program in Boot Sector Code
 
Loading Program in Boot Sector Code
A boot sector has maximally 512 bytes, and cannot store a larger program. For an operating system, the boot sector contains a small program, e.g., MBR that is part of the operating system and whose job is almost solely to load a large program. This larger program is typically either the operating system kernel, or a rather sophisticated boot loader (or boot manager) program that is responsible to load the operating system kernel. Nevertheless, the boot sector code must be able to load and execute another program on the disk. Since the operating system has not loaded any file systems code, often a large chunk of code, the system cannot easily load and read a file.
For convenience, we call the program that the boot sector code is to load and execute the second program. In this section, we devise a very simple method for the boot sector code to locate, load, and execute the second program, i.e., we place the code of the second program immediately following the boot sector.
Experiment and Programming Environment
Refer to the bootstrap tutorial for the experiment and programming environment, and for how to compile and run the programs here.
Example 0 Memory Segment and Segment Registers
;
; load0.asm
;
mov ax, 0x7c0
mov ds, ax
mov bx, MSG
mov ah, 0x0e
LOOP:
    mov al,[ds:bx]
    cmp al, 0
    je DONE_PRINT
    int 0x10
    inc bx
    jmp LOOP
DONE_PRINT:
    jmp $
MSG:
    db "Hello, Segment!", 0
times 510-($-$$) db 0
dw 0xaa55
Example 1 Print Message Function Revisited
;
; load1.asm
;
mov ax, 0x7c0
mov ds, ax
mov ax, HELLO_MSG ; 1st variable
mov cx, WORLD_MSG ; 2nd variable
mov bx, ax        ; pass argument via bx and ds
call print_msg    ; call the function
mov bx, cx        ; pass argument via bx and ds
call print_msg    ; call the function
mov bx, ax
call print_msg
mov bx, cx
call print_msg
jmp $
%include "print_msg.asm"
HELLO_MSG :
    db 'Hello!', 0 ;
WORLD_MSG :
    db 'World!', 0 ;
times 510-($-$$) db 0
dw 0xaa55
and
;
; print_msg.asm
;
; we implement a function with interface
;     void print_msg(char* msg)
; where we pass the argument msg via ds:bx
print_msg:
    pusha           ; push all registers to stack
    mov ah, 0x0e
LOOP:
    mov al, [ds:bx]
    cmp al, 0
    je DONE_PRINT_MSG
    int 0x10
    inc bx
    jmp LOOP
DONE_PRINT_MSG:
    popa            ; pop all registers to stack
    ret
Example 2 Querying Disk Drive Paramegers
[org 0x7c00]
; query disk drive parameters
; 
; load2.asm
;
; using PC BIOS's 0x13 interrupt
; See
;   https://en.wikipedia.org/wiki/INT_13H#INT_13h_AH=08h:_Read_Drive_Parameters
READ_DISK:
mov ah, 0x08
mov dl, 0x80    ; 1st HDD
mov ax, 0
mov es, ax
mov di, ax
int 0x13
jc .DISK_ERROR  ; using local label. local labels start from "."
push cx
push bx
mov bx, DISK_NO
call print_msg
mov cl, dl
call print_byte
pop bx
call print_line
push bx
mov bx, HEAD_IDX
call print_msg
mov cl, dh
call print_byte
pop bx
call print_line
pop cx
push cx
push bx
mov bx, SECTOR_NUM
call print_msg
pop bx
mov cl, ch
call print_byte
call print_space
pop cx
call print_byte
pop cx
jmp $    
.DISK_ERROR:
    mov bx, DISK_ERROR_MSG
    call print_msg
    
    jmp $
    
%include "print_byte.asm"
%include "print_msg.asm"
%include "print_line.asm"
%include "print_space.asm"
DISK_ERROR_MSG:
    db "Disk error", 0
DISK_NO:
    db "DISK # (hex): ", 0
HEAD_IDX:
    db "HEAD INDEX (hex): ", 0
SECTOR_NUM:
    db "CYL/SEC NUMBERS (hex): ", 0
times 510-($-$$) db 0
dw 0xaa55
times 512 db 0
times 512 db 0
Example 3 Reading Data from Disks
;
; load3.asm
;
[org 0x7c00]
START:
mov [BOOT_DRIVE], dl
mov bp, 0x8000
mov sp, bp
mov ah, 0x00                ; resetting disk 
int 0x13 
; pass arguments
mov ah, 0x02                ; int 13h function 02
mov al, 0x02                ; read 2 sectors
push ax
mov ch, 0x00                ; cylinder no.
mov cl, 0x02                ; sector no. (sector 1 is boot sector)
push cx
mov dh, 0x00                ; head no.
mov dl, [BOOT_DRIVE]        ; drive no.
push dx
push es                     ; read disk data to es:bx
mov bx, 0x9000
push bx
call read_disk              ; read disk data
mov cl, [0x9000]
call print_byte
call print_space
mov cl, [0x9000+512]
call print_byte
jmp $
%include "read_disk.asm"
%include "print_byte.asm"
%include "print_mem.asm"
%include "print_msg.asm"
%include "print_space.asm"
%include "print_line.asm"
BOOT_DRIVE:
    db 0
times 510 -($-$$) db 0
dw 0xaa55
times 512 db 0xab
times 512 db 0xba
Example 4 Loading Program Code from Disk
;
; load4.asm
;
[org 0x7c00]
START:
mov [BOOT_DRIVE], dl
mov bp, 0x8000
mov sp, bp
mov ah, 0x00                ; resetting disk 
int 0x13 
; pass arguments
mov ah, 0x02                ; int 13h function 02
mov al, 0x02                ; read 2 sectors
push ax
mov ch, 0x00                ; cylinder no.
mov cl, 0x02                ; sector no. (sector 1 is boot sector)
push cx
mov dh, 0x00                ; head no.
mov dl, [BOOT_DRIVE]        ; drive no.
push dx
mov bx, 0x0000              ; read disk data to es:bx
mov es, bx 
push es
mov bx, 0x9000  
push bx
call read_disk              ; read disk data
mov bx, READ_DISK
call print_msg
call print_line
mov cl, [0x9000] 
call print_byte
call print_space
mov cl, [0x9001] 
call print_byte
call print_space
mov cl, [0x9002] 
call print_byte
call print_space
mov cl, [0x9003] 
call print_byte
call print_space
mov bx, 0x0000 
mov es, bx 
push es
mov bx, 0x9000  
push bx
retf
%include "read_disk.asm"
%include "print_byte.asm"
%include "print_mem.asm"
%include "print_msg.asm"
%include "print_space.asm"
%include "print_line.asm"
BOOT_DRIVE:
    db 0
READ_DISK:
    db "Read new program from disk sectors.", 0
times 510 -($-$$) db 0
dw 0xaa55
main:
    mov ah, 0x0e
    mov al, 0x0d
    int 0x10
    mov ah, 0x0e
    mov al, 0x0a
    int 0x10
    mov ah, 0x0e 
    mov al, 'S'
    int 0x10
    mov ah, 0x0e 
    mov al, 't'
    int 0x10
    mov ah, 0x0e 
    mov al, 'a'
    int 0x10
    mov ah, 0x0e 
    mov al, 'r'
    int 0x10
    mov ah, 0x0e 
    mov al, 't'
    int 0x10
    mov ah, 0x0e 
    mov al, 'e'
    int 0x10
    mov ah, 0x0e 
    mov al, 'd'
    int 0x10
    mov ah, 0x0e 
    mov al, ' '
    int 0x10
    mov ah, 0x0e 
    mov al, 'n'
    int 0x10
    mov ah, 0x0e 
    mov al, 'e'
    int 0x10
 
    mov ah, 0x0e 
    mov al, 'w'
    int 0x10
    mov ah, 0x0e 
    mov al, ' '
    int 0x10
    mov ah, 0x0e 
    mov al, 'p'
    int 0x10
    mov ah, 0x0e 
    mov al, 'r'
    int 0x10
    mov ah, 0x0e 
    mov al, 'o'
    int 0x10
    mov ah, 0x0e 
    mov al, 'g'
    int 0x10
 
    mov ah, 0x0e 
    mov al, 'r'
    int 0x10
    mov ah, 0x0e 
    mov al, 'a'
    int 0x10
    mov ah, 0x0e 
    mov al, 'm'
    int 0x10
    mov ah, 0x0e 
    mov al, '.'
    int 0x10
    jmp $
ANNOUNCEMENT:
    db "New program starts...", 0
times 1024-($-$$) db 0
times 512 db 0
Required Subroutines
The above example programs reference to a few subroutines.
Printing a Byte in Hexidecimal
;
; print_byte.asm
;
; print a byte in register cl to hex assuming ASCII encoding
;    void * print_byte(char c) 
print_byte:
    pusha ; Push AX, CX, DX, BX, original SP, BP, SI, and DI.
    mov ch, cl
    shr ch, 4
    and ch, 0x0f
    push cx
    mov cl, ch
    call print_low_4bits
    pop cx
    mov ch, cl
    and ch, 0x0f
    push cx
    mov  cl, ch
    call print_low_4bits
    pop cx
    popa
    ret
; print lower 4 bits in register cl to hex assuming ASCII encoding
;    void * print_byte(char c) 
print_low_4bits:
    pusha
    mov ah, 0x0e
    mov ch, cl
    and ch, 0x0f
    cmp ch, 0x09
    jg A_TO_F
    add ch, '0'
    jmp PRINT_CHAR
A_TO_F:
    sub ch, 0x0a
    add ch, 'A'
    jmp PRINT_CHAR
PRINT_CHAR:
    mov al, ch
    int 0x10
    popa
    ret
Printing Link Break
;
; print_line.asm
;
print_line:
    pusha
    ; line feed    
    mov ah, 0x0e
    mov al, 0x0a
    int 0x10
    ; carriage return
    mov ah, 0x0e
    mov al, 0x0d
    int 0x10
    popa
    ret
Printing Memory Buffer in Hexadecimals
;
; print_mem.asm
;
; print memory in hex byte by byte separated by a space
;   void print_mem(void *, short num);
; the parameter is passed via registers ds:bx and cl
print_mem:
    pusha
    mov ah, 0x0e
    
    cmp cl, 0
    je NOTHING_TO_PRINT
    xor ch, ch
LOOP:
    push cx
    mov cl, [ds:bx] 
    call print_byte
    pop cx
    inc bx
    dec cl
    inc ch
    cmp cl, 0
    je NOTHING_TO_PRINT
    and ch, 0x0f
    cmp ch, 0
    jnz DO_PRINT_SPACE 
    call print_line
    jmp LOOP
DO_PRINT_SPACE:
    call print_space
    jmp LOOP
NOTHING_TO_PRINT:
    popa
    ret
Printing String
; 
; print_msg.asm
;
; we implement a function with interface
;     void print_msg(char* msg)
; where we pass the argument msg via ds:bx
print_msg:
    pusha           ; push all registers to stack
    mov ah, 0x0e
.LOOP:
    mov al, [ds:bx]
    cmp al, 0
    je DONE_PRINT_MSG
    int 0x10
    inc bx
    jmp .LOOP
DONE_PRINT_MSG:
    popa            ; pop all registers to stack
    ret
Printing Space
;
; print_space.asm
;
print_space:
    pusha
    mov ah, 0x0e
    mov al, ' '
    int 0x10
    popa
    ret
Reading Disk Sectors
;
; read_disk.asm
;
; read disk sectors with prototype
;   uint16 read_disk(
;       uint16 offset,
;       uint16 segment,
;       uint8 sector_count, 
;       uint8 cylinder, 
;       uint8 sector, 
;       uint8 head, 
;       uint8 drive)
; the subroutine expects the parameters passed in stack 
;       offset
;       segment
;       cylinder sector
;       head rive
;       0000 0010b count
;       
; return value is also in stack
;       uint16
; the subroutine reads disk via PC BIOS int13h
; See https://en.wikipedia.org/wiki/INT_13H
;
read_disk:
;   for near call
;      ip on the top of the stack
    push ax
    push cx
    push dx
    push bp
;   parameters are at sp+10
    mov bp, sp 
    mov bx, [bp+10]         ; disk data to es:bx
    mov ax, [bp+12]
    mov es, ax
    mov dx, [bp+14]         ; dh dl are head no. and drive no.
    mov cx, [bp+16]         ; ch cl are cylinder and sector no.'s
    mov ax, [bp+18]
    mov ah, 0x02
    push ax
    int 0x13
    pop dx
    jc .DISK_ERROR
    cmp dh, al
    jne .DISK_SHORT_READ
    pop bp
    pop dx
    pop cx
    pop ax
    sub bp, 2*5
    ret
.DISK_ERROR:
    mov bx, DISK_ERROR_MSG
    call print_msg
    jmp $
.DISK_SHORT_READ:
    mov bx, SHORT_READ_MSG
    call print_msg
    jmp $
DISK_ERROR_MSG:
    db "Disk read error!", 0
SHORT_READ_MSG:
    db "Disk read error, short read!", 0