为什么在尝试处理286而不是现代CPU或Bochs上的异常时出现三重错误?

Rus*_*lan 6 x86 assembly protected-mode interrupt-handling 80286

我正在尝试通过AMD 286系统上的异常处理来初始化保护模式。我已经在Bochs上调试了以下代码,并且在这里可以正常工作。在奔腾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
Run Code Online (Sandbox Code Playgroud)

我想我正在使用286不支持的某些CPU功能,但是到底在哪里?

Mic*_*tch 6

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

    lidt     [idtr]
    mov      cx, DATA_SELECTOR
    mov      es, cx
    mov      ss, cx
    mov      ds, cx
    
    Run Code Online (Sandbox Code Playgroud)

    这取决于在执行保护之前将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。您不应假定在进入引导加载程序时便清除了方向标志。

其中的许多问题都在我的通用Bootloader提示中的另一个Stackoverflow答案中捕获。

  • 实际上,是的,该代码的开头一开始是`jmp 0x0000:start`,在我制作MCVE时被剥离了,没有注意到我依赖CS == 0的地方。这就是为什么我在那里有“ lgdt [cs:gdtr]”。 (2认同)
  • @Ruslan:在这种情况下,您可以通过FAR JMP将CS设置为0,我只能按照显示的代码进行操作。没问题。 (2认同)