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.
CodePudding user response:
Your bpb is in the wrong place and has the wrong length!
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.
Setting up segment registers is wrong.
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
Why it has to fail reading from disk
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.
CodePudding user response:
How to deal with errors while reading disk in x86 assembly?
In general, for dealing with errors:
a) Try to work around it (e.g. if it's a "bad sector" error maybe switch to a different redundant copy of the same data), and try to design the OS so that common problems can be worked around. For boot code (where user can't use the OS to access the operating system's help or the internet because the OS won't boot) this is more important than it is for normal code.
b) Try to report the error to the user (if there is a user) using nice/easily understood language (with no cryptic nonsense or error codes) to increase the chance that the user can figure out how to fix the problem (e.g. so if there's hardware faulty, they know their hardware is faulty and can replace the right piece of hardware). For boot code (where user can't use the OS to access the operating system's help or the internet because the OS won't boot) this is more important than it is for normal code.
c) Try to provide detailed information that the user can include in an bug report designed to help the developer/you fix the problem. This is also important while writing the code because it makes it much much easier for you to fix bugs you find.
d) Try to log the error somehow. This is important for intermittent problems (e.g. "fails on cold mornings, works at other times" may be a very important clue) and for unattended systems (e.g. remote servers where an admin might log in remotely and check logs).
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:
The "(PANIC) Disk error
" error message is essentially useless - it doesn't help anyone determine if it was the user's mistake (e.g. unplugged/ejected the disk at the wrong time), or if there's a hardware failure and not a bug, or if it's a software bug. Imagine a frustrated IT person ringing you on the phone saying "The OS won't boot. It's costing our company $123456.78 per day in lost productivity. We're going to sue you if it's not fixed by tomorrow. The error message is Disk error
.".
If you look at the list of possible error codes (e.g. at http://www.ctyme.com/intr/rb-0606.htm ) that int 0x13, ah=0x02
can return in AH
; you'll notice that some of these error codes make it easy to determine that it's probably the user's fault (e.g. a "disk changed" error) or a hardware failure (e.g. "bad sector detected") or a software bug (e.g. "invalid function in AH or invalid parameter"); and all of them give a very useful clue in what the problem is.
Note that (real) floppy disks are notoriously unreliable (high chance of intermittent read errors); and (if the disk wasn't used recently) can report errors simply because the motor isn't spinning fast enough yet. For these reasons, on floppy, it's recommended that software retries 3 times (in an attempt to work around the problem) before assuming the error will persist; and this is where you might reset the disk system (e.g. maybe every second retry). Also; don't forget that the BIOS successfully loaded your boot sector from the disk immediately before starting your boot loader, so there's no reason to reset the disk system at the start of your boot loader (where you already have proof that the disk system is working properly).