6

我正在尝试在 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 功能,但究竟是什么以及在哪里?

4

1 回答 1

6
  • 在您的保护模式代码中,您有:

    lidt     [idtr]
    mov      cx, DATA_SELECTOR
    mov      es, cx
    mov      ss, cx
    mov      ds, cx
    

    这依赖于在进入保护模式之前将DS设置为 0x0000(并且在DS描述符缓存中相应的基地址为 0),然后再执行lidt [idtr]. 该指令有一个隐含的DS段。lidt在使用 16 位选择器设置段寄存器之后放置指令,而不是之前。

  • 尽管它并没有表现为您的硬件上的错误,但在实模式下,您的代码还依赖于将CS设置为 0x0000 的指令lgdt [cs:gdtr]不能保证CS为 0x0000,因为某些 BIOS 很可能使用非零CS来访问您的引导加载程序。例如 0x07c0:0x0000 也会到达物理地址 0x07c00 (0x07c0<<4+0x0000=0x07c00)。在实模式代码中,我建议将DS设置为零并使用lgdt [gdtr].

  • 一旦进入保护模式并且在使用堆栈之前,您应该设置SP。中断将要求堆栈指针在某处有效。将其初始化为 0x0000 将使堆栈从 64KiB 段的顶部向下增长。您不应该依赖它碰巧指向某个在保护模式下不会干扰您正在运行的系统的地方(即在您的引导加载程序代码/数据之上)。

  • 在使用任何字符串指令(如 STOS/SCAS/CMPS/LODS)之前,您应确保按预期设置方向标志。由于您依赖向前移动,您应该使用 清除方向标志CLD。您不应该假设在进入引导加载程序时方向标志是清除的。

其中许多问题都在我的另一个 Stackoverflow 答案中的通用引导加载程序提示中捕获。

于 2019-08-17T08:02:09.730 回答