Home > Back-end >  How to deal with errors while reading disk in x86 assembly?
How to deal with errors while reading disk in x86 assembly?

Time:10-31

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).

  • Related