中断和异常
注意,两者谁也不包含谁。
中断由外部硬件产生。如敲键盘、点鼠标。并且这是异步事件(可不处理)。
异常是指cpu内部问题,是一个同步事件(必须处理)。
总之,cpu外部中断简称中断,cpu内部中断就叫异常。
外部中断由pic控制(与if有关),中断发生时,cpu从pic读取两个字节0xcd 0xXX
,即int XX
。
windows中,两者统一管理,处理函数叫做陷阱处理器。
!idt /a
查看中断描述表。
idtr
寄存器保存了idt。
PC-Hunter内核钩子可以查看IDT。
异常分类
根据cpu报告方式和指令能否安全执行,分为3类:
- 错误:可恢复,保存context中的eip指向异常指令,如除0;
- 陷阱:可恢复,保存context中的eip指向异常指令的下一条(异常指令已经执行),如int 3;
- 终止:很严重,不可恢复。如不能及时发现的错误,处理异常时又产生异常。
windows异常处理
推荐书籍:软件调试(张银奎)。
SEH
SEH由VS提供,不属于c/c++,要区分。
注意:except finally
不能一块使用,但能嵌套使用!!!!!!!!!!
try+finally
__try{
return 0;
//goto leave;
__leave
}
__finally{
printf("111\n");
}
leave:
离开被保护的代码:
- 正常退出:leave或者执行完代码
- 非正常退出:return/goto/异常/break等
__finally
无论如何都会调用,即使return/goto/break等跳转。但会生成冗余代码调用__unwind
找到finally。
__leave
则是直接跳到finally,不需要调用函数。
判断是否正常离开:int __cdecl AbnormalTermination();
在保护区尽量使用__leave
except
__except(/*过滤表达式*/)
过滤表达式的值:
// Defined values for the exception filter expression
#define EXCEPTION_EXECUTE_HANDLER 1 //我行我上
#define EXCEPTION_CONTINUE_SEARCH 0 //找别人吧
#define EXCEPTION_CONTINUE_EXECUTION (-1) //我再试试
过滤表达式可以是数值、函数调用、运算表达式,有两个函数很有用:
unsigned long __cdecl GetExceptionCode(void);
/*
EXCEPTION_ACCESS_VIOLATION
EXCEPTION_DATATYPE_MISALIGNMENT
EXCEPTION_BREAKPOINT
EXCEPTION_SINGLE_STEP
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_FLT_DIVIDE_BY_ZERO
*/
它用来获取异常的类型,只能写在过滤表达式中,或者异常处理块中
另一个获取异常的基本信息和寄存器,只能写在过滤表达式中。
(struct _EXCEPTION_POINTERS*) __cdecl GetExceptionInformation(void);
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
我们可以写一个过滤函数示例:
#include <windows.h>
#include <stdio.h>
DWORD ExceptionFilter(int ExceptionCode, PEXCEPTION_POINTERS ExceptionInfo)
{
printf("ExceptionCode: %08X\n", ExceptionCode);
// 判断当前的异常是否时可以处理的类型
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
ExceptionInfo->ContextRecord->Ecx = 1;
// 处理了异常应该返回 EXCEPTION_CONTINUE_EXECUTION
return EXCEPTION_CONTINUE_EXECUTION;
}
// 交给上层进行处理
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
__try
{
printf("__try{...}\n");
__asm xor eax, eax
__asm xor edx, edx
__asm xor ecx, ecx
__asm idiv ecx
printf("Exception is handled\n");
}
__except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation()))
{
// 过滤表达式必须是 1 才会被执行
printf("EXCEPTION_EXECUTE_HANDLER\n");
}
system("pause");
return 0;
}
/*
__try{...}
ExceptionCode: C0000094
Exception is handled
*/
UEH
本质上属于SEH,可以添加默认SEH异常处理程序,是一个API和一个回调函数。
UEH是异常处理的最后一道关卡,通常用于执行错误信息的收集工作
进程相关,被存放在一个全局变量中,只能有一个
UEH的调用位于SEH之后,如果所有SEH都无法处理才会被调用
如果程序处于被调试状态,那么UEH函数不会被调用
设置UEH的API:
LPTOP_LEVEL_EXCEPTION_FILTER
WINAPI
SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
_In_ struct _EXCEPTION_POINTERS *ExceptionInfo
);
typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;
参数是我们提供的回调函数。
运行了SEH却不能处理时,才会运行这个回调函数。UEH是异常处理的最后一环。
调试状态UEH不会执行,咱们的例子应该直接运行。
UEH是全局的。
#include <windows.h>
#include <stdio.h>
LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
printf("ExceptionCode: %08X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
ExceptionInfo->ContextRecord->Ecx = 1;
// 处理了异常应该返回 EXCEPTION_CONTINUE_EXECUTION
return EXCEPTION_CONTINUE_EXECUTION;
}
// 交给上层进行处理
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
__try
{
printf("__try{...}\n");
__asm xor eax, eax
__asm xor edx, edx
__asm xor ecx, ecx
__asm idiv ecx
printf("Exception is handled\n");
}
// 返回的是不能处理,所有的 SEH 无法处理
__except(EXCEPTION_CONTINUE_SEARCH)
{
// 过滤表达式必须是 1 才会被执行
printf("You can't see this.\n");
}
system("pause");
return 0;
}
VEH
向量化处理有VEH和VCH。
使用VEH需要提供一个回调函数。
PVOID WINAPI
AddVectoredExceptionHandler(
_In_ ULONG First,
_In_ PVECTORED_EXCEPTION_HANDLER Handler
);
typedef LONG (NTAPI *PVECTORED_EXCEPTION_HANDLER)(
struct _EXCEPTION_POINTERS *ExceptionInfo
);
- First:函数添加在容器头部还是尾部;
If the parameter is nonzero, the handler is the first handler to be called. If the parameter is zero, the handler is the last handler to be called.
VEH 和 VCH 都被存放在一个全局的链表中,可以设置多个
VEH的调用是最先的,如果所有VEH都无法处理异常,则会调用SEH和UEH
代码示例:
我们用刚才的除零处理函数来处理内存访问错误,显然处理不了。
#include <windows.h>
#include <stdio.h>
LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
printf("ExceptionCode: %08X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
ExceptionInfo->ContextRecord->Ecx = 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
// 交给上层进行处理
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
AddVectoredExceptionHandler(1, ExceptionHandler);
__try
{
printf("__try{...}\n");
__asm xor eax, eax
__asm xor edx, edx
__asm xor ecx, ecx
__asm idiv ecx
printf("Exception is handled\n");
}
// 返回的是不能处理,所有的 SEH 无法处理
__except(EXCEPTION_EXECUTE_HANDLER)
{
// 过滤表达式必须是 1 才会被执行
printf("VEH can't handle, then you can see this.\n");
}
system("pause");
return 0;
}
/*
__try{...}
ExceptionCode: C0000094
VEH can't handle, then you can see this.
*/
VCH
VCH只会在异常被处理的情况下执行,并且始终是最后一个执行的。
它的使用其实就是VEH换了个API名字。
PVOID WINAPI
AddVectoredContinueHandler(
_In_ ULONG First,
_In_ PVECTORED_EXCEPTION_HANDLER Handler
);
示例代码:
#include <windows.h>
#include <stdio.h>
LONG WINAPI VectorExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
printf("VEH\n");
return EXCEPTION_CONTINUE_SEARCH;
}
DWORD ExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
{
printf("SEH\n");
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
printf("UEH\n");
ExceptionInfo->ContextRecord->Ecx = 1;
//return EXCEPTION_CONTINUE_SEARCH;
return EXCEPTION_EXECUTE_HANDLER;
}
LONG WINAPI VectorContinueHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
printf("VCH\n");
ExceptionInfo->ContextRecord->Ecx = 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
AddVectoredExceptionHandler(TRUE, VectorExceptionHandler);
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
AddVectoredContinueHandler(TRUE, VectorContinueHandler);
__try
{
printf("__try{...}\n");
__asm xor eax, eax
__asm xor edx, edx
__asm xor ecx, ecx
__asm idiv ecx
printf("Exception is handled\n");
}
// 返回的是不能处理,所有的 SEH 无法处理
__except(ExceptionFilter(GetExceptionInformation()))
{
// 过滤表达式必须是 1 才会被执行
printf("You can't see this.\n");
}
printf("return\n");
system("pause");
return 0;
}
正常运行:
__try{...}
VEH
SEH
UEH
调试:
__try{...}
VEH
SEH
VCH
Exception is handled
return
在调试时,无法解决也会传给VCH。
顺序
异常调用顺序:
- VEH
- SEG
- UEH
- 以上能够处理则会调用VCH
下图是直接运行、非调试时的流程:
graph LR
VEH[VEH]--无法处理-->SEH[SEH]
SEH--无法处理-->UEH[UEH]
UEH--可以处理-->VCH[VCH]
VEH--可以处理-->VCH
SEH--可以处理-->VCH