第23章 SEH结构化异常处理(1)

23.1 基础知识

23.1.1 Windows下的软件异常

(1)中断和异常

  ①中断是由外部硬件设备或异步事件产生的

  ②异常是由内部事件产生的,可分为故障、陷阱和终止三类。

(2)两种异常处理机制:SEH和VEH(WindowsXP以上新引进)

(3)结构化异常处理(SEH)是Windows操作系统提供的强大异常处理功能。而Visual C++中的__try{} __finally{}和__try{} __except{}结构本质上是对Windows提供的SEH的封装。

23.1.2 SEH的分类

(1)Per-Thread类型SEH(也称为线程异常处理),用来监视某线程代码是否发生异常。

(2)Final类型SEH(也称为进程异常处理、筛选器或顶层异常处理),用于监视整个进程中所有线程是否发生异常。在整个进程中,该类型异常处理过程只有一个,可通过SetUnhandledExceptionFilter设置。

23.1.3 SEH相关的数据结构

(1) 线程信息块TIB(Thread Information Block或TEB)

typedef struct _NT_TIB {
     struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //异常的链表

     PVOID StackBase;
     PVOID StackLimit;
     PVOID SubSystemTib;

     union {
         PVOID FiberData;
         DWORD Version;
     };

     PVOID ArbitraryUserPointer;
     struct _NT_TIB *Self;
} NT_TIB;

Fs:[0]总是指向当前线程的TIB,其中0偏移的指向线程的异常链表,即ExceptionList是指向异常处理链表(EXCEPTION_REGISTRATION结构)的一个指针。

(2)EXCEPTION_REGISTRATION结构

typedef struct _EXCEPTION_REGISTRATION_RECORD {
     struct _EXCEPTION_REGISTRATION_RECORD *Prev; //指向前一个EXCEPTION_REGISTRATION的指针
     PEXCEPTION_ROUTINE Handler; //当前异常处理回调函数的地址
} EXCEPTION_REGISTRATION_RECORD;

(3)EXCEPTION_RECORD结构

typedef struct _EXCEPTION_RECORD {
    DWORD    ExceptionCode;  //异常码,以STATUS_或EXCEPTION_开头,可自定义。(sehdef.inc)
    DWORD ExceptionFlags;    //异常标志。0可修复;1不可修复;2正在展开,不要试图修复
    struct _EXCEPTION_RECORD *ExceptionRecord; //指向嵌套的异常结构,通常是异常中又引发异常
    PVOID ExceptionAddress;  //异常发生的地址
    DWORD NumberParameters;  //下面ExceptionInformation所含有的dword数目
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //附加消息,如读或写冲突
} EXCEPTION_RECORD;

(4)CONTEXT结构(可通过GetThreadContext和SetThreadContext来读写)

typedef struct _CONTEXT {
     DWORD ContextFlags; //用来表示该结构中的哪些域有效
     DWORD   Dr0, Dr2, Dr3, Dr4, Dr5, Dr6, Dr7; //调试寄存器
     FLOATING_SAVE_AREA FloatSave; //浮点寄存器区
     DWORD   SegGs, SegFs, SegEs, Seg Ds; //段寄存器
     DWORD   Edi, Esi, Ebx, Edx, Ecx, Eax; //通用寄存器组
     DWORD   Ebp, Eip, SegCs, EFlags, Esp, SegSs; //控制寄存器组

     //扩展寄存器,只有特定的处理器才有
     BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;

(5)EXCEPTION_POINTERS结构(传递给顶层型异常处理回调函数的参数)

typedef struct _EXCEPTION_POINTERS {
    PEXCEPTION_RECORD ExceptionRecord; //指向ExceptionRecord的指针
    PCONTEXT ContextRecord; //指向Context结构的指针
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

23.1.4 异常处理相关API

(1)SetErrorMode:控制错误模式,如是否出现错误对话框

(2)SetUnhandledExceptionFilter:设定顶层异常处理回调函数

(3)RaiseException:用于引发异常,可指定自己的异常代码及相关信息

(4)GetThreadContext/SetThreadContext:获取或设置线程环境

(5)RtlUnwind:栈展开操作(后面会详细介绍)

23.1.5 顶层异常处理

(1)顶层异常处理是进程相关的,只要没有由线程处理过程或调试器处理掉,最终均要交由顶层异常回调函数处理

(2)注册顶层异常处理:SetUnhandledExceptionFilter,每个进程只能注册一个。返回值为前一个异常回调函数的地址。

(3)回调函数

LONG UnhandledExceptionFilter(STRUCT _EXCEPTION_POINTERS *ExceptionInfo){

   //三种返回值,决定系统下一步的动作
   //1.EXCEPTION_EXECUTE_HANDLE(1):表示异常己被处理。程序可以优雅结束
   //2.EXCEPTION_CONTINUE_SEARCH(0):表示顶层异常处理不能处理,需交给其他异常处理过程
   //3.EXCEPTION_CONTINUE_EXECUTION(-1):表示顶层异常过程处理了异常,且程序应从原异常发生的指令重新继续执行一下。(可以通过改变CONTEXT内容来达到改变程序执行环境)
}

23.1.6 线程异常处理(局部的,仅仅监视进程中某特定线程是否发生异常)

(1)线程异常处理特点

  ①Windows系统为每个线程单独提供了一种异常处理的方法,当一个线程出现错误时,操作系统调用用户定义的一系列回调函数,在这些回调函数中,可以进行修复错误或其它的一些操作,最后的返回值告系统系统下一步的动作(如继续搜索异常处理程序或终止程序等)。

  ②SEH是基于线程的,使用SEH可以为每个线程设置不同的异常处理程序(回调函数)而且可以为每个线程设置多个异常处理程序。

  ③ 由于SEH使用了与硬件平台相关的数据指针,所以不同硬件平台使用SHE的方法有所不同。

(2)回调函数原型

EXCEPTION_DISPOSITION __cdecl _except_handler(
     struct _EXCEPTION_RECORD *ExceptionRecord,//指向包含异常信息的EXCEPTION_RECORD结构
     void* EstablisherFrame,//指向该异常相关的EXCEPTION_REGISTRATION结构
     struct _CONTEXT *ContextRecord,//指向线程环境CONTEXT结构的指针
     void* DispatcherContext){ //该域暂无意义

     ……

    //4种返回值及含义
    //1.ExceptionContinueExecution(0):回调函数处理了异常,可以从异常发生的指令处重新执行。
    //2.ExceptionContinueSearch(1):回调函数不能处理该异常,需要要SEH链中的其他回调函数处理。
    //3.ExceptionNestedException(2):回调函数在执行中又发生了新的异常,即发生了嵌套异常
    //4.ExceptionCollidedUnwind(3):发生了嵌套的展开操作

    return …
}

(3)线程异常处理的注册

push _exception_handler //异常回调函数_exception_handler的地址,即handler
push fs:[0]             //保存前一个异常回调函数的地址,即prev
mov fs:[0],esp          //安装新的EXCEPTION_REGISTRATION结构(两个成员:prev,handler)。
                        //此时栈顶分别是prev和handler,为新的EXCEPTION_REGISTRATION结
                        //构,mov fs:[0],esp,就可以让fs:[0]指向该指构。

(4)异常回调函数的调用过程

  ①线程信息块(TIB),永远放在fs段选择器指定的数据段的0偏移处,即fs:[0]的地方就是TIB结构。对不同的线程fs寄存器的内容有所有不同,但fs:[0]都是指向当前线程的TIB结构体,所以fs:[0]是一个EXCEPTION_REGISTRATION结构体的指针。

  ②当异常发生时,系统从fs:[0]指向的内存地址处取出ExceptionList字段,然后从ExceptionList字段指向的EXCEPTION_REGISTRATION结构中取出handler字段,并根据其中的地址去调用异常处理程序(回调函数)。

【MySeh程序】安装线程异常处理程序

/************************************************************************
MYSEH - Matt Pietrek 1997
Microsoft Systems Journal,January 1997
cl.exe myseh.cpp /link -SAFESEH:NO
用IDE编译时须将项目的“链接器”→“高级”→“映像具有安全异常处理程序”设为SAFESEH:NO(Debug或Release版要分别设置)
用命令行CL MYSHE.CPP编译里可参考下面的MySeh.bat批处理的设置。
************************************************************************/
//MySeh.bat
//set PATH = %PATH%; E:\读书笔记\Windows核心编程\SEH\SEH; D:\VisualStudio\VC\bin
//set include = C:\Program Files\Windows Kits\8.1\Include\um; C:\Program Files\Windows Kits\8.1\Include\shared; D:\VisualStudio\VC\include
//set lib = C:\Program Files\Windows Kits\8.1\Lib\winv6.3\um\x86; D:\VisualStudio\VC\lib
//cl.exe E : \读书笔记\Windows核心编程\SEH\SEH\myseh.cpp / link - SAFESEH:NO
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

DWORD scratch;

EXCEPTION_DISPOSITION __cdecl _except_handler(struct _EXCEPTION_RECORD *ExceptionRecord,
                                              void* EstablisherFrame,
                                              struct _CONTEXT *ContextRecord,
                                              void* DispatcherContext){

    //指明是我们让流程转到我们的异常处理程序的
    printf("进入Seh异常处理程序!\n");

    //改变CONTEXT结构体EAX的值,以便它指向可以成功进行写操作的位置
    ContextRecord->Eax = (DWORD)&scratch;

    printf("异常处理完毕!\n");

    //告诉操作系统重新执行出错的指令
    return ExceptionContinueExecution;
}

int main(){

    DWORD handler = (DWORD)_except_handler;

    printf("安装seh异常回调函数!\n");
    __asm{

        //创建EXCEPTION_REGISTRATION结构:
        push handler  //handler函数的地址
        push fs:[0]   //保存前一个handler函数地址,即EXCEPTION_REGISTRATION结构的prev

        mov fs:[0],ESP  //安装新的EXCEPTION_REGISTRATION结构

    }
    printf("安装完毕,向0地址写入数据,引发异常!\n");
    __asm
    {
        xor eax, eax                    //EAX = 0
        mov dword ptr[eax], 1234h    //写EAX指向的内存从而故意引发一个异常!
    }
    printf("写入数据完毕,scratch=0x%x!\n",scratch);
    printf("卸载seh自定义的异常回调函数!\n");
    __asm{
        // 移去我们的EXECEPTION_REGISTRATION结构
        mov eax, [ESP]    // 此时栈顶为prev,后进先出原理。获取前一个EXECEPTION_REGISTRATION结构的指针
        mov fs:[0], EAX // 安装前一个结构
        add esp, 8       // 将我们的EXECEPTION_REGISTRATION弹出堆栈
    }

    system("Pause");
    return 0;
}

(5)SEH链及异常的传递(通知调试器→SEH链→顶层异常处理→系统默认处理)

(1)系统查看产生异常的进程是否被正在被调试,如果正在被调试,那么向调试器发送EXCEPTION_DEBUG_EVENT事件。

(2)如果进程没有没有被调试或者调试器不去处理这个异常,那么系统检查异常所处的线程并在这个线程环境中查看fs:[0]来确定是否安装SEH异常处理回调函数,如果有则调用它。

(3)回调函数尝试处理这个异常,如果可以正确处理的话,则修正错误并将返回值设置为ExceptionContinueExecution,这时系统将结束整个查找过程。

(4)如果回调函数返回ExceptionContinueSearch,相当于告诉系统它无法处理这个异常,系统将根据SEH链中的prev字段得到前一个回调函数地址并重复步骤3,直至链中的某个回调函数返回ExceptionContinueExection为止,查找结束。

(5)如果到了SEH链的尾部却没有一个回调函数愿意处理这个异常,那么系统会再被检查进程是否正在被调试,如果被调试的话,则再一次通知调试器。

(6)如果调试器还是不去处理这个异常或进程没有被调试,那么系统检查有没有Final型的异常处理回调函数,如果有,就去调用它,当这个回调函数返回时,系统会根据这个函数的返回值做相应的动作。

(7)如果没有安装Final型回调函数,系统直接调用默认的异常处理程序终止进程,但在终止之前,系统再次调用发生异常的线程中的所有异常处理过程,目的是让线程异常处理过程获得最后清理未释放资源的机会,其后程序终止。

【MySEH2】SEH链及异常传递

/************************************************************************
MYSEH2 - Matt pietrek 1997
Microsoft Systems Journal,january 1997
File: MySEH2.cpp
用IDE编译时须将项目的“链接器”→“高级”→“映像具有安全异常处理程序”设为SAFESEH:NO(Debug或Release版要分别设置)
************************************************************************/
#include <windows.h>
#include <stdio.h>

//异常处理回调函数
EXCEPTION_DISPOSITION __cdecl _except_handler(
       struct _EXCEPTION_RECORD *ExceptionRecord,
       void * EstablisherFrame,
       struct _CONTEXT *ContextRecord,
       void * DispatcherContext){
    //显示异常信息
    printf("HomeGrown异常处理回调函数:异常码(%08X) 标志(%X)",
           ExceptionRecord->ExceptionCode,ExceptionRecord->ExceptionFlags);
    if (ExceptionRecord->ExceptionFlags & 1)
        printf(" EH_NONCONTINUABLE");

    if (ExceptionRecord->ExceptionFlags & 2)
        printf(" EH_UNWINDING");

    if (ExceptionRecord->ExceptionFlags & 4)
        printf(" EH_EXIT_UNWIND");

    if (ExceptionRecord->ExceptionFlags & 8) //
        printf(" EH_STACK_INVALID");

    if (ExceptionRecord->ExceptionFlags & 0x10) //
        printf(" EH_NESTED_CALL");

    printf("\n");

    //我们不想处理这个异常,让其它SEH链的其他回调函数去处理
    return ExceptionContinueSearch;
}

void HomeGrownFrame(void){
    DWORD  handler = (DWORD)_except_handler;

    __asm{
        //创建EXCEPTION_REGISTRATION结构
        push handler //handler函数的地址
        push FS :[0] //保存前一个handler地址
        mov FS :[0],ESP //安装新的EXCEPTION_REGISTRATION结构
    }

    *(PDWORD)0 = 0; //向地址0数据,从而引发一个异常

    printf("_except_handle函数不处理异常,此句永远不会被执行!\n");

    __asm{
        //移去我个的EXCEPTION_REGISTRATION结构
        mov eax, [ESP]   //获取前一个结构
        mov FS : [0],eax //安装前一个结构
        add esp,8        //把我们的EXCEPTION_REGISTRATION结构弹出
    }
}

int main(){
    //为了简化代码,使用编译器层面的异常处理,即__try{} __excetp{}
    __try{
        HomeGrownFrame();
    }
    __except (EXCEPTION_EXECUTE_HANDLER){  //这里处理了异常,不再向外层传递
        printf("主函数main中的异常处理过程\n");
    }

    return 0;
}
时间: 2024-10-05 13:14:56

第23章 SEH结构化异常处理(1)的相关文章

第23章 SEH结构化异常处理(2)

23.2 编译器层面对系统SEH机制的封装 23.2.1 扩展的EXCEPTION_REGISTRATION级相关结构:VC_EXCEPTION_REGISTRATION (1)VC_EXCEPTION_REGISTRATION结构 struct VC_EXCEPTION_REGISTRATION { VC_EXCEPTION_REGISTRATION* prev; //前一个结构体的指针 FARPROC handler; //永远指向_exception_handler4回调函数 scopet

第24章 SEH结构化异常处理—异常处理及软件异常

24.1  程序的结构 (1)try/except框架 __try{ //被保护的代码块 …… } __except(except fileter/*异常过滤程序*/){ //异常处理程序 } (2)说明 ①当__try块中的代码发生异常时,__except()中的过滤程序就被调用. ②过滤程序可以是一个简单的表达式或一个函数(返回值应为EXCEPTION_CONTINUE_SEARCH.EXCEPT_CONTINUE_EXECUTE或EXCEPT_EXECUTE_HANDLER之一) ③过滤表

第25章 SEH结构化异常处理_未处理异常及向量化异常

25.1 UnhandledExceptionFilter函数详解 25.1.1 BaseProcessStart伪代码(Kernel32内部) void BaseProcessStart(PVOID lpfnEntryPoint) //参数为线程函数的入口地址 { DWORD retValue; DWORD currentESP; DWORD exceptionCode; currentESP = ESP; //lpfnEntryPoint被try/except封装着,这是系统安装的默认的异常

Windows内核读书笔记——SEH结构化异常处理

SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 1 __try 2 { 3 //要保护的代码 4 } 5 __finally 6 { 7 //终结处理块 8 } 退出保护块的方式:正常结束和非正常结束两种 1.正常结束 正常执行并顺序进入终结处理块称为正常结束 2.非正常结束 因为发生异常或是因为return.goto.break.continue等流程控制语句而离开被

关于SEH(结构化异常处理)的一些知识

梳理老罗win32汇编关于SEH一章的知识. 异常处理方式有两种: 筛选器异常处理和结构化异常处理,筛选器是全局性的,无法为一个线程或一个子程序单独设置一个异常处理回调函数,而结构化异常处理(Structured Exception Handing)SEH提供了每个线程之间独立的异常处理方法. 以下以两个例子来学习SEH 例子1:不含栈展开操作的异常处理(栈展开会在例子二中介绍) .386 .model flat,stdcall option casemap:none ;>>>>&

解析结构化异常处理(SEH)(第二部分)

书接上一篇:http://www.cnblogs.com/ONDragon/p/6855174.html 虽然这个异常回调机制很好,但它并不是一个完美的解决方案.对于稍微复杂一些的应用程序来说,仅用一个 函数就能处理程序中任何地方都可能发生的异常是相当困难的.一个更实用的方案应该是有多个异常处理例程,每个例程针对程序中的一部分.实际上,操作系统提 供的正是这个功能. 还 记得系统用来查找异常回调函数的EXCEPTION_REGISTRATION结构吗?这个结构的第一个成员,称为prev,前面我们

深入研究 Win32 结构化异常处理(作者博客有许多SEH的研究文章)

摘要 就像人们常说的那样,Win32 结构化异常处理(SEH)是一个操作系统提供的服务.你能找到的所有关于 SEH 的文档讲的都是针对某个特定编译器的.建立在操作系统层之上的封装库.我将从 SEH 的最基本概念讲起. Matt Pietrek 著董岩 译Victor 转载自 Xfocus 并整理 在所有 Win32 操作系统提供的机制中,使用最广泛的未公开的机制恐怕就要数结构化异常处理(structured exception handling,SEH)了.一提到结构化异常处理,可能就会令人想起

结构化异常处理(通过设置CONTEXT结构实现反跟踪)

结构化异常处理是Windows操作系统处理程序错误或异常的技术.在一个应用程序发生错误时,Windows挂起程序,并交由调试器处理,当调试器不能处理时,则调用程序的线程相关的SEH异常处理函数. 异常处理函数可以有一个或多个,采用链表的结构将他们链接起来.当前的处理函数如果不处理,并且有多个处理函数时,可以交由链起来的其它异常处理过程进行处理. 如果程序线程的异常处理函数均选择不处理,如果处于被调试状态,操作系统挂起程序通知调试器. 这时有两种可能: (1)如果程序未处于被调试状态或者调试器仍然

深入研究 Win32 结构化异常处理(好多相关文章)

摘要 就像人们常说的那样,Win32 结构化异常处理(SEH)是一个操作系统提供的服务.你能找到的所有关于 SEH 的文档讲的都是针对某个特定编译器的.建立在操作系统层之上的封装库.我将从 SEH 的最基本概念讲起. Matt Pietrek 著董岩 译Victor 转载自 Xfocus 并整理 在所有 Win32 操作系统提供的机制中,使用最广泛的未公开的机制恐怕就要数结构化异常处理(structured exception handling,SEH)了.一提到结构化异常处理,可能就会令人想起