“Exceptions”的版本间差异
(创建页面,内容为“{| align="right" | __TOC__ |} 如本文所述'''异常(Exceptions)''',是CPU在发生 “错误” 时生成的一种中断。 在大多数情况下,有些异常并不是真正的错误,例如页面错误。 例外情况分类为: * '''错误(故障)-Faults''': 这些可以更正,程序可能会像什么都没发生一样继续进行。 * '''陷阱-Traps''': 陷阱在执行陷阱指令后立即报告。 * ''…”) |
小 |
||
(未显示同一用户的1个中间版本) | |||
第103行: | 第103行: | ||
| Yes | | Yes | ||
|- | |- | ||
! [[#Page Fault|页面错误]] | |||
| 14 (0xE) | | 14 (0xE) | ||
| Fault | | Fault | ||
第115行: | 第115行: | ||
| No | | No | ||
|- | |- | ||
! [#x87 Floating-Point Exception| x87浮点异常]] | ! [[#x87 Floating-Point Exception| x87浮点异常]] | ||
| 16 (0x10) | | 16 (0x10) | ||
| Fault | | Fault | ||
第139行: | 第139行: | ||
| No | | No | ||
|- | |- | ||
! [[#Virtualization Exception|虚拟化异常]] | |||
| 20 (0x14) | | 20 (0x14) | ||
| Fault | | Fault | ||
第145行: | 第145行: | ||
| No | | No | ||
|- | |- | ||
![[#Control Protection Exception|控制保护异常]] | ! [[#Control Protection Exception|控制保护异常]] | ||
| 21 (0x15) | | 21 (0x15) | ||
| Fault | | Fault |
2022年3月18日 (五) 07:06的最新版本
如本文所述异常(Exceptions),是CPU在发生 “错误” 时生成的一种中断。 在大多数情况下,有些异常并不是真正的错误,例如页面错误。
例外情况分类为:
- 错误(故障)-Faults: 这些可以更正,程序可能会像什么都没发生一样继续进行。
- 陷阱-Traps: 陷阱在执行陷阱指令后立即报告。
- 中止-Aborts:一些严重的不可恢复的错误。
有些异常会将32位的 “错误代码” 推到堆栈的顶部,从而提供有关错误的其他信息。 在将控制权返回到当前运行的程序之前,必须从堆栈中提取该值。(即在调用IRET之前)
名称 | 向量 | 类型 | 助记符 | 是否有错误代码 |
---|---|---|---|---|
除零错误(Divide-by-zero Error) | 0 (0x0) | Fault | #DE | No |
Debug | 1 (0x1) | Fault/Trap | #DB | No |
不可屏蔽中断(Non Maskable Interrupt) | 2 (0x2) | Interrupt | - | No |
断点 | 3 (0x3) | Trap | #BP | No |
溢出(Overflow) | 4 (0x4) | Trap | #OF | No |
超出界限范围(Bound Range Exceeded) | 5 (0x5) | Fault | #BR | No |
无效的操作码(Invalid Opcode) | 6 (0x6) | Fault | #UD | No |
设备不可用(Device Not Available) | 7 (0x7) | Fault | #NM | No |
双重故障(Double Fault) | 8 (0x8) | Abort | #DF | Yes (Zero) |
9 (0x9) | Fault | - | No | |
无效TSS | 10 (0xA) | Fault | #TS | Yes |
段不存在 | 11 (0xB) | Fault | #NP | Yes |
堆栈段故障 | 12 (0xC) | Fault | #SS | Yes |
一般保护故障 | 13 (0xD) | Fault | #GP | Yes |
页面错误 | 14 (0xE) | Fault | #PF | Yes |
保留 | 15 (0xF) | - | - | No |
x87浮点异常 | 16 (0x10) | Fault | #MF | No |
对齐检查 | 17 (0x11) | Fault | #AC | Yes |
机器检查 | 18 (0x12) | Abort | #MC | No |
SIMD浮点异常 | 19 (0x13) | Fault | #XM/#XF | No |
虚拟化异常 | 20 (0x14) | Fault | #VE | No |
控制保护异常 | 21 (0x15) | Fault | #CP | Yes |
保留 | 22-27 (0x16-0x1B) | - | - | No |
虚拟机监控程序注入异常 | 28 (0x1C) | Fault | #HV | No |
VMM通信异常 | 29 (0x1D) | Fault | #VC | Yes |
安全异常 | 30 (0x1E) | Fault | #SX | Yes |
保留 | 31 (0x1F) | - | - | No |
三重故障 | - | - | - | No |
IRQ 13 | Interrupt | #FERR | No |
异常
故障(Faults)
Divide-by-zero Error
当使用DIV或IDIV指令将任何数字除以0时,或者当除法结果太大而无法在目标中表示时,会出现Divide-by-zero Error(零除错误)。 由于出错的DIV或IDIV指令很容易插入到代码中的任何位置,因此许多操作系统开发人员使用此异常来测试他们的异常处理代码是否正常工作。
保存的指令指针指向导致异常的DIV或IDIV指令。
Bound Range Exceeded
执行BOUND指令时可能会发生此异常。 Bound指令将数组索引与数组的下界和上界进行比较。 当索引超出范围时,会发生边界范围超出的异常。
保存的指令指针指向导致异常的绑定指令。
Invalid Opcode
当处理器尝试执行无效或未定义的操作码或具有无效前缀的指令时,就会发生无效的操作码异常。 在其他情况下也会发生这种情况,例如:
- 指令长度超过15个字节,但这仅在冗余前缀的情况下发生。
- 该指令尝试访问不存在的控制寄存器 (例如,
mov cr6,eax
)。 - 执行UD指令。(译者注:应该指的故意执行CPU指令)
保存的指令指针指向导致异常的指令。
Device Not Available
当尝试使用FPU指令但没有FPU(浮点运算单元)时,设备不可用异常发生。 这不太可能,因为现代处理器都内置了FPU。 但是,CR0寄存器中有禁用FPU/MMX/SSE指令的标志,因此在尝试这些指令时会导致此异常。 此功能很有用,因为操作系统可以检测用户程序何时使用FPU或XMM寄存器,然后在多任务处理时适当地保存/恢复它们。
保存的指令指针指向导致异常的指令。
Invalid TSS
当无效的段选择器作为任务的一部分被引用时,或者作为通过门描述符的控制转移的结果,导致在TSS中使用SS选择器的无效堆栈段引用,则会发生无效的TSS异常。
当在从TSS加载段选择器之前发生异常时,保存的指令指针指向导致异常的指令。 否则,更常见的是,它指向新任务中的第一条指令。
Error code: 无效的TSS异常将一个设置错误代码,该错误代码是选择器索引。
Segment Not Present
尝试加载将其当前位设置为0的段或门时,会发生段不存在异常。 但是,当加载引用不存在的描述符的堆栈段选择器时,会发生[#Stack-Segment Fault|堆栈段故障(Stack-Segment Fault)]]。
如果在硬件任务切换期间发生异常,处理程序不应依赖段值。 也就是说,处理程序应该在尝试恢复新任务之前检查它们。 根据英特尔的文档,有三种方法可以做到这一点。
保存的指令指针指向导致异常的指令。
Error code: 段未出现(Segment Not Present)异常将一个设置错误代码,该错误代码是导致异常的段描述符的 段选择器索引。
Stack-Segment Fault
堆栈段故障发生在以下情况:
- 加载引用不存在的段描述符的堆栈段。
- 执行任何PUSH或POP指令或使用ESP或EBP作为基本寄存器的任何指令,而堆栈地址不是规范形式。
- 当堆栈限制检查失败时。
如果在硬件任务切换期间发生异常,处理程序不应依赖段值。 也就是说,处理程序应该在尝试恢复新任务之前检查它们。 根据英特尔的文档,有三种方法可以做到这一点。
保存的指令指针指向导致异常的指令,除非故障是由于在硬件任务切换期间加载不存在的堆栈段而发生的,在这种情况下,它指向新任务的下一条指令。
Error code: 堆栈段故障会设置一个错误代码,该错误代码是在硬件任务切换期间引用非当前段描述符或限制检查失败时的堆栈 段选择器索引。 否则(对于当前段和已在使用的段),错误代码为0。
General Protection Fault
一般保护故障可能由于各种原因而发生。 最常见的是:
- 段错误(权限、类型、限制、读/写权限)。
- CPL!=0时执行特权指令。(current privilege level-CPL)
- 在保留寄存器字段中写入1或写入无效的值组合(例如,带有PE=0和PG=1的CR0)。
- 引用或访问空描述符。
保存的指令指针指向导致异常的指令。
Error code: 一般保护故障会设置一个错误代码,当异常与段相关时,该错误代码为[#Selector Error Code|段选择器索引]]。 否则为0。
Page Fault
页面错误发生在以下情况:
- 物理内存中不存在页面目录或表条目。
- 尝试加载带有不可执行页面翻译的指令TLB。
- 保护检查(权限、读/写)失败。
- 页目录或表项中的保留位设置为1。
保存的指令指针指向导致异常的指令。
Error code
页面错误设置了以下错误代码:
31 15 4 0 +---+-- --+---+-----+---+-- --+---+----+----+---+---+---+---+---+ | Reserved | SGX | Reserved | SS | PK | I | R | U | W | P | +---+-- --+---+-----+---+-- --+---+----+----+---+---+---+---+---+
长度 | 名称 | 描述 | |
---|---|---|---|
P | 1 bit | Present | 设置1时,页面错误是页面保护违规造成的。如果未设置,则它是由non-present页引起的。 |
W | 1 bit | Write | 设置1时,页面错误是由写访问引起的。未设置时,这是由读取访问引起的。 |
U | 1 bit | User | 设置1时,当CPL=3时会导致页面错误。这并不一定意味着页面错误是特权侵犯。 |
R | 1 bit | Reserved write | 设置1时,一个或多个页面目录项包含保留位,设置为1,仅当CR4中的PSE或PAE标志设置为1时才有效。 |
I | 1 bit | Instruction Fetch | 设置时,页面错误是由指令获取引起的。这仅在支持和启用No-Execute位时适用。 |
PK | 1 bit | Protection key | 设置1时,页面错误是由违反保护密钥引起的。 PKRU寄存器(用于用户模式访问)或PKRS MSR(用于管理员模式访问)指定保护密钥权限。 |
SS | 1 bit | Shadow stack | 设置1时,页面错误是由影子堆栈访问(shadow stack access,译者注:一种函数返回地址保护机制,请参考Task State Segment)引起的。 |
SGX | 1 bit | Software Guard Extensions | 设置1时,故障是由于SGX违规引起的。该故障与普通分页无关。 |
此外,它将CR2寄存器的值设置为导致页面错误的虚拟地址。
x87 Floating-Point Exception
执行FWAIT或WAIT指令或任何等待的浮点指令时,如果满足以下条件,则会发生x87浮点异常:
- CR0.NE为1;
- 未屏蔽的x87浮点异常正在挂起 (即x87浮点状态字寄存器中的异常位设置为1)。
保存的指令指针指向异常发生时即将执行的指令。 x87指令指针寄存器包含导致异常的最后一条指令的地址。
Error Code: 异常不会推送错误代码。 但是,x87状态字寄存器中有异常信息。
Alignment Check
当启用对齐检查并执行未对齐的内存数据引用时,会发生对齐检查异常。 校准检查仅在CPL 3中执行。
默认情况下禁用对齐检查。 要启用它,请将 CR0.AM 和 RFLAGS.AC 位都设置为1。
保存的指令指针指向导致异常的指令。
SIMD Floating-Point Exception
发生未屏蔽128位媒体浮点异常且CR4.OSXMMEXCPT位设置为1时,会发生SIMD浮点异常。 如果未设置OSXMMEXCPT标志,则SIMD浮点异常将导致未定义的操作码异常,而不是此异常。
保存的指令指针指向导致异常的指令。
Error Code: 异常不会推送错误代码。但是,MXCSR寄存器中提供了例外信息。
陷阱(Traps)
Debug
在以下情况下发生调试异常:
- 指令获取断点 (故障)
- 一般检测条件(故障)
- 数据读写断点(Trap)
- I/O读或写断点 (陷阱)
- 单步(陷阱)
- 任务切换(陷阱)
当异常是故障时,保存的指令指针指向导致异常的指令。 当异常是陷阱时,保存的指令指针指向导致异常的指令之后的指令。
Error code: 调试异常不设置错误代码。但是,异常信息在调试寄存器(CPU_Registers_x86#Debug_Registers)中提供。
Breakpoint
执行INT3指令时发生断点异常。 一些调试软件用INT3指令替换指令。 当捕获断点时,它用原始指令替换INT3指令,并将指令指针减1。
保存的指令指针指向INT3指令之后的字节。
Overflow
在RFLAGS中的溢出位设置为1时,执行INTO指令时会引发溢出异常。
保存的指令指针指向INTO指令之后的指令。
中止(Aborts)
Double Fault
当未处理异常或CPU尝试调用异常处理程序时发生异常时,会发生双重故障。 通常,同时处理两个异常会一个接一个地处理,但在某些情况下,这是不可能的。 例如,如果发生页错误,但异常处理程序位于不存在的页中,则会发生两个页错误,并且这两个错误都无法处理。 会发生双重故障。
双重故障总是会生成一个值为零的错误代码。
保存的指令指针未定义。 无法恢复双重故障。 必须终止故障过程。
在几个启动自制操作系统中,在PIC尚未重新编程的情况下,双重故障通常也因为误诊IRQ0。
Machine Check
机器检查异常是特定于模型的,不需要处理器实现来支持它。 它使用model-specific寄存器提供错误信息。 默认情况下,它处于禁用状态。 要启用它,请将CR4.MCE位设置为1。
当处理器检测到内部错误(如坏内存、总线错误、高速缓存错误等)时,会发生机器检查异常。
保存的指令指针的值取决于实现和异常。
Triple Fault
- 正文: Triple Fault
三重故障实际上并一种真正的例外,它也没有相关的向量号。 尽管如此,如果在尝试调用双重故障异常处理程序时生成异常,则会发生三重故障。 它会导致处理器重置。 有关可能的原因以及如何避免这些原因的更多信息,请参阅本文的主要部分。
Selector Error Code
31 16 15 3 2 1 0 +---+-- --+---+---+-- --+---+---+---+---+ | Reserved | Index | Tbl | E | +---+-- --+---+---+-- --+---+---+---+---+
长度 | 名称 | 描述 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
E | 1 bit | External | 设置1时,异常源自处理器外部。 | ||||||||||
Tbl | 2 bits | IDT/GDT/LDT table | 这是以下值之一:
| ||||||||||
Index | 13 bits | 选择器索引 | GDT、IDT或LDT中的索引。 |
遗留异常
以下异常情况发生在过时的技术上,但不再使用或应避免使用。 它们主要适用于英特尔386及更早版本,也可能包括大约同时来自其他制造商的CPU。
FPU Error Interrupt
在过去,浮点单元是可以连接到处理器的专用芯片。 它没有将FPU错误直接连接到处理器,因此它使用IRQ 13,允许CPU自行处理错误。 当开发486并添加多处理器支持时,FPU被嵌入芯片上,FPU的全局中断变得不受欢迎,取而代之的是获得了直接错误处理的选项。 默认情况下,此方法在启动时不启用,以实现向后兼容性,但是操作系统应相应地更新设置。
协处理器段溢出
当FPU仍在处理器外部时,它在保护模式下有单独的段检查。 从486开始,这是由 GPF处理的,而不是像它已经对非FPU内存访问所做的那样。
另见
外部链接
- 英特尔®64和IA-32架构软件开发人员手册, 第3卷 (系统编程指南),第6章 (中断和异常处理)