zoukankan      html  css  js  c++  java
  • 调试与异常一

    调试与异常

    终止处理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

  • 相关阅读:
    Hibernate4究竟怎么玩目录
    Hibernate检索策略之5.2多对一单向关联检索策略——Hibernate4究竟怎么玩
    Struts2 OGNL表达式中调用java静态方法无效的解决办法
    JDBC API中的execute返回值问题
    Hibernate检索策略之5.1类级别检索策略——Hibernate4究竟怎么玩
    PowerDesigner逆向工程mysql
    三种批量增加的性能分析
    Apache FtpServer的使用
    杭电1013
    杭电1008
  • 原文地址:https://www.cnblogs.com/TJTO/p/11373984.html
Copyright © 2011-2022 走看看