Extended Sassy Examples

16-bit x86 boot sector

This very simple assembly file is placed as the boot sector of a hard disk and chainloads the next two sectors off the disk to a special location and jumps there. This code could be in a file named stage1.sassy.

;; stage1        ;; the boot sector chainloader codebase
;; Written by Peter Keller (psilord@cs.wisc.edu) 03/06/2006
;; Usual BSD license text...

;; Simply chainload the next few sectors and jump to 0000:0800.

;; I'm assembling it for address 0000:0600, but the bios loads it into
;; 0000:7c00. However, after the mov of the code to the right place,
;; the string addresses and jmp relations will be correct.

;; All addresses in 16 bit mode will be in relation to segment 0.
(macro STACK_LOC #xffbf)    ;; THe stack we set up for ourselves
(macro BIOS_LOAD #x7c00)    ;; Location bootloader loads first sector of disk
(macro CRAWL_SPACE #x0600)    ;; where we move ourselves when chainloading
(macro CHAINLOAD_ADDR #x0800) ;; where we load the real OS boot sector and jmp
(macro SECTOR_LOAD_RETRY #x5) ;; how many times we attempt to load the OS.

(org CRAWL_SPACE) ;; notice this is NOT the BIOS_LOAD addr, we move ourselves...

(bits 16) 

(data
    (label err_load_msg     
        (asciiz "Error chainloading bootloader...Halting!\n\r")))

(text
    ;; DX contains booting drive via BIOS (hopefully)

    (cli)                ;; no interrupts!
    (cld)                ;; increment string operations

    (xor ax ax)            ;; set up segments for 0000
    (mov ss ax)
    (mov es ax)
    (mov ds ax)

    (mov sp STACK_LOC)        ;; set stack

    (mov si BIOS_LOAD)        ;; copy myself
    (mov cx #x100)            ;; all 512 bytes (in 16-bit word chunks)
    (mov di CRAWL_SPACE)    ;; to 0000:0600
    (rep (movsw))            ;; right now...

    (jmp #x0 moved_location)        ;; ...and jump there
    (nop)

(label moved_location)
    (xor ax ax)
    (mov es ax)
    (mov ds ax)
    (mov fs ax)
    (mov gs ax)

    ;; load the next few sectors into 0000:0800 and jmp there
    ;; XXX How many 512 byte sectors? Let's say two...

    ;; DX is set to the booting drive, and we haven't touched it yet.

    ;; try and chainload the bootloader program

    (mov di SECTOR_LOAD_RETRY)    ;; try loading sectors 5 times only

(label retry_chainload)
    (mov bx CHAINLOAD_ADDR)    ;; write disk info to (es)0000:0800

    (mov ch #x0)            ;; start at track 0
    (mov cl #x2)            ;; start at sector 2 (instead of sector 1)
                            ;; note the first sector on a disk is sector 1
                            ;; not sector zero as one would assume....

    (mov ah #x02)            ;; bios read function
    (mov al #x02)            ;; number of sectors to read  (1024 bytes)

    (push di)
    (int #x13)                ;; read some disk sectors specified in al
    (pop di)

    (jnc chainloaded)        ;; loaded the sectors, so done

    (xor ax ax)                ;; reset disk, DX is still the same
    (int #x13)

    (dec di)                ;; try again
    (jnz retry_chainload)

(label disk_error)
    (mov si err_load_msg)    ;; spew failure message.
    (call msg_loop)

    ;; End game if we failed.
(label halt)
    (jmp halt)

(label chainloaded)
    ;; sanity check the loaded stage2 to see if valid?
    ;; XXX implement later.

(label chain_trampoline)
    ;; make the registers look like how they did for me at 0x7c00 for the
    ;; bootloader program.
    (xor ax ax)
    (mov es ax)
    (mov ds ax)
    (mov fs ax)
    (mov gs ax)
    (mov cx ax)
    (mov bx ax)
    (mov si ax)
    (mov sp ax)
    (mov di ax)
    ;; leave dx alone as that contains the booting drive
    (jmp #x0 CHAINLOAD_ADDR)        ;; jump to loaded code   

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;; utility functions
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    ;;;;;;
    ;; BIOS utility routine to print out a message in ds:si
    ;;;;;;
(label msg_loop)
    (push bx)
    (push ax)
    (push di)
(label another_char)
    (lodsb)            ;; gets a char from ds:si and puts into al
    (or al al)        ;; if nul, then done
    (jz return)
    (mov ah #x0e)    ;; write 'char' teletype mode
    (mov bx #x0007)    ;; page 0 in bh, attribute 07 in bl
    (int #x10)        ;; emit character
    (jmp another_char)
(label return)
    (pop di)
    (pop ax)
    (pop bx)
    (ret))

Once the above file is in stage1.sassy, you need to have some scheme code to assemble it using sassy. Such code could look live in a file called boot.scm and look like this:

(define (assemble)

    (display "------------------------\n")
    (display "| Boot Sector (stage1) |\n")
    (display "------------------------\n")
    (sassy-make-bin
        "stage1"
        (sassy "stage1.sassy")
        'boot 'stats))