中断和异常
中断
中断通常是由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不总能保重报告的导致异常的指令地址是精确的,另外,出于安全的考虑,这类异常可能是由于导致该异常的程序执行非法操作导致的。
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
是异常代码,可以是上表中的代码,也可以是应用程序自定义的代码。lpArguments
和nNumberOfArguments
用来定义异常的常数,相当于EXCEPTION_RECORD
结构中的ExceptionInformation
和NumberParameters
。RaiseException
只是将参数放入一个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环还是0环
00441341 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地址是不是在3环
00441355 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