中断和异常


中断和异常

中断

中断通常是由CPU外部的输入输出设备所触发的,供外部设备通知CPU“有事情需要处理”,因此又叫中断请求。中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理历程。

由于有些任务不可中断,为了防止这类情况,可以通过执行CLI指令清除cflags的IF位。中断机制位CPU和外部设备间的通信提供了一种高效的方法,有了中断机制,CPU就可以不用去频繁地查询外部设备的状态了,当外部设备有事时可以发出中断请求通知CPU。

异常

异常通常时CPU在执行指令时因为检测到预先定义的某个或多个条件而产生的同步事件。异常来自于CPU本身,是CPU主动产生的,而中断来自于外部设备,是中断源发起的,CPU是被动的。

根据CPU报告异常的方式和导致异常的指令是否可以安全地重新执行,IA-32CPU把异常分为3类:错误、陷阱和中止

异常分类

错误

错误类异常通常可以被纠正,程序可以无损失地回复执行。此类异常的一个最常见的例子就是内存页错误,xp系统大概每秒钟60-80次,当出现缺页错误时,系统会把对应的内存页交换回物理内存,再让CPU去执行导致异常的指令。

陷阱

当CPU报告陷阱类异常时,导致该异常的指令已经执行完毕,压入栈的CS和EIP值时导致该异常的指令执行后接着要执行的下一条指令。下一条指令并不一定是与导致异常的指令相邻的下一条。如果导致异常的指令是跳转指令或函数调用指令,那么下一条指令可能是内存地址不相邻的另一条指令。

INT3导致的断电异常就属于陷阱类异常。

中止

中止类异常主要用来报告严重的错误,比如硬件错误和系统表中包含非法值或不一致的状态。这类异常不允许恢复继续执行。因为这类异常发生时CPU不总能保重报告的导致异常的指令地址是精确的,另外,出于安全的考虑,这类异常可能是由于导致该异常的程序执行非法操作导致的。

中断和异常列表1

image-20230223230151574

中断和异常列表1

EXCEPTION_RECORD

为了更好地管理异常,Windows定义了专门的数据结构来描述异常

typedef struct _EXCEPTION_RECORD {
  DWORD                    ExceptionCode;             //异常代码
  DWORD                    ExceptionFlags;            //异常标志
  struct _EXCEPTION_RECORD *ExceptionRecord;          //相关的另一个异常
  PVOID                    ExceptionAddress;          //异常发生地址
  DWORD                    NumberParameters;          //参数数组中的元素个数
  ULONG_PTR                ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];           //参数数组
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

windbg查看结构体:dt _exception_record

ExceptionFlags用来记录异常标志,目前定义的标志位有:

Value 含义
EXCEPTION_ACCESS_VIOLATION 0xC0000005L 线程尝试读取或写入其没有相应访问权限的虚拟地址。
EXCEPTION_ARRAY_BOUNDS_EXCEEDED 0xC000008CL 线程尝试访问超出边界的数组元素,基础硬件支持边界检查。
EXCEPTION_BREAKPOINT 0x80000003L 遇到断点。
EXCEPTION_DATATYPE_MISALIGNMENT 0x80000002L 线程尝试读取或写入硬件上未提供对齐方式的数据。 例如,必须在 2 字节边界上对齐 16 位值;4 字节边界上的 32 位值等。
EXCEPTION_FLT_DENORMAL_OPERAND 0xC000008DL 浮点运算中的操作数之一是非正态。 非规范值太小,无法表示为标准浮点值。
EXCEPTION_FLT_DIVIDE_BY_ZERO 0xC0000094L 线程尝试将浮点值除以零的浮点除数。
EXCEPTION_FLT_INEXACT_RESULT 0xC000008FL 浮点运算的结果不能完全表示为小数分数。
EXCEPTION_FLT_INVALID_OPERATION 0xC0000090L 此异常表示此列表中未包含的任何浮点异常。
EXCEPTION_FLT_OVERFLOW 0xC0000091L 浮点运算的指数大于相应类型允许的量级。
EXCEPTION_FLT_STACK_CHECK 0xC0000092L 由于浮点操作,堆栈溢出或下溢。
EXCEPTION_FLT_UNDERFLOW 0xC0000093L 浮点运算的指数小于相应类型允许的量级。
EXCEPTION_ILLEGAL_INSTRUCTION 0xC0000096L 线程尝试执行无效指令。
EXCEPTION_IN_PAGE_ERROR 0xC0000006L 线程尝试访问不存在的页面,并且系统无法加载页面。 例如,如果在通过网络运行程序时网络连接丢失,则可能会出现此异常。
EXCEPTION_INT_DIVIDE_BY_ZERO 0xC0000094L 线程尝试将整数值除以零的整数除数。
EXCEPTION_INT_OVERFLOW 0xC0000095L 整数运算的结果导致执行结果的最大有效位。
EXCEPTION_INVALID_DISPOSITION 0xC0000026L 异常处理程序将无效处置返回到异常调度程序。 使用高级语言(如 C)的程序员不应遇到此异常。
EXCEPTION_NONCONTINUABLE_EXCEPTION 0xC0000025L 发生不可连续异常后,线程尝试继续执行。
EXCEPTION_PRIV_INSTRUCTION 0xC000001DL 线程尝试执行其操作在当前计算机模式下不允许的指令。
EXCEPTION_SINGLE_STEP 0x80000004L 跟踪陷阱或其他单指令机制指示已执行一个指令。
EXCEPTION_STACK_OVERFLOW 0xC00000FDL 线程已用到其堆栈。
CONTROL_C_EXIT 0xC000013AL 仅适用于控制台程序,当用户按Ctrl+C或Ctrl+Break键时。

ExceptionAddress字段用来记录异常地址,对于硬件异常,它的值因为异常类型不同而可能时导致异常的那条指令的地址,或者时导致异常指令的下一条指令地址,_EXCEPTION_RECORD 把所有的异常链起来了

NumberParameters是附加参数的个数,即ExceptionInformation数组中包含的有效参数个数,最多允许储存15个附加参数

登记CPU异常

对于CPU异常,KiTrapXX在完成针对本异常的特别动作后,通常会调用CommonDispatchException函数,并通过寄存器将如下信息传递给这个函数

  • 将唯一标识该异常的一个异常代码放入EAX寄存器
  • 将导致异常的指令地址放入EBX寄存器
  • 将其他信息作为附带参数分别放入EDX(参数1)、ESI(参数2)、和EDI(参数3)寄存器,并将参数个数放入寄存器

CommonDispatchException被调用后,它会在栈中分配一个EXCEPTION_RECORD结构,并把一上异常信息存储到该结构中,之后他会调用内核中的KiDispatchException函数来分发异常

登记软件异常

软件异常时通过直接或间接调用用内核服务NtRaiseException而产生的

用户模式中的程序可以通过RaiseException()API来调用这个内核服务。RaiseException API是由KERNEL32.DLL导出API,供应用程序产生自定义的异常

void RaiseException(
  [in] DWORD           dwExceptionCode,
  [in] DWORD           dwExceptionFlags,
  [in] DWORD           nNumberOfArguments,
  [in] const ULONG_PTR *lpArguments
);

dwExceptionCode是异常代码,可以是上表中的代码,也可以是应用程序自定义的代码。lpArgumentsnNumberOfArguments用来定义异常的常数,相当于EXCEPTION_RECORD结构中的ExceptionInformationNumberParametersRaiseException只是将参数放入一个EXCEPTION_RECORD后便嗲用NTDLL中的RtlRaiseException()RtlRaiseException会将当前的执行上下文放入CONTEXT结构,然后通过NTDLL中的系统服务调用机制调用内核中的NtRaiseException

NtRaiseException内部会调用另一个内核函数KiRaiseException

NTSTATUS
NTAPI
KiRaiseException(IN PEXCEPTION_RECORD ExceptionRecord,
                 IN PCONTEXT Context,
                 IN PKEXCEPTION_FRAME ExceptionFrame,
                 IN PKTRAP_FRAME TrapFrame,
                 IN BOOLEAN SearchFrames)

ExceptionRecord是指向异常记录的指针,ContextRecord是指向线程上下文结构CONTEXT的指针,ExceptionFrame对于x86平台总为空,TrapFrame就是栈帧的基地址,FirstChance标识这是该异常的第一轮(true)还是第二轮处理机会(false)

源码分析

以_KiTrap00为例,在处理完一系列异常动作后,会从一个jmp跳转到CommonDispatchException

ext:00407588                 sti                     ; 开中断
.text:00407589                 push    ebp
.text:0040758A                 call    _Ki386CheckDivideByZeroTrap@4 ;0异常的函数
.text:0040758F                 mov     ebx, [ebp+68h]
.text:00407592                 jmp     loc_407385

.text:00407385 loc_407385:
.text:00407385                 xor     ecx, ecx
.text:00407387                 call    CommonDispatchException

CommonDispatchException

004073A6                 sub     esp, 50h        ; 异常结构的大小
004073A9                 mov     [esp+_EXCEPTION_RECORD.ExceptionCode], eax ; 错误码
004073AC                 xor     eax, eax
004073AE                 mov     [esp+_EXCEPTION_RECORD.ExceptionFlags], eax ; 0
004073B2                 mov     [esp+_EXCEPTION_RECORD.ExceptionRecord], eax ; 0
004073B6                 mov     [esp+_EXCEPTION_RECORD.ExceptionAddress], ebx ; 出现异常的地址
004073BA                 mov     [esp+_EXCEPTION_RECORD.NumberParameters], ecx ; 异常的参数
004073BE                 cmp     ecx, 0
004073C1                 jz      short loc_4073CF
004073C3                 lea     ebx, [esp+_EXCEPTION_RECORD.ExceptionInformation]
004073C7                 mov     [ebx], edx      ; 参数1
004073C9                 mov     [ebx+4], esi    ; 参数2
004073CC                 mov     [ebx+8], edi    ; 参数3
004073CF
004073CF loc_4073CF:
004073CF                 mov     ecx, esp
004073D1                 test    [ebp+_KTRAP_FRAME.EFlags], 20000h
004073D8                 jz      short loc_4073E1
004073DA                 mov     eax, 0FFFFh

004073E4                 and     eax, 1
004073E7                 push    1               ; FirstChance
004073E9                 push    eax             ; PreviousMode
004073EA                 push    ebp             ; TrapFrame
004073EB                 push    0               ; ExceptionFrame
004073ED                 push    ecx             ; ExceptionRecord
004073EE                 call    _KiDispatchException@20
004073F3                 mov     esp, ebp
004073F5                 jmp     Kei386EoiHelper@0 ; Kei386EoiHelper()

_KiDispatchException

00422C5D                 push    390h
00422C62                 push    offset stru_422D60
00422C67                 call    __SEH_prolog
00422C6C                 mov     eax, ds:___security_cookie
00422C71                 mov     [ebp+var_SEH], eax
00422C74                 mov     esi, [ebp+ExceptionRecord]
00422C77                 mov     [ebp+var_ExceptionRecord], esi
00422C7D                 mov     ecx, [ebp+ExceptionFrame]
00422C80                 mov     [ebp+var_ExceptionFrame], ecx
00422C86                 mov     ebx, [ebp+TrapFrame]
00422C89                 mov     [ebp+var_TrapFrame], ebx
00422C8F                 mov     eax, large fs:_KPCR.Prcb
00422C95                 inc     [eax+_KPRCB.KeExceptionDispatchCount] ; 异常派发+1
00422C9B                 mov     [ebp+Context.ContextFlags], 10017h ; =| CONTEXT_FULL
00422CA5                 cmp     byte ptr [ebp+PreviousMode], 1 ; 判断模式是3环还是0环
00422CA9                 jz      short loc_422CB4 ; 3环过来的 =| CONTEXT_FLOATING_POINT
00422CAB                 cmp     ds:_KdDebuggerEnabled, 0 ; 如果是0环,判断是否开启内核调试器
00422CB2                 jz      short loc_422CD1
00422CB4
00422CB4 loc_422CB4:                             ; CODE XREF: KiDispatchException(x,x,x,x,x)+4C↑j
00422CB4                 mov     [ebp+Context.ContextFlags], 1001Fh ; 3环过来的 =| CONTEXT_FLOATING_POINT
00422CBE                 cmp     ds:_KeI386XMMIPresent, 0
00422CC5                 jz      short loc_422CD1
00422CC7                 mov     [ebp+Context.ContextFlags], 1003Fh ; 全包了 |= CONTEXT_EXTENDED_REGISTERS
00422CD1
00422CD1 loc_422CD1:
00422CD1                 lea     eax, [ebp+Context]
00422CD7                 push    eax
00422CD8                 push    ecx
00422CD9                 push    ebx
00422CDA                 call    _KeContextFromKframes@12 ; 把KTrap_Frame转换成Context
00422CDF                 mov     eax, [esi+_EXCEPTION_RECORD.ExceptionCode]
00422CE1                 cmp     eax, 80000003h  ; 判断异常是不是INT3断点
00422CE6                 jnz     loc_441136      ; 内存访问错误异常
00422CEC                 dec     [ebp+Context._Eip] ; 修复到原来异常的地址
00422CF2
00422CF2 loc_422CF2:
00422CF2                 xor     edi, edi
00422CF4 loc_422CF4: 
00422CF4                 cmp     byte ptr [ebp+PreviousMode], 0 ; 判断是否为0环
00422CF8                 jnz     loc_440FC6      ; 不等于则是3环
00422CFE                 cmp     [ebp+FirstChance], 1 ; 判断是否是第一次异常
00422D02                 jnz     loc_44B0D6      ; 再次判断有没有内核调试器
00422D08                 mov     eax, ds:_KiDebugRoutine ; 如果是第一次,再判断调试路由是否为NULL
00422D0D                 cmp     eax, edi
00422D0F                 jz      loc_4335B6      ; 如果没有内核调试器或者内核调试器没有处理成功
00422D15                 push    edi
00422D16                 push    edi
00422D17                 lea     ecx, [ebp+Context]
00422D1D                 push    ecx
00422D1E                 push    esi
00422D1F                 push    [ebp+var_ExceptionFrame]
00422D25                 push    ebx
00422D26                 call    eax ; _KiDebugRoutine ; 如果KiDebugRoutine不为0 调用KiDebugRoutine
00422D28                 test    al, al
00422D2A                 jz      loc_4335B6      ; 如果没有内核调试器或者内核调试器没有处理成功
00422D30
00422D30 loc_422D30:
00422D30                 push    [ebp+PreviousMode]
00422D33                 push    [ebp+Context.ContextFlags]
00422D39                 lea     eax, [ebp+Context]
00422D3F                 push    eax
00422D40                 push    [ebp+var_ExceptionFrame]
00422D46                 push    ebx
00422D47                 call    _KeContextToKframes@20
00422D4C loc_422D4C:
00422D4C                 mov     ecx, [ebp+var_SEH]
00422D4F                 call    sub_402C62
00422D54                 call    __SEH_epilog
00422D59                 retn    14h

KiRaiseException

004412E7                 push    34Ch
004412EC                 push    offset stru_441458
004412F1                 call    __SEH_prolog
004412F6                 mov     eax, ds:___security_cookie
004412FB                 mov     [ebp+var_SEH], eax
004412FE                 mov     ebx, [ebp+pExceptionRecord]
00441301                 mov     ecx, [ebp+Context]
00441304                 mov     [ebp+var_Context], ecx
0044130A                 mov     eax, [ebp+ExceptionFrame]
0044130D                 mov     [ebp+var_ExceptionFrame], eax
00441313                 mov     eax, [ebp+TrapFrame]
00441316                 mov     [ebp+BugCheckParameter3], eax
0044131C                 xor     edi, edi
0044131E                 mov     [ebp-4], edi
00441321                 mov     eax, large fs:_KPCR.PrcbData.CurrentThread
00441327                 mov     [ebp+var_CurrentThread], eax
0044132D                 mov     al, [eax+_KTHREAD.PreviousMode]
00441333                 mov     byte ptr [ebp+PreviousMode], al
00441339                 test    al, al
0044133B                 jz      loc_441401      ; 判断当前模式是3环还是000441341                 test    cl, 3           ; 判断字节是否对齐
00441344                 jnz     loc_44B667      ; 触发80000002异常
00441344                                         ; Exception_DataType_Misalignment
00441344                                         ; CPU的对齐检查异常
0044134A loc_44134A:
0044134A                 mov     eax, ds:_MmUserProbeAddress
0044134F                 cmp     [ebp+var_Context], eax ; 判断CONTEXT地址是不是在300441355                 jnb     loc_44B671
0044135B
0044135B loc_44135B:
0044135B                 mov     esi, ebx        ; 判断异常结构体是否对齐
0044135D                 and     esi, 3
00441360                 jnz     loc_44B678
00441366
00441366 loc_441366:
00441366                 mov     eax, ds:_MmUserProbeAddress ; 判断异常结构体是否在3环
0044136B                 cmp     ebx, eax
0044136D                 jnb     loc_44B682
00441373 loc_441373:
00441373                 mov     eax, [ebx+10h]
00441376                 mov     [ebp+var_2F8], eax
0044137C                 cmp     eax, 0Fh        ; 判断参数的个数
0044137F                 ja      loc_44B689      ; 如果大于,参数无效
00441385                 lea     ecx, [eax*4+_EXCEPTION_RECORD.ExceptionInformation]
0044138C                 mov     [ebp+var_ParamAddress], ecx
00441392                 cmp     ecx, edi        ; 判断参数是否为0
00441394                 jz      short loc_4413B4
00441396                 cmp     esi, edi        ; 判断异常结构体是否为0
00441398                 jnz     loc_44B697
0044139E loc_44139E:
0044139E                 add     ecx, ebx
004413A0                 cmp     ecx, ebx
004413A2                 jb      loc_44B6AD
004413A8                 cmp     ecx, ds:_MmUserProbeAddress
004413AE                 ja      loc_44B6AD
004413B4 loc_4413B4:
004413B4                 mov     ecx, 0B3h
004413B9                 mov     esi, [ebp+var_Context]
004413BF                 lea     edi, [ebp+var_R0Context]
004413C5                 rep movsd               ; 拷贝CONTEXT结构体
004413C7                 mov     ecx, [ebp+var_ParamAddress]
004413CD                 mov     esi, ebx
004413CF                 lea     edi, [ebp+var_ExceptionInformation]
004413D5                 mov     edx, ecx
004413D7                 shr     ecx, 2
004413DA                 rep movsd               ; 拷贝参数
004413DC                 mov     ecx, edx
004413DE                 and     ecx, 3          ; 拷贝其他字节
004413E1                 rep movsb
004413E3                 lea     ecx, [ebp+var_R0Context]
004413E9                 mov     [ebp+var_Context], ecx
004413EF                 lea     ebx, [ebp+var_ExceptionInformation]
004413F5                 mov     [ebp+var_R0ExpectionInformation], ebx
004413FB                 mov     [ebp+var_R0ParamNumber], eax
00441401 loc_441401:
00441401                 or      [ebp+ms_exc.disabled.TryLevel], 0FFFFFFFFh
00441405                 push    [ebp+PreviousMode]
0044140B                 mov     eax, [ebp+var_Context]
00441411                 push    dword ptr [eax]
00441413                 push    eax
00441414                 push    [ebp+var_ExceptionFrame]
0044141A                 push    [ebp+BugCheckParameter3]
00441420                 call    _KeContextToKframes@20 ; 把Context转换成KTrap_Frame结构体
00441425                 and     byte ptr [ebx+3], 0EFh ; 最高位清零,以便把软件产生的异常和CPU产生的异常区分开来
00441429                 push    dword ptr [ebp+SearchFrames] ; FirstChance
0044142C                 push    [ebp+PreviousMode] ; PreviousMode
00441432                 push    [ebp+BugCheckParameter3] ; TrapFrame
00441438                 push    [ebp+var_ExceptionFrame] ; ExceptionFrame
0044143E                 push    ebx             ; ExceptionRecord
0044143F                 call    _KiDispatchException@20 ; KiDispatchException(x,x,x,x,x)
00441444                 xor     eax, eax
00441446 loc_441446:
00441446                 mov     ecx, [ebp+var_SEH]
00441449                 call    sub_402C62
0044144E                 call    __SEH_epilog
00441453                 retn    14h

文章作者: Kevin。
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kevin。 !
评论
 本篇
中断和异常 中断和异常
中断和异常中断中断通常是由CPU外部的输入输出设备所触发的,供外部设备通知CPU“有事情需要处理”,因此又叫中断请求。中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理历程。 由于有些任务不可中断,为
2023-02-23
下一篇 
Windows句柄表 Windows句柄表
Windows句柄表句柄表介绍句柄表分为全局句柄表和进程句柄表,Windows用句柄来管理进程中的对象引用,进程句柄表仅在一个进程范围内才有效,若将该进程中的一个句柄传递给另一个进程后,该句柄不再有效。而全局句柄表在操作系统全局都有效,在w
2022-10-04
  目录