为什么在 286 上尝试处理异常时会出现三重错误,但在现代 CPU 或 Bochs 上却没有?
Why do I get triple fault when trying to handle an exception on 286 but not on a modern CPU nor Bochs?
我正在尝试在 AMD 286 系统上使用异常处理来初始化保护模式。我已经在 Bochs 上调试了下面的代码,它在那里运行良好。在 Pentium 4 机器上 运行 时也是如此。但是在 286 上,当它到达 int3
指令时,它只会出现三重故障。可观察到的行为是:如果我注释掉 int3
,我会无限期地在屏幕上显示 "OK",而按原样使用代码,系统会重新启动。
代码将被FASM编译,二进制文件放入HDD或FDD的引导扇区。我实际上是从一张 1.4M 的软盘 运行 中下载它的。
org 0x7c00
use16
CODE_SELECTOR = code_descr - gdt
DATA_SELECTOR = data_descr - gdt
; print "OK" on the screen to see that we've actually started
push 0xb800
pop es
xor di,di
mov ax, 0x0700+'O'
stosw
mov ax, 0x0700+'K'
stosw
; clear the rest of the screen
mov cx, 80*25*2-2
mov ax, 0x0720
rep stosw
lgdt [cs:gdtr]
cli
smsw ax
or al, 1
lmsw ax
jmp CODE_SELECTOR:enterPM
enterPM:
lidt [idtr]
mov cx, DATA_SELECTOR
mov es, cx
mov ss, cx
mov ds, cx
int3 ; cause an exception
jmp $
intHandler:
jmp $
gdt:
dq 0
data_descr:
dw 0xffff ; limit
dw 0x0000 ; base 15:0
db 0x00 ; base 23:16
db 10010011b ; present, ring0, non-system, data, extending upwards, writable, accessed
dw 0 ; reserved on 286
code_descr:
dw 0xffff ; limit
dw 0x0000 ; base 15:0
db 0x00 ; base 23:16
db 10011011b ; present, ring0, non-system, code, non-conforming, readable, accessed
dw 0 ; reserved on 286
gdtr:
dw gdtr-gdt-1
gdtBase:
dd gdt
idt:
rept 14
{
dw intHandler
dw CODE_SELECTOR
db 0
db 11100111b ; present, ring3, system, 16-bit trap gate
dw 0 ; reserved on 286
}
idtr:
dw idtr-idt-1
idtBase:
dd idt
finish:
db (0x7dfe-finish) dup(0)
dw 0xaa55
我想我正在使用一些 286 不支持的 CPU 功能,但究竟是什么以及在哪里?
在你的保护模式代码中你有:
lidt [idtr]
mov cx, DATA_SELECTOR
mov es, cx
mov ss, cx
mov ds, cx
这依赖于 DS 在进入保护模式之前被设置为 0x0000(并且在 DS 描述符中相应的基地址为 0缓存)在执行 lidt [idtr]
之前。该指令有一个隐含的 DS 段。在使用 16 位选择器设置段寄存器之后放置 lidt
指令,而不是之前。
虽然它并没有在您的硬件上表现为一个错误,但在实模式下您的代码还依赖于 CS 被设置为 0x0000 的指令lgdt [cs:gdtr]
。不能保证 CS 为 0x0000,因为某些 BIOS 很可能使用非零 CS 来访问您的引导加载程序。例如 0x07c0:0x0000 也会到达物理地址 0x07c00 (0x07c0<<4+0x0000=0x07c00)。在实模式代码中,我建议将 DS 设置为零并使用 lgdt [gdtr]
.
进入保护模式后,在使用堆栈之前,您应该设置SP。中断将要求堆栈指针在某处有效。将其初始化为 0x0000 将使堆栈从 64KiB 段的顶部向下增长。你不应该依赖它碰巧指向某个地方,一旦进入保护模式就不会干扰你的 运行 系统(即在你的引导加载程序 code/data 之上)。
在使用 STOS/SCAS/CMPS/LODS 等任何字符串指令之前,您应该确保方向标志已按照您的预期设置。由于您依赖于向前移动,因此您应该使用 CLD
清除方向标志。您不应该假设在进入引导加载程序时方向标志是明确的。
我的 在另一个 Whosebug 答案中捕获了其中的许多问题。
我正在尝试在 AMD 286 系统上使用异常处理来初始化保护模式。我已经在 Bochs 上调试了下面的代码,它在那里运行良好。在 Pentium 4 机器上 运行 时也是如此。但是在 286 上,当它到达 int3
指令时,它只会出现三重故障。可观察到的行为是:如果我注释掉 int3
,我会无限期地在屏幕上显示 "OK",而按原样使用代码,系统会重新启动。
代码将被FASM编译,二进制文件放入HDD或FDD的引导扇区。我实际上是从一张 1.4M 的软盘 运行 中下载它的。
org 0x7c00
use16
CODE_SELECTOR = code_descr - gdt
DATA_SELECTOR = data_descr - gdt
; print "OK" on the screen to see that we've actually started
push 0xb800
pop es
xor di,di
mov ax, 0x0700+'O'
stosw
mov ax, 0x0700+'K'
stosw
; clear the rest of the screen
mov cx, 80*25*2-2
mov ax, 0x0720
rep stosw
lgdt [cs:gdtr]
cli
smsw ax
or al, 1
lmsw ax
jmp CODE_SELECTOR:enterPM
enterPM:
lidt [idtr]
mov cx, DATA_SELECTOR
mov es, cx
mov ss, cx
mov ds, cx
int3 ; cause an exception
jmp $
intHandler:
jmp $
gdt:
dq 0
data_descr:
dw 0xffff ; limit
dw 0x0000 ; base 15:0
db 0x00 ; base 23:16
db 10010011b ; present, ring0, non-system, data, extending upwards, writable, accessed
dw 0 ; reserved on 286
code_descr:
dw 0xffff ; limit
dw 0x0000 ; base 15:0
db 0x00 ; base 23:16
db 10011011b ; present, ring0, non-system, code, non-conforming, readable, accessed
dw 0 ; reserved on 286
gdtr:
dw gdtr-gdt-1
gdtBase:
dd gdt
idt:
rept 14
{
dw intHandler
dw CODE_SELECTOR
db 0
db 11100111b ; present, ring3, system, 16-bit trap gate
dw 0 ; reserved on 286
}
idtr:
dw idtr-idt-1
idtBase:
dd idt
finish:
db (0x7dfe-finish) dup(0)
dw 0xaa55
我想我正在使用一些 286 不支持的 CPU 功能,但究竟是什么以及在哪里?
在你的保护模式代码中你有:
lidt [idtr] mov cx, DATA_SELECTOR mov es, cx mov ss, cx mov ds, cx
这依赖于 DS 在进入保护模式之前被设置为 0x0000(并且在 DS 描述符中相应的基地址为 0缓存)在执行
lidt [idtr]
之前。该指令有一个隐含的 DS 段。在使用 16 位选择器设置段寄存器之后放置lidt
指令,而不是之前。虽然它并没有在您的硬件上表现为一个错误,但在实模式下您的代码还依赖于 CS 被设置为 0x0000 的指令
lgdt [cs:gdtr]
。不能保证 CS 为 0x0000,因为某些 BIOS 很可能使用非零 CS 来访问您的引导加载程序。例如 0x07c0:0x0000 也会到达物理地址 0x07c00 (0x07c0<<4+0x0000=0x07c00)。在实模式代码中,我建议将 DS 设置为零并使用lgdt [gdtr]
.进入保护模式后,在使用堆栈之前,您应该设置SP。中断将要求堆栈指针在某处有效。将其初始化为 0x0000 将使堆栈从 64KiB 段的顶部向下增长。你不应该依赖它碰巧指向某个地方,一旦进入保护模式就不会干扰你的 运行 系统(即在你的引导加载程序 code/data 之上)。
在使用 STOS/SCAS/CMPS/LODS 等任何字符串指令之前,您应该确保方向标志已按照您的预期设置。由于您依赖于向前移动,因此您应该使用
CLD
清除方向标志。您不应该假设在进入引导加载程序时方向标志是明确的。
我的