x86程序集读取磁盘时如何处理错误?

How to deal with errors while reading disk in x86 assembly?

我正在尝试在 GAS 程序集中制作我自己的引导加载程序。到目前为止,我能够使用 BIOS 中断打印到屏幕。我试图将磁盘读入内存,但模拟器中的输出是:

Booting...
(PANIC) Disk error
Press any key to reboot..._

这是我的代码:

.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 [=11=], %al     # test if character is 0 (end)
    je print_done   # jump to end if 0.

    mov [=11=]x0e, %ah  # set teletype output
    mov , %bx     # set bh (page no.) to 0, and bl (attribute) to white (9)
    int [=11=]x10       # 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 [=11=]x00, %ah
    mov [=11=]x00, %al
    int [=11=]x16            # wait for a key press
    .byte 0xea           # machine language to jump to ffff:0000 (reboot)
    .word 0x0000
    .word 0xffff
.endfunc

.func readSector
readSector:
    mov [=11=]x00, %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 [=11=]x00, %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 [=11=]x00, %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 [=11=]x02, %ah          # subfunction 2
    mov [=11=]x01, %al          # no. of sectors to read
    mov iBootDrive, %dl     # drive number
    pop %bx                 # restore data buffer offset
    int [=11=]x13
    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 , %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 [=11=]x00, %ah
    mov [=11=]x00, %al
    int [=11=]x13

    # 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 [=11=]x7c00, %sp     # Stack grows down from offset 0x7c00 toward 0x0000.
    sti

    # Clear the screen
    mov [=11=]x00, %ah
    mov [=11=]x03, %al # Set video mode (80x25 text mode, 16 colors)
    int [=11=]x10

    # Reset disk system
    # Jump to bootFailure on error
    mov iBootDrive, %dl   # drive to reset
    mov [=11=]x00, %ah
    mov [=11=]x00, %al
    int [=11=]x13
    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

我做错了什么?另外,如果有更好更高效的写这段代码的方法,请告诉。

你的bpb放错了地方,长度也不对!

    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

因为 start 标签距离初始 jmp 超过 127 个字节,汇编程序将使用 3 个字节对跳转进行编码。但是随后额外的 nop 将使 bpb 从偏移量 4 开始,合法地它必须从偏移量 3 开始。要么删除 nop 要么带上起点更近。

BS_OEMName 字段的长度必须为 8 个字节。你的“金丝雀”太短了。

BS_VolLab 字段的长度必须为 11 个字节。你的“VOLUME A”太短了。

BS_FilSysType 字段的长度必须为 8 个字节。你的“FAT16”太短了。


设置段寄存器错误。

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 [=11=]x7c00, %sp     # Stack grows down from offset 0x7c00 toward 0x0000.
    sti

无法保证当您的引导扇区程序启动时 %cs 代码段寄存器将为 0,但您将其内容复制到其他段寄存器。此外,使用 .org 0x0 加载到段寄存器中的正确值必须是 0x07C0,而不是零!
当然,您应该在正确加载 %ds 之后推迟保存引导驱动器代码。

我的建议是:

.org 0x7C00

...

start:
  cli
  xor  %ax, %ax
  mov  %ax, %ds
  mov  %ax, %es
  mov  %ax, %ss
  mov  [=12=]x7C00, %sp
  mov  %dl, iBootDrive
  sti

为什么从磁盘读取失败

call print

call readSector

您没有指定要读取的逻辑块!
print 例程肯定使用 %ax 并在其中留下一个无意义的值,并且在调用 readSector 之前,您永远不会在 %ax 中设置所需的 LBA 。那永远行不通。此外,您还忘记在 %bx.

中指定数据缓冲区偏移量

修改完以上所有内容后,您可以尝试加载磁盘的第二个扇区(CHS = 0,0,2)以验证读取是否正常:

mov  [=14=]x00, %dh       # dh = head
mov  iBootDrive, %dl  # drive number
mov  [=14=]x0002, %cx     # ch = cylinder, cl = sector
mov  [=14=]x7E00, %bx     # data buffer offset
mov  [=14=]x0201, %ax     # ah = subfunction, al = no. of sectors to read
int  [=14=]x13
jc   readFailure

祝你好运!

Also, if there is a better more efficient way to write this code, please tell.

我没有过多地验证 readSector 例程中的代码。我有点假设它无论如何都被复制了。重点是,如果您有兴趣优化代码,我建议您在更正后,post Code Review 论坛上的工作版本。

How to deal with errors while reading disk in x86 assembly?

一般来说,对于错误的处理:

a) 尝试解决它(例如,如果它是一个“坏扇区”错误,可能会切换到相同数据的不同冗余副本),并尝试设计 OS 以便常见问题可以解决被解决。对于启动代码(用户无法使用 OS 访问操作系统的帮助或互联网,因为 OS 无法启动)这比普通代码更重要。

b) 尝试使用 nice/easily 理解的语言(没有神秘的废话或错误代码)向用户(如果有用户)报告错误,以增加用户弄清楚如何解决的机会解决问题(例如,如果硬件有故障,他们知道硬件有故障并且可以更换正确的硬件)。对于启动代码(用户无法使用 OS 访问操作系统的帮助或互联网,因为 OS 无法启动)这比普通代码更重要。

c) 尝试提供详细信息,用户可以将其包含在旨在帮助 developer/you 解决问题的错误报告中。这在编写代码时也很重要,因为它使您更容易修复发现的错误。

d) 尝试以某种方式记录错误。这对于间歇性问题(例如“在寒冷的早晨失败,在其他时间工作”可能是一个非常重要的线索)和无人值守的系统(例如管理员可能远程登录并检查日志的远程服务器)很重要。

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:

(PANIC) Disk error”错误信息本质上是无用的——它不能帮助任何人确定这是不是用户的错误(例如 unplugged/ejected 磁盘在错误的时间),或者是否有硬件故障而不是错误,或者它是软件错误。想象一下,一个沮丧的 IT 人员在 phone 上给你打电话说“OS 无法启动。我们公司每天因生产力损失而损失 123456.78 美元。我们要起诉你如果明天还没有修好。错误消息是 Disk error.".

如果您查看可能的错误代码列表(例如 http://www.ctyme.com/intr/rb-0606.htm ),int 0x13, ah=0x02 可以 return in AH;您会注意到其中一些错误代码可以很容易地确定它可能是用户的错误(例如“磁盘已更改”错误)或硬件故障(例如“检测到坏扇区”)或软件错误(例如“无效AH 中的函数或无效参数");所有这些都为问题所在提供了非常有用的线索。

请注意,(真正的)软盘是出了名的不可靠(间歇性读取错误的可能性很高);并且(如果最近没有使用磁盘)可以报告错误只是因为电机旋转得不够快。出于这些原因,在软盘上,建议软件重试 3 次以上(以尝试解决问题),然后假设错误会持续存在;这是您可以重置磁盘系统的地方(例如,可能每秒钟重试一次)。还;不要忘记 BIOS 在启动引导加载程序之前立即从磁盘成功加载了引导扇区,因此没有理由在引导加载程序启动时重置磁盘系统(您已经证明磁盘系统工作正常)。