windows异常处理

中断和异常

注意,两者谁也不包含谁。

中断由外部硬件产生。如敲键盘、点鼠标。并且这是异步事件(可不处理)。

异常是指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。


顺序

异常调用顺序:

  1. VEH
  2. SEG
  3. UEH
  4. 以上能够处理则会调用VCH

下图是直接运行、非调试时的流程:

graph LR
    VEH[VEH]--无法处理-->SEH[SEH]
    SEH--无法处理-->UEH[UEH]
    UEH--可以处理-->VCH[VCH]
    VEH--可以处理-->VCH
    SEH--可以处理-->VCH
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值