当前位置: 代码迷 >> Windows >> windows软件工程师进阶系列:《软件调试》之三:中断和错误
  详细解决方案

windows软件工程师进阶系列:《软件调试》之三:中断和错误

热度:7514   发布时间:2013-02-26 00:00:00.0
windows程序员进阶系列:《软件调试》之三:中断和异常

    windows程序员进阶系列:《软件调试》之三:中断和异常 

 

     中断和异常是计算机原理中很重要的概念。在任务切换、时间更新、系统调用、软件调试等的许多功能都是依靠中断和异常机制实现的。

 

      EIP存储着cpu下一条要执行的指令的地址,执行完一条再取一条,如此往复循环。有了中断机制可以让cpu暂时停下当前的任务,转去处理突然事件或其他需要处理的任务。

 

中断

     中断通常是由外部输入输出设备产生的。外部设备通过中断来通知cpu有事情要处理,因此又叫中断请求。当中断到来时cpu就要暂停当前的工作,转而去执行中断请求所对应的中断处理程序。

 

     为了使cpu在执行工作时不被干扰,可以设置标记寄存器的IF位。设置后只有不可屏蔽中断(non-maskbale InterruptNMI到来时,cpu才会被打扰。对于其他一般中断cpu会忽略它们。

 

     中断机制为cpu和外部设备间的通信提供一种高效的方法。有了中断机制之后cpu就不必频繁的去查询外部设备的状态了。当外部设备有请求要处理时,它会通过中断机制主动去通知cpu

 

      在硬件级,中断是由一块芯片负责的,它被称为中断控制器(interrupt controller)。它负责分配中断资源和管理各个中断源发出的中断请求。为了便于标识各个中断请求,中断管理器通常用IRQInterrupt request)后面加上数字来表示不同的中断请求信号。比如IR0,IR1。根据约定IR0被分配给时钟设备。IR1是分配给键盘的。IRQ3IRQ4是分配给串口一和串口二

 

异常

     与中断不同,异常通常是cpu在执行指令因为检测到预先定义的某个条件而产生的同步事件。

     导致异常有三种情况:

      一:程序错误。遇到操作数有错误或有非法的情况。如除数为0

      二:某些特殊指令导致异常。这些指令的目的就是为了产生异常。如INT3指令,它的目标就是产生一个断点异常,让cpu中断进调试器。这样的指令还有INTOINT n

      三:奔腾cpu引入的机器检查异常。当cpu在执行指令时,检测到cpu内部或外部的硬件错误就会产生异常。

 

中断与异常的差异

 

      异常来自cpu本身,是cpu主动产生的。而中断来自于外部设备,是中断源发起的。对于机器异常,虽然有时是外部设备通过特殊管脚触发的,但从产生角度看,仍然是cpu检测到管脚信号然后产生的异常。因此机器检查异常仍然是cpu内部产生的。

      在后面的介绍中,如不加说明,所谓的中断就是从cpu外部硬件中断。

 

异常分类

 

      IA-32cpu把异常分为3类:错误、陷阱和终止。

 

一:错误类异常

 

     错误类异常通常可以恢复,一旦纠正后cpu可以无损的恢复执行。最常见的一个例子是内存页错误(缺页异常)。页错误异常几乎每时每刻都在发生。这是因为页错误是虚拟内存机制的基础。当程序访问不在物理内存上的地址时,cpu便会产生一个页错误异常。并转而去处理该异常的中断处理程序。后者会调用内存管理把对应的内存页交换到物理内存。然后再让cpu返回到导致该异常的那条指令处恢复执行。再次执行时,由于该内存页已处于内存中,因此不会导致异常。

       某些错误类异常也是不可恢复的,比如POPAD指令时栈指针超出了栈所在段的边界,此时cpu会报告栈错误异常。对于这种情况尽管CSEIP可以被恢复成执行POPAD指令执行前的状态,但是处理器的装填可能已经发生变化。某些寄存器的值可能也已发生变化,操作系统应该终止出现这类异常的程序。

 

二:陷阱类异常

 

      与错误类异常不同,当cpu报告陷阱类异常时,导致该异常的指令已经执行完毕。压入栈的CSEIP值(异常处理程序的返回地址)是导致该异常的指令执行后,执行的下一条指令。导致陷阱类异常的情况通常也是可以无损失的恢复执行的。比如INT3导致的断点异常就属于陷阱异常,该异常会使cpu中断到调试器。系统调用导致的异常也属于陷阱类异常,在此异常的异常处理程序来调用相应的系统调用。

 

 

三:终止类异常

 

      终止类异常主要用来报告严重的错误。比如硬件错误或是系统表中值非法。这类异常不允许恢复。这类异常可能是由于导致该异常的程序执行非法操作而导致的,出于安全性的考虑,该程序会被迫退出。

 

 

异常列表

 

      每个中断或异常都会被赋予一个整数ID,这被称为向量号。系统通过向量号来识别中断和异常。IA-32架构定义了0-31号向量供cpu设计者使用。32-255号向量供操作系统和计算机生产商或其他硬件生产商使用。

 

 

错误代码

 

      cpu在产生某些异常时,会向栈中压入一个32位的错误代码。其格式为:

      EXTExternal Event):为1时,表示外部事件导致该异常。

      DL(descriptor loacation):描述符位置。为1时,表示错误码的段选择子索引部分指向的是IDT表中的门描述符。为0时,表示索引部分指向的是LDTGDT中的描述符。

      TI位仅当IDT位为0时有效。当该位为1时,表示索引部分指向的LDT中的段或门描述符。如果该位为0时,表示索引部分指向的是GDT中的描述符。

      段选择子索引域表示与该错误有关的描述符在IDTLDTGDT表中的索引。

      页错误异常的错误码没有使用上面的错误格式。

 

中断、异常的优先级。

 

       cpu在同一时间只能执行一个程序。如果多个中断请求或异常同时发生时,cpu就会按照 优先级的高低来依次处理。

目前IA-32构架的cpu定义了10个中断优先级。从101最高,10级最低。

 

 

中断/异常处理

       虽然中断和异常从产生的根源来看具有本质的区别。但是系统却使用同一种方式来处理它们。中断和异常的核心数据结构是中断描述表IDT。当中断和异常发生时,cpu通过查看IDT表来定位中断处理程序的地址,然后再调用此函数。操作系统在初始化阶段就已经准备好中断处理例程和IDT表。cpuIDTR寄存器获得IDT表的位置。IDTR长度为48位。高32位表示IDT的基地址,低16位表示IDT表的边界。LIDTSIDT用以设置和读取IDTR寄存器。但是只能在0特权级下运行这两条指令。

 

      在cpu被加电或系统重新启动后(处于实模式状态)IDTR的高32位会被设为:0x00000000,低16位会被设为0xFFFF。这恰好与8086cpudos系统中断向量表(Interrupt vector tableIVT的位置相同。实模式下IVT表位于物理地址0开始的1kB范围内。每个IVT项是4B,共有256项,与x86256个中断向量一一对应。每个IVT表项可以分为两部分,高两个字节为中断例程的段地址,低两个字节为中断例程的偏移地址。此时,段地址左移四位(实模式地址)再加上偏移地址,便可以得到20位的中断例程地址。

 

      实模式下IA32cpu响应中断和异常的过程为:

      1:将代码段寄存器cs和指令指针寄存器EIP的低16位压入堆栈(中断处理程序返回地址)。

      2:将EFLAGS的低16位压入堆栈。

      3:清除标志寄存器IF标志,此后再到来的中断会被屏蔽。

      4:清除标志寄存器的TFRFAC寄存器。

      5:使用向量号作为索引,在IVT中找到对应项。

      6:将从IVT表中得到的段地址和偏移地址分别装入CSEIP寄存器,并执行。

      7:中断例程总是以IRET指令结束。该指令会从堆栈中弹出前面保存的CSIP和标志寄存器。

     保护模式下的中断和异常处理,会在后面介绍。

     异常与调试有着更为密切的关系。本文从cpu的角度首次介绍了异常的基本概念。后面会从编译器和操作系统的角度来做进一步阐释。

                                 本文参考自《软件调试》张银奎著。如有纰漏,请不吝指正!
                                      2013.2.2于山西大同
  相关解决方案