BIOS int 13h 无法读取第一条轨道

Posted

技术标签:

【中文标题】BIOS int 13h 无法读取第一条轨道【英文标题】:BIOS int 13h can't read past the first track 【发布时间】:2016-03-04 10:37:02 【问题描述】:

我正在尝试使用 Bios 中断 13h 从我的 FAT12 引导加载程序从软盘加载扇区号 [head = 0, cylinder(track) = 1, sector = 1]。

我使用子程序read_sectors 读取扇区并将其加载到es:bx

此代码适用于第一个轨道的任何扇区,但它仅从其他轨道的任何扇区读取 0,而这些扇区实际上已填充。例如,对于第 18 个扇区,cx0x0041,这是正确的。问题是,中断设置CF,说有错误。它还将 ah(返回码)设置为 1,将 al(读取的扇区)设置为 1。

这是完整的引导加载程序 .asm 文件

bits 16
org 0

start: jmp load

nop
OEM:                    DB "ptiaOS  "
bytesPerSector:     DW 512
sectorsPerCluster:  DB 1
reservedSectors:    DW 1
numberOfFATs:       DB 2
rootEntries:        DW 224
totalSectors:       DW 2880
media:              DB 0xf8
sectorsPerFAT:      DW 9
sectorsPerTrack:    DW 18
headsPerCylinder:   DW 2
hiddenSectors:      DD 0
totalSectorsBig:        DD 0
driveNumber:            DB 0
unused:         DB 0
extBootSignature:   DB 0x29
serialNumber:           DD 0xa0a1a2a3
volumeLabel:            DB "PTIAOS FLP "
fileSystem:             DB "FAT12   "

load:
  ;The bootloader is loaded at the address 0x7C00 and is 0x200 (512) bytes long
  cli
  mov ax, 0x07C0 ; setup registers to point to our segment
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  sti

  mov si, hello_string
  call prints

  mov si, try_string
  call prints

  mov ax, 18
  call lba_to_chs

  mov al, 2
  mov bx, 0x200
  call read_sectors

  mov si, success_string
  call prints

  mov si, 0x200
  call prints

  cli
  hlt ;halt



;--------DATA--------
hello_string db `Hi, bootloader of ptiaOS here\n\r`, 0
success_string db `Successfully loaded from floppy\n\r`, 0
try_string db `Loading more data from floppy...\n\r`, 0
;CHS position of the sector to read
sector_number db 0 ;1 is the first (they're 18 per track)
cilinder_number db 0 ;track number: 0 is the first (they're 80 per side)
head_number db 0 ;0 is the first (the're 2)

;---SOTTOPROGRAMMI---
;print a 0-terminated string pointed by ds:si
prints:
  mov ah, 0x0E ;dico all'interrupt del BIOS video di eseguire la funzione di stampa [al: carattere, bh: pagina] 
  .prints_printchar:
    lodsb ;al = *(si++)
    cmp al, 0
    je .prints_end ;if(al == 0) goto print_string_end
      int 0x10 ;chiamo l'interrupt di i/o dello schermo, con ah = 0x0E per stampare il carattere in al
      jmp .prints_printchar
  .prints_end:
  ret


 ;Read sectors from floppy at the address specified by CHS variables, and load them in es:bx
read_sectors:
  mov ah, 0x02 ;function 0x02, interrupt 0x13: read sectors
  ;al (the number of sectors to read), es:bx (destination) are set as arguments
  xor cx, cx
  mov cl, [cylinder_number]
  shl cl, 6
  or cl, [sector_number]
  mov dh, [head_number]
  mov dl, 0
  int 0x13
  jnc .sectors_read_successfully ;CF = 0 if no errors, 1 otherwise
  ;if errors occured, try to reset floppy
  .flp_reset:
    mov ah, 0 ;function 0, interrupt 0x13: reset disk
    mov dl, 0 ;disk to reset: 0=floppy
    int 0x13
    jc .flp_reset ;CF = 0 if no errors, 1 otherwise
  jmp read_sectors
  .sectors_read_successfully:
  ret

lba_to_chs:
  mov cx, ax

  mov bl, [sectorsPerTrack]
  div bl
  inc ah ;ah = lba % 18 + 1
  mov byte [sector_number], ah

  mov ax, cx
  mov bl, [sectorsPerTrack]
  div bl ;al = lba / 18
  cbw ;ax = lba / 18
  mov bl, [headsPerCylinder]
  div bl ;al = lba / 18 / 2; ah = lba / 18 % 2
  mov byte [cilinder_number], ah
  mov byte [head_number], al

  ret

times 510-($-$$) db 0
dw 0xAA55

我在 qemu 上运行此代码,来自 Ubuntu,并使用它进行编译

nasm -f bin -o ptiaos.bin ptiaboot.asm
nasm -f bin -o BSTAGE2.SYS blstage2.asm

mkdir floppy
dd status=noxfer conv=notrunc if=ptiaos.bin of=ptiaos.flp
sudo mount -o loop ptiaos.flp floppy
sudo cp BSTAGE2.SYS floppy

sleep 0.1
sudo umount floppy
rm BSTAGE2.SYS
rm ptiaos.bin
rmdir floppy

【问题讨论】:

您需要为您正在阅读的设备的几何形状计算正确的 CHS 地址,我在您的代码中没有看到。这不是微不足道的。我发现this OSdev page 对my implementation 很有帮助。 其实我是用一个基于OSdev页面的子程序来计算CHS地址的。我只是从问题中省略了它,因为它似乎工作正常,并将 chs 地址直接传递给 read_sectors 函数。地址 [head = 0, cilinder(track) = 1, Sector = 1] 实际上是 LBA (0-indexed) 地址 18。如果你认为我需要在这里显示子程序,我会发布它。感谢您这么快回答。 不客气。我只是在检查-根据我的经验,问题通常出在 CHS 计算中,或者假设驱动器的柱面/磁头/磁道数量错误。因此,您在读取 ​​LBA 地址 19 及以后遇到问题:您是否手动检查过 LBA 19 是否已转换为正确的 CHS?如果是这样,则无需发布 CHS 计算。还有一件事:数据被读取到es:bx,我看不到bx是如何初始化的。 我通过直接修改.flp文件填充了扇区17-18(0索引),它适用于扇区17。如果我从17开始加载2个扇区,我放入扇区18的数据是也加载了。只有当我直接从 18 或之后加载时它才起作用。 是的,转换为 H=0 C=1 S=1 是对的。也许我将这些地址放入 CX 的方式是错误的?它位于 read_sectors 子程序中,就在调用中断之前。它基于wiki.osdev.org/ATA_in_x86_RealMode_%28BIOS%29 描述的算法,“从 CHS 地址读取扇区”一节 【参考方案1】:

我将提出一个修复建议,但有一个假设。 lba_to_chs 似乎设计用于较小的磁盘大小,其中柱面数不超过 0xff (255)。这对于传统的软盘大小来说很好,因为柱面的数量通常会比这少很多。

首先这段代码有个bug:

  mov ax, cx
  mov bl, [sectorsPerTrack]
  div bl ;al = lba / 18
  cbw ;ax = lba / 18
  mov bl, [headsPerCylinder]
  div bl ;al = lba / 18 / 2; ah = lba / 18 % 2
  mov byte [cilinder_number], ah
  mov byte [head_number], al

在最后的划分中,AL 应该包含柱面编号,AH 应该包含磁头编号。在您的代码中,您已经反转了这些。最后两行应该是:

  mov byte [cilinder_number], al
  mov byte [head_number], ah

假设柱面不超过 255,可以稍微修改 read_sector 代码。 INT 13h AH=02h 要求柱面号和扇区号以这种方式放在 CX 中:

CX =       ---CH--- ---CL---
cylinder : 76543210 98
sector   :            543210

他们还给出了位操作的等式:

CX := ( ( 柱面和 255 ) shl 8 ) 或 ( (柱面和 768 ) shr 2 ) 或扇区;

由于我们的气缸数不会超过 255,因此等式简化为:

CX := ( ( 柱面和 255 ) shl 8 ) 或扇区;

这与简单地将柱面存储在 CH 和将扇区存储在 CL 相同。因此,您用于设置 CXCLCH)的代码显示为:

  mov ah, 0x02 ;function 0x02, interrupt 0x13: read sectors
  ;al (the number of sectors to read), es:bx (destination) are set as arguments
  xor cx, cx
  mov cl, [cylinder_number]
  shl cl, 6
  or cl, [sector_number]
  mov dh, [head_number]
  mov dl, 0
  int 0x13

看起来像:

  mov ah, 0x02 ;function 0x02, interrupt 0x13: read sectors
  ;al (the number of sectors to read), es:bx (destination) are set as arguments mov ch, [cilinder_number]
  mov ch, [cilinder_number]
  mov cl, [sector_number]
  mov dl, 0
  mov dh, [head_number]
  int 0x13

上面的代码还有另一个缺陷,那就是 DL 寄存器被设置为 0。这是读取扇区的驱动器号。这应该设置为 BIOS 在 DL 中在跳转到内存地址 0x07c00 时传递给我们的引导加载程序的驱动器号。我们应该在启动时保存该值,然后在读取扇区时将其复制到 DL。这允许我们从一个可能不是第一张启动软盘的驱动器(磁盘号 0x00)启动。

可以通过将 boot_drive 变量添加到您的数据区来修改代码:

boot_drive db 0

初始化段寄存器后,保存传递给我们的引导加载程序的DL(引导驱动器):

mov [boot_drive], dl

然后在load_sector中改变:

mov dl, 0

到:

mov dl, [boot_drive]

上述所有建议的修复和更改后的最终代码可能如下所示:

bits 16
org 0

GLOBAL main
main:
start: jmp load

nop
OEM:                    DB "ptiaOS  "
bytesPerSector:     DW 512
sectorsPerCluster:  DB 1
reservedSectors:    DW 1
numberOfFATs:       DB 2
rootEntries:        DW 224
totalSectors:       DW 2880
media:              DB 0xf8
sectorsPerFAT:      DW 9
sectorsPerTrack:    DW 18
headsPerCylinder:   DW 2
hiddenSectors:      DD 0
totalSectorsBig:        DD 0
driveNumber:            DB 0
unused:         DB 0
extBootSignature:   DB 0x29
serialNumber:           DD 0xa0a1a2a3
volumeLabel:            DB "PTIAOS FLP "
fileSystem:             DB "FAT12   "

load:
  ;The bootloader is loaded at the address 0x7C00 and is 0x200 (512) bytes long
  cli
  mov ax, 0x07C0 ; setup registers to point to our segment
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax
  sti
  mov [boot_drive], dl

  mov si, hello_string
  call prints

  mov si, try_string
  call prints

  mov ax, 18
  call lba_to_chs

  mov al, 1
  mov bx, 0x200
  call read_sectors

  mov si, success_string
  call prints

  mov si, 0x200
  call prints

  cli
  hlt ;halt



;--------DATA--------
boot_drive db 0
hello_string db `Hi, bootloader of ptiaOS here\n\r`, 0
success_string db `Successfully loaded from floppy\n\r`, 0
try_string db `Loading more data from floppy...\n\r`, 0
;CHS position of the sector to read
sector_number db 0 ;1 is the first (they're 18 per track)
cilinder_number db 0 ;track number: 0 is the first (they're 80 per side)
head_number db 0 ;0 is the first (the're 2)

;---SOTTOPROGRAMMI---
;print a 0-terminated string pointed by ds:si
prints:
  mov ah, 0x0E ;dico all'interrupt del BIOS video di eseguire la funzione di stampa [al: carattere, bh: pagina]
  .prints_printchar:
    lodsb ;al = *(si++)
    cmp al, 0
    je .prints_end ;if(al == 0) goto print_string_end
      int 0x10 ;chiamo l'interrupt di i/o dello schermo, con ah = 0x0E per stampare il carattere in al
      jmp .prints_printchar
  .prints_end:
  ret
;Read sectors from floppy at the address specified by CHS variables, and load them in es:bx
read_sectors:
  mov ah, 0x02 ;function 0x02, interrupt 0x13: read sectors
  ;al (the number of sectors to read), es:bx (destination) are set as arguments  mov ch, [cilinder_number]
  mov ch, [cilinder_number]
  mov cl, [sector_number]
  mov dl, [boot_drive]
  mov dh, [head_number]
  int 0x13
  jnc .sectors_read_successfully ;CF = 0 if no errors, 1 otherwise
  ;if errors occured, try to reset floppy
  .flp_reset:
    mov ah, 0 ;function 0, interrupt 0x13: reset disk
    mov dl, 0 ;disk to reset: 0=floppy
    int 0x13
    jc .flp_reset ;CF = 0 if no errors, 1 otherwise
  jmp read_sectors
  .sectors_read_successfully:
  ret

lba_to_chs:
  mov cx, ax

  mov bl, [sectorsPerTrack]
  div bl
  inc ah ;ah = lba % 18 + 1
  mov byte [sector_number], ah

  mov ax, cx
  mov bl, [sectorsPerTrack]
  div bl ;al = lba / 18
  cbw ;ax = lba / 18
  mov bl, [headsPerCylinder]
  div bl ;al = lba / 18 / 2; ah = lba / 18 % 2
  mov byte [cilinder_number], al
  mov byte [head_number], ah

  ret

times 510-($-$$) db 0
dw 0xAA55

【讨论】:

以上是关于BIOS int 13h 无法读取第一条轨道的主要内容,如果未能解决你的问题,请参考以下文章

BIOS INT 13H 问题(从驱动器读取扇区)

BIOS int 13H,AH=02(读取软盘)和堆栈段

读取磁盘:CHS方式

在引导加载程序中使用 int 13h ah=42h 读取 LBA 硬盘

[汇编]《汇编语言》第17章 使用BIOS进行键盘输入和磁盘读写

最简单的链式加载启动管理器