Table of Content

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