软盘读取 (AH=0x2, int 0x13) 未完成
Floppy Read (AH=0x2, int 0x13) doesn't complete
在我的引导加载程序的第二阶段,我试图将一些扇区从虚拟软盘加载到 bochs 中的内存中,但是在调用 int 0x13
时,例程没有 return.
我相信我第二阶段的相关代码是:
bootsys_start:
mov %cs, %ax
mov %ax, %ds
/*
* Remap IRQs. Interrupts have been disabled in the
* bootloader already.
*/
mov i8259A_ICW1($i8259A_IC4), %al
out %al, i8259A_ICW1_ADDR($i8259A_MASTER)
out %al, i8259A_ICW1_ADDR($i8259A_SLAVE)
mov i8259A_ICW2($USER_INT_START), %al
out %al, i8259A_ICW2_ADDR($i8259A_MASTER)
mov i8259A_ICW2($USER_INT_START + 8), %al
out %al, i8259A_ICW2_ADDR($i8259A_SLAVE)
mov i8259A_ICW3([=10=]x4), %al
out %al, i8259A_ICW3_ADDR($i8259A_MASTER)
mov i8259A_ICW3([=10=]x2), %al
out %al, i8259A_ICW3_ADDR($i8259A_SLAVE)
mov i8259A_ICW4($i8259A_uPM & i8259A_x86), %al
out %al, i8259A_ICW4_ADDR($i8259A_MASTER)
out %al, i8259A_ICW4_ADDR($i8259A_SLAVE)
call mm_detect
/* Load the kernel now. */
xor %bp, %bp
1:
mov $KERNEL_ORG >> 0x4, %ax
mov %ax, %es
mov $KERNEL_ORG & 0xf, %bx
mov [=10=]x200 | KERNEL_SECTORS, %ax
mov $(KERNEL_C << 0x8) | KERNEL_S, %cx
mov $(KERNEL_H << 0x8) | FLOPPY_DRV, %dx
int [=10=]x13 /* <--- This int 0x13 doesn't seem to return */
jnc 1f
cmp [=10=]x2, %bp
je floppy_err
inc %bp
xor %ah, %ah
int [=10=]x13
jmp 1b
所有代码都可以在我的Github repository中找到。要构建只需使用 make all
然后使用 运行 和 BOCHS 使用命令 bochs
我做的第一件事是验证我确实正确设置了所有参数。 r
在 bochs 的 shell 中产生:
CPU0:
rax: 00000000_534d0201 rcx: 00000000_00000005
rdx: 00000000_534d0000 rbx: 00000000_00000000
rsp: 00000000_00007700 rbp: 00000000_00000000
rsi: 00000000_000e0005 rdi: 00000000_00000316
r8 : 00000000_00000000 r9 : 00000000_00000000
r10: 00000000_00000000 r11: 00000000_00000000
r12: 00000000_00000000 r13: 00000000_00000000
r14: 00000000_00000000 r15: 00000000_00000000
rip: 00000000_00000036
eflags 0x00007046: id vip vif ac vm rf NT IOPL=3 of df if tf sf ZF af PF cf
ah = 0x2
(例程ID),al = 0x1
(扇区数),ch = 0x0
(柱面号低字节),cl = 0x5
(扇区号和高两位柱面号的位),dh = 0x0
(磁头号),dl = 0x0
(驱动器号)。
sreg
打印 es
:
es:0x0000
和 bx = 0x0
,所以扇区被加载到 0x0:0x0
,正如我预期的那样。
我尝试了几种方法:
加载到物理地址0x600
我认为在执行 BIOS 中断例程期间覆盖 IVT 或 BDA 可能不是一个好主意,所以我尝试将扇区加载到 0x600
(es = 0x60
,bx = 0x0
)(我知道 BDA 的大小只有 256 个字节)。相同的结果。
加载磁盘上的第一个扇区
也许读取第五扇区有点越界之类的?使用 int 0x13
读取我的第二阶段的代码按预期工作。我第二阶段的 int 0x13
是相似的,所以我希望它能工作。作为测试,我将第二阶段更改为读取扇区 1,但它仍然不起作用。
将 eax
的上部归零
我想也许 BIOS 例程中确实存在错误,不知何故使用了 eax
而不是 ax
。我尝试将 eax
的高 16 位部分清零...无济于事。
正如我之前所说,我已经从磁盘加载了一些扇区到内存中。 int 0x13
之前的GPR内容如下(在bochsshell中使用r
获得):
CPU0:
rax: 00000000_00000203 rcx: 00000000_00090002
rdx: 00000000_00000000 rbx: 00000000_00000000
rsp: 00000000_00007700 rbp: 00000000_00000000
rsi: 00000000_000e7cdd rdi: 00000000_000000e2
r8 : 00000000_00000000 r9 : 00000000_00000000
r10: 00000000_00000000 r11: 00000000_00000000
r12: 00000000_00000000 r13: 00000000_00000000
r14: 00000000_00000000 r15: 00000000_00000000
rip: 00000000_00007c59
eflags 0x00007046: id vip vif ac vm rf NT IOPL=3 of df if tf sf ZF af PF cf
sreg
产生 es:0x8f60
,这是一个动态计算的地址,就在 EBDA 之前。
比较两者,我没有发现可能影响中断例程功能的显着差异,因此问题不可能出在通过寄存器传递的参数上。
有人对做什么有其他建议吗?
您的 Int 13h/AH=02h 软盘读取代码有几个问题:
您已经在问题中确定了这一点。当您处于 运行 实模式时,读取 0x0000:0x0000 顶部的扇区是一个坏主意。这将破坏中断向量 table (IVT)。从0x0000:0x0000到0x0040:0x0000的区域是IVT; 0x0040:0x0000 到 0x0060:0x0000 的区域是 BIOS Data Area (BDA)。 BDA 应被视为实模式 BIOS 例程可能使用的暂存区。
要修复它,请将其加载到安全的地方,例如 0x0060:0x0000(物理地址 0x00600)。
一旦进入保护模式,0x00000000 和 0x00000600 之间的区域就可以回收用于其他用途。 注意 : 不要使用 Extended BIOS Data Area (EBDA) memory area as general purpose memory because System Management Mode (SMM) and Advanced Configuration and Power Interface (ACPI) 可能会写入它。
您的代码重新映射 8259A 以准备进入保护模式。这样做时,IRQ 被重新映射到 IVT 的不同部分。 Int 13h 例程可能依赖于触发中断和 BIOS 中断例程来执行软盘读取所需的工作。可以使用 IRQ0(系统定时器)和 IRQ6(软盘控制器)。如果您将 8259A 的基址重新映射到别处,则 BIOS 安装的中断例程将不会执行。这可能会导致意外行为,包括 Int 13h 永远不会返回。
要解决此问题,我建议您在处于保护模式后重新映射 8259A PIC 的基址。到那时你可能已经完成了 BIOS 中断,所以这应该不是问题。
在我的引导加载程序的第二阶段,我试图将一些扇区从虚拟软盘加载到 bochs 中的内存中,但是在调用 int 0x13
时,例程没有 return.
我相信我第二阶段的相关代码是:
bootsys_start:
mov %cs, %ax
mov %ax, %ds
/*
* Remap IRQs. Interrupts have been disabled in the
* bootloader already.
*/
mov i8259A_ICW1($i8259A_IC4), %al
out %al, i8259A_ICW1_ADDR($i8259A_MASTER)
out %al, i8259A_ICW1_ADDR($i8259A_SLAVE)
mov i8259A_ICW2($USER_INT_START), %al
out %al, i8259A_ICW2_ADDR($i8259A_MASTER)
mov i8259A_ICW2($USER_INT_START + 8), %al
out %al, i8259A_ICW2_ADDR($i8259A_SLAVE)
mov i8259A_ICW3([=10=]x4), %al
out %al, i8259A_ICW3_ADDR($i8259A_MASTER)
mov i8259A_ICW3([=10=]x2), %al
out %al, i8259A_ICW3_ADDR($i8259A_SLAVE)
mov i8259A_ICW4($i8259A_uPM & i8259A_x86), %al
out %al, i8259A_ICW4_ADDR($i8259A_MASTER)
out %al, i8259A_ICW4_ADDR($i8259A_SLAVE)
call mm_detect
/* Load the kernel now. */
xor %bp, %bp
1:
mov $KERNEL_ORG >> 0x4, %ax
mov %ax, %es
mov $KERNEL_ORG & 0xf, %bx
mov [=10=]x200 | KERNEL_SECTORS, %ax
mov $(KERNEL_C << 0x8) | KERNEL_S, %cx
mov $(KERNEL_H << 0x8) | FLOPPY_DRV, %dx
int [=10=]x13 /* <--- This int 0x13 doesn't seem to return */
jnc 1f
cmp [=10=]x2, %bp
je floppy_err
inc %bp
xor %ah, %ah
int [=10=]x13
jmp 1b
所有代码都可以在我的Github repository中找到。要构建只需使用 make all
然后使用 运行 和 BOCHS 使用命令 bochs
我做的第一件事是验证我确实正确设置了所有参数。 r
在 bochs 的 shell 中产生:
CPU0:
rax: 00000000_534d0201 rcx: 00000000_00000005
rdx: 00000000_534d0000 rbx: 00000000_00000000
rsp: 00000000_00007700 rbp: 00000000_00000000
rsi: 00000000_000e0005 rdi: 00000000_00000316
r8 : 00000000_00000000 r9 : 00000000_00000000
r10: 00000000_00000000 r11: 00000000_00000000
r12: 00000000_00000000 r13: 00000000_00000000
r14: 00000000_00000000 r15: 00000000_00000000
rip: 00000000_00000036
eflags 0x00007046: id vip vif ac vm rf NT IOPL=3 of df if tf sf ZF af PF cf
ah = 0x2
(例程ID),al = 0x1
(扇区数),ch = 0x0
(柱面号低字节),cl = 0x5
(扇区号和高两位柱面号的位),dh = 0x0
(磁头号),dl = 0x0
(驱动器号)。
sreg
打印 es
:
es:0x0000
和 bx = 0x0
,所以扇区被加载到 0x0:0x0
,正如我预期的那样。
我尝试了几种方法:
加载到物理地址
0x600
我认为在执行 BIOS 中断例程期间覆盖 IVT 或 BDA 可能不是一个好主意,所以我尝试将扇区加载到
0x600
(es = 0x60
,bx = 0x0
)(我知道 BDA 的大小只有 256 个字节)。相同的结果。加载磁盘上的第一个扇区
也许读取第五扇区有点越界之类的?使用
int 0x13
读取我的第二阶段的代码按预期工作。我第二阶段的int 0x13
是相似的,所以我希望它能工作。作为测试,我将第二阶段更改为读取扇区 1,但它仍然不起作用。将
的上部归零eax
我想也许 BIOS 例程中确实存在错误,不知何故使用了
eax
而不是ax
。我尝试将eax
的高 16 位部分清零...无济于事。
正如我之前所说,我已经从磁盘加载了一些扇区到内存中。 int 0x13
之前的GPR内容如下(在bochsshell中使用r
获得):
CPU0:
rax: 00000000_00000203 rcx: 00000000_00090002
rdx: 00000000_00000000 rbx: 00000000_00000000
rsp: 00000000_00007700 rbp: 00000000_00000000
rsi: 00000000_000e7cdd rdi: 00000000_000000e2
r8 : 00000000_00000000 r9 : 00000000_00000000
r10: 00000000_00000000 r11: 00000000_00000000
r12: 00000000_00000000 r13: 00000000_00000000
r14: 00000000_00000000 r15: 00000000_00000000
rip: 00000000_00007c59
eflags 0x00007046: id vip vif ac vm rf NT IOPL=3 of df if tf sf ZF af PF cf
sreg
产生 es:0x8f60
,这是一个动态计算的地址,就在 EBDA 之前。
比较两者,我没有发现可能影响中断例程功能的显着差异,因此问题不可能出在通过寄存器传递的参数上。
有人对做什么有其他建议吗?
您的 Int 13h/AH=02h 软盘读取代码有几个问题:
您已经在问题中确定了这一点。当您处于 运行 实模式时,读取 0x0000:0x0000 顶部的扇区是一个坏主意。这将破坏中断向量 table (IVT)。从0x0000:0x0000到0x0040:0x0000的区域是IVT; 0x0040:0x0000 到 0x0060:0x0000 的区域是 BIOS Data Area (BDA)。 BDA 应被视为实模式 BIOS 例程可能使用的暂存区。
要修复它,请将其加载到安全的地方,例如 0x0060:0x0000(物理地址 0x00600)。
一旦进入保护模式,0x00000000 和 0x00000600 之间的区域就可以回收用于其他用途。 注意 : 不要使用 Extended BIOS Data Area (EBDA) memory area as general purpose memory because System Management Mode (SMM) and Advanced Configuration and Power Interface (ACPI) 可能会写入它。
您的代码重新映射 8259A 以准备进入保护模式。这样做时,IRQ 被重新映射到 IVT 的不同部分。 Int 13h 例程可能依赖于触发中断和 BIOS 中断例程来执行软盘读取所需的工作。可以使用 IRQ0(系统定时器)和 IRQ6(软盘控制器)。如果您将 8259A 的基址重新映射到别处,则 BIOS 安装的中断例程将不会执行。这可能会导致意外行为,包括 Int 13h 永远不会返回。
要解决此问题,我建议您在处于保护模式后重新映射 8259A PIC 的基址。到那时你可能已经完成了 BIOS 中断,所以这应该不是问题。