首页 > 解决方案 > Intel x86 - 中断服务例程责任

问题描述

我没有真正意义上的问题,而是我会尝试澄清一个内容问题。假设我们有一个微内核(PC Intel x86;32 位保护模式),每个 CPU 异常都有工作中断描述符表 (IDT)中断服务例程 (ISR)。ISR 被成功调用,例如在发生Division by Zero异常的情况下。

global ir0
extern isr_handler

isr0:

    cli
    push 0x00   ; Dummy error code
    push %1     ; Interrupt number

    jmp isr_exc_handler

isr_exc_handler:

; Save the current processor state

    pusha

    mov ax, ds
    push eax

    mov ax, 0x10 ; Load kernel data segment descriptor
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; Push current stack pointer

    mov eax, esp
    push eax

    call isr_handler ; Additional C/C++ handler function

    pop eax     ; Remove pushed stack pointer

    pop ebx     ; Restore original data segment descriptor
    mov ds, bx
    mov es, bx
    mov fs, bx
    mov gs, bx

    popa

    add esp, 0x08 ; Clean up pushed error code and ISR number
    sti

    iret

问题是中断被一次又一次地抛出。结果,ISR 被一次又一次地调用。通过反复试验,我发现引发异常的行 int x = 5 / 0是循环执行的,因此指令指针(EIP) 不会递增

当我手动增加 IP 推送到堆栈的值时,会发生预期的行为。CPU 然后执行恶意代码行之后的下一条指令。当然,在 ISR 被调用一次之后。

对于我的实际问题:ISR 是否有必要增加 IP?或者这是“CPU/硬件”的责任?继续前进的正确行为是什么?

标签: assemblyx86intelinterruptosdev

解决方案


您有责任了解处理器如何以及为何调用您的中断服务例程并相应地为您的 ISR 编写代码。您正在尝试将除以零错误产生的异常视为由硬件中断产生。然而,这不是 Intel x86 处理器处理此类异常的方式。

x86 处理器如何处理中断和异常

有几种不同类型的事件将导致处理器调用中断向量表中给出的中断服务程序。这些统称为中断和异常,处理器可以通过三种不同的方式处理中断或异常,作为故障、作为陷阱或作为中止。您的除法指令生成除法错误 (#DE) 异常,该异常作为故障处理。硬件和软件中断作为陷阱处理,而其他类型的异常则作为这三种方式之一处理,具体取决于异常的来源。

故障

如果异常的性质允许以某种方式对其进行更正,则处理器将异常作为故障处理。正因为如此,压入堆栈的返回地址指向产生异常的指令,因此故障处理程序知道是什么确切指令导致了故障,并在解决问题后恢复执行故障指令成为可能。页面错误 (#PF) 异常就是一个很好的例子。它可用于通过让故障处理程序为故障指令试图访问的地址提供有效的虚拟映射来实现虚拟内存。有了有效的页面映射,指令就可以恢复并执行,而不会产生另一个页面错误。

陷阱

中断和某些类型的异常,所有这些都是软件异常,都作为陷阱处理。陷阱并不意味着执行指令时出错。硬件中断发生在指令执行之间,软件中断和某些软件异常有效地模仿了这种行为。通过推入正常执行的下一条指令的地址来处理陷阱。这允许陷阱处理程序恢复中断代码的正常执行。

中止

严重且不可恢复的错误将作为中止处理。只有两个异常会产生中止,机器检查 (#MC) 异常和双重故障 (#DF)。机器检查指令是检测到处理器本身的硬件故障的结果,这是无法修复的,并且无法可靠地恢复正常执行。当在处理中断或异常过程中发生异常时,就会发生双重故障异常。这会使 CPU 处于一种不一致的状态,处于调用 ISR 的所有必要步骤的中间某个位置,该 ISR 无法恢复。压入堆栈的返回值可能与导致中止的任何原因有关,也可能没有任何关系。

除法错误异常通常如何处理

通常,大多数操作系统通过将除法错误异常传递给正在执行的进程中的处理程序进行处理来处理除法错误异常,或者通过终止进程来失败,表明它已经崩溃。例如,大多数 Unix 系统向进程发送 SIGFPE 信号,而 Windows 使用其结构化异常处理机制执行类似的操作。这样,进程的编程语言运行时就可以设置自己的处理程序来实现所使用的编程语言所需的任何行为。由于在 C 和 C++ 中除以零会导致未定义的行为,因此崩溃是一种可接受的行为,因此这些语言通常不会安装除以零处理程序。

请注意,虽然您可以通过“增加 EIP”来处理除法错误异常,但这比您想象的要难,并且不会产生非常有用的结果。您不能只向 EIP 添加一个或某个其他常量值,您需要跳过可能是 2 到 15 个字节长的整个指令。有 3 条指令会导致此异常,即 AAM、DIV 和 IDIV,它们可以使用各种前缀和操作数字节进行编码。您需要对指令进行解码以确定它有多长。执行此增量的结果将就好像该指令从未执行过一样。错误指令不会计算出有意义的值,您也不会知道程序为什么不能正确运行。

阅读文档

如果您正在编写自己的操作系统,那么您需要准备好英特尔软件开发人员手册,以便您可以经常查阅。特别是,您需要阅读和学习第 3 卷:系统编程指南中的几乎所有内容,不包括虚拟机扩展章节和之后的所有内容。那里详细介绍了您需要了解的有关中断和异常的所有内容,以及您需要了解的许多其他内容。


推荐阅读