VEH

  1. 优先级最高

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

    1
    2
    3
    4
    5
    // 在主线程注册VEH
    AddVectoredExceptionHandler(0, GlobalHandler);

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

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

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

SEH

  1. 基于栈帧,线程布局

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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. 全局唯一,最后防线

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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. 无条件调用

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

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

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

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

SEH Function Definition:

1
2
3
4
5
6
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 的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

线程上下文,没有细看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/* 浮点寄存器 */
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

1
2
3
4
5
6
7
__asm
{
push HandlerAddr;
mov eax, fs:[0];
push eax;
mov fs:[0], esp;
}

或者 __try/__except

Others

SetUnhandledExceptionFilter

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

RemoveVectoredExceptionHandler

AddVectoredContinueHandler

RemoveVectoredContinueHandler

1
2
3
4
5
6
LONG PvectoredExceptionHandler(
_EXCEPTION_POINTERS *ExceptionInfo
)
{
...
}

异常处理程序的参数

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

返回值

EXCEPTION_CONTINUE_EXECUTION 表示继续执行;

EXCEPTION_CONTINUE_SEARCH 表示继续查找


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

1
2
3
4
5
6
typedef enum _EXCEPTION_DISPOSITION {
ExceptionContinueExecution,
ExceptionContinueSearch,
ExceptionNestedException,
ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;

__except & SEH

__except 是对 _except_handler 函数的封装


EH 调用顺序

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#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;
}