Reactos 第 8 章 结构化异常处理 — 8.3 用户空间的结构化异常处理

第 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 的核心组件包括:

  1. 入口与桥接层KeUserExceptionDispatcher(ntdll 入口)、RtlpCaptureContext(汇编捕获 CONTEXT)
  2. 异常分派层RtlDispatchException(遍历 SEH 链表)
  3. 栈展开层RtlUnwind(反向调用 handler 进行清理)
  4. 主动抛出层RtlRaiseException / RtlRaiseStatus(代码生成异常)
  5. 向量化异常层(VEH)RtlCallVectoredExceptionHandlers(链式 VEH 处理)
  6. 顶层过滤器RtlUnhandledExceptionFilter(最后兜底)
  7. 辅助层RtlpExecuteHandlerForException / RtlpExecuteHandlerForUnwind(汇编包装,含 frame 保护)

理解这套体系的关键是把握 三个阶段

  • 分派阶段RtlDispatchExceptionFS:[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 优先级最高,无须在栈上注册)
  • 理解 RtlRaiseExceptionRtlRaiseStatus 的差异
  • 了解 RtlpExecuteHandlerForException 的汇编级 frame 保护机制

涉及的内核子系统

子系统 头文件/源文件 核心作用
用户态分派 sdk/lib/rtl/i386/except.c(file:///d:/reactos/sdk/lib/rtl/i386/except.c) x86 RtlDispatchExceptionRtlUnwind
用户态分派(其他架构) 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) RtlRaiseExceptionRtlRaiseStatusRtlUnhandledExceptionFilter
向量化异常 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) RtlpCaptureContextRtlpExecuteHandlerForException
通用栈展开 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();
  • RtlpGetStackLimitsNtCurrentTeb()->StackLimit / StackBase 读取
  • RtlpGetExceptionListmov 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 决定后续动作。

关键设计要点

  1. 栈合法性检查 :每个 RegistrationFrame 必须满足:

    • RegistrationFrame + sizeof(...) ≤ StackHigh(不在栈顶之上)
    • RegistrationFrame ≥ StackLow(不在栈底之下)
    • 4 字节对齐

    这防止恶意代码通过伪造链表节点来劫持控制流。

  2. DPC 栈特殊处理 :当异常发生在 DPC(Deferred Procedure Call)路径时,当前栈可能是 DPC 栈。RtlpHandleDpcStackException 会切换到 DPC 栈边界重新开始遍历。

  3. 嵌套异常跟踪 :通过 NestedFrame 记录"嵌套异常最深处",遍历时跳过已处理过的节点。

  4. 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);
}

关键设计要点

  1. 构造"伪上下文"RtlUnwind 必须用 RtlpCaptureContext 捕获当前 CONTEXT,并且要 调整 ESP 以跳过它自己的 4 个参数(因为展开完成后这些参数不在栈上)。

  2. EAX 特殊处理Context->Eax = ReturnValue,这是把"返回值"传回给 TargetFrame 之上的那一层(x86 函数返回值约定)。

  3. handler 收到 EXCEPTION_UNWINDING 标志 :handler 在展开时通常执行 __finally 块;如果 handler 返回 EXCEPTION_CONTINUE_SEARCH(默认),则从链表中移除该节点;如果返回 EXCEPTION_COLLIDED_UNWIND,则跳到 DispatcherContext.RegistrationPointer 指定的 frame(用于跨线程的展开)。

  4. 目标检查 :如果 TargetFrame 比当前 frame 还浅(地址更低),说明调用方给出了无效目标,抛出 STATUS_INVALID_UNWIND_TARGET

  5. STATUS_BAD_STACK 异常 :链表节点不在合法栈范围内,抛出 STATUS_BAD_STACK

  6. 最终汇合

    • 到达 TargetFrameZwContinue(Context, FALSE) 恢复执行
    • 到达 EXCEPTION_CHAIN_ENDTargetFrame == EXCEPTION_CHAIN_ENDZwContinue(不返回)
    • 到达链尾但 TargetFrame 不是 EXCEPTION_CHAIN_ENDZwRaiseException(通常不会再有人处理)

RtlDispatchException 的对照

维度 RtlDispatchException RtlUnwind
调用方向 FS:[0](栈浅)→ 栈深 FS:[0](栈浅)→ 栈深
目的 找到能处理异常的 handler 反向调用每个 handler 的清理代码
handler 标志 UNWINDING EXCEPTION_UNWINDING(或 +EXIT_UNWIND
Disposition 含义 处理/搜索/嵌套 搜索/冲突
终止条件 任意 handler 处理 到达 TargetFrame
副作用 修改链表节点中的 Next 把已处理的节点从链表中移除

8.3.4 RtlRaiseException / RtlRaiseStatus 主动抛出

RtlRaiseExceptionRtlRaiseStatus 是用户态 主动抛出异常 的 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

两者的 算法骨架 都是:

  1. RtlCaptureContext(&Context) 捕获当前 CONTEXT
  2. 填充 ExceptionRecord(包括 ExceptionAddress
  3. RtlpCheckForActiveDebugger():检查用户态调试器是否激活
  4. 若有调试器 → ZwRaiseException(..., FirstChance=TRUE) 优先走调试器
  5. 否则尝试 RtlDispatchException 在本进程内处理
  6. 失败 → 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 内部:

  1. 构造 EXCEPTION_POINTERS ExceptionInfo
  2. 进入临界区 RtlpVectoredHandlerLock
  3. 遍历链表,每调用一个 handler 前先 Refs++ 引用,调用后 Refs--
  4. 调用完 RtlLeaveCriticalSectionRtlEnterCriticalSection 重新加锁
  5. 解码指针 RtlDecodePointer 防止恶意改写
  6. 任意 handler 返回 EXCEPTION_CONTINUE_EXECUTION → 停止遍历并返回 TRUE
  7. 检查 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

主要差异:

  1. PE 文件中的 .pdata / .xdata :每个函数都有 展开数据(Unwind Data),描述如何恢复每个非易失寄存器
  2. RtlLookupFunctionEntry :通过 RIP 查找函数对应的 UNWIND_INFO
  3. RtlVirtualUnwind:使用表驱动算法展开栈帧
  4. __try/__except 编译为 __C_specific_handler 调用:在 excpt.h(file:///d:/reactos/sdk/include/vcruntime/excpt.h#L60-L74) 中可见
  5. 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 的完整视图。关键要点:

  1. 三层协作RtlDispatchException(分派)→ handler 遍历 → RtlUnwind(展开)
  2. VEH 优先级最高RtlCallVectoredExceptionHandlers 在链表遍历前调用
  3. 链表遍历RtlDispatchExceptionFS:[0] 开始,逐层调用 handler
  4. 四种 Disposition
    • ExceptionContinueExecution(已处理)
    • ExceptionContinueSearch(继续搜索)
    • ExceptionNestedException(handler 内部抛新异常)
    • ExceptionCollidedUnwind(unwind 冲突)
  5. 栈展开RtlUnwindEXCEPTION_UNWINDING 标志反向调用 handler,强制执行 __finally
  6. 主动抛出RtlRaiseException(带 record) vs RtlRaiseStatus(带 STATUS,不可继续)
  7. 汇编包装RtlpExecuteHandlerForException / RtlpExecuteHandlerForUnwind 通过 protector 保护嵌套异常
  8. 顶层兜底RtlUnhandledExceptionFilter / RtlSetUnhandledExceptionFilter 兜底
  9. 跨架构一致性:x86(链表)vs x64/ARM(表驱动)算法骨架相同

8.4 节将讨论 软异常------所有通过软件(API、断言、调试器断点)发起的异常。


本章代码索引

文件 内容
i386/except.c(file:///d:/reactos/sdk/lib/rtl/i386/except.c) x86 RtlDispatchExceptionRtlUnwind
i386/except_asm.s(file:///d:/reactos/sdk/lib/rtl/i386/except_asm.s) x86 汇编包装(RtlpCaptureContextRtlpExecuteHandlerForExceptionRtlpExecuteHandlerForUnwind_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) RtlRaiseExceptionRtlRaiseStatusRtlUnhandledExceptionFilterRtlSetUnhandledExceptionFilterRtlCaptureStackBackTrace
vectoreh.c(file:///d:/reactos/sdk/lib/rtl/vectoreh.c) VEH 链表实现(RtlpCallVectoredHandlersAddVectoredExceptionHandler
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_RECORDEXCEPTION_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_ENDExRaiseStatus
dll/ntdll(file:///d:/reactos/dll/ntdll) KeUserExceptionDispatcher 实现
相关推荐
caimouse2 小时前
Reactos 第 9 章 设备驱动 — 9.6 中断处理
网络·windows
caimouse2 小时前
Reactos 第 7 章 视窗报文 — 7.6 键盘输入线程
windows
yinhunzw2 小时前
Claude code windows 安装
windows
七仔啊3 小时前
windows server 2022 部署前后端项目
windows
caimouse3 小时前
Reactos 第 7 章 视窗报文 — 7.4 用户空间的外挂函数
windows
辣香牛肉面4 小时前
Windows发票工具大全
windows·发票助手
caimouse4 小时前
Reactos 第 9 章 设备驱动 — 9.3 DPC函数及其执行
windows
caimouse5 小时前
Reactos 第 9 章 设备驱动 — 9.8 设备驱动模块的装载
windows
caimouse5 小时前
Reactos 第 9 章 设备驱动 — 9.2 一个“老式“驱动模块的实例
windows