为了创建一个简单的独立程序,我在第一部分编写了一个简单的引导加载程序。其目的是将程序加载到内存中。为此,我使用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
我在VirtualBox 5.2.8中进行了所有练习,效果很好。将所有内容移动到具有VirtualBox 6.0.14的第二台计算机上均无法通过实验。中断在CF置1时结束,指示失败。
阅读Boot loader doesn't jump to kernel code中的出色答案,我已解决了未指定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 plnz na po nc
00:00:05.930864 cs = {0000 base = 0000000000000000限制= 0000ffff标志= 0000009b} dr0 = 00000000 dr1 = 00000000
00:00:05.930877 ds = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} dr2 = 00000000 dr3 = 00000000
00:00:05.930884 es = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} dr4 = 00000000 dr5 = 00000000
00:00:05.930891 fs = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} dr6 = ffff0ff0 dr7 = 00000400
00:00:05.930898 gs = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} cr0 = 00000010 cr2 = 00000000
00:00:05.930904 ss = {0000 base = 0000000000000000限制= 0000ffff标志= 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限制= 0000ffff标志= 0000009b} dr0 = 00000000 dr1 = 00000000
00:00:08.984909 ds = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} dr2 = 00000000 dr3 = 00000000
00:00:08.984917 es = {07e0 base = 0000000000007e00限制= 0000ffff标志= 00000093} dr4 = 00000000 dr5 = 00000000
00:00:08.984925 fs = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} dr6 = ffff0ff0 dr7 = 00000400
00:00:08.984934 gs = {0000 base = 0000000000000000限制= 0000ffff标志= 00000093} cr0 = 00000010 cr2 = 00000000
00:00:08.984941 ss = {0000 base = 0000000000000000限制= 0000ffff标志= 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,在此声明:
缓冲区的寻址应保证完整的缓冲区位于给定的段内,即(BX + size_of_buffer)<= 10000h。
这将解释我的最初问题,因此我进行了另一项修复以设置es=0x7e0
和bx=0
。这是上面显示的代码的状态。但是,即使该代码在上述状态下也会失败。
进一步的测试表明,我最多可以成功读取65个扇区,但失败66个或更多。由于它是一个怪异的数字,我计算出第65个扇区的结尾:0xffff。因此问题变得更加混乱。
我的es=0x7e0
和bx=0
解决方案是否应避免段交叉(据我所知)?
如果这样做,为什么在跨越线性地址时似乎是问题所在?
或者一个人可以穿过该段,但不能穿过线性地址中的0xffff标记吗?
谢谢您的帮助。
问题是您“尝试在64K边界上尝试DMA”。这是事实,您的目标缓冲区的范围从物理地址0x07E00到0x17E00,跨越了0x10000的64K边界。 (这与段边界无关,因此,使用哪个段和偏移值到达0x07E00物理地址都没有关系。)
这样做的原因在于如何以便宜的价格设计原始的IBM PC。他们没有使用带有16位总线和16位支持芯片的16位8086,而是使用了带有8位总线的便宜16位8088 CPU,可以与便宜的8位支持芯片一起使用。特别是the DMA controller they chose仅能寻址64K存储器,这是DMA控制器设计用于8位CPU的典型寻址限制。他们通过使用单独的芯片来提供DMA地址的高四位来使其工作,从而允许对8088的完整1024K地址空间进行寻址。 (在IBM PC AT上,它已扩展为提供高8位,以便可以访问整个80286 16M地址空间。)
[不幸的是,这意味着在DMA操作期间DMA地址的高四位是固定的,这实际上将1024K地址空间划分为十六个64K页。任何跨越64K边界执行从一页到另一页的DMA操作的尝试都将环绕到页面的开头。尽管BIOS可以通过将读取分为两个单独的读取来解决此问题,每个64K页面一个,但它仅返回错误。
注意,由于硬盘接口通常不使用IBM PC DMA控制器,因此通常这只是软盘访问的问题。
由于跨轨道和圆柱边界时也存在类似的潜在边界问题,因此解决此问题的最佳方法是一次只读取一个扇区,如Jester和Michael Petch在评论中所述。作为一个简单的解决方法,您可以移动缓冲区,使其从物理地址0x10000开始,但是您可能会发现在现实世界的系统中,您只能读取轨道上的其余扇区。