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

Wol*_*der 3 x86 assembly virtualbox bios bootloader

描述:

在我努力创建一个简单的独立程序的过程中,我在第一个扇区中编写了一个简单的引导加载程序。它的目的是将程序加载到内存中。为此,我使用了 AH=2 的 INT 13h。代码是:

disk_load:
  push dx           ; Store DX on stack so later we can recall how many sectors were requested to be read,
                    ; even if it is altered in the meantime.
  mov ah, 0x02        ; BIOS read sector.
  mov al, dh          ; Read DH sectors.
  mov ch, 0x00        ; Select cylinder 0.
  mov dh, 0x00        ; Select head 0.
  mov cl, 0x02        ; Start reading from second sector (i.e. after the boot sector).
  int 0x13            ; BIOS interrupt.
                      ;  <!----here
  pop dx
  ret

load_software:
  mov bx, 0x7e0
  mov es, bx
  xor bx, bx
  mov dh, 66
  mov dl, [BOOT_DRIVE]
  call disk_load
Run Code Online (Sandbox Code Playgroud)

我在 VirtualBox 5.2.8 中锻炼了所有东西,它运行得非常好。将所有内容移动到第二台装有 VirtualBox 6.0.14 的机器上实验失败。中断以 CF 设置结束,表示失败。

阅读Boot loader 中的优秀答案并不会跳转到内核代码我已经修复了可能导致问题的未指定 DS 值的潜在问题。如果我在int 0x13调用之前停止并转储 CPU 状态,我会在两个 VirtualBox 上获得一致的状态:

00:00:05.930849 eax=00000280 ebx=00007e00 ecx=00000002 edx=00000000 esi=00000000 edi=0000fff0

00:00:05.930857 eip=00007cc8 esp=00007bf9 ebp=00007bff iopl=0 nv up ei pl nz na po nc

00:00:05.930864 cs={0000 base=0000000000000000 limit=0000ffff flags=0000009b} dr0=00000000 dr1=00000000

00:00:05.930877 ds={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr2=00000000 dr3=00000000

00:00:05.930884 es={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr4=00000000 dr5=00000000

00:00:05.930891 fs={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr6=ffff0ff0 dr7=00000400

00:00:05.930898 gs={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr0=00000010 cr2=00000000

00:00:05.930904 ss={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr3=00000000 cr4=00000000

00:00:05.930910 gdtr=00000000000fe89f:0047 idtr=0000000000000000:ffff eflags=00200246

解析所有值我只能得出结论,中断的所有输入参数都设置正确。转储后的状态设置了 CF 和错误代码:

00:00:08.984877 eax=00000900 ebx=00000000 ecx=00000002 edx=00000000 esi=00000000 edi=0000fff0

00:00:08.984887 eip=00007cca esp=00007bf9 ebp=00007bff iopl=0 nv up ei pl nz na po cy

00:00:08.984896 cs={0000 base=0000000000000000 limit=0000ffff flags=0000009b} dr0=00000000 dr1=00000000

00:00:08.984909 ds={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr2=00000000 dr3=00000000

00:00:08.984917 es={07e0 base=0000000000007e00 limit=0000ffff flags=00000093} dr4=00000000 dr5=00000000

00:00:08.984925 fs={0000 base=0000000000000000 limit=0000ffff flags=00000093} dr6=ffff0ff0 dr7=00000400

00:00:08.984934 gs={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr0=00000010 cr2=00000000

00:00:08.984941 ss={0000 base=0000000000000000 limit=0000ffff flags=00000093} cr3=00000000 cr4=00000000

00:00:08.984948 gdtr=00000000000fe89f:0047 idtr=0000000000000000:ffff eflags=00200247

注意到错误代码AH=9 数据边界错误(尝试跨 64K 边界或 >80h 扇区的 DMA)使我转到https://en.wikipedia.org/wiki/INT_13H,在此声明:

Buffer 的寻址应该保证完整的缓冲区在给定的段内,即 ( BX + size_of_buffer ) <= 10000h。

这可以解释我最初的问题,所以我做了另一个修复来设置es=0x7e0和bx=0。这是上面显示的代码的状态。但是,即使此代码也会因上述状态而失败。

进一步测试表明,我可以成功读取多达 65 个扇区,但 66 个或更多扇区失败。由于这是一个奇怪的数字,我计算了第 65 个扇区的结尾:0xffff。所以问题变得有点混乱。

问题:

我的es=0x7e0和bx=0解决方案是否应该避免段交叉(据我所知)?

如果是,为什么在跨越线性地址时似乎是问题?

或者可以跨段,但不能跨线性地址中的 0xffff 标记?

感谢您的帮助。

Ros*_*dge 5

问题是您“尝试跨 64K 边界进行 DMA”。事实上,您的目标缓冲区从物理地址 0x07E00 跨越到 0x17E00,它跨越了 0x10000 处的 64K 边界。(这与段边界无关,因此使用什么段和偏移值到达 0x07E00 物理地址并不重要。)

这样做的原因与最初的 IBM PC 是如何以便宜的方式设计的有关。他们没有使用带有 16 位总线和 16 位支持芯片的 16 位 8086,而是使用更便宜的带有 8 位总线的 16 位 8088 CPU,该 CPU 可以与更便宜的 8 位支持芯片一起使用。特别是他们选择的 DMA 控制器只能寻址 64K 内存,这是 DMA 控制器设计用于 8 位 CPU 的典型寻址限制。他们通过使用一个单独的芯片来实现它的工作,该芯片提供 DMA 地址的高四位,允许对 8088 的完整 1024K 地址空间进行寻址。(在 IBM PC AT 上,这被扩展为提供高八位,以便可以访问整个 80286 16M 地址空间。)

不幸的是,这意味着 DMA 地址的高四位在 DMA 操作期间是固定的,这有效地将 1024K 地址空间划分为 16 个 64K 页。任何尝试执行跨 64K 边界从一个页面到下一个页面的 DMA 操作的尝试都将返回到页面的开头。虽然 BIOS 可以通过将读取分成两个单独的读取来解决这个问题,每个 64K 页一个,但它只是返回一个错误。

请注意,这通常只是软盘访问的问题,因为硬盘接口通常不使用 IBM PC DMA 控制器。

由于在跨越磁道和柱面边界时也存在类似的潜在边界问题,因此解决此问题的最佳方法是一次只读取一个扇区,就像 Jester 和 Michael Petch 在评论中所说的那样。作为一个简单的解决方法,您可以移动缓冲区,使其从物理地址 0x10000 开始,但您可能会发现在现实世界的系统上,您只能读取轨道上的剩余扇区。