【问题标题】:Intel x86 - Interrupt Service Routine responsibilityIntel x86 - 中断服务例程责任
【发布时间】:2020-03-31 17:39:11
【问题描述】:

我没有真正意义上的问题,但我会尝试澄清一个内容问题。假设我们有一个微内核(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/硬件”的责任?继续前进的正确行为是什么?

【问题讨论】:

  • 由于这是一个例外,之前的 EIP 指向错误指令,如果您想继续,您必须自己更改它。硬件对此无能为力。通常代码将被终止,因此没有理由对其进行更改,并且有一个指向错误发生的实际位置的指针会更有用。

标签: assembly x86 intel interrupt osdev


【解决方案1】:

当我手动增加推送到堆栈的 IP 值时,会发生预期的行为。

这不是预期的行为。异常可以被认为是严重的故障,需要终止程序。因此,简单地恢复业务通常不是一种选择。

ISR 是否有必要增加 IP?

没有。通常,该过程会以“一般保护错误”或“除以零错误”或类似的东西终止。

或者这是“CPU/硬件”的责任?

如果您想在某处继续执行代码(例如 SEH(结构化异常处理)的情况,您的操作系统必须对此进行管理。您可以随时执行此操作,您可以选择清理可能出现的混乱。

继续前进的正确行为是什么?

正确的行为是你喜欢的,因为你是操作系统设计者,不是吗? ;-) CPU/硬件只是通知你当前的状态。

【讨论】:

  • 理论上 ISR 可以找到纠正 div 0 错误并继续执行代码的方法,例如在不得停止执行的简单嵌入式代码中,但实际上它更容易提前检查 0 除数并采取补救措施。如果你还没有,那么情况是意料之外的,一个错误被困住了,而不是继续以某种方式执行。
  • @WeatherVane:我实际上对此进行了一些思考:我如何(数学上)证明除以零是可以的 - 并使其成为操作系统的合法部分......但 ATM 我不能详细说明,因为我不记得了。对不起。
  • 没问题,我只是想添加到您的答案中并点赞。
  • 操作系统过于笼统,无法知道如何处理特定实例 - 当故障出现在应用程序中时,它一无所知。
【解决方案2】:

Intel64 和 IA-32 架构软件开发人员手册第 3 卷(3A、3B、3C 和 3D):系统编程指南,第 6.5 章例外分类说:

故障故障是通常可以纠正的异常 一旦更正,允许程序重新启动而不会丢失 的连续性。报告故障时,处理器恢复 机器状态到开始执行之前的状态 错误指令。返回地址(保存的 CS 内容和 故障处理程序的 EIP 寄存器)指向故障 指令,而不是故障后的指令 说明。

虽然除以零通常无法更正,表 6-1。 Protected-Mode Exceptions and Interrupts 仍然表明 cpu 设计者认为 #DE Divide 错误 应该是故障类型的异常。

【讨论】:

  • 可以纠正除零错误,例如通过调整寄存器内容,使除法产生 0 或其他所需值。
  • @fuz:对于整数除法,它无法正确纠正。您只能允许“错误更正”的值传播,同时使识别错误的根本原因变得更加困难。对于浮点有更多选项(“signalling NaN”,无穷大),但仍然没有“始终正确”选项。
【解决方案3】:

您有责任了解处理器将如何以及为何调用您的中断服务例程并相应地为您的 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 卷:系统编程指南中的几乎所有内容,不包括虚拟机扩展章节和之后的所有内容。此处详细介绍了您需要了解的有关中断和异常的所有内容,以及您需要了解的许多其他内容。

【讨论】:

    【解决方案4】:

    继续前进的正确行为是什么?

    让我们谈谈程序员检测(然后修复)错误的能力。按照从好到坏的顺序(或按照“程序员发现错误的速度”的顺序),选项如下:

    • 在程序员键入源代码时检测错误

    • 在编译/链接时检测错误

    • 在运行时检测错误

    • 在花了 3 个月的时间试图弄清楚为什么您会收到一波来自最终用户的恶意“您的软件是狡猾的垃圾”电子邮件(其中不包含任何有用的线索)后发现该错误

    对于整数除以零,在程序员键入时检测错误将需要为此目的设计的语言和 IDE(这对于大多数现有语言来说并不实用);即便如此,它也不能 100% 有效(例如,错误可能在编译器中,而不是在程序员的源代码中)。在编译/链接时检测错误也有类似的问题。

    这意味着“最糟糕的实际选择”是在运行时检测错误。

    但是;检测错误只是第一步 - 例如如果一个随机/未知的最终用户在英国的笔记本电脑上运行该软件而开发人员在美国时检测到该错误,那么开发人员如何获得修复该错误所需的信息?

    理想情况下;您想要某种自动化系统,其中(在有问题的进程中的所有线程都停止之后,但在进程终止之前)所有相关信息(错误发生在哪个程序的哪个版本中,以及寄存器的内容等) 被收集,然后最终用户被提示“你想将此信息作为错误报告提交”对话框,然后(如果用户同意)信息被转发到某种“错误收集数据库”,允许跟踪统计信息(以便开发人员可以确定错误发生的频率、错误是否仅针对使用特定版本的人发生、错误是否仅在人们使用特定功能时发生等)。

    注意:80x86 上的“除法错误”表示溢出,而不是被零除(被零除只是溢出的原因之一)。例如,如果使用DIV指令将64位整数相除,得到32位结果;然后是“0x0123456789ABCDEF / 3 = 除法错误异常”,因为结果不适合 32 位。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      • 2019-01-30
      • 2019-03-29
      • 2013-11-08
      • 2011-09-01
      • 1970-01-01
      相关资源
      最近更新 更多