调试与异常一

调试与异常

终止处理SEH

终结处理器: 保证程序在执行的过程中,一定会执行 _finally 块的代码

  • 保证无论 __ try 是以何种方式退出的,最终都会执行 __finally
    • 不能够处理异常,通常只能用于执行清理
    • __ try: 保存的通常是需要进行检测的代码
      __ finally: 保存的是一定会执行的一段代码
      __ leave: 用于退出 __try 块
int main()
{
	__try 
	{
		printf("__try { ... }
");

		// 推荐使用 __leave 退出代码块,使用跳转语句会产生多余的函数调用
		// __leave 对应实际是一条 jmp 语句,执行更加的迅速
		__leave;

		// 使用跳转指令退出 __try 块,例如 continue break goto return
		goto label_exit;
	}
	__finally
	{
		// 通常用于执行某一些特定的清理工作,比如关闭句柄或释放内存
		printf("__finally { ... }
");

		// 使用 AbnormalTermination 判断是否是正常退出的
		if (AbnormalTermination())
			printf("异常退出代码块");
		else
			printf("正常退出代码块");
	}

label_exit:

	return 0;
}

异常处理SEH

  • SEH 的两种处理实现方式是不能同时存在的,但是可以嵌套
  • SEH的处理函数被保存在了栈中,所以不同的线程拥有各自的处理函数

异常处理程序SEH:可以用于捕获产生的异常,并且对它执行相应的处理函数

__try :是需要被保护的(可能产生异常)的代码

__except:存放过滤表达式和异常处理块

  • 保存异常信息和线程环境的异常结构体

    typedef struct _EXCEPTION_POINTERS {
         PEXECPTION_RECORD ExceptionRecord;   //保存了[异常类型]和[产生异常的指令所在的位置]
         PCONTEXT ContextRecord;             //保存的是异常发生时的寄存器环境,通过修改可以恢复异常
    } EXCEPTION_POINTERS, * PEXCEPTION_POINTERS;
    
  • 过滤函数:用于根据不同的情况,返回不同类型的值

    DWORD FilterHandler(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionInfo)
    {
    	// 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    	if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    	{
    		ExceptionInfo->ContextRecord->Ecx = 1;
    		return EXCEPTION_CONTINUE_EXECUTION;
    	}
    
    	// 否则其它的异常不做处理,向上传递
    	return EXCEPTION_CONTINUE_SEARCH;
    }
    
  • __except()过滤表达式的内容可以是任意形式的,但是它的值必须是下面三个之一,通常是函数调用

    1. EXCEPTION_EXECUTE_HANDLER(1): 表示捕获到了异常,需要执行异常处理块的代码,并继续执行
    2. EXCEPTION_CONTINUE_SEARCH(0): 表示无能为力,交给其它异常处理函数,通常没有处理的返回这一个。
    3. EXCEPTION_CONTINUE_EXECUTION(-1): 表示不相信不能执行,需要重新执行一遍,只有处理了的异常才会使用。
  • 异常过滤函数通常要用到两个函数调用

    • GetExceptionCode(): 获取产生的异常的的类型,只能在过滤表达式异常处理块中使用
    • GetExceptionInformation(): 获取异常的信息和异常产生时的线程环境,只能在过滤表达式中使用
int main()
{
	__try
	{
		printf("__try{ ... }
");

		//可能会产生异常的指令,只有产生了异常才会执行 __except
		__asm mov eax, 100
		__asm xor edx, edx
		__asm xor ecx, ecx
		__asm idiv ecx

		printf("异常已经被处理了!
");

		//用来触发内存访问异常
		//*(DWORD*)0 = 0;
	}

	__except (FilterHandler(GetExceptionCode(), GetExceptionInformation()))
	{
		//只有在异常类型为EXCEPTION_EXCUTE_HANDLER才会执行
		printf("__except (EXCEPTION_EXECUTE_HANDLER) { ... }");
	}

	return 0;

顶层异常(UEH)

  • 顶层异常处理(UEH): 是应用程序的最后一道防线,当所有的 SEH都没有能够处理异常,就会执行它
    • UEH通常被用于执行内存转储操作,将收集的错误信息(异常类型、线程上下文和内存)提交到服务器
    • UEH 在64位操作系统下的调试器内是永远不会执行的,需要单独的运行。

自定义的顶层异常处理函数,即使没有自定义,也会有一个默认的处理函数,且只有一个,它的返回值类型和SEH 是相同的,但是缺少了 EXCEPTION_EXECUTE_HANDLER

LONG WINAPI TopLevelExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
	printf("TopLevelExceptionHandler(): %08X
", ExceptionInfo->ExceptionRecord->ExceptionCode);

	// 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
	if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
	{
		ExceptionInfo->ContextRecord->Ecx = 1;
		return EXCEPTION_CONTINUE_EXECUTION;	// -1
	}

	// 否则其它的异常不做处理,向上传递
	return EXCEPTION_CONTINUE_SEARCH;			// 0
}



int main()
{
	// 顶层异常处理的设置依赖于一个函数
	SetUnhandledExceptionFilter(TopLevelExceptionHandler);

	__try
	{
		// 产生除零异常
		__asm mov eax, 100
		__asm xor edx, edx
		__asm xor ecx, ecx
		__asm idiv ecx
	}
	__except (EXCEPTION_CONTINUE_SEARCH)
	{
		// 这里永远不会执行,因为不是 EXCEPTION_EXECUTE_HANDLER(1)
		printf("__except (EXCEPTION_CONTINUE_SEARCH)
");

	}

	printf("异常处理成功!");
	system("pause");

	return 0;
}

向量化异常处理程序(VEH

  • 用户层支持的一种机制,在 SEH 之前被执行,保存在一个全局的链表中,整个进程都可以访问到。
  • 自定义的 VEH 异常处理函数,它的执行位于 SEH 之前,如果 VEH 没有处理成功,才会调用 SEH
LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
	printf("VectoredExceptionHandler(): %08X
", ExceptionInfo->ExceptionRecord->ExceptionCode);

	// 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
	if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
	{
		ExceptionInfo->ContextRecord->Ecx = 1;
		return EXCEPTION_CONTINUE_EXECUTION;	// 0
	}

	// 否则其它的异常不做处理,向上传递
	return EXCEPTION_CONTINUE_SEARCH;			// 1
}


int main()
{
	// 设置一个向量化异常处理函数(VEH),参数一表示添加到异常处理函数链表的位置
	AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler);

	__try
	{
		// 产生除零异常
		__asm mov eax, 100
		__asm xor edx, edx
		__asm xor ecx, ecx
		__asm idiv ecx
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		// 这里永远不会执行,因为不是 EXCEPTION_EXECUTE_HANDLER(1)
		printf("__except (EXCEPTION_EXECUTE_HANDLER)
");

	}

	printf("异常处理成功!");
	system("pause");

	return 0;
}

向量异常处理程序VCH

  • 向量化异常处理程序(VCH):用户层支持的一种机制,在最后被执行

    • 保存在一个全局的链表中,整个进程都可以访问到,和 VEH在同一个表中,只是标志位不同
    • VCH 只会在异常被处理的情况下,最后被执行
    • VEH -> SEH -> UEH -> VCH
  • 自定义 UEH函数, 在 SEH 之后执行

    LONG WINAPI TopLevelExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
    {
    	printf("TopLevelExceptionHandler(): %08X
    ", ExceptionInfo->ExceptionRecord->ExceptionCode);
    
    	// 假设产生的是一个整数除零异常,尝试对异常进行处理,并返回继续执行
    	if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
    	{
    		ExceptionInfo->ContextRecord->Ecx = 1;
    		return EXCEPTION_CONTINUE_EXECUTION;
    	}
    
    	// 否则其它的异常不做处理,向上传递
    	return EXCEPTION_CONTINUE_SEARCH;
    }
    
  • 自定义的 VEH 异常处理函数,他的执行位于 SEH 之前,如果 VEH 没有处理成功,才会调用 SEH

    LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
    {
    	printf("VectoredExceptionHandler(): %08X
    ", ExceptionInfo->ExceptionRecord->ExceptionCode);
    
    	// 否则其它的异常不做处理,向上传递
    	return EXCEPTION_CONTINUE_SEARCH;
    }
    
  • 自定义的 VCH 异常处理函数,只有在异常处理成功的情况下,最后才会被调用

    LONG WINAPI VectoredContinueHandler(EXCEPTION_POINTERS* ExceptionInfo)
    {
    	printf("VectoredContinueHandler(): %08X
    ", ExceptionInfo->ExceptionRecord->ExceptionCode);
    	return EXCEPTION_CONTINUE_SEARCH;
    }
    
  • 过滤函数:用于根据不同的情况,返回不同的类型的值

    DWORD FilterHandler(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionInfo)
    {
    	printf("FilterHandler(): %08X
    ", ExceptionInfo->ExceptionRecord->ExceptionCode);
    
    	// 否则其它的异常不做处理,向上传递
    	return EXCEPTION_CONTINUE_SEARCH;
    }
    
int main()
{
	// 设置一个向量化异常处理函数(VEH)
	AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler);
	// 设置一个向量化异常处理函数(VCH)
	AddVectoredContinueHandler(TRUE, VectoredContinueHandler);
	// UEH 处理函数
	SetUnhandledExceptionFilter(TopLevelExceptionHandler);

	__try
	{
		// 产生除零异常
		__asm mov eax, 100
		__asm xor edx, edx
		__asm xor ecx, ecx
		__asm idiv ecx
	}
	__except (FilterHandler(GetExceptionCode(), GetExceptionInformation()))
	{
		// 这里永远不会执行,因为不是 EXCEPTION_EXECUTE_HANDLER(1)
		printf("__except (EXCEPTION_EXECUTE_HANDLER)
");
	}

	printf("异常处理成功!");
	system("pause");

	return 0;
}

SEH原理剖析

  • SEH 保存在TEB 结构体偏移为0的地方,是一个链表,SEH链表可以通过FS : [0] 这样一个地址找到

    TEB可以通过FS:[0X18]这样一个地址找到。

  • try except的原理就是在 SEH链表中添加一个新的节点

// 自定义的SEH 函数
EXCEPTION_DISPOSITION NTAPI ExceptionHandler(
	struct _EXCEPTION_RECORD* ExceptionRecord,
	PVOID EstablisherFrame,
	struct _CONTEXT* ContextRecord,
	PVOID DispatcherContext)
{
	return ExceptionContinueSearch;
}

//遍历当前线程中的所有 SEH函数
void GetThreadList()
{
	//1. 获取当前 SEH 链表的头节点
	PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
	__asm
	{
		push FS : [0]
		POP ExceptionList
	}

	//遍历 SEH 中的所有函数
	while (ExceptionList != (PEXCEPTION_REGISTRATION_RECORD)-1)
	{
		//输出当前层,对应的处理函数
		printf("0x%08X
", ExceptionList->Handler);

		//将指针指向下一个节点
		ExceptionList = ExceptionList->Next;
	}
	printf("
");
}

int main()
{
	// 0 保存 SEH 头节点,主要用于恢复
	PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
	__asm
	{
		push FS : [0];
		pop ExceptionList;
	}

	// 1 添加自定义 SEH 函数之前的 SEH 链
	GetThreadList();

	// 2 添加自定义 SEH 函数之前的 SEH 链
	__asm
	{
		push ExceptionHandler
		push fs : [0]
		mov fs : [0], esp
	}

	// 3 添加自定义 SEH 函数之后的 SEH 链
	GetThreadList();

	// 4 恢复旧的 SEH 头节点
	__asm
	{
		add esp, 0x08
		mov eax, ExceptionList
		mov fs : [0], eax
	}

	// 5 应该和以前的节点是相同的
	GetThreadList();

	return 0;
}

异常分发流程

异常分发流程_00

  1. 处理int3异常的的函数为KitTrap03
  2. 在开始异常处理之初,先构造TRAP_FRAME陷阱帧结构,陷阱帧是指一个结构体,用来保存系统调用、中断、异常发生时的寄存器现场,方便以后回到用户空间/回到中断处时,恢复那些寄存器的值,继续执行。
  3. 注意到KitTrap03实际上调用了CommonDispatchException

补充

IDT:中断描述表(Interrupt Descriptor Table

CPU 特权级别 R0 ~R3 共三个级别,操作系统将执行状态分成了内核态用户态,内核态时的CPU特别级置为R0,用户台的特别级置为R3

原文地址:https://www.cnblogs.com/TJTO/p/11373984.html