VEH#

  1. 优先级最高

  2. 进程级别,跨线程有效

    // 在主线程注册VEH
    AddVectoredExceptionHandler(0, GlobalHandler);
    
    // 在工作线程中发生的异常也会被处理
    Create(NULL, 0, WorkerThread, NULL, 0, NULL);
    
  3. 可以注册多个,形成处理链

    PVOID handler1 = AddVectoredExceptionHandler(0, Handler1);
    PVOID handler2 = AddVectoredExceptionHandler(0, Handler2);
    // 执行顺序: Handler1 -> Handler2 (如果 Handler1 返回 CONTINUE_SEARCH)
    
  4. 储存在堆内存中

    • 不依赖栈帧,即使栈损坏也能工作
    • 适合处理栈溢出等严重异常

SEH#

  1. 基于栈帧,线程布局

    __try
    {
        *((int*)0) = 42;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        printf("Access violation caught\n");
    }
    
  2. 嵌套结构,就近原则

    void OuterFunction()
    {
        __try
        {
            InnerFunction();
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            printf("Outer handler\n");	// executes second
        }
    }
    void InnerFunction()
    {
        __try
        {
            *((int*)0) = 42;
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            printf("Inner handler\n");	// executes first
        }
    }
    
  3. 编译器集成,语法简洁

UEF (Unhandled Exception Filter)#

  1. 全局唯一,最后防线

    LPTOP_LEVEL_EXCEPTION_FILTER oldFilter = SetUnhandledExceptionFilter(MyUEF);
    // 新的 UEF 会覆盖旧的UEF
    
  2. 返回值直接决定程序生死

    LONG NTAPI UEFHandler (struct _EXCEPTION_POINTERS* ExceptionInfo)
    {
        if (CanRecover (ExceptionInfo))
        {
            FixTheException (ExceptionInfo);
            return EXCEPTION_CONTINUE_EXECUTION; // 程序继续运行
        }
        else
        {
            GenerateCrashDump (ExceptionInfo);
            return EXCEPTION_EXECUTE_HANDLER;	// 程序终止
        }
    }
    
  3. 不受调试器影响

    • 即使在调试器下运行,UEF 仍然会被调用
    • 适合实现产品级的崩溃报告系统

VCH (Vectored Continue Handler)#

它的名字比较有迷惑性。VCH 的执行在所有其它异常处理程序之后,不管前面的异常处理结果如何都不影响它的执行,即使程序崩溃,它也会发出最后的波纹。

  1. 无条件调用

    // 无论异常是否被处理,VCH 都会被调用
    LONG NTAPI AlwaysCalledHandler (struct _EXCEPTION_POINTERS* ExceptionInfo)
    {
        printf("This ALWAYS executes\n");
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
  2. 主要用于监控和清理

    LONG NTAPI MonitoringContinueHandler (struct _EXCEPTION_POINTERS* ExceptionInfo)
    {
        // 记录所有异常,包括已被处理的
        LogExceptionToFile (ExceptionInfo);
        // 更新性能计数器
        IncrementExceptionCounter();
        // 不影响程序执行
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
  3. 可以注册多个,形成处理链

VCH 不是为了“挽救”失败的异常处理而设计的,而是为了:

  1. 监控和日志记录
    • 记录所有异常,包括致命的、已被处理的
    • 统计异常发生频率和类型
  2. 资源清理和数据保存
    • 在程序崩溃前保存重要数据
    • 清理可能导致资源泄露的对象
  3. 调试和诊断
    • 在异常处理完成后执行额外的调试逻辑
    • 收集诊断信息

SEH Function Definition:

EXCEPTION_DISPOSITION __cdecl _except_handler(
	_In_ struct _EXCEPTION_RECORD *_ExceptionRecord, // 异常记录结构 指针
    _In_ void * _EstablisherFrame, 					 // 指向 SEH 链(EXCEPTION_REGISTRATION)
    _Inout_ struct _CONTEXT *_ContextRecord, 		 // Context 结构指针,即线程上下文(寄存器)
    _Inout_ void * _DispatherContext				 // 调度器上下文,无意义
)

UEF, VEH, VCH 函数的参数只有一个,即指向结构体 _EXCEPTION_POINTERS 的指针。

typedef struct _EXCEPTION_POINTERS {
    PEXCEPTION_RECORD ExceptionRecord;			// pointer of EXCEPTION_RECORD
    PCONTEXT ContextRecord;						// pointer of CONTEXT
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

typedef struct _EXCEPTION_RECORD {
    DWORD ExceptionCode; 						// 异常号
    DWORD ExceptionFlags; 						// 异常标志
    struct _EXCEPTION_RECORD *ExceptionReccord; // 指向 **下一个** 节点,即下一个异常记录
    PVOID ExceptionAddress; 					// 发生异常的地址
    DWORD NumberParameters;						// 异常信息的个数(数组ExceptionInformation的长度)
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; // 异常信息数组
} EXCEPTION_RECORD, *PECEPTION_RECORD;

线程上下文,没有细看

/* 浮点寄存器 */
typedef struct _FLOATING_SAVE_AREA {
    DWORD   ControlWord;
    DWORD   StatusWord;
    DWORD   TagWord;
    DWORD   ErrorOffset;
    DWORD   ErrorSelector;
    DWORD   DataOffset;
    DWORD   DataSelector;
    BYTE    RegisterArea[SIZE_OF_80387_REGISTERS];
    DWORD   Spare0;
} FLOATING_SAVE_AREA;
 
typedef FLOATING_SAVE_AREA *PFLOATING_SAVE_AREA;
 
typedef struct _CONTEXT {
 
    //
    // The flags values within this flag control the contents of
    // a CONTEXT record.
    //
    // If the context record is used as an input parameter, then
    // for each portion of the context record controlled by a flag
    // whose value is set, it is assumed that that portion of the
    // context record contains valid context. If the context record
    // is being used to modify a threads context, then only that
    // portion of the threads context will be modified.
    //
    // If the context record is used as an IN OUT parameter to capture
    // the context of a thread, then only those portions of the thread's
    // context corresponding to set flags will be returned.
    //
    // The context record is never used as an OUT only parameter.
    //
 
    DWORD ContextFlags;
 
    //
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
    // included in CONTEXT_FULL.
    //
 
    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;
 
    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
    //
 
    FLOATING_SAVE_AREA FloatSave;
 
    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_SEGMENTS.
    //
 
    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;
 
    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_INTEGER.
    //
 
    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;
 
    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_CONTROL.
    //
 
    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;              // MUST BE SANITIZED
    DWORD   EFlags;             // MUST BE SANITIZED
    DWORD   Esp;
    DWORD   SegSs;
 
    //
    // This section is specified/returned if the ContextFlags word
    // contains the flag CONTEXT_EXTENDED_REGISTERS.
    // The format and contexts are processor specific
    //
 
    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
 
} CONTEXT;
 
typedef CONTEXT *PCONTEXT;

一些使用方法/API#

SEH#

__asm
{
    push HandlerAddr;
    mov eax, fs:[0];
    push eax;
    mov fs:[0], esp;
}

或者 __try/__except

Others#

SetUnhandledExceptionFilter

AddVectoredEcceptionHandler(ULONG first, // 1 表示添加到链表头部,0 表示添加到链表i尾部
                           PVECTORED_EXCEPTION_HANDLER handler	// 异常处理器的地址
                           );

RemoveVectoredExceptionHandler

AddVectoredContinueHandler

RemoveVectoredContinueHandler

LONG PvectoredExceptionHandler(
    _EXCEPTION_POINTERS *ExceptionInfo
)
{
    ...
}

异常处理程序的参数#

typedef struct _EXCEPTION_POINTERS
{
    PEXCEPTION_RECORD ExceptionRecord; // 记录异常的信息
    PCONTEXT ContextRecord; // 表示寄存器的内容
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

返回值#

EXCEPTION_CONTINUE_EXECUTION 表示继续执行;

EXCEPTION_CONTINUE_SEARCH 表示继续查找


SEH 的返回值 (VEH, VCH 只有前两种)#

typedef enum _EXCEPTION_DISPOSITION {
    ExceptionContinueExecution, 
    ExceptionContinueSearch, 	
    ExceptionNestedException,  	
    ExceptionCollidedUnwind 	
} EXCEPTION_DISPOSITION;

__except & SEH#

__except 是对 _except_handler 函数的封装


EH 调用顺序#

调试器 > VEH > SEH > UEF > VCH > 调试器 > 调用异常端口通知 csrss.exe (弹出一个对话框, 但在Win11没有见到, 可能和系统版本有关)


#include<Windows.h>
#include<iostream>
EXCEPTION_DISPOSITION myExceptHandler(
    struct _EXCEPTION_RECORD* ExceptionRecord,
    PVOID EstablisherFrame,
    PCONTEXT pcontext,
    PVOID DispatcherContext
)
{
    MessageBoxA(0, "SEH处理了异常", "异常报错", MB_OK);
    pcontext->Eip = pcontext->Eip+ 4;
    return ExceptionContinueExecution;
}

LONG WINAPI PvectoredExceptionHandler(
    _EXCEPTION_POINTERS* ExceptionInfo
)
{
    MessageBoxA(0, "VEH处理了异常", "异常处理", MB_OK);
    ExceptionInfo->ContextRecord->Eip += 4;
    return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
    AddVectoredExceptionHandler(1, PvectoredExceptionHandler);
    DWORD execptionFuncAddr = (DWORD)myExceptHandler;//异常处理的函数地址
    __asm
    {
        push execptionFuncAddr;
        mov eax, fs: [0] ;
        push eax;
        mov fs : [0] , esp;
        //添加异常,并且把异常放到异常链里面,并且把异常的头指针指向新的头结点
    }
    char* str = NULL;
    str[0] = 'a';
    printf("Sna1lGo\n");
    system("pause");
    return 0;
}