I am trying to make my own bootloader in GAS assembly. So far, I am able to print to the screen using BIOS interrupts. I tried to read the disk into memory, but the output in the emulator is:
Booting... (PANIC) Disk error Press any key to reboot..._
This is my code:
.code16
.text
.org 0x0
.global main
main:
jmp start # jump to beginning of code
nop
bpb:
iOEM: .ascii "Canary" # OEM String
iSectSize: .word 0x200 # bytes per sector
iClustSize: .byte 1 # sectors per cluster
iResSect: .word 1 # #of reserved sectors
iFatCnt: .byte 2 # #of FAT copies
iRootSize: .word 224 # size of root directory
iTotalSect: .word 2880 # total # of sectors if over 32 MB
iMedia: .byte 0xf0 # media Descriptor
iFatSize: .word 9 # size of each FAT
iTrackSect: .word 9 # sectors per track
iHeadCnt: .word 2 # number of read-write heads
iHiddenSect: .int 0 # number of hidden sectors
iSect32: .int 0 # # sectors for over 32 MB
iBootDrive: .byte 0 # holds drive that the boot sector came from
iReserved: .byte 0 # reserved, empty
iBootSign: .byte 0x29 # extended boot sector signature
iVolID: .ascii "seri" # disk serial
acVolumeLabel: .ascii "VOLUME A" # volume label
acFSType: .ascii "FAT16" # file system type
.func print
print:
lodsb # load byte from si into al, increment si
cmp $0, %al # test if character is 0 (end)
je print_done # jump to end if 0.
mov $0x0e, %ah # set teletype output
mov $9, %bx # set bh (page no.) to 0, and bl (attribute) to white (9)
int $0x10 # int 10h
jmp print # repeat for next character.
print_done:
ret
.endfunc
.func reboot
reboot:
mov $rebootmsg, %si # load address of reboot message into si
call print # print the string
mov $0x00, %ah
mov $0x00, %al
int $0x16 # wait for a key press
.byte 0xea # machine language to jump to ffff:0000 (reboot)
.word 0x0000
.word 0xffff
.endfunc
.func readSector
readSector:
mov $0x00, %cx # counter = 0
read:
push %ax # store logical block in stack
push %cx # store counter in stack
push %bx # store data buffer offset in stack
# Cylinder = (LBA / SectorsPerTrack) / NumHeads
# Sector = (LBA mod SectorsPerTrack) + 1
# Head = (LBA / SectorsPerTrack) mod NumHeads
mov iTrackSect, %bx # get sectors per track
mov $0x00, %dx
# Divide (dx:ax/bx to ax,dx)
# Quotient (ax) = LBA / SectorsPerTrack
# Remainder (dx) = LBA mod SectorsPerTrack
div %bx
inc %dx # increment remainder since it is a sector
mov %dl, %cl # store result in cl to use for int 13h
mov iHeadCnt, %bx # get number of heads
mov $0x00, %dx
# Divide (dx:ax/bx to ax,dx)
# Quotient (ax) = Cylinder
# Remainder (dx) = Head
div %bx
mov %al, %ch # ch = cylinder
mov %dl, %dh # dh = head
mov $0x02, %ah # subfunction 2
mov $0x01, %al # no. of sectors to read
mov iBootDrive, %dl # drive number
pop %bx # restore data buffer offset
int $0x13
jc readFailure # retry if carry flag is set (error)
pop %cx
pop %ax
ret
# On error, retry 4 times before jumping to bootFailure
readFailure:
pop %cx # get counter from stack
inc %cx
cmp $4, %cx # check if we completed 4 tries
je bootFailure # jump to bootFailure if even after 4 tries we get an error
# Reset disk system
mov $0x00, %ah
mov $0x00, %al
int $0x13
# Retry
pop %ax
jmp read
.endfunc
start:
# Setup segments:
cli
mov %dl, iBootDrive # save what drive we booted from (should be 0x0)
mov %cs, %ax # cs = 0x0, since that's where boot sector is (0x07c00)
mov %ax, %ds # cs = cs = 0x0
mov %ax, %es # cs = cs = 0x0
mov %ax, %ss # cs = cs = 0x0
mov $0x7c00, %sp # Stack grows down from offset 0x7c00 toward 0x0000.
sti
# Clear the screen
mov $0x00, %ah
mov $0x03, %al # Set video mode (80x25 text mode, 16 colors)
int $0x10
# Reset disk system
# Jump to bootFailure on error
mov iBootDrive, %dl # drive to reset
mov $0x00, %ah
mov $0x00, %al
int $0x13
jc bootFailure # display error message if carry set (error)
# Display message if successful
mov $msg, %si
call print
call readSector
# Reboot
call reboot
bootFailure:
mov $diskerror, %si
call print
call reboot
# Program Data
msg: .asciz "Booting...\r\n"
diskerror: .asciz "(PANIC) Disk error\r\n"
rebootmsg: .asciz "Press any key to reboot..."
.fill (510-(.-main)), 1, 0 # Pad with nulls up to 510 bytes (excl. boot magic)
.word 0xaa55 # magic word for BIOS
What am I doing wrong? Also, if there is a better more efficient way to write this code, please tell.
jmp start # jump to beginning of code nop bpb: iOEM: .ascii "Canary" # OEM String ... acVolumeLabel: .ascii "VOLUME A" # volume label acFSType: .ascii "FAT16" # file system type
Because the start label is more than 127 bytes away from this initial jmp
, the assembler will encode the jump using 3 bytes. But then the additional nop
will make the bpb start at offset 4, where legally it has to start at offset 3. Either you drop the nop
or you bring the start closer by.
The BS_OEMName field must be 8 bytes long. Your "Canary" is too short.
The BS_VolLab field must be 11 bytes long. Your "VOLUME A" is too short.
The BS_FilSysType field must be 8 bytes long. Your "FAT16" is too short.
start: # Setup segments: cli mov %dl, iBootDrive # save what drive we booted from (should be 0x0) mov %cs, %ax # cs = 0x0, since that's where boot sector is (0x07c00) mov %ax, %ds # cs = cs = 0x0 mov %ax, %es # cs = cs = 0x0 mov %ax, %ss # cs = cs = 0x0 mov $0x7c00, %sp # Stack grows down from offset 0x7c00 toward 0x0000. sti
There's no guarantee whatsoever that the %cs
code segment register will be 0 when your bootsector program starts, yet you copy its contents to the other segment registers. Moreover, with an .org 0x0
the correct value to load in the segment registers would have to be 0x07C0, not zero!
And of course you should defer saving the bootdrive code to after you have loaded %ds
correctly.
My suggestion would be:
.org 0x7C00
...
start:
cli
xor %ax, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %ss
mov $0x7C00, %sp
mov %dl, iBootDrive
sti
call print call readSector
You don't specify the logical block that you want to read from!
The print routine certainly uses %ax
leaving a non-sensical value in it, and you never setup the desired LBA in %ax
before calling readSector. That's never going to work. Additionally you also forget to specify the data buffer offset in %bx
.
Once you'll have amended all of the above, you could try to load the second sector of the disk (CHS = 0,0,2) so as to verify that reading is fine:
mov $0x00, %dh # dh = head
mov iBootDrive, %dl # drive number
mov $0x0002, %cx # ch = cylinder, cl = sector
mov $0x7E00, %bx # data buffer offset
mov $0x0201, %ax # ah = subfunction, al = no. of sectors to read
int $0x13
jc readFailure
Good luck!
Also, if there is a better more efficient way to write this code, please tell.
I didn't verify the code within the readSector routine too much. I'm kind of assuming that it copied anyway. Point being, if you're interested in optimizing the code, I suggest that you, after corrections, post a working version of it on the Code Review forum.