第 8 章 结构化异常处理 --- 8.3 用户空间的结构化异常处理
本节深入剖析用户空间(Ring 3)下 ReactOS 的结构化异常处理实现。 用户态 SEH 是 8.2 节中跳转 KeUserExceptionDispatcher 之后真正执行的核心逻辑------RtlDispatchException 遍历 EXCEPTION_REGISTRATION_RECORD 链表、RtlUnwind 在栈展开时反向调用 handler 的"展开回调"、以及 RtlRaiseException / RtlRaiseStatus 这两个最常用的"主动抛出"API。本节是 C/C++ 程序员日常打交道最多的部分:编译器生成的 __try / __except / __finally 最终都汇入这里。
概述
用户态 SEH 的核心组件包括:
- 入口与桥接层 :
KeUserExceptionDispatcher(ntdll 入口)、RtlpCaptureContext(汇编捕获 CONTEXT) - 异常分派层 :
RtlDispatchException(遍历 SEH 链表) - 栈展开层 :
RtlUnwind(反向调用 handler 进行清理) - 主动抛出层 :
RtlRaiseException/RtlRaiseStatus(代码生成异常) - 向量化异常层(VEH) :
RtlCallVectoredExceptionHandlers(链式 VEH 处理) - 顶层过滤器 :
RtlUnhandledExceptionFilter(最后兜底) - 辅助层 :
RtlpExecuteHandlerForException/RtlpExecuteHandlerForUnwind(汇编包装,含 frame 保护)
理解这套体系的关键是把握 三个阶段:
- 分派阶段 :
RtlDispatchException从FS:[0]沿链表向下(向栈深)调用 handler - 过滤阶段 :每个 handler 用
Disposition返回值告诉框架"我处理 / 继续搜索 / 嵌套 / 冲突" - 展开阶段 :
RtlUnwind从链表底部向上(向栈顶)调用 handler 的"展开回调"(即__finally块)
本节内容概览
- 8.3.0 框架图:用户态 SEH 数据流
- 8.3.1 用户态 SEH 入口:
KeUserExceptionDispatcher - 8.3.2
RtlDispatchException完整实现 - 8.3.3
RtlUnwind栈展开机制 - 8.3.4
RtlRaiseException/RtlRaiseStatus主动抛出 - 8.3.5 向量化异常处理(VEH)
- 8.3.6 汇编包装与 frame 保护
- 8.3.7 顶层未处理异常过滤器
- 8.3.8 x64 与 ARM 实现差异
学习目标
- 能够画出
RtlDispatchException/RtlUnwind遍历链表的完整算法 - 理解四种
EXCEPTION_DISPOSITION返回值的语义 - 解释 VEH 与 SEH 链表的差异(VEH 优先级最高,无须在栈上注册)
- 理解
RtlRaiseException与RtlRaiseStatus的差异 - 了解
RtlpExecuteHandlerForException的汇编级 frame 保护机制
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| 用户态分派 | sdk/lib/rtl/i386/except.c(file:///d:/reactos/sdk/lib/rtl/i386/except.c) | x86 RtlDispatchException、RtlUnwind |
| 用户态分派(其他架构) | sdk/lib/rtl/amd64/except.c(file:///d:/reactos/sdk/lib/rtl/amd64/except.c) | x64 RtlDispatchException |
| 栈展开(x64) | sdk/lib/rtl/amd64/unwind.c(file:///d:/reactos/sdk/lib/rtl/amd64/unwind.c) | x64 RtlUnwind |
| 展开汇编(x64) | sdk/lib/rtl/amd64/unwind-asm.s(file:///d:/reactos/sdk/lib/rtl/amd64/unwind-asm.s) | x64 unwind 汇编 |
| ARM 用户态分派 | sdk/lib/rtl/arm/except.c(file:///d:/reactos/sdk/lib/rtl/arm/except.c) | ARM RtlDispatchException |
| RTL 公共 | sdk/lib/rtl/exception.c(file:///d:/reactos/sdk/lib/rtl/exception.c) | RtlRaiseException、RtlRaiseStatus、RtlUnhandledExceptionFilter |
| 向量化异常 | sdk/lib/rtl/vectoreh.c(file:///d:/reactos/sdk/lib/rtl/vectoreh.c) | VEH 链 |
| 用户态汇编 | sdk/lib/rtl/i386/except_asm.s(file:///d:/reactos/sdk/lib/rtl/i386/except_asm.s) | RtlpCaptureContext、RtlpExecuteHandlerForException 等 |
| 通用栈展开 | sdk/lib/rtl/exception.c(file:///d:/reactos/sdk/lib/rtl/exception.c) | RtlCaptureStackBackTrace |
| 系统调用桥接 | dll/ntdll(file:///d:/reactos/dll/ntdll) | KeUserExceptionDispatcher 实现 |
8.3.0 框架图
用户态 (Ring 3)
+-------------------------------------+
| 应用程序 __try / __except |
| 编译器插入 EXCEPTION_REGISTRATION_ |
| RECORD 到 FS:[0] 链表 |
+-------------------------------------+
| 异常发生
v
+-------------------------------------+
| (1) 硬件 trap → int 0E |
| (2) 软件 API RtlRaiseException |
| (3) 系统调用 NtRaiseException |
+-------------------------------------+
| (内核态已完成 KiDispatchException)
| (并设置 EIP = KeUserExceptionDispatcher)
v
+-------------------------------------+
| KeUserExceptionDispatcher (ntdll) |
| 调用 RtlDispatchException |
+-------------------------------------+
|
v
+-------------------------------------+
| RtlDispatchException |
| (1) RtlCallVectoredException- |
| Handlers (VEH 链) |
| (2) RtlpGetStackLimits |
| (3) 从 FS:[0] 取出链表头 |
| (4) 遍历链表调用 handler |
| RtlpExecuteHandlerFor- |
| Exception |
| (5) 根据 EXCEPTION_DISPOSITION |
| 分派 |
+-------------------------------------+
|
+----------+----------+
| |
v v
继续搜索 已处理
(下个 handler) |
| 返回 TRUE → 调 RtlCallVectoredContinueHandlers
| → NtContinue 回用户态
v
链表遍历完
返回 FALSE → 回到内核做 LastChance 处理
|
v
+-------------------------------------+
| RtlUnwind (栈展开) |
| - 构造 CONTEXT |
| - 标记 EXCEPTION_UNWINDING |
| - 反向遍历链表 |
| - 调用每个 handler 的"展开回调" |
| - 到达 TargetFrame → ZwContinue |
| - 到达链表末尾 → ZwRaiseException |
+-------------------------------------+
8.3.1 用户态 SEH 入口:KeUserExceptionDispatcher
KeUserExceptionDispatcher 是 ntdll 提供的用户态入口,签名:
c
VOID NTAPI KeUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context);
它的实现(在 ntdll 内部)本质上就是:
c
VOID NTAPI KeUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context)
{
DWORD DispatcherResult;
/* 调用 RtlDispatchException 遍历 SEH 链表 */
DispatcherResult = RtlDispatchException(ExceptionRecord, Context);
if (DispatcherResult != 0)
{
/* 已处理:调用 continue 处理器,然后继续 */
RtlCallVectoredContinueHandlers(ExceptionRecord, Context);
NtContinue(Context, FALSE);
}
else
{
/* 未处理:返回内核做 LastChance */
NtRaiseException(ExceptionRecord, Context, FALSE);
}
}
注意这里的递归:如果 RtlDispatchException 没有找到任何 handler 处理异常,会再次调用 NtRaiseException 系统调用,把"第二次机会"的异常送回内核,让内核的 DbgkForwardException / KeBugCheckEx 兜底。
8.3.2 RtlDispatchException 完整实现
RtlDispatchException 是用户态 SEH 的核心,定义于 sdk/lib/rtl/i386/except.c:65(file:///d:/reactos/sdk/lib/rtl/i386/except.c#L65-L223)。它的整体算法如下:
步骤 1:调用 VEH 链
c
if (RtlCallVectoredExceptionHandlers(ExceptionRecord, Context))
{
/* Exception handled, now call vectored continue handlers */
RtlCallVectoredContinueHandlers(ExceptionRecord, Context);
return TRUE;
}
VEH(Vectored Exception Handling) 是 Windows XP 引入的优先级最高的异常处理机制,独立于 EXCEPTION_REGISTRATION_RECORD 链表。如果 VEH 链中任何一个 handler 返回 EXCEPTION_CONTINUE_EXECUTION,则 RtlDispatchException 立即返回 TRUE,不再遍历 SEH 链表。
步骤 2:获取栈边界与链表头
c
RtlpGetStackLimits(&StackLow, &StackHigh);
RegistrationFrame = RtlpGetExceptionList();
RtlpGetStackLimits从NtCurrentTeb()->StackLimit/StackBase读取RtlpGetExceptionList是mov eax, fs:[0]的内联包装
步骤 3:遍历 SEH 链表
c
while (RegistrationFrame != EXCEPTION_CHAIN_END)
{
ASSERT(RegistrationFrame != NULL);
/* Find out where it ends */
RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame + sizeof(EXCEPTION_REGISTRATION_RECORD);
/* Make sure the registration frame is located within the stack */
if ((RegistrationFrameEnd > StackHigh) ||
((ULONG_PTR)RegistrationFrame < StackLow) ||
((ULONG_PTR)RegistrationFrame & 0x3))
{
/* Check if this happened in the DPC Stack */
if (RtlpHandleDpcStackException(RegistrationFrame, RegistrationFrameEnd, &StackLow, &StackHigh))
{
continue; /* DPC stack 特殊处理 */
}
ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
return FALSE;
}
/* TODO: RtlIsValidHandler (SafeSEH) */
RtlpCheckLogException(ExceptionRecord, Context, RegistrationFrame, sizeof(*RegistrationFrame));
Disposition = RtlpExecuteHandlerForException(ExceptionRecord,
RegistrationFrame,
Context,
&DispatcherContext,
RegistrationFrame->Handler);
/* Check if this is a nested frame */
if (RegistrationFrame == NestedFrame)
{
ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL;
NestedFrame = NULL;
}
switch (Disposition)
{
case ExceptionContinueExecution:
if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
{
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
ExceptionRecord2.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION;
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
ExceptionRecord2.NumberParameters = 0;
RtlRaiseException(&ExceptionRecord2);
}
else
{
RtlCallVectoredContinueHandlers(ExceptionRecord, Context);
return TRUE;
}
case ExceptionContinueSearch:
if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID) return FALSE;
break;
case ExceptionNestedException:
ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
if (DispatcherContext.RegistrationPointer > NestedFrame)
NestedFrame = DispatcherContext.RegistrationPointer;
break;
default:
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
ExceptionRecord2.NumberParameters = 0;
RtlRaiseException(&ExceptionRecord2);
break;
}
RegistrationFrame = RegistrationFrame->Next;
}
return FALSE;
步骤 4:处理 EXCEPTION_DISPOSITION 四个返回值
| 返回值 | 含义 | RtlDispatchException 反应 |
|---|---|---|
ExceptionContinueExecution |
handler 已修复 CONTEXT,准备恢复执行 | 若异常为 EXCEPTION_NONCONTINUABLE 则抛出 STATUS_NONCONTINUABLE_EXCEPTION;否则调用 VEH continue 处理器并返回 TRUE |
ExceptionContinueSearch |
handler 不处理,继续搜索 | 移动到下一个节点;若发现 EXCEPTION_STACK_INVALID 则停止并返回 FALSE |
ExceptionNestedException |
handler 内部抛出了新异常 | 标记 EXCEPTION_NESTED_CALL,更新 NestedFrame |
ExceptionCollidedUnwind |
展开过程中遇到不同线程 frame | 跳转(仅在 unwind 路径出现) |
| 其他 / 默认 | 非法返回值 | 抛出 STATUS_INVALID_DISPOSITION 异常 |
步骤 5:链表耗尽
如果遍历到 EXCEPTION_CHAIN_END(-1)还没有 handler 处理,则返回 FALSE,由 KeUserExceptionDispatcher 决定后续动作。
关键设计要点
-
栈合法性检查 :每个
RegistrationFrame必须满足:RegistrationFrame + sizeof(...) ≤ StackHigh(不在栈顶之上)RegistrationFrame ≥ StackLow(不在栈底之下)- 4 字节对齐
这防止恶意代码通过伪造链表节点来劫持控制流。
-
DPC 栈特殊处理 :当异常发生在 DPC(Deferred Procedure Call)路径时,当前栈可能是 DPC 栈。
RtlpHandleDpcStackException会切换到 DPC 栈边界重新开始遍历。 -
嵌套异常跟踪 :通过
NestedFrame记录"嵌套异常最深处",遍历时跳过已处理过的节点。 -
SafeSEH 占位 :源码中有
// TODO: Implement and call here RtlIsValidHandler(...)注释,表示后续会加入 SafeSEH 校验(防止 handler 被改写)。
8.3.3 RtlUnwind 栈展开机制
RtlUnwind 是栈展开入口,定义于 sdk/lib/rtl/i386/except.c:230(file:///d:/reactos/sdk/lib/rtl/i386/except.c#L230-L402)。它的触发场景:
__finally块执行(无论 try 块是否异常退出)EXCEPTION_EXECUTE_HANDLER分支被采用后跳转到 except 块时- 显式调用
RtlUnwind主动展开
签名
c
VOID NTAPI
RtlUnwind(IN PVOID TargetFrame OPTIONAL,
IN PVOID TargetIp OPTIONAL,
IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
IN PVOID ReturnValue);
参数:
TargetFrame:展开目标(展开到哪一层停止);为EXCEPTION_CHAIN_END(-1)表示展开到链尾TargetIp:恢复执行的 EIP(可选)ExceptionRecord:用于通知 handler 的异常记录ReturnValue:作为函数返回值传递给 TargetFrame 之上的那一层
完整算法
c
VOID NTAPI
RtlUnwind(IN PVOID TargetFrame OPTIONAL,
IN PVOID TargetIp OPTIONAL,
IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
IN PVOID ReturnValue)
{
PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, OldFrame;
DISPATCHER_CONTEXT DispatcherContext;
EXCEPTION_RECORD ExceptionRecord2, ExceptionRecord3;
EXCEPTION_DISPOSITION Disposition;
ULONG_PTR StackLow, StackHigh;
ULONG_PTR RegistrationFrameEnd;
CONTEXT LocalContext;
PCONTEXT Context;
/* Get the current stack limits */
RtlpGetStackLimits(&StackLow, &StackHigh);
/* If no exception record, create a STATUS_UNWIND one */
if (!ExceptionRecord)
{
ExceptionRecord = &ExceptionRecord3;
ExceptionRecord3.ExceptionFlags = 0;
ExceptionRecord3.ExceptionCode = STATUS_UNWIND;
ExceptionRecord3.ExceptionRecord = NULL;
ExceptionRecord3.ExceptionAddress = _ReturnAddress();
ExceptionRecord3.NumberParameters = 0;
}
/* Set UNWINDING flag (and EXIT_UNWIND if no target) */
if (TargetFrame)
ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING;
else
ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND);
/* Capture current context (with adjusted ESP to skip our 4 args) */
Context = &LocalContext;
LocalContext.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS;
RtlpCaptureContext(Context);
Context->Esp += sizeof(TargetFrame) + sizeof(TargetIp) + sizeof(ExceptionRecord) + sizeof(ReturnValue);
Context->Eax = (ULONG)ReturnValue;
RegistrationFrame = RtlpGetExceptionList();
/* Now loop every frame */
while (RegistrationFrame != EXCEPTION_CHAIN_END)
{
ASSERT(RegistrationFrame != NULL);
/* If this is the target frame, stop and continue */
if (RegistrationFrame == TargetFrame) ZwContinue(Context, FALSE);
/* If target is past current frame (lower address), invalid */
if ((TargetFrame) && ((ULONG_PTR)TargetFrame < (ULONG_PTR)RegistrationFrame))
{
ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
ExceptionRecord2.NumberParameters = 0;
RtlRaiseException(&ExceptionRecord2);
}
/* Validate frame in stack */
RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame + sizeof(EXCEPTION_REGISTRATION_RECORD);
if ((RegistrationFrameEnd > StackHigh) || ((ULONG_PTR)RegistrationFrame < StackLow) || ((ULONG_PTR)RegistrationFrame & 0x3))
{
if (RtlpHandleDpcStackException(RegistrationFrame, RegistrationFrameEnd, &StackLow, &StackHigh))
continue;
ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK;
ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
ExceptionRecord2.ExceptionRecord = ExceptionRecord;
ExceptionRecord2.NumberParameters = 0;
RtlRaiseException(&ExceptionRecord2);
}
/* Call handler with EXCEPTION_UNWINDING flag */
Disposition = RtlpExecuteHandlerForUnwind(ExceptionRecord,
RegistrationFrame,
Context,
&DispatcherContext,
RegistrationFrame->Handler);
switch (Disposition)
{
case ExceptionContinueSearch:
break;
case ExceptionCollidedUnwind:
RegistrationFrame = DispatcherContext.RegistrationPointer;
break;
default:
ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
...
RtlRaiseException(&ExceptionRecord2);
break;
}
/* Remove this handler from the list */
OldFrame = RegistrationFrame;
RegistrationFrame = RegistrationFrame->Next;
RtlpSetExceptionList(OldFrame);
}
/* Reached the end */
if (TargetFrame == EXCEPTION_CHAIN_END)
ZwContinue(Context, FALSE);
else
ZwRaiseException(ExceptionRecord, Context, FALSE);
}
关键设计要点
-
构造"伪上下文" :
RtlUnwind必须用RtlpCaptureContext捕获当前 CONTEXT,并且要 调整 ESP 以跳过它自己的 4 个参数(因为展开完成后这些参数不在栈上)。 -
EAX 特殊处理 :
Context->Eax = ReturnValue,这是把"返回值"传回给 TargetFrame 之上的那一层(x86 函数返回值约定)。 -
handler 收到
EXCEPTION_UNWINDING标志 :handler 在展开时通常执行__finally块;如果 handler 返回EXCEPTION_CONTINUE_SEARCH(默认),则从链表中移除该节点;如果返回EXCEPTION_COLLIDED_UNWIND,则跳到DispatcherContext.RegistrationPointer指定的 frame(用于跨线程的展开)。 -
目标检查 :如果
TargetFrame比当前 frame 还浅(地址更低),说明调用方给出了无效目标,抛出STATUS_INVALID_UNWIND_TARGET。 -
STATUS_BAD_STACK异常 :链表节点不在合法栈范围内,抛出STATUS_BAD_STACK。 -
最终汇合:
- 到达
TargetFrame→ZwContinue(Context, FALSE)恢复执行 - 到达
EXCEPTION_CHAIN_END且TargetFrame == EXCEPTION_CHAIN_END→ZwContinue(不返回) - 到达链尾但 TargetFrame 不是
EXCEPTION_CHAIN_END→ZwRaiseException(通常不会再有人处理)
- 到达
与 RtlDispatchException 的对照
| 维度 | RtlDispatchException |
RtlUnwind |
|---|---|---|
| 调用方向 | 从 FS:[0](栈浅)→ 栈深 |
从 FS:[0](栈浅)→ 栈深 |
| 目的 | 找到能处理异常的 handler | 反向调用每个 handler 的清理代码 |
| handler 标志 | 无 UNWINDING |
EXCEPTION_UNWINDING(或 +EXIT_UNWIND) |
| Disposition 含义 | 处理/搜索/嵌套 | 搜索/冲突 |
| 终止条件 | 任意 handler 处理 | 到达 TargetFrame |
| 副作用 | 修改链表节点中的 Next |
把已处理的节点从链表中移除 |
8.3.4 RtlRaiseException / RtlRaiseStatus 主动抛出
RtlRaiseException 和 RtlRaiseStatus 是用户态 主动抛出异常 的 API,分别定义于 exception.c:32(file:///d:/reactos/sdk/lib/rtl/exception.c#L32-L69) 和 exception.c:85(file:///d:/reactos/sdk/lib/rtl/exception.c#L85-L120)。
RtlRaiseException(非 x86 平台参考实现)
c
VOID NTAPI
RtlRaiseException(IN PEXCEPTION_RECORD ExceptionRecord)
{
CONTEXT Context;
NTSTATUS Status;
/* Capture the context */
RtlCaptureContext(&Context);
/* Save the exception address */
ExceptionRecord->ExceptionAddress = _ReturnAddress();
Context.ContextFlags = CONTEXT_FULL;
if (RtlpCheckForActiveDebugger())
{
Status = ZwRaiseException(ExceptionRecord, &Context, TRUE);
}
else
{
if (!RtlDispatchException(ExceptionRecord, &Context))
Status = ZwRaiseException(ExceptionRecord, &Context, FALSE);
else
Status = ZwContinue(&Context, FALSE);
}
RtlRaiseStatus(Status);
}
x86 平台上有特殊的汇编实现(见 8.3.6),但算法一致。
RtlRaiseStatus
c
VOID NTAPI
RtlRaiseStatus(IN NTSTATUS Status)
{
EXCEPTION_RECORD ExceptionRecord;
CONTEXT Context;
RtlCaptureContext(&Context);
ExceptionRecord.ExceptionAddress = _ReturnAddress();
ExceptionRecord.ExceptionCode = Status;
ExceptionRecord.ExceptionRecord = NULL;
ExceptionRecord.NumberParameters = 0;
ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
Context.ContextFlags = CONTEXT_FULL;
if (RtlpCheckForActiveDebugger())
ZwRaiseException(&ExceptionRecord, &Context, TRUE);
else
{
RtlDispatchException(&ExceptionRecord, &Context);
Status = ZwRaiseException(&ExceptionRecord, &Context, FALSE);
}
RtlRaiseStatus(Status);
}
对比
| 维度 | RtlRaiseException |
RtlRaiseStatus |
|---|---|---|
| 参数 | 完整 EXCEPTION_RECORD* |
仅 NTSTATUS Status |
ExceptionFlags |
0(默认可继续) | EXCEPTION_NONCONTINUABLE |
NumberParameters |
由调用者提供 | 0 |
ExceptionAddress |
调用者提供(一般填 _ReturnAddress()) |
自动填 _ReturnAddress() |
| 典型用途 | 抛出 C++ 异常、显式 SEH | 检查状态码失败时抛异常 |
| 失败行为 | 若 RtlDispatchException 不处理,系统调用走 FALSE |
同 |
两者的 算法骨架 都是:
RtlCaptureContext(&Context)捕获当前 CONTEXT- 填充
ExceptionRecord(包括ExceptionAddress) RtlpCheckForActiveDebugger():检查用户态调试器是否激活- 若有调试器 →
ZwRaiseException(..., FirstChance=TRUE)优先走调试器 - 否则尝试
RtlDispatchException在本进程内处理 - 失败 →
ZwRaiseException(..., FirstChance=FALSE)走 LastChance
8.3.5 向量化异常处理(VEH)
VEH(Vectored Exception Handling)是 Windows XP 引入的 独立于 SEH 链表 的异常处理机制。它的优势:
- 无须在栈上注册 :通过
AddVectoredExceptionHandler注册一个全局函数指针 - 优先级最高 :在
RtlDispatchException第一时间被调用 - 可动态启用/禁用 :通过
RemoveVectoredExceptionHandler移除
数据结构
定义于 vectoreh.c:16-24(file:///d:/reactos/sdk/lib/rtl/vectoreh.c#L16-L24):
c
RTL_CRITICAL_SECTION RtlpVectoredHandlerLock;
LIST_ENTRY RtlpVectoredExceptionList, RtlpVectoredContinueList;
typedef struct _RTL_VECTORED_HANDLER_ENTRY
{
LIST_ENTRY ListEntry;
PVECTORED_EXCEPTION_HANDLER VectoredHandler;
ULONG Refs;
} RTL_VECTORED_HANDLER_ENTRY, *PRTL_VECTORED_HANDLER_ENTRY;
全局有两个链表:
RtlpVectoredExceptionList:异常发生时的 handler 链RtlpVectoredContinueList:异常处理完后、恢复执行前的 continue 链
关键 API
| API | 作用 |
|---|---|
AddVectoredExceptionHandler |
注册 VEH handler,返回 cookie |
RemoveVectoredExceptionHandler |
移除 VEH handler |
RtlCallVectoredExceptionHandlers |
遍历 VEH 链调用 |
RtlCallVectoredContinueHandlers |
遍历 continue 链调用 |
遍历算法
RtlpCallVectoredHandlers 内部:
- 构造
EXCEPTION_POINTERS ExceptionInfo - 进入临界区
RtlpVectoredHandlerLock - 遍历链表,每调用一个 handler 前先
Refs++引用,调用后Refs-- - 调用完
RtlLeaveCriticalSection再RtlEnterCriticalSection重新加锁 - 解码指针
RtlDecodePointer防止恶意改写 - 任意 handler 返回
EXCEPTION_CONTINUE_EXECUTION→ 停止遍历并返回 TRUE - 检查
Refs==0的 handler(说明在调用期间被其他线程删除),按需删除节点
8.3.6 汇编包装与 frame 保护
为了在调用 handler 期间提供 安全的 frame 环境,ReactOS 用汇编实现了一组包装函数。定义于 sdk/lib/rtl/i386/except_asm.s(file:///d:/reactos/sdk/lib/rtl/i386/except_asm.s):
RtlpCaptureContext
捕获当前 CONTEXT 但 保留非易失寄存器:
asm
_RtlpCaptureContext@4:
; Context* in [esp+4]
mov ebx, [esp+4]
; Save volatiles (Eax/Edx/Ecx) and non-volatiles
mov [ebx+CONTEXT_EAX], eax
mov [ebx+CONTEXT_EDX], edx
mov [ebx+CONTEXT_ECX], ecx
mov [ebx+CONTEXT_EBX], ebx
mov [ebx+CONTEXT_ESI], esi
mov [ebx+CONTEXT_EDI], edi
; Get the caller's Eip and Ebp
mov eax, [esp+4] ; return address of caller
mov [ebx+CONTEXT_EIP], eax
mov eax, [ebp+0] ; caller's Ebp
mov [ebx+CONTEXT_EBP], eax
lea eax, [ebp+8] ; caller's Esp + 8 (skip return addr)
mov [ebx+CONTEXT_ESP], eax
注意:Ebx/Esi/Edi 虽是"非易失"寄存器,但 RtlpCaptureContext 仍然保存它们,因为调用方可能希望恢复这些值。
RtlpExecuteHandlerForException / RtlpExecuteHandlerForUnwind
这两个函数是 frame 保护器 (protector),它们在调用真正的 handler 之前 ,构造一个新的"伪 SEH 链表节点"插入 FS:[0],确保即使 handler 自身抛出异常,RTL 也能正确处理。
asm
_RtlpExecuteHandlerForException@20:
mov edx, offset _RtlpExceptionProtector
jmp _RtlpExecuteHandler@20
_RtlpExecuteHandlerForUnwind@20:
mov edx, offset _RtlpUnwindProtector
jmp _RtlpExecuteHandler@20
.PROC _RtlpExecuteHandler@20
push ebx
push esi
push edi
; Clear registers (隐藏信息)
xor eax, eax
xor ebx, ebx
xor esi, esi
xor edi, edi
push [esp+32] ; 5 个参数都重新压一遍
push [esp+32]
push [esp+32]
push [esp+32]
push [esp+32]
call _RtlpExecuteHandler2@20
pop edi
pop esi
pop ebx
ret 20
.ENDP
_RtlpExecuteHandler2 把 protector(_RtlpExceptionProtector 或 _RtlpUnwindProtector)注册到 FS:[0],然后调用真正的 handler:
asm
_RtlpExecuteHandler2@20:
push ebp
mov ebp, esp
push [ebp+12] ; RegistrationFrame
push edx ; Protector (ExceptionProtector/UnwindProtector)
push [fs:TEB_EXCEPTION_LIST]
mov [fs:TEB_EXCEPTION_LIST], esp ; 插入 protector
; 调用真正的 handler
push [ebp+20] ; handler
push [ebp+16] ; DispatcherContext*
push [ebp+12] ; Context*
push [ebp+8] ; RegistrationFrame
mov ecx, [ebp+24] ; ExceptionRecord*
call ecx ; 调用 handler
; 取消 protector
mov esp, [fs:TEB_EXCEPTION_LIST]
pop [fs:TEB_EXCEPTION_LIST]
mov esp, ebp
pop ebp
ret 20
_RtlpExceptionProtector 与 _RtlpUnwindProtector
Protector 的作用是 保护嵌套异常:
asm
_RtlpExceptionProtector:
; 假设返回 ExceptionContinueSearch
mov eax, ExceptionContinueSearch
; 检查是否处于展开状态
mov ecx, [esp+4] ; ExceptionRecord*
test dword ptr [ecx+EXCEPTION_RECORD_EXCEPTION_FLAGS], EXCEPTION_UNWINDING + EXCEPTION_EXIT_UNWIND
jnz return ; 展开状态,不处理嵌套
; 提取嵌套 frame
mov ecx, [esp+8] ; RegistrationFrame
mov edx, [esp+16] ; DispatcherContext*
mov eax, [ecx+8] ; RegistrationFrame.prev (nested)
mov [edx], eax ; 写入 DispatcherContext
; 返回 ExceptionNestedException
mov eax, ExceptionNestedException
return:
ret 16
异常时(在执行正常 handler 的过程中又抛出异常):
- 如果当前不在
EXCEPTION_UNWINDING状态 → 返回ExceptionNestedException,让RtlDispatchException跳过这个 frame - 如果处于
EXCEPTION_UNWINDING状态 → 返回ExceptionContinueSearch,让展开继续
_RtlpUnwindProtector 行为相反:
- 处于展开状态 → 返回
ExceptionCollidedUnwind - 不在展开状态 → 返回
ExceptionContinueSearch
这套机制确保 即使 handler 自身抛出异常 (例如在 __except 块中调用了 RtlRaiseException),RTL 也能正确地处理嵌套/冲突。
8.3.7 顶层未处理异常过滤器
如果 RtlDispatchException / RtlUnwind 都没找到处理者,ntdll 会调用 顶层未处理异常过滤器。定义于 exception.c:311(file:///d:/reactos/sdk/lib/rtl/exception.c#L311-L317):
c
LONG
NTAPI
RtlUnhandledExceptionFilter(_In_ PEXCEPTION_POINTERS ExceptionInfo)
{
return RtlUnhandledExceptionFilter2(ExceptionInfo, "");
}
RtlUnhandledExceptionFilter2:
c
LONG NTAPI
RtlUnhandledExceptionFilter2(_In_ PEXCEPTION_POINTERS ExceptionInfo, _In_ PCSTR Function)
{
UNIMPLEMENTED;
ASSERT(ExceptionInfo && ExceptionInfo->ExceptionRecord);
PrintStackTrace(ExceptionInfo);
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_POSSIBLE_DEADLOCK)
return EXCEPTION_CONTINUE_EXECUTION;
return EXCEPTION_CONTINUE_SEARCH;
}
RtlSetUnhandledExceptionFilter 允许应用程序注册自己的顶层过滤器:
c
VOID NTAPI
RtlSetUnhandledExceptionFilter(IN PRTLP_UNHANDLED_EXCEPTION_FILTER TopLevelExceptionFilter)
{
RtlpUnhandledExceptionFilter = RtlEncodePointer(TopLevelExceptionFilter);
}
注意使用 RtlEncodePointer 防止被攻击者改写指针。
PrintStackTrace 打印当前栈回溯,对于诊断崩溃非常有用:
c
static VOID PrintStackTrace(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
PVOID StartAddr;
CHAR szMod[128] = "";
PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord;
PCONTEXT ContextRecord = ExceptionInfo->ContextRecord;
DbgPrint("Unhandled exception\n");
DbgPrint("ExceptionCode: %8x\n", ExceptionRecord->ExceptionCode);
if ((NTSTATUS)ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION &&
ExceptionRecord->NumberParameters == 2)
{
DbgPrint("Faulting Address: %8x\n", ExceptionRecord->ExceptionInformation[1]);
}
_dump_context(ContextRecord);
_module_name_from_addr(ExceptionRecord->ExceptionAddress, &StartAddr, szMod, sizeof(szMod));
DbgPrint("Address: %8x+%-8x %s\n", ...);
#ifdef _M_IX86
DbgPrint("Frames:\n");
_SEH2_TRY
{
UINT i;
PULONG Frame = (PULONG)ContextRecord->Ebp;
for (i = 0; Frame[1] != 0 && Frame[1] != 0xdeadbeef && i < 128; i++)
{
...
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint("<error dumping stack trace: 0x%x>\n", _SEH2_GetExceptionCode());
}
_SEH2_END;
#endif
}
8.3.8 x64 与 ARM 实现差异
x64 实现(sdk/lib/rtl/amd64/)
x64 平台上的 SEH 有 根本性差异 ------x64 不使用 EXCEPTION_REGISTRATION_RECORD 链表,而是采用 表驱动 的栈展开(Table-Driven Unwind)。相关文件:
- sdk/lib/rtl/amd64/except.c(file:///d:/reactos/sdk/lib/rtl/amd64/except.c):平台无关逻辑
- sdk/lib/rtl/amd64/unwind.c(file:///d:/reactos/sdk/lib/rtl/amd64/unwind.c):栈展开算法
- sdk/lib/rtl/amd64/unwind-asm.s(file:///d:/reactos/sdk/lib/rtl/amd64/unwind-asm.s):汇编支持
- sdk/lib/rtl/amd64/stubs.c(file:///d:/reactos/sdk/lib/rtl/amd64/stubs.c):辅助 stub
主要差异:
- PE 文件中的
.pdata/.xdata段 :每个函数都有 展开数据(Unwind Data),描述如何恢复每个非易失寄存器 RtlLookupFunctionEntry:通过 RIP 查找函数对应的UNWIND_INFORtlVirtualUnwind:使用表驱动算法展开栈帧__try/__except编译为__C_specific_handler调用:在 excpt.h(file:///d:/reactos/sdk/include/vcruntime/excpt.h#L60-L74) 中可见- CONTEXT 更大:包含 XSAVE 区(512 字节浮点 + 扩展状态)
ARM 实现(sdk/lib/rtl/arm/)
ARM 平台同样使用 表驱动展开 ,并通过 __C_specific_handler 实现 __try/__except。相关文件:
- sdk/lib/rtl/arm/except.c(file:///d:/reactos/sdk/lib/rtl/arm/except.c):ARM 用户态分派
- sdk/lib/rtl/arm/debug_asm.S(file:///d:/reactos/sdk/lib/rtl/arm/debug_asm.S):ARM 汇编支持
ARM 的 EXCEPTION_DISPOSITION 处理逻辑与 x86 相同,但栈展开使用 ARM AAPCS(ARM Architecture Procedure Call Standard)规则。
算法框架一致性
虽然实现细节不同,三个架构在 算法骨架 上完全一致:
RtlDispatchException函数签名相同- 遍历方式:x86 用链表,x64/ARM 用表驱动查找
EXCEPTION_DISPOSITION四种返回值的语义相同RtlRaiseException/RtlRaiseStatus算法相同- VEH 链接口相同(架构无关)
这就是为什么用户态 C 代码(__try/__except)可以在 ReactOS 的所有架构上编译运行。
总结
8.3 节建立了用户态 SEH 的完整视图。关键要点:
- 三层协作 :
RtlDispatchException(分派)→ handler 遍历 →RtlUnwind(展开) - VEH 优先级最高 :
RtlCallVectoredExceptionHandlers在链表遍历前调用 - 链表遍历 :
RtlDispatchException从FS:[0]开始,逐层调用 handler - 四种 Disposition :
ExceptionContinueExecution(已处理)ExceptionContinueSearch(继续搜索)ExceptionNestedException(handler 内部抛新异常)ExceptionCollidedUnwind(unwind 冲突)
- 栈展开 :
RtlUnwind用EXCEPTION_UNWINDING标志反向调用 handler,强制执行__finally块 - 主动抛出 :
RtlRaiseException(带 record) vsRtlRaiseStatus(带 STATUS,不可继续) - 汇编包装 :
RtlpExecuteHandlerForException/RtlpExecuteHandlerForUnwind通过 protector 保护嵌套异常 - 顶层兜底 :
RtlUnhandledExceptionFilter/RtlSetUnhandledExceptionFilter兜底 - 跨架构一致性:x86(链表)vs x64/ARM(表驱动)算法骨架相同
8.4 节将讨论 软异常------所有通过软件(API、断言、调试器断点)发起的异常。
本章代码索引
| 文件 | 内容 |
|---|---|
| i386/except.c(file:///d:/reactos/sdk/lib/rtl/i386/except.c) | x86 RtlDispatchException、RtlUnwind |
| i386/except_asm.s(file:///d:/reactos/sdk/lib/rtl/i386/except_asm.s) | x86 汇编包装(RtlpCaptureContext、RtlpExecuteHandlerForException、RtlpExecuteHandlerForUnwind、_RtlRaiseException、_RtlRaiseStatus) |
| amd64/except.c(file:///d:/reactos/sdk/lib/rtl/amd64/except.c) | x64 RtlDispatchException |
| amd64/unwind.c(file:///d:/reactos/sdk/lib/rtl/amd64/unwind.c) | x64 栈展开(表驱动) |
| amd64/unwind-asm.s(file:///d:/reactos/sdk/lib/rtl/amd64/unwind-asm.s) | x64 展开汇编 |
| arm/except.c(file:///d:/reactos/sdk/lib/rtl/arm/except.c) | ARM RtlDispatchException |
| arm/debug_asm.S(file:///d:/reactos/sdk/lib/rtl/arm/debug_asm.S) | ARM 汇编支持 |
| exception.c(file:///d:/reactos/sdk/lib/rtl/exception.c) | RtlRaiseException、RtlRaiseStatus、RtlUnhandledExceptionFilter、RtlSetUnhandledExceptionFilter、RtlCaptureStackBackTrace |
| vectoreh.c(file:///d:/reactos/sdk/lib/rtl/vectoreh.c) | VEH 链表实现(RtlpCallVectoredHandlers、AddVectoredExceptionHandler) |
| debug.c(file:///d:/reactos/sdk/lib/rtl/debug.c) | RtlpCheckForActiveDebugger |
| rtl.h(file:///d:/reactos/sdk/lib/rtl/rtl.h) | RTL 函数声明 |
| vcruntime/excpt.h(file:///d:/reactos/sdk/include/vcruntime/excpt.h) | EXCEPTION_DISPOSITION 枚举、_except_handler、__C_specific_handler 声明 |
| wine/winnt.h(file:///d:/reactos/sdk/include/wine/winnt.h) | EXCEPTION_REGISTRATION_RECORD 定义 |
| xdk/rtltypes.h(file:///d:/reactos/sdk/include/xdk/rtltypes.h) | EXCEPTION_RECORD、EXCEPTION_POINTERS 定义 |
| xdk/winnt_old.h(file:///d:/reactos/sdk/include/xdk/winnt_old.h) | EXCEPTION_* 标志位 |
| psdk/ntstatus.h(file:///d:/reactos/sdk/include/psdk/ntstatus.h) | 所有 STATUS_* 异常码 |
| ntosifs.h(file:///d:/reactos/sdk/include/ntosifs.h) | EXCEPTION_CHAIN_END、ExRaiseStatus |
| dll/ntdll(file:///d:/reactos/dll/ntdll) | KeUserExceptionDispatcher 实现 |