• SEH分析笔记(X64篇)


    SEH分析笔记(X64篇)
    v1.0.0
    boxcounter

    历史:
    v1.0.0, 2011-11-4:最初版本。

    [不介意转载,但请注明出处 www.boxcounter.com 
    附件里有本文的原始稿,一样的内容,更好的高亮和排版。
    本文的部分代码可能会因为论坛的自动换行变得很乱,需要的朋友手动复制到自己的代码编辑器就可以正常显示了]

    在之前的《SEH分析笔记(X86篇)》中,我借助 wrk1.2 介绍了 x86 下 windows 系统内核中的 SEH 实现。这次我们来看看 x64 位 windows 系统内核中 SEH 的实现。
    本文需要大家熟悉 x64 位系统的一些特性,比如调用约定、Prolog 和 Epilog。可以通过这几篇文章熟悉一下:
    Overview of x64 Calling Conventions, MSDN
    The history of calling conventions, part 5: amd64 , The Old New Thing
    Everything You Need To Know To Start Programming 64-Bit Windows Systems, Matt Pietrek

    首先回顾一下前一篇文章。
    在 x86 windows 中,函数通过以下几个步骤来参与 SEH :
    1. 在自身的栈空间中分配并初始化一个 EXCEPTION_REGISTRATION(_RECORD) 结构体。
    2. 将该 EXCEPTION_REGISTRATION(_RECORD) 挂入当前线程的异常链表。

    当某函数触发异常时,系统首先会通过调用 KiDispatchException 来给内核调试器一个机会,如果内核调试器没有处理该异常,则该机会被转给 RtlDispatchException,这个函数就开始分发该异常。分发过程为:
    从当前线程的异常链表头开始遍历,对于每一个 SEH 注册信息(即 EXCEPTION_REGISTRATION(_RECORD)),调用其 Handler。根据 Handler 的返回值做相应的后续处理:
    1. 返回 ExceptionContinueExecution,表示 Handler 已经修复了异常触发点,从异常触发点继续执行。
    2. 返回 ExceptionContinueSearch,表示该 Handler 没有处理该异常,继续遍历异常链表。
    3. Handler 没有修复异常触发点,但是却能处理该异常(某个 __except 过滤代码返回 EXCEPTION_EXECUTE_HANDLER)。这种情况下,处理完该异常后就从异常解决代码(__except 代码块)继续执行,Handler 不会返回。
    以上是简略的 x86 SEH 流程,其中省略了很多细节,比如展开、错误处理、ExceptionNestedException 和 ExceptionCollidedUnwind 等等。

    之所以在这里重温这个流程,是因为 x64 中 SEH 的流程总体思路也是如此,只是细节上做了一些修改。但这并不表示熟悉 x86 SEH 就能很轻松的掌握 x64 SEH。

    本文分为四个部分:“异常注册”、“异常分发”、“展开、解决”和“ExceptionNestedException 和 ExceptionCollidedUnwind”。依然以 MSC 的增强版为分析对象。分析环境为:WDK 7600.16385.1,内置的 cl 的版本是15.00.30729.207,link 的版本是9.00.30729.207,测试虚拟机系统为 amd64 WinXP + wrk1.2。

    在讲述之前,需要先定义几个名词,以简化后续的讲述。

    RVA —— 熟悉 PE 格式的朋友都懂的,表示某个绝对地址相对于所在模块的基地址的偏移。
    EXCEPT_POINT —— 异常触发点。
    EXCEPT_FILTER —— __except 小括号内的异常过滤代码。
    EXCEPT_HANDLER —— __except 大括号内的异常解决代码。
    FINALLY_HANDLER —— __finally 大括号内的代码。

    以下面的伪码为例,

    Code:
        1  __try
        2  {
        3      __try
        4      {
        5           *((ULONG*)NULL) = 0; 
        6      }
        7      __except((STATUS_INVALID_PARAMETER == GetExceptionCode()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER)
        8      {
        9          ...
        10     }
        11 }
        12 __finally
        13 {
        14     ...
        15 {
    EXCEPT_POINT 指的是行5中的代码。
    EXCEPT_FILTER 指的是行7中的“(STATUS_INVALID_PARAMETER == GetExceptionCode()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER”。
    EXCEPT_HANDLER 指的是行8到行10中所有的代码。
    FINALLY_HANDLER 指的是行13到行15中所有的代码。


    一、异常注册

    在 x64 windows 中,异常注册信息发生了巨大的改变。x86 中异常注册信息是在函数执行过程中在栈中分配并初始化的。x64 中变成这样:
    异常注册信息不再是动态创建,而是编译过程中生成,链接时写入 PE+ 头中的 ExceptionDirectory(参考 winnt.h 中 IMAGE_RUNTIME_FUNCTION_ENTRY 的定义)。ExceptionDirectory 里包含几乎所有函数的栈操作、异常处理等信息。

    来看看新异常注册信息的数据结构:

    Code:
        typedef struct _RUNTIME_FUNCTION {
            ULONG BeginAddress;
            ULONG EndAddress;
            ULONG UnwindData;
        } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

        typedef enum _UNWIND_OP_CODES {
            UWOP_PUSH_NONVOL = 0,
            UWOP_ALLOC_LARGE,       // 1
            UWOP_ALLOC_SMALL,       // 2
            UWOP_SET_FPREG,         // 3
            UWOP_SAVE_NONVOL,       // 4
            UWOP_SAVE_NONVOL_FAR,   // 5
            UWOP_SPARE_CODE1,       // 6
            UWOP_SPARE_CODE2,       // 7
            UWOP_SAVE_XMM128,       // 8
            UWOP_SAVE_XMM128_FAR,   // 9
            UWOP_PUSH_MACHFRAME     // 10
        } UNWIND_OP_CODES, *PUNWIND_OP_CODES;

        typedef union _UNWIND_CODE {
            struct {
                UCHAR CodeOffset;
                UCHAR UnwindOp : 4;
                UCHAR OpInfo : 4;
            };
        
            USHORT FrameOffset;
        } UNWIND_CODE, *PUNWIND_CODE;
        
        #define UNW_FLAG_NHANDLER 0x0
        #define UNW_FLAG_EHANDLER 0x1
        #define UNW_FLAG_UHANDLER 0x2
        #define UNW_FLAG_CHAININFO 0x4

        typedef struct _UNWIND_INFO {
            UCHAR Version : 3;
            UCHAR Flags : 5;
            UCHAR SizeOfProlog;
            UCHAR CountOfCodes;
            UCHAR FrameRegister : 4;
            UCHAR FrameOffset : 4;
            UNWIND_CODE UnwindCode[1];
        
        //
        // The unwind codes are followed by an optional DWORD aligned field that
        // contains the exception handler address or a function table entry if
        // chained unwind information is specified. If an exception handler address
        // is specified, then it is followed by the language specified exception
        // handler data.
        //
        //  union {
        //      struct {
        //          ULONG ExceptionHandler;
        //          ULONG ExceptionData[];
        //      };
        //
        //      RUNTIME_FUNCTION FunctionEntry;
        //  };
        //
        
        } UNWIND_INFO, *PUNWIND_INFO;

        typedef struct _SCOPE_TABLE {
            ULONG Count;
            struct
            {
                ULONG BeginAddress;
                ULONG EndAddress;
                ULONG HandlerAddress;
                ULONG JumpTarget;
            } ScopeRecord[1];
        } SCOPE_TABLE, *PSCOPE_TABLE;
    x64 中,MSC 为几乎所有的函数都登记了完备的信息,用来在展开过程中完整的回滚函数所做的栈、寄存器操作。登记的信息包括:
    函数是否使用了 SEH、
    函数使用的是什么组合的 SEH(__try/__except?__try/__finally?)、
    函数申请了多少栈空间、
    函数保存了哪些寄存器、
    函数是否建立了栈帧,
    等等,
    同时也记录了这些操作的顺序(以保证回滚的时候不会乱套)。

    这些信息就存储在 UNWIND_INFO 之中。
    UNWIND_INFO 相当于 x86 下的 EXCEPTION_REGISTRATION。它的成员分别是:
    Version —— 结构体的版本。
    Flags —— 标志位,可以有这么几种取值:
    UNW_FLAG_NHANDLER (0x0): 表示既没有 EXCEPT_FILTER 也没有 EXCEPT_HANDLER。
    UNW_FLAG_EHANDLER (0x1): 表示该函数有 EXCEPT_FILTER & EXCEPT_HANDLER。
    UNW_FLAG_UHANDLER (0x2): 表示该函数有 FINALLY_HANDLER。
    UNW_FLAG_CHAININFO (0x4): 表示该函数有多个 UNWIND_INFO,它们串接在一起(所谓的 chain)。
    SizeOfProlog —— 表示该函数的 Prolog 指令的大小,单位是 byte。
    CountOfCodes —— 表示当前 UNWIND_INFO 包含多少个 UNWIND_CODE 结构。
    FrameRegister —— 如果函数建立了栈帧,它表示栈帧的索引(相对于 CONTEXT::RAX 的偏移,详情参考 RtlVirtualUnwind 源码)。否则该成员的值为0。
    FrameOffset —— 表示 FrameRegister 距离函数最初栈顶(刚进入函数,还没有执行任何指令时的栈顶)的偏移,单位也是 byte。
    UnwindCode —— 是一个 UNWIND_CODE 类型的数组。元素数量由 CountOfCodes 决定。
    需要说明几点:
    1. 如果 Flags 设置了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,那么在最后一个 UNWIND_CODE 之后存放着 ExceptionHandler(相当于 x86 EXCEPTION_REGISTRATION::handler)和 ExceptionData(相当于 x86 EXCEPTION_REGISTRATION::scopetable)。
    2. UnwindCode 数组详细记录了函数修改栈、保存非易失性寄存器的指令。
    3. MSDN 中有 UNWIND_INFO 和 UNWIND_CODE 的详细说明,推荐阅读。

    那 UNWIND_INFO 是如何与其描述的函数关联起来的呢?答案是:通过一个 RUNTIME_FUNCTION 结构体。
    RUNTIME_FUNCTION::BeginAddress 同 RUNTIME_FUNCTION::EndAddress 一起以 RVA 形式描述了函数的范围。
    RUNTIME_FUNCTION::UnwindData 就是 UNWIND_INFO 了,它也是一个 RVA 值。

    PE+ 中的 ExceptionDirectory 中存放着所有函数的 RUNTIME_FUNCTION,按 RUNTIME_FUNCTION::BeginAddress 升序排列。一旦触发异常,系统可以通过 EXCEPT_POINT 的 RVA 在 ExceptionDirectory 中二分查找到 RUNTIME_FUNCTION,进而找到 UNWIND_INFO。

    前面有提到,MSC 为几乎所有的函数都登记了完毕的信息,那是不是有一些特殊函数没有登记信息呢?
    是的。x64 新增了一个概念,叫做“叶函数”。熟悉数据结构的朋友可能第一时间就联想到“叶节点”。没错,“叶函数”的含义跟“叶节点”很类似,叶函数不会有子函数,也就是说它不会再​调用任何函数。另外 x64 对这个概念额外加了一些要求:不修改栈指针(比如分配栈空间)、没有使用 SEH。总结下来就是:既不调用函数、又没有修改栈指针,也没有使用 SEH 的函数就叫做“叶函数”。
    叶函数可以没有登记信息,原因很简单,它根本就没信息需要登记~

    还有一个 SCOPE_TABLE 结构,熟悉 x86 SEH 的朋友应该很眼熟 :-),它等同于 x86 SEH 中的 REGISTRATIOIN_RECORD::scopetable 的类型。其成员有:
    Count —— 表示 ScopeRecord 数组的大小。
    ScopeRecord —— 等同于 x86 中的 scopetable_entry 成员。其中,
    BeginAddress 和 EndAddress 表示某个 __try 保护域的范围。
    HandlerAddress 和 JumpTarget 表示 EXCEPTION_FILTER、EXCEPT_HANDLER 和 FINALLY_HANDLER。具体对应情况为:
    对于 __try/__except 组合,HandlerAddress 代表 EXCEPT_FILTER,JumpTarget 代表 EXCEPT_HANDLER。
    对于 __try/__finally 组合,HandlerAddress 代表 FINALLY_HANDLER,JumpTarget 等于 0。
    这四个域通常都是 RVA,但当 EXCEPT_FILTER 简单地返回或等于 EXCEPTION_EXECUTE_HANDLER 时,HandlerAddress 可能直接等于 EXCEPTION_EXECUTE_HANDLER,而不再是一个 RVA。

    我们可以通过 windbg 中的 .fnent 命令来查看某个函数的异常注册信息。比如,

    1 kd> .fnent passThrough!SehTest
    2 Debugger function entry 00000000`00778210 for:
    3 d:workspacecodemycode 0passthroughpassthrough.c(51)
    4 (fffffadf`f140f020) PassThrough!SehTest | (fffffadf`f140f0c0) PassThrough!Caller2
    5 Exact matches:
    6 PassThrough!SehTest (void)

    8 BeginAddress = 00000000`00001020
    9 EndAddress = 00000000`000010b2
    10 UnwindInfoAddress = 00000000`00002668
    11 
    12 Unwind info at fffffadf`f1410668, 10 bytes
    13 version 1, flags 1, prolog 4, codes 1
    14 handler routine: PassThrough!_C_specific_handler (fffffadf`f140f4ce), data 3
    15 00: offs 4, unwind op 2, op info 4 UWOP_ALLOC_SMALL.

    行8到行10描述的是 RUNTIME_FUNCTION。
    行12到行15描述的是 UNWIND_INFO。

    对于叶函数,输出是这样的,

    kd> .fnent passthrough!LeafTest
    No function entry for fffffadf`f240c080

    到这里,异常注册就讲完了,我们认识了相关的数据结构和定位方法。下面我们进入异常分发流程。


    二、异常分发

    x64 异常分发过程使用的仍然是 KiDispatchException、RtlDispatchException、RtlpExecuteHandlerForException 等函数。

    其中,KiDispatchException 中有关内核异常部分的代码完全没有变化,这里我偷懒直接拷贝《SEH分析笔记(X86篇)》中的部分描述,

    原型:
    Code:
        VOID
        KiDispatchException (
            IN PEXCEPTION_RECORD ExceptionRecord,
            IN PKEXCEPTION_FRAME ExceptionFrame,
            IN PKTRAP_FRAME TrapFrame,
            IN KPROCESSOR_MODE PreviousMode,
            IN BOOLEAN FirstChance
            );
    对于内核异常,它的处理步骤如下:
    如果 FirstChance 为 TRUE,那么,
    1. 首先将该异常传达给内核调试器(KD),如果 KD 处理了该异常,那么函数返回。
    2. KD 没有处理,调用 RtlDispatchException 进行异常分发。如果分发成功,RtlDispatchExcetpion 返回 TRUE,或者根本不返回。
    3. RtlDispatchException 分发失败,那么再给 KD 一次处理机会,如果还是没有处理,那么 BUGCHECK。
    如果 FirstChance 为 FALSE,那么将该异常传达给 KD,如果 KD 没有处理,那么 BUGCHECK。
    它的源码实现位于 $WRK-v1.2ase toskeamd64exceptn.c:430。

    RtlDispatchException 发生了一些改变。
    从之前描述的异常注册信息的数据结构可以发现,x64 中已经不存在异常注册链这个概念了(虽然 _NT_TIB 结构体中还保留了 ExceptionList 域)。那 x64 中 RtlDispatchException 是如何遍历异常注册信息的呢?前面虽然有提到如何通过 EXCEPT_POINT 找到 UNWIND_INFO 结构,但是假如这个 UNWIND_INFO 没有处理该异常,如何继续遍历呢?

    在解答这个问题之前,我们先来认识一个新函数和相关的数据结构,

    Code:
        #define UNWIND_HISTORY_TABLE_SIZE 12
        
        typedef struct _UNWIND_HISTORY_TABLE_ENTRY {
                ULONG64 ImageBase;
                PRUNTIME_FUNCTION FunctionEntry;
        } UNWIND_HISTORY_TABLE_ENTRY, *PUNWIND_HISTORY_TABLE_ENTRY;
        
        #define UNWIND_HISTORY_TABLE_NONE 0
        #define UNWIND_HISTORY_TABLE_GLOBAL 1
        #define UNWIND_HISTORY_TABLE_LOCAL 2
        
        typedef struct _UNWIND_HISTORY_TABLE {
                ULONG Count;
                UCHAR Search;
                ULONG64 LowAddress;
                ULONG64 HighAddress;
                UNWIND_HISTORY_TABLE_ENTRY Entry[UNWIND_HISTORY_TABLE_SIZE];
        } UNWIND_HISTORY_TABLE, *PUNWIND_HISTORY_TABLE;

        PRUNTIME_FUNCTION
        RtlLookupFunctionEntry (
            IN ULONG64 ControlPc,
            OUT PULONG64 ImageBase,
            IN OUT PUNWIND_HISTORY_TABLE HistoryTable OPTIONAL
            );
    RtlLookupFunctionEntry 的功能是查找指定地址所在函数的 RUNTIME_FUNCTION 和所在模块的基地址。它的参数分为为,
    ControlPc —— 需要查找的指令地址,
    ImageBase —— 返回的模块基地址,
    HistoryTable —— 用于加速查找。

    工作流程:
    RtlLookupFunctionEntry 在搜索的过程会根据是否传入 HistoryTable 而采取不同的搜索方法:
    如果传入 HistoryTable,则根据 HistoryTable->Search 表示的搜索方式在表中进行搜索:
    如果搜索方式为 UNWIND_HISTORY_TABLE_NONE,那么不在 HistoryTable 中进行搜索。
    如果搜索方式为 UNWIND_HISTORY_TABLE_GLOBAL,则首先在全局表 RtlpUnwindHistoryTable 开始搜索,如果搜索到,则结束搜索,函数返回。否则再在 HistoryTable 中搜索。
    如果搜索方式为 UNWIND_HISTORY_TABLE_LOCAL,那么在 HistoryTable 中搜索。
    如果上述过程中没有搜索到需要的结果,那么找到模块基地址,从模块的 PE+ 头结构中解析出 RUNTIME_FUNCTION。如果搜索方式为 UNWIND_HISTORY_TABLE_NONE,还会将解决加入到 HistoryTable。

    之前的描述中 RtlDispatchException 定位 EXCEPT_POINT 所对应的 RUNTIME_FUNCTION 就是通过调用 RtlLookupFunctionEntry 实现的。

    回到刚才的问题,如何推动遍历呢?为了解决这个问题,x64 又引进了一个新函数。现在我们有请 x64 SEH 核心成员 RtlVirtualUnwind 登场~

    先来看看它的原型:
    Code:
        PEXCEPTION_ROUTINE
        RtlVirtualUnwind (
            IN ULONG HandlerType,
            IN ULONG64 ImageBase,
            IN ULONG64 ControlPc,
            IN PRUNTIME_FUNCTION FunctionEntry,
            IN OUT PCONTEXT ContextRecord,
            OUT PVOID *HandlerData,
            OUT PULONG64 EstablisherFrame,
            IN OUT PKNONVOLATILE_CONTEXT_POINTERS ContextPointers OPTIONAL
            );
    它的主要功能是:
    根据传入的 ControlPc 和 ContextRecord 等参数虚拟(模拟)展开该函数,并返回该函数的一些信息,比如 HandlerData(SCOPE_TABLE)、EstablisherFrame(rsp 或 栈帧)。
    流程是:
    1. 通过 FunctionEntry 和 ImageBase 找到 UNWIND_INFO。根据 UNWIND_INFO 中记录的信息,查找 EstablisherFrame(即栈帧或者 rsp)。
    2. 根据 ControlPc 分如下两种情况展开:
    a. ControlPc >= EpilogOffset,即 ControlPc 在 Epilog 之中。那么把剩余的 Epilog 指令模拟执行完毕即可。
    所谓模拟是指,如果下一条 EpiLog 指令是“sub rsp, 0x32”,那么将 ContextRecord->rsp 减去0x32。并不是真正执行 sub 指令。
    b. ControlPc < EpilogOffset,那么把 Prolog 反向模拟回滚一遍即可。
    所谓反向回滚是指,如果 Prolog 的指令是
    mov [RSP + 8], RCX
    push R15
    push R14
    push R13 
    那么反向回滚就是
    pop ContextRecord->R13 (实际上是从 ContextRecord->Rsp 指向的内存中取出值存入 ContextRecord->R13,然后 ContextRecord->Rsp 加上8。并不是真正执行 pop)
    pop ContextRecord->R14
    pop ContextRecord->R15
    mov ContextRecord->RCX, [ContextRecord->RSP+8]
    然后把 ContextRecord->Rip 修改为 ControlPc 所在函数的返回地址,即父函数中的某一处 call 的下一条指令。
    这样,ContextRecord 就被恢复成父函数在调用 ControlPc 所在函数之后的状态了。
    3. 如果 HandlerType 包含 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,那么将 UNWIND_INFO::ExceptionData 赋给传出参数 HandlerData,并返回 UNWIND_INFO::ExceptionRoutine。
    对于 MSC 编译器生成的模块,UNWIND_INFO::ExceptionRoutine 一般指向 nt!__C_specific_handler。UNWIND_INFO::ExceptionData 指向 ControlPc 所在函数的 SCOPE_TABLE。
    RtlVirtualUnwind 的实现源码位于 $WRK-v1.2ase tos tlamd64exdsptch.c:1202。

    RtlVirtualUnwind 返回后,RtlDispatchException 就可以根据 ContextRecord->Rip 找到父函数对应的 RUNTIME_FUNCTION,进而找到 UNWIND_INFO。就这样推动整个遍历过程。

    这是一般情况,对于没有 UNWIND_INFO 的叶函数呢?
    对于叶函数,RtlLookupFunctionEntry 返回 NULL,于是 RtlDispatchException 知道这是个叶函数,就找到该叶函数的父函数,从父函数继续遍历。也就是完全无视叶函数,因为叶函数对整个异常处理过程没有任何影响。

    RtlDispatchException 调用 UNWIND_INFO::ExceptionHandler 依然是通过 RtlpExecuteHandlerForException,其函数原型没有变化:

    Code:
        EXCEPTION_DISPOSITION
        RtlpExecuteHandlerForException (
            IN PEXCEPTION_RECORD ExceptionRecord,
            IN PVOID EstablisherFrame,
            IN OUT PCONTEXT ContextRecord,
            IN OUT PVOID DispatcherContext
            );
    该函数的实现源码位于 $WRK-v1.2ase tos tlamd64xcptmisc.asm:84。
    RtlpExecuteHandlerForException 的逻辑较 x86 版本没什么大变化,内部注册了一个异常处理函数 RtlpExceptionHandler。RtlpExceptionHandler 相当于 x86 中的 nt!ExecuteHandler2,其内部会返回 ExceptionNestedException 或 ExceptionContinueSearch。它的实现源码位于 $WRK-v1.2ase tos tlamd64xcptmisc.asm:26。

    需要一提的是,最后一个参数 DispatchContext 的类型是 DISPATCHER_CONTEXT,相对于 x86 版本,它扩充了很多,

    Code:
        typedef struct _DISPATCHER_CONTEXT {
            ULONG64 ControlPc;
            ULONG64 ImageBase;
            PRUNTIME_FUNCTION FunctionEntry;
            ULONG64 EstablisherFrame;
            ULONG64 TargetIp;
            PCONTEXT ContextRecord;
            PEXCEPTION_ROUTINE LanguageHandler;
            PVOID HandlerData;
            PUNWIND_HISTORY_TABLE HistoryTable;
            ULONG ScopeIndex;
            ULONG Fill0;
        } DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
    成员分别为:
    ControlPc —— 异常触发点。
    ImagePase —— ControlPc 所在模块的基地址。
    FunctionEntry —— ControlPc 所在函数的 RUNTIME_FUNCTION。
    EstablisherFrame —— ControlPc 所在函数的栈帧(如果建立了栈帧)或 RSP。
    TargetIp —— 解决异常的 EXCEPT_HANDLER 地址,该成员只在展开的过程中被使用。RtlpExecuteHandlerForException 没有使用它。
    ContextRecord —— 供展开过程中使用,只有当展开过程中触发新异常(返回 ExceptionCollidedUnwind)时,才会被 RtlDispatchException 真正的使用到(参考 RtlDispatchException 处理 ExceptionCollidedUnwind 的代码)。
    LanguageHandler —— ControlPc 所在函数的 UNWIND_INFO::ExceptionRoutine。
    HandlerData —— ControlPc 所在函数的 UNWIND_INFO::ExceptionData。
    ScopeIndex —— UNWIND_INFO::ExceptionData 中 SCOPE_TABLE::ScopeRecord 的索引,通常设置为0(注:请不要与 x86 中运行时不断改变的 EXCEPTION_REGISTRATION::trylevel 相混淆,ScopeIndex 不会在在函数执行过程中改变)
    Fill0 —— 未用。

    再看一下它的 .fnent 输出,

    1 kd> .fnent nt!RtlpExecuteHandlerForException
    2 Debugger function entry 00000000`01458210 for:
    3 (fffff800`008bd950) nt!RtlpExecuteHandlerForException | (fffff800`008bd970) nt!RtlpUnwindHandler
    4 Exact matches:
    5 nt!RtlpExecuteHandlerForException (void)

    7 BeginAddress = 00000000`000bd950
    8 EndAddress = 00000000`000bd963
    9 UnwindInfoAddress = 00000000`000dfeb8
    10 
    11 Unwind info at fffff800`008dfeb8, 10 bytes
    12 version 1, flags 3, prolog 4, codes 1
    13 handler routine: nt!RtlpExceptionHandler (fffff800`008bd920), data 0
    14 00: offs 4, unwind op 2, op info 4 UWOP_ALLOC_SMALL.

    行12中显示 flags 等于3,即 UNW_FLAG_EHANDLER (0x1) | UNW_FLAG_UHANDLER (0x2),说明行13中显示的异常处理函数 nt!RtlpExceptionHandler 既负责解决异常,也负责展开。

    RtlpExecuteHandlerForException 会调用 DISPATCHER_CONTEXT::LanguageHandler。对于 MSC 编译得到的模块,它是 nt!__C_specific_handler,我们来看看这个函数,

    原型:
    Code:
        EXCEPTION_DISPOSITION 
        __C_specific_handler (
            IN PEXCEPTION_RECORD pExceptionRecord,
            IN PVOID pEstablisherFrame,
            IN OUT PCONTEXT pContext,
            IN OUT PVOID pDispatcherContext
            );
    反汇编码:
    Code:
    kd> uf nt!__C_specific_handler
                   nt!__C_specific_handler:
                   fffff800`008a42d0 mov     qword ptr [rsp+10h],rdx ; 在栈上保存 pEstablisherFrame
                   fffff800`008a42d5 mov     rax,rsp
                   fffff800`008a42d8 sub     rsp,88h
                   fffff800`008a42df mov     qword ptr [rax-8],rbx
                   fffff800`008a42e3 mov     qword ptr [rax-10h],rbp
                   fffff800`008a42e7 mov     rbp,qword ptr [r9]      ; rbp = pDispatcherContext->ControlPc
                   fffff800`008a42ea mov     qword ptr [rax-18h],rsi
                   fffff800`008a42ee mov     qword ptr [rax-20h],rdi
                   fffff800`008a42f2 mov     qword ptr [rax-28h],r12
                   fffff800`008a42f6 mov     r12,qword ptr [r9+38h]  ; r12 = pDispatcherContext->HandlerData
                   fffff800`008a42fa mov     qword ptr [rax-30h],r13
                   fffff800`008a42fe mov     qword ptr [rax-38h],r14
                   fffff800`008a4302 mov     r14,qword ptr [r9+8]    ; r14 = pDispatcherContext->ImageBase
                   fffff800`008a4306 mov     qword ptr [rax-40h],r15
                   fffff800`008a430a mov     r13,r9                  ; r13 = pDispatcherContext
                   fffff800`008a430d sub     rbp,r14                 ; l_OffsetInFunc = pDispatcherContext->ControlPc - pDispatcherContext->ImageBase
                   fffff800`008a4310 test    byte ptr [rcx+4],66h    ; pExceptionRecord->ExceptionFlags, EXCEPTION_UNWIND (0x66)
                   fffff800`008a4314 mov     rsi,rdx                 ; rsi = pEstablisherFrame
                   fffff800`008a4317 mov     r15,rcx                 ; r15 = pExceptionRecord
    <              fffff800`008a431a jne     nt!__C_specific_handler+0xf5 (fffff800`008a43c5)
    :              
    :              -------------------------------------------------------------------
    :              nt!__C_specific_handler+0x50:
    :              fffff800`008a4320 movsxd  rdi,dword ptr [r9+48h]  ; l_ScopeIndex (rdi) = pDispatcherContext->ScopeIndex
    :              fffff800`008a4324 mov     qword ptr [rax-58h],rcx ; [rax-58h] = pExceptionRecord,供给 GetExceptionCode(Information) 使用
    :              fffff800`008a4328 mov     qword ptr [rax-50h],r8  ; [rax-50h] = pContext,供给 GetExceptionCode(Information) 使用
    :              fffff800`008a432c cmp     edi,dword ptr [r12]     ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
    :              fffff800`008a4330 mov     rax,rdi                 ; rax = l_ScopeIndex
    :<             fffff800`008a4333 jae     nt!__C_specific_handler+0x166 (fffff800`008a4436)
    ::             
    ::             nt!__C_specific_handler+0x69:
    ::             fffff800`008a4339 add     rax,rax             ; 这里 *2,下面紧接着 *8,目的是跳过指定数目的 ScopeRecord(大小为16字节)
    ::             fffff800`008a433c lea     rbx,[r12+rax*8+0Ch] ; rbx = &(pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress)
    ::             
    ::             nt!__C_specific_handler+0x71:
    ::             ; 检查 ControlPc 处于哪个 __try 保护域,之步骤一
    ::      >      fffff800`008a4341 mov     eax,dword ptr [rbx-8] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
    ::      :      fffff800`008a4344 cmp     rbp,rax               ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
    ::<     :      fffff800`008a4347 jb      nt!__C_specific_handler+0xdd (fffff800`008a43ad)
    :::     :      
    :::     :      nt!__C_specific_handler+0x79:
    :::     :      ; 检查 ControlPc 处于哪个 __try 保护域,之步骤二
    :::     :      fffff800`008a4349 mov     eax,dword ptr [rbx-4] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
    :::     :      fffff800`008a434c cmp     rbp,rax               ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
    :::<    :      fffff800`008a434f jae     nt!__C_specific_handler+0xdd (fffff800`008a43ad)
    ::::    :      
    ::::    :      nt!__C_specific_handler+0x81:
    ::::    :      ; 判断是否是 __try/__finally(JumpTarget 为 NULL)。如果是,那么跳转到下一个 ScopeRecord 继续遍历。
    ::::    :      fffff800`008a4351 cmp     dword ptr [rbx+4],0 ; cmp pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget, NULL
    ::::<   :      fffff800`008a4355 je      nt!__C_specific_handler+0xdd (fffff800`008a43ad)
    :::::   :      
    :::::   :      nt!__C_specific_handler+0x87:
    :::::   :      ; 到这里,已经找到与异常地址最匹配的 __try/__except
    :::::   :      fffff800`008a4357 mov     eax,dword ptr [rbx] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress
    :::::   :      fffff800`008a4359 cmp     eax,1               ; cmp pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress, EXCEPTION_EXECUTE_HANDLER (0x1)
    :::::<  :      fffff800`008a435c je      nt!__C_specific_handler+0xa3 (fffff800`008a4373) ; 如果返回 EXCEPTION_EXECUTE_HANDLER 则跳转
    ::::::  :      
    ::::::  :      nt!__C_specific_handler+0x8e:
    ::::::  :      ; 是 __try/__except,且过滤域并不是 EXCEPTION_EXECUTE_HANDLER,执行 HandlerAddress 
    ::::::  :      ; (注:HandlerAddress 指向的函数仍有可能会返回 EXCEPTION_EXECUTE_HANDLER) 
    ::::::  :      fffff800`008a435e lea     rcx,[rsp+30h]
    ::::::  :      fffff800`008a4363 add     rax,r14 ; rax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].HandlerAddress + pDispatcherContext->ImageBase
    ::::::  :      fffff800`008a4366 mov     rdx,rsi ; rdx = pEstablisherFrame
    ::::::  :      fffff800`008a4369 call    rax     ; 调用 EXCEPT_FILTER
    ::::::  :      fffff800`008a436b test    eax,eax
    ::::::< :      fffff800`008a436d js      nt!__C_specific_handler+0xee (fffff800`008a43be) ; 返回 EXCEPTION_CONTINUE_EXECUTION (-1) 则跳转
    ::::::: :      
    ::::::: :      nt!__C_specific_handler+0x9f:
    ::::::: :      fffff800`008a436f test    eax,eax
    :::::::<:      fffff800`008a4371 jle     nt!__C_specific_handler+0xdd (fffff800`008a43ad) ; 返回 EXCEPTION_CONTINUE_SEARCH (0) 则跳转
    :::::::::      
    :::::::::      nt!__C_specific_handler+0xa3:
    :::::::::      ; 返回的是 EXCEPTION_EXECUTE_HANDLER
    :::::>:::      fffff800`008a4373 mov     ecx,dword ptr [rbx+4] ; ecx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
    ::::: :::      fffff800`008a4376 mov     r8d,1
    ::::: :::      fffff800`008a437c mov     rdx,rsi ; rdx = pEstablisherFrame
    ::::: :::      fffff800`008a437f add     rcx,r14 ; rcx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget + pDispatcherContext->ImageBase
    ::::: :::      fffff800`008a4382 call    nt!_NLG_Notify (fffff800`008b1460)
    ::::: :::      fffff800`008a4387 mov     rax,qword ptr [r13+40h] ; rax = pDispatcherContext->HistoryTable
    ::::: :::      fffff800`008a438b mov     edx,dword ptr [rbx+4]   ; edx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
    ::::: :::      fffff800`008a438e movsxd  r9,dword ptr [r15]      ; r9 = pExceptionRecord->ExceptionCode
    ::::: :::      fffff800`008a4391 mov     qword ptr [rsp+28h],rax ; _ARG_6 = pDispatcherContext->HistoryTable
    ::::: :::      fffff800`008a4396 mov     rax,qword ptr [r13+28h] ; rax = pDispatcherContext->ContextRecord
    ::::: :::      fffff800`008a439a add     rdx,r14 ; rdx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget + pDispatcherContext->ImageBase
    ::::: :::      fffff800`008a439d mov     r8,r15  ; r8 = pExceptionRecord
    ::::: :::      fffff800`008a43a0 mov     rcx,rsi ; rcx = pEstablisherFrame
    ::::: :::      fffff800`008a43a3 mov     qword ptr [rsp+20h],rax ; _ARG_5 = pDispatcherContext->ContextRecord
    ::::: :::      fffff800`008a43a8 call    nt!RtlUnwindEx (fffff800`00891e80) ; 这里不会返回
    ::::: :::      ; RtlUnwindEx(pEstablisherFrame, 
    ::::: :::      ;             pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget + pDispatcherContext->ImageBase
    ::::: :::      ;             pExceptionRecord,
    ::::: :::      ;             pExceptionRecord->ExceptionCode
    ::::: :::      ;             pDispatcherContext->ContextRecord,
    ::::: :::      ;             pDispatcherContext->HistoryTable)
    ::::: :::      
    ::::: :::      nt!__C_specific_handler+0xdd:
    ::>>> :>:      fffff800`008a43ad inc     edi     ; l_ScopeIndex += 1
    ::    : :      fffff800`008a43af add     rbx,10h ; 调整到下一个 ScopeRecord::HandlerAddress
    ::    : :      fffff800`008a43b3 cmp     edi,dword ptr [r12] ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
    ::    : <      fffff800`008a43b7 jb      nt!__C_specific_handler+0x71 (fffff800`008a4341)
    ::    :        
    ::    :        nt!__C_specific_handler+0xe9:
    ::    :        ; pDispatcherContext->HandlerData 遍历完毕
    ::<   :        fffff800`008a43b9 jmp     nt!__C_specific_handler+0x166 (fffff800`008a4436)
    :::   :        
    :::   :        nt!__C_specific_handler+0xee:
    :::   >        fffff800`008a43be xor     eax,eax ; eax = ExceptionContinueExecution
    :::<           fffff800`008a43c0 jmp     nt!__C_specific_handler+0x16b (fffff800`008a443b)
    ::::           
    ::::           -------------------------------------------------------------------------------------
    ::::           nt!__C_specific_handler+0xf5:
    ::::           ; 设置了 EXCEPTION_UNWIND,当前是展开过程
    >:::           fffff800`008a43c5 movsxd  rdi,dword ptr [r9+48h] ; l_ScopeIndex (rdi) = pDispatcherContext->ScopeIndex
    :::           fffff800`008a43c9 mov     rsi,qword ptr [r9+20h] ; rsi = pDispatcherContext->TargetIp
    :::           fffff800`008a43cd sub     rsi,r14                ; rsi = pDispatcherContext->TargetIp - pDispatcherContext->ImageBase
    :::           fffff800`008a43d0 cmp     edi,dword ptr [r12]    ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
    :::           fffff800`008a43d4 mov     rax,rdi                ; rax = l_ScopeIndex
    :::<          fffff800`008a43d7 jae     nt!__C_specific_handler+0x166 (fffff800`008a4436)
    ::::          
    ::::          nt!__C_specific_handler+0x109:
    ::::          fffff800`008a43d9 add     rax,rax ; 
    ::::          fffff800`008a43dc lea     rbx,[r12+rax*8+8] ; rbx = &(pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress)
    ::::          
    ::::          nt!__C_specific_handler+0x111:
    ::::          ; 检查 ControlPc 处于哪个 __try 保护域,之步骤一
    ::::        > fffff800`008a43e1 mov     eax,dword ptr [rbx-4] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
    ::::        : fffff800`008a43e4 cmp     rbp,rax ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
    ::::<       : fffff800`008a43e7 jb      nt!__C_specific_handler+0x15a (fffff800`008a442a)
    :::::       : 
    :::::       : nt!__C_specific_handler+0x119:
    :::::       : ; 检查 ControlPc 处于哪个 __try 保护域,之步骤二
    :::::       : fffff800`008a43e9 mov     ecx,dword ptr [rbx] ; ecx = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
    :::::       : fffff800`008a43eb cmp     rbp,rcx ; cmp l_OffsetInFunc, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
    :::::<      : fffff800`008a43ee jae     nt!__C_specific_handler+0x15a (fffff800`008a442a)
    ::::::      : 
    ::::::      : nt!__C_specific_handler+0x120:
    ::::::      : ; 到这里,已经找到与异常地址匹配的最内层(如果有多层) __try/__except
    ::::::      : fffff800`008a43f0 cmp     rsi,rax ; cmp pDispatcherContext->TargetIp - pDispatcherContext->ImageBase, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].BeginAddress
    ::::::<     : fffff800`008a43f3 jb      nt!__C_specific_handler+0x131 (fffff800`008a4401)
    :::::::     : 
    :::::::     : nt!__C_specific_handler+0x125:
    :::::::     : fffff800`008a43f5 cmp     rsi,rcx ; cmp pDispatcherContext->TargetIp - pDispatcherContext->ImageBase, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].EndAddress
    :::::::<    : fffff800`008a43f8 ja      nt!__C_specific_handler+0x131 (fffff800`008a4401)
    ::::::::    : 
    ::::::::    : nt!__C_specific_handler+0x12a:
    ::::::::    : ; 如果标记了 EXCEPTION_TARGET_UNWIND,说明是最后一个需要局部展开的函数。但是该次局部展开只展开到 EXCEPT_HANDLER(不包含 EXCEPT_HANDLER),所以需要判断 TargetIp
    ::::::::    : fffff800`008a43fa test    byte ptr [r15+4],20h ; test pExceptionRecord->ExceptionFlags, EXCEPTION_TARGET_UNWIND (0x20)
    ::::::::<   : fffff800`008a43ff jne     nt!__C_specific_handler+0x166 (fffff800`008a4436)
    :::::::::   : 
    :::::::::   : nt!__C_specific_handler+0x131:
    ::::::>>:   : fffff800`008a4401 mov     eax,dword ptr [rbx+8] ; eax = pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
    ::::::  :   : fffff800`008a4404 test    eax,eax ; 判断 pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget 是否为 NULL,即是否是 __try/__finally
    ::::::  :<  : fffff800`008a4406 je      nt!__C_specific_handler+0x13f (fffff800`008a440f) ; 如果是 __try/__finally 则跳转
    ::::::  ::  : 
    ::::::  ::  : nt!__C_specific_handler+0x138:
    ::::::  ::  : fffff800`008a4408 cmp     rsi,rax ; cmp pDispatcherContext->TargetIp, pDispatcherContext->HandlerData->ScopeRecord[l_ScopeIndex].JumpTarget
    ::::::  ::< : fffff800`008a440b je      nt!__C_specific_handler+0x166 (fffff800`008a4436)
    ::::::  ::: : 
    ::::::  ::: : nt!__C_specific_handler+0x13d:
    ::::::  :::<: fffff800`008a440d jmp     nt!__C_specific_handler+0x15a (fffff800`008a442a)
    ::::::  ::::: 
    ::::::  ::::: nt!__C_specific_handler+0x13f:
    ::::::  ::::: ; 注意这里是先修改 pDispatcherContext->ScopeIndex,然后调用 EXCEPT_HANDLER。这样如果 EXCEPT_HANDLER 触发异常,后续展开就会跳过这个 EXCEPT_HANDLER。
    ::::::  :>::: fffff800`008a440f mov     rdx,qword ptr [rsp+98h]
    ::::::  : ::: fffff800`008a4417 lea     eax,[rdi+1]             ; eax = l_ScopeIndex + 1
    ::::::  : ::: fffff800`008a441a mov     cl,1
    ::::::  : ::: fffff800`008a441c mov     dword ptr [r13+48h],eax ; pDispatcherContext->ScopeIndex = eax
    ::::::  : ::: fffff800`008a4420 mov     r8d,dword ptr [rbx+4]   ; r8d = pDispatcherContext->HandlerData->ScopeRecord[i].HandlerAddress
    ::::::  : ::: fffff800`008a4424 add     r8,r14                  ; r8 = pDispatcherContext->HandlerData->ScopeRecord[i].HandlerAddress + pDispatcherContext->ImageBase
    ::::::  : ::: fffff800`008a4427 call    r8                      ; 调用 __finally 处理块,会返回(注:对于 __try/__finally,HandlerAddress 保存的是 __finally 代码块的 RVA)
    ::::::  : ::: 
    ::::::  : ::: nt!__C_specific_handler+0x15a:
    ::::>>  : :>: fffff800`008a442a inc     edi                 ; l_ScopeIndex += 1
    ::::    : : : fffff800`008a442c add     rbx,10h             ; 调整到下一个 ScopeRecord::HandlerAddress
    ::::    : : : fffff800`008a4430 cmp     edi,dword ptr [r12] ; cmp l_ScopeIndex, pDispatcherContext->HandlerData->Count
    ::::    : : < fffff800`008a4434 jb      nt!__C_specific_handler+0x111 (fffff800`008a43e1)
    ::::    : :   
    ::::    : :   nt!__C_specific_handler+0x166:
    >>:>    > >   fffff800`008a4436 mov     eax,1 ; eax = ExceptionContinueSearch (0n1)
       :           
       :           nt!__C_specific_handler+0x16b:
       >           fffff800`008a443b mov     r15,qword ptr [rsp+48h]
                   fffff800`008a4440 mov     r14,qword ptr [rsp+50h]
                   fffff800`008a4445 mov     r13,qword ptr [rsp+58h]
                   fffff800`008a444a mov     r12,qword ptr [rsp+60h]
                   fffff800`008a444f mov     rdi,qword ptr [rsp+68h]
                   fffff800`008a4454 mov     rsi,qword ptr [rsp+70h]
                   fffff800`008a4459 mov     rbp,qword ptr [rsp+78h]
                   fffff800`008a445e mov     rbx,qword ptr [rsp+80h]
                   fffff800`008a4466 add     rsp,88h
                   fffff800`008a446d ret
    nt!__C_specific_handler 相当于 x86 中的 nt!_except_handler3。从上面的反汇编代码也可以看出它的逻辑跟 nt!_except_handler3 基本上一致。
    函数代码不长。主要分为两个大分支,一个分支处理异常,一个分支处理展开(我用横线分隔开了)。

    异常解决的代码负责遍历 SCOPE_TABLE,依次调用 SCOPE_TABLE::ScopeRecord.HandlerAddress 代表的 EXCEPT_FILTER,并针对返回值做出相应的处理:
    1. 返回 EXCEPTION_CONTINUE_EXECUTION,说明异常已经被 EXCEPT_FILTER 修复。返回 ExceptionContinueExecution。
    2. 返回 EXCEPTION_CONTINUE_SEARCH,继续遍历下一个 ScopeRecord。
    3. 返回 EXCEPTION_EXECUTE_HANDLER,说明当前 ScopeRecord.JumpTarget 代表的 EXCEPT_HANDLER 可以处理该异常。那么调用 RtlUnwindEx 进行展开。

    熟悉 x86 的朋友可能会疑惑:在 x86 中 nt!_except_handler3 先进行全局展开,然后对本函数自身进行不完全的局部展开,最后执行 EXCEPT_HANDLER。而在 nt!__C_specific_handler 中却找不到执行 EXCEPT_HANDLER 的指令,这是怎么回事?
    实际上,x64 对这个流程做了一些调整,EXCEPT_HANDLER 不是由 nt!__C_specific_handler 直接调用,而是作为参数传给 RtlUnwindEx,RtlUnwindEx 处理完展开之后才执行 EXCEPT_HANDLER。后续我们在讲展开的时候会看到具体的方法。

    __C_specific_handler 的展开分支,是对 SCOPE_TABLE 进行展开,逻辑很简单,不多讲了。

    更详细的信息,请参考上面反汇编代码中我附的注释。

    另外还需要说一下 SCOPE_TABLE。
    在 x86 中,遍历 scopetable 时是通过运行时动态改变的 EXCEPTION_REGISTRATION::trylevel 来确定应该首先遍历哪一个 scopetable_entry。而 x64 中没有等同于 trylevel 的数据,有的朋友可能会说“SCOPE_TABLE 中不是有每个 __try 保护域的范围 RVA 吗?通过范围不就可以确定在哪个 __try 中触发了异常吗?”。
    我们可以先试试这种方法,以下面这段伪码为例,

    Code:
        1 VOID SehTest()
        2 {
        3     __try // 1
        4     {
        5     }
        6     __except()
        7     {
        8     }
        9 
        10    __try // 2
        11    {
        12        __try // 3
        13        {
        14            ...
        15        }
        16        __except()
        17        {
        18        }
        19    }
        20    __except()
        21    {
        22    }
        23
        24    __try // 4
        25    {
        26    }
        27    __finally()
        28    {
        29    }
        30}
    上述伪码中总共有4个 __try,按照 x86 中的方法,SCOPE_TABLE 的内容应该是顺序排列的,像这样:

    SCOPE_TABLE::Count 等于4,
    SCOPE_TABLE::ScopeRecord[0] 表示行3开始的 __try/__except,
    SCOPE_TABLE::ScopeRecord[1] 表示行10开始的 __try/__except,
    SCOPE_TABLE::ScopeRecord[2] 表示行12开始的 __try/__except,
    SCOPE_TABLE::ScopeRecord[3] 表示行24开始的 __try/__finally。

    假设行14处触发了异常,遍历过程应该是这样,
    首先检查 ScopeRecord[0],发现其范围不包含 EXCEPT_POINT,继续下一个,
    开始检查 ScopeRecord[1],范围匹配了。

    那是不是该把异常交给 ScopeRecord[1] 处理呢?
    不是!从伪码中可以很明显的看出,行14触发的异常应该首先由行12开始的 __try/__except,即 ScopeRecord[1] 处理。

    可见这种方法是行不通的。
    MSC 通过调整 SCOPE_TABLE::ScopeRecord 的排列顺序来解决这个问题:

    SCOPE_TABLE::Count 等于4,
    SCOPE_TABLE::ScopeRecord[0] 表示行3开始的 __try/__except,
    SCOPE_TABLE::ScopeRecord[1] 表示行12开始的 __try/__except,
    SCOPE_TABLE::ScopeRecord[2] 表示行10开始的 __try/__except,
    SCOPE_TABLE::ScopeRecord[3] 表示行24开始的 __try/__finally。

    即对于嵌套的 __try/__except/__finally,ScopeRecord 的排列顺序是,最内层的 __try 排在前面,其次是次内层的,依次排到最外层。
    这样就能正确的遍历 SCOPE_TABLE 了。

    再用伪码完整的展示一下 SCOPE_TABLE 的布置,

    SCOPE_TABLE::Count = 4。

    SCOPE_TABLE::ScopeRecord[0].BeginAddress = RVA_L4; (行4的 RVA) // 第一个 __try
    SCOPE_TABLE::ScopeRecord[0].EndAddress = RVA_L5;
    SCOPE_TABLE::ScopeRecord[0].HandlerAddress = RVA_L6_EXCEPT_FILTER; (行6 __except 过滤代码首地址的 RVA)
    SCOPE_TABLE::ScopeRecord[0].JumpTarget = RVA_L7; 

    SCOPE_TABLE::ScopeRecord[1].BeginAddress = RVA_L13; // 第三个 __try
    SCOPE_TABLE::ScopeRecord[1].EndAddress = RVA_L15;
    SCOPE_TABLE::ScopeRecord[1].HandlerAddress = RVA_L16_EXCEPT_FILTER; 
    SCOPE_TABLE::ScopeRecord[1].JumpTarget = RVA_L7; 

    SCOPE_TABLE::ScopeRecord[2].BeginAddress = RVA_L11; // 第二个 __try
    SCOPE_TABLE::ScopeRecord[2].EndAddress = RVA_L19;
    SCOPE_TABLE::ScopeRecord[2].HandlerAddress = RVA_L20_EXCEPT_FILTER;
    SCOPE_TABLE::ScopeRecord[2].JumpTarget = RVA_L21; 

    SCOPE_TABLE::ScopeRecord[3].BeginAddress = RVA_L25; // 第四个 __try
    SCOPE_TABLE::ScopeRecord[3].EndAddress = RVA_L26;
    SCOPE_TABLE::ScopeRecord[3].HandlerAddress = RVA_L28;
    SCOPE_TABLE::ScopeRecord[3].JumpTarget = 0; 

    我们再模拟一下 nt!__C_specific_handler 是如何遍历 SCOPE_TABLE 的:
    1. 首先通过传入参数中的 pDispatcherContext->ControlPc 和 pDispatcherContext->ImageBase 计算出异常触发点的 RVA(简称 E_RVA)。参见 fffff800`008a430d 处的指令。
    2. 通过 pDispatcherContext->ScopeIndex 确认是否需要遍历。如果需要遍历,则从它指定的 ScopeRecord 开始遍历。pDispatcherContext->ScopeIndex 一般都为0,只有返回 ExceptionCollidedUnwind 时,RtlDispatchException 才可能将它设置为其他值。
    3. 通过比较 E_RVA 和 ScopeRecord[?].BeginAddress、ScopeRecord[?].EndAddress 来找到正确的处理函数,
    首先 ScopeRecord[0] 范围不匹配,遍历下一个,
    然后 ScopeRecord[1],发现范围匹配,并且是 __try/__except 组合。于是调用 ScopeRecord[1].HandlerAddress,假设它返回的是 EXCEPTION_CONTINUE_SEARCH,那么继续遍历下一个,
    这次是 ScopeRecord[2],发现范围匹配,并且是 __try/__except 组合。于是调用 ScopeRecord[2].HandlerAddress,假设它返回的是 EXCEPTION_EXECUTE_HANDLER,那么说明找到了解决方案。
    4. 调用 RtlUnwindEx,把 ScopeRecord[2].JumpTarget 对应的绝对地址作为 TargetIp 参数传给它。RtlUnwindEx 全局展开完毕后执行 TargetIp。

    到这里,异常分发就大致讲述完毕。接下来是关于展开和解决的内容。


    三、展开、解决

    x64 中展开使用的函数有 RtlVirtualUnwind、RtlUnwindEx 和 RtlpExecuteHandlerForUnwind。其中 RtlVirtualUnwind 已经讲了,我们来看看余下的两个。

    首先是 RtlUnwindEx,原型如下:

    Code:
        VOID
        RtlUnwindEx (
            IN PVOID TargetFrame OPTIONAL,
            IN PVOID TargetIp OPTIONAL,
            IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL,
            IN PVOID ReturnValue,
            IN PCONTEXT OriginalContext,
            IN PUNWIND_HISTORY_TABLE HistoryTable OPTIONAL
            );
    参数分别是:
    TargetFrame —— 目标帧,即最后一个需要展开的帧。
    TargetIp —— 前面有讲过,它就是 ScopeRecord[?].JumpTarget 代表的地址,即 EXCEPT_HANDLER。
    ExceptionRecord —— 异常信息。
    ReturnValue —— 传递给 TargetIp 的返回值,分析过程中没发现它有什么用处。
    OriginalContext —— 虽然被声明为 IN,但是实际上 RtlUnwindEx 并没有使用它内部的数据,
    HistoryTable —— 用于加速查找 RUNTIME_FUNCTION。

    主要功能是:
    从自身开始展开,到 TargetFrame 停止。然后跳转到 TargetIp 继续执行。
    流程:
    1. 申请一个类型为 CONTEXT 的局部变量 l_Context,调用 RtlCaptureContext 将当前自身的环境复制到 l_Context。
    2. 通过 RtlVirtualUnwind 对 l_Context 进行模拟展开,推动遍历。对每个遍历到的 UNWIND_INFO,检查 UNWIND_INFO::Flags 是否包含 UNW_FLAG_UHANDLER。如果包含,则调用 UNWIND_INFO::ExceptionHandler 进行局部展开。否则继续遍历下一个。
    循环本步骤,直到展开到 TargetFrame,即到达解决异常的 EXCEPT_HANDLER 所在的函数(简称为 ExceptionHandlerFunc)了。
    3. 这时 l_Context 内已经是从 RtlUnwindEx 完整展开到 EXCEPT_HANDLER 的环境了。即此时 l_Context 已经是 ExceptionHandlerFunc 的执行环境了。
    调用 RtlRestoreContext,用 l_Context 替换当前线程的执行环境,于是就跳转到 EXCEPT_HANDLER 继续执行。
    这样就完美的从触发异常的环境跳到了新的环境中。

    这个过程有点类似这种手法:
    1. 将某台机器的系统 ghost 到一个 bak.gho 文件。
    2. 把 bak.gho 恢复到一台临时机器,然后对这台临时机器做一些调整。调整完毕后制作一个临时机器的 bak_mod.gho。
    3. 将 bak_mod.gho 恢复到原来那台机器。

    这个流程很重要,我手绘了一副图帮助理解,

    伪码:
    Code:
        __try
        {
            ExRaiseStatus(STATUS_INVALID_PARAMETER);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
        }

    图:
    1. 异常解决流程,从 EXCEPT_POINT 到 RtlUnwindEx,途中已经找到能够解决该异常的 EXCEPT_HANDLER 了(以参数 TargetIp 表示),当前线程状态为 ThreadContext
    Code:
    +---------------------------------------+
    | ......                                |
    | RtlRaiseStatus                    |调 |
    | RtlDispatchException              |用 |-> ThreadContext &
    | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler
    | __C_specific_handler              |向 |
    | RtlUnwindEx                       v   |
    |                                       |
    +---------------------------------------+

    2. RtlUnwindEx 将当前自身状态复制到 ThreadContext_Copy 中
    Code:
    +---------------------------------------+                                     +---------------------------------------+
    | ......                                |                                     | ......                                |
    | RtlRaiseStatus                    |调 |                                     | RtlRaiseStatus                    |调 |
    | RtlDispatchException              |用 |-> ThreadContext &                   | RtlDispatchException              |用 |-> ThreadContext_Copy &
    | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler       | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler
    | __C_specific_handler              |向 |                                     | __C_specific_handler              |向 |
    | RtlUnwindEx                       v   |                                     | RtlUnwindEx                       v   |
    |                                       |                                     |                                       |
    +---------------------------------------+                                     +---------------------------------------+

    3. 用 ThreadContext_Copy 进行展开,一直展开到异常触发点停止。
    Code:
    +---------------------------------------+                                     +---------------------------------------+
    | .....                                 |                                     | ......                                |
    | RtlRaiseStatus                    |调 |                                     | RtlRaiseStatus                    ^展 |
    | RtlDispatchException              |用 |-> ThreadContext                     | RtlDispatchException              |开 |-> ThreadContext_Copy &
    | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler       | RtlpExecuteHandlerForException    |方 |   TargetIp = ExceptionHandler
    | __C_specific_handler              |向 |                                     | __C_specific_handler              |向 |
    | RtlUnwindEx                       v   |                                     | RtlUnwindEx                       |   |
    |                                       |                                     |                                       |
    +---------------------------------------+                                     +---------------------------------------+

    4. 将 ThreadContext_Copy.Rip 设置为 TargetIp,以 ThreadContext_Copy 为参数调用 RtlpRestoreContext。跳转到 TargetIp 继续执行。
    Code:
    +---------------------------------------+
    | ......                                |
    | EXCEPT_HANDLER                        |-> ThreadContext (ThreadContext.Rip = TargetIp)
    |                                       |
    +---------------------------------------+
    这样就完成了展开和执行 EXCEPT_HANDLER 的工作。

    RtlpExecuteHandlerForUnwind 没有什么改变,原型依旧:

    Code:
        EXCEPTION_DISPOSITION
        RtlpExecuteHandlerForUnwind (
            IN PEXCEPTION_RECORD ExceptionRecord,
            IN PVOID EstablisherFrame,
            IN OUT PCONTEXT ContextRecord,
            IN OUT PVOID DispatcherContext
            );
    它会注册一个异常处理函数 RtlpUnwindHandle,当触发新异常时 RtlpUnwindHandler 会返回 ExceptionCollidedUnwind。关于 ExceptionCollidedUnwind,我们后面还会详细讲述。
    RtlpExecuteHandlerForUnwind 的实现源码位于 $WRK-v1.2ase tos tlamd64xcptmisc.asm:199。
    RtlpUnwindHandle 的实现源码位于 $WRK-v1.2ase tos tlamd64xcptmisc.asm:136。

    到这里,我们讲完了展开的逻辑。接下来我们要讲述两个比较特殊的返回值: ExceptionNestedException 和 ExceptionCollidedUnwind。


    四、ExceptionNestedException 和 ExceptionCollidedUnwind

    之所以专门讲述这两个返回值,是因为在分析过程中,我感觉常规情况的 SEH 流程理解起来并不困难,难理解的是这两种不一般的情况。它们不一般之处在于:在处理异常的过程中又触发了新的异常。
    先来讲一下这两个返回值的含义:
    ExceptionNestedException —— 在异常分发过程中触发新的异常,比如执行 EXCEPT_FILTER 时触发异常。
    ExceptionCollidedUnwind —— 在展开过程中触发新的异常,比如执行 FINALLY_HANDLER 时触发异常。

    首先来讲讲 ExceptionNestedException,以如下伪码为例:

    Code:
        1  VOID SehTest()
        2  {
        3      __try
        4      {
        5          ExRaiseStatus();
        6      }
        7      __except(ExRaiseStatus(), EXCEPTION_CONTINUE_SEARCH) // EXCEPT_FILTER_1
        8      { // EXCEPT_HANDLER_1
        9      }
        10 }
        11 
        12 VOID Caller()
        13 {
        14     __try
        15     {
        16         SehTest();
        17     }
        18     __except(EXCEPTION_EXECUTE_HANDLER) // EXCEPT_FILTER_2
        19     { // EXCEPT_HANDLER_2
        20     }
        21 }
    上述代码会两次触发异常,第一次是行5的 ExRaiseStatus,第二次是行7的 ExRaiseStatus。为了方便区分,我将它们分别标记为 EXCEPT_POINT#1、EXCEPT_POINT#2。
    我们来看一下这两个异常的处理流程:

    1. ExRaiseStatus#1 会创建保存 EXCEPT_POINT#1 触发时的状态 Context#1,并构建一个 EXCEPTION_RECORD,然后将他们作为参数来调用 RtlDispatchException#1。(注:这种方式的的触发点是 ExRaiseStatus 内部,而非 SehTest 的第5行。即Context#1 记录的异常触发点是 ExRaiseStatus#1 内部)

    2. RtlDispatchException#1 根据 Context#1 首先找到 EXCEPT_POINT#1 所在函数 ExRaiseStatus#1 的 UNWIND_INFO,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是继续遍历。

    3. RtlDispatchException#1 遍历到 SehTest,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_EHANDLER,于是调用其 UNWIND_INFO::ExceptionHandler,即 __C_specific_handler$2。__C_specific_handler$2 遍历 SehTest 的 SCOPE_TABLE,发现唯一的一个 ScopeRecord。于是执行 ScopeRecord[0].HandlerAddress,即行7的 SehTest::EXCEPT_FILTER_1#1。此时的调用栈如下(竖线后的内容为函数的 UNWIND_INFO::Flags 和 UNWIND_INFO::ExceptionHandler,其中 Flags 缩写为 E、U、N):
    Code:
            (1)  Caller                                 | E  & __C_specific_handler$1
            (2)  SehTest                                | E  & __C_specific_handler$2
            (3)  ExRaiseStatus#1                        | N
            (4)  RtlDispatchException#1                 | N
            (5)  RtlpExecuteHandlerForException#1       | EU & RtlpExceptionHandler$5
            (6)  __C_specific_handler$2                 | N
            (7)  EXCEPT_FILTER_1#1                      | N

    4. EXCEPT_FILTER#1 触发 EXCEPT_POINT#2。同步骤1类似,ExRaiseStatus 会调用 RtlDispatchException#2,这个过程中同样会创建保存 EXCEPT_POINT#2 的状态,我们称之为 Context#2。

    5. RtlDispatchException#2 根据 Context#2 找到了 EXCEPT_POINT#2 所在函数 ExRaiseStatus#2,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是继续遍历。

    6. RtlDispatchException#2 遍历到 EXCEPT_FILTER_1#1,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是继续遍历。(注:EXCEPT_FILTER 虽然代码形式上从属于 SehTest 函数,但实际上它是一个单独的函数,有自己的 UNWIND_INFO,跟 SEH 的 UNWIND_INFO 并不是同一个)

    7. RtlDispatchException#2 遍历到 __C_specific_handler$2、RltpExecuteHandlerForException#1、RtlDispatchException#1、E​xRaiseStatus#1,这些函数要么被标记为 UNW_FLAG_NHANDLER,要么 UNWIND_INFO::ExceptionHandler 返回 ExcetpionNestedException,结果都是继续遍历,所以不再一一讲述。继续遍历下一个。

    8. RtlDispatchException#2 遍历到 SehTest,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_EHANDLER,于是调用其 UNWIND_INFO::ExceptionRoutine,即 __C_specific_handler$2,发现范围匹配,于是调用 EXCEPT_FILTER_1#1,于是又触发异常,这次是 #3 异常。此时的调用栈如下:
    Code:
            (1)  Caller                                 | E  & __C_specific_handler$1
            (2)  SehTest                                | E  & __C_specific_handler$2
            (3)  ExRaiseStatus#1                        | N
            (4)  RtlDispatchException#1                 | N
            (5)  RtlpExecuteHandlerForException#1       | EU & RtlpExceptionHandler$5
            (6)  __C_specific_handler$2                 | N
            (7)  EXCEPT_FILTER_1#1                      | N
            (8)  ExRaiseStatus#2                        | N
            (9)  RtlDispatchException#2                 | N
            (10) RtlpExecuteHandlerForException#2       | EU & RtlpExceptionHandler$10
            (11) __C_specific_handler$2                 | N
            (12) EXCEPT_FILTER_1#2                      | N
            (13) ExRaiseStatus#3                        | N
    9. #3 异常的处理流程同 #2 的处理流程类似,也会再遍历到 __C_specific_handler$2,也会再调用 EXCEPT_FILTER_1,于是会触发 #4 异常、#5 异常等等。最终内核栈溢出,BSOD。

    以上就是 ExceptionNestedException 的产生以及处理的流程。过程中还有一些细节操作,为了描述简洁,我没有在上述过程中一一讲述。


    再来看看 ExceptionCollidedUnwind。它比 ExceptionNestedException 更复杂一些,我们以如下伪码为例,

    Code:
        1  VOID SehTest()
        2  {
        3      __try
        4      {
        5          ExRaiseStatus();
        6      }
        7      __finally
        8      { // FINALLY_HANDLER_1
        9          ExRaiseStatus();
        10     }
        11 }
        12 
        13 VOID Caller()
        14 {
        15     __try
        16     {
        17         SehTest();
        18     }
        19     __except(EXCEPTION_EXECUTE_HANDLER) // EXCEPT_FILTER_2
        20     { // EXCEPT_HANDLER_2
        21     }
        22 }
    伪码中也有两处触发异常的地方,第一次在行5,第二次在行9。也分别标记为 EXCEPT_POINT#1 和 EXCEPT_POINT#2。处理流程:

    1. ExRaiseStatus#1 创建保存 EXCEPT_POINT#1 的状态 Context#1,并构建一个 EXCEPTION_RECORD,然后将他们作为参数来调用 RtlDispatchException#1。

    2. RtlDispatchException#1 根据 Context#1 开始遍历:
    首先遍历到 EXCEPT_POINT#1 所在函数 ExRaiseStatus,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是遍历下一个。
    然后遍历到 SehTest,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_UHANDLER,于是继续遍历下一个。
    然后遍历到 Caller,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_EHANDLER,于是调用其 UNWIND_INFO::ExceptionRoutine 即 __C_specific_handler。__C_specific_handler 发现可以处理该异常,于是以 EXCEPT_HANDLER_2 为 TargetIp 参数调用 RtlUnwindEx。

    3. RtlUnwindEx 从自身开始展开,展开到 SehTest,执行 FINALLY_HANDLER_1 时触发新异常。此时调用栈为:
    Code:
        (1)  Caller                              | E  & __C_specific_handler$1
           (2)  SehTest                             | U  & __C_specific_handler$2
           (3)  ExRaiseStatus#1                     | N
           (4)  RtlDispatchException#1              | N
           (5)  RtlpExecuteHandlerForException#1    | EU & RtlpExceptionHandler$5
           (6)  __C_specific_handler$1              | N
           (7)  RtlUnwindEx#1                       | N
           (8)  RtlpExecuteHandlerForUnwind#1       | EU & RtlpUnwindHandler$8
           (9)  __C_specific_handler$2              | N
           (10) FINALLY_HANDLER_1                   | N
           (11) ExRaiseStatus#2                     | N
    需要说明的是,调用栈(7) RtlUnwindEx 创建并初始化了一个 DISPATCHER_CONTEXT 变量(后续称之为 pDispatcherContextForUnwind),并作为参数传递给调用栈(8) RltpExecuteHandlerForUnwind,后者在调用(9) __C_specific_handler$2 之前将 pDispatcherContextForUnwind 保存在自己的栈中。此时 pDispatcherContextForUnwind 的内容表示的是调用栈(2) SehTest 的情况。后续步骤会用到这个 pDispatcherContextForUnwind。

    4. (11) ExRaiseStatus#2 将 EXCEPT_POINT#2 触发时的状态保存到 Context#2,然后调用 RtlDispatchException#2 进行 EXCEPT_POINT#2 的分发。

    5. RtlDispatchException#2 根据 Context#2 开始遍历,
    首先遍历到 EXCEPT_POINT#2 所在函数 ExRaiseStatus#2,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是遍历下一个。
    然后遍历到 FINALLY_HANDLER_1(同前面提到的 EXCEPT_FILTER 一样,FINALLY_HANDLER 实际上也是一个单独的函数,有自己的 RUNTIME_FUNCTION 和 UNWIND_INFO),发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是遍历下一个。
    然后遍历到(9) __C_specific_handler$2,发现其 UNWIND_INFO::Flags 为 UNW_FLAG_NHANDLER,于是继续遍历。
    然后遍历到(8) RtlpExecuteHandlerForUnwind#1,发现其 UNWIND_INFO::Flags 包含 UNW_FLAG_EHANDLER。于是调用其 UNWIND_INFO::ExceptionRoutine 即 RtlpUnwindHandler$8。RtlpUnwindHandler$8 会取出步骤3中所提到的 pDispatcherContextForUnwind,将其内容拷贝到自己的传出参数(参考 RtlpUnwindHandler 的函数原型)pDispatcherContext 中,然后返回 ExceptionCollidedUnwind。

    6. RtlDispatchException#2 收到 ExceptionCollidedUnwind 后,从传回来的 pDispatchContext 中取出诸如 ControlPc、EstablisherFrame 等值(如步骤3所说,此时这些值反应的是(2) SehTest 的状态),用这些值来继续遍历。
    首先遍历到(2) SehTest,调用 RtlpExecuteHandlerForException#2,进而调用 __C_specific_handler$2,但是发现 pDispatcherContext->ScopeIndex(步骤(9)中在调用(10) FINALLY_HANDLER_1 之前+1了,参考 __C_specific_handler 反汇编码)等于 pDispatcherContext->HandlerData->Count。于是继续遍历。
    然后遍历到(1) Caller,调用 RtlpExecuteHandlerForException#2,进而调用 __C_specific_handler$1,发现它可以处理 #2 异常,于是以 EXCEPT_HANDLER#2 为 TargetIp 参数调用 RtlUnwindEx。此时调用栈如下:
    Code:
        (1)  Caller                              | E  & __C_specific_handler$1
           (2)  SehTest                             | U  & __C_specific_handler$2,但没有 EXCEPT_HANDLER
           (3)  ExRaiseStatus#1                     | N
           (4)  RtlDispatchException#1              | N
           (5)  RtlpExecuteHandlerForException#1    | EU & RtlpExceptionHandler$6
           (6)  __C_specific_handler$1              | N
           (7)  RtlUnwindEx#1                       | N
           (8)  RtlpExecuteHandlerForUnwind#1       | EU & RtlpUnwindHandler$8
           (9)  __C_specific_handler$2              | N
           (10) FINALLY_HANDLER#1                   | N
           (11) ExRaiseStatus#2                     | N
           (12) RtlDispatchException#2              | N
           (13) RtlpExecuteHandlerForException#2    | EU & RtlpExceptionHandler
           (14) __C_specific_handler$1              | N
           (15) RtlUnwindEx                         | N
    7. (17) RtlUnwindEx 展开完毕后,通过 RtlRestoreContext 跳转到 EXCEPT_HANDLER#2 继续执行。

    在上述过程中,我们可以发现,遍历过程中 RtlDispatchException 等系统函数被频繁遍历到。于是就有了前面提到的全局展开历史表 RtlpUnwindHistoryTable,这个表中存放着 RtlDispatchException、RtlUnwindEx 等函数的 RUNTIME_FUNCTION 和 ImageBase 信息,这样就不用每次都去解析 PE+ 中的 ExceptionDirectory,实现了加速。


    到这里,我们就讲完了 x64 SEH 的实现。可以发现,x64 和 x86 的 SEH 思想或者说框架是一样的:
    1. RtlDispatchException 和 RtlUnwindEx 都对异常注册信息进行遍历。前者是为了分发异常而遍历,后者是为了展开而遍历。
    2. MSC 提供的异常处理函数按照“异常解决”和“展开”两个分支,对 SCOPE_TABLE/scopetable 进行遍历。前者是为了找到 EXCEPT_FILTER & EXCEPT_HANDLER,后者是为了找到 FINALLY_HANDLER。
    3. RtlDispatchException 和 RtlUnwindEx 借助 MSC 提供的异常处理函数这个桥梁,配合处理异常。
    主要的改变有两点:
    1. RtlDispatchException 和 RtlUnwindEx 通过调用 RtlVirtualUnwind 推动遍历。
    2. 所有的非叶函数都参与到 SEH,尽管大部分的函数都没有使用到 SEH。

    以上我们主要讲述的是 x64 SEH 的内部实现。对于使用者,也有一个好消息,我们来看看,

    C 代码:

    Code:
        VOID SehTest()
        {
            __try
            {
                __try
                {
                    ExRaiseStatus(STATUS_INVALID_PARAMETER);
                    DbgPrint("%u [%s] __try ", __LINE__, __FILE__);
                }
                __except((STATUS_INVALID_PARAMETER == GetExceptionCode()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER)
                {
                    DbgPrint("%u [%s] __except ", __LINE__, __FILE__);
                }
            }
            __finally
            
                DbgPrint("%u [%s] __finally ", __LINE__, __FILE__);
            }
        }
    反汇编码:

    Code:
        kd> uf passthrough!SehTest
        PassThrough!SehTest:
        fffffadf`f1100020 sub     rsp,38h
        fffffadf`f1100024 mov     ecx,0C000000Dh
        fffffadf`f1100029 call    qword ptr [PassThrough!_imp_ExRaiseStatus (fffffadf`f1101050)]
        fffffadf`f110002f lea     r8,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100500)]
        fffffadf`f1100036 mov     edx,39h
        fffffadf`f110003b lea     rcx,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100540)]
        fffffadf`f1100042 call    PassThrough!DbgPrint (fffffadf`f11004a6)
        fffffadf`f1100047 jmp     PassThrough!SehTest+0x42 (fffffadf`f1100062)
        
        PassThrough!SehTest+0x42:
        fffffadf`f1100062 lea     r8,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100500)]
        fffffadf`f1100069 mov     edx,42h
        fffffadf`f110006e lea     rcx,[PassThrough! ?? ::FNODOBFM::`string' (fffffadf`f1100570)]
        fffffadf`f1100075 call    PassThrough!DbgPrint (fffffadf`f11004a6)
        fffffadf`f110007a add     rsp,38h
        fffffadf`f110007e ret
    我们发现 SehTest 内部完全没有任何 SEH 的踪迹,不像 x86 那样会有创建、销毁 EXCEPTION_REGISTRATION_RECORD 和调整 EXCEPTION_REGISTRATION_RECORD::trylevel 等操作。
    这样的好处就是使用者无需再担心性能损耗,可以放心大胆的使用 SEH 机制了。


    附录一

    为了方便自己分析,我写了一个简单的 windbg 扩展,提供了几个 x64 seh 常用功能:
    Code:
        !boxr.unwindinfo    module-name    unwindinfo_addr
        功能:
            用于查询指定 UNWIND_INFO 结构的详细信息。
        参数说明:
            module-name —— 待查询的 UNWIND_INFO 结构对应函数的模块名
            unwindinfo_addr —— UNWIND_INFO 结构的绝对地址

    Code:
        !boxr.rtfn    option    module    runtimefunction_addr
        功能:
            用于查询指定 RUNTIME_FUNCTION 结构的详细信息。(rtfn 表示 RunTime_FunctioN)
        参数说明:
            option —— 参数选项,目前支持两种:
                /a 表示 module 参数为模块基地址
                /n 表示 module 参数为模块名称
            module —— RUNTIME_FUNCTION 结构对应函数所在的模块,具体形式根据 option 而定。
            runtimefunction_addr —— 需要查询的 RUNTIME_FUNCTION 结构体的绝对地址。支持 @rax 操作方式,但不支持复杂的组合,比如 @rax+8。
    使用的方法是:用 .extpath+ 命令将 boxr.dll 所在的目录添加到 windbg 的搜索路径中,然后就可以使用了。需要卸载时就 .unload。

    简单说明一下这两个命令。

    比如我们要查看下面这个函数的 UNWIND_INFO 信息:
    Code:
        VOID SehTest()
        {
            __try
            {
                DbgPrint("%u [%s] __try ", __LINE__, __FILE__);
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                DbgPrint("%u [%s] __except ", __LINE__, __FILE__);
            }
        
            __try
            {
                __try
                {
                    __try
                    {
                        CollidedUnwind();
                    }
                    __except(EXCEPTION_EXECUTE_HANDLER)
                    {
                        DbgPrint("%u [%s] __except ", __LINE__, __FILE__);
                    }
                }
                __except(EXCEPTION_EXECUTE_HANDLER)
                {
                    DbgPrint("%u [%s] __except ", __LINE__, __FILE__);
                }
            }
            __finally
            {
                DbgPrint("%u [%s] __finally ", __LINE__, __FILE__);
            }
        
            __try
            {
                DbgPrint("%u [%s] __try ", __LINE__, __FILE__);
        
                __try
                {
                    DbgPrint("%u [%s] __try ", __LINE__, __FILE__);
                }
                __finally
                {
                    DbgPrint("%u [%s] __finally ", __LINE__, __FILE__);
                }
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                DbgPrint("%u [%s] __except ", __LINE__, __FILE__);
            }
        
            return;
        }

    操作步骤:
    1. 使用 .fnent 命令获得 SehTest 的基本信息,
    Code:
            kd> .fnent passthrough!SehTest
            Debugger function entry 00000000`00758210 for:
            (fffffadf`f135d080)   PassThrough!SehTest   |  (fffffadf`f135d180)   PassThrough!LeafTest
            Exact matches:
                PassThrough!SehTest (void)
            
            BeginAddress      = 00000000`00001080
            EndAddress        = 00000000`00001175
            UnwindInfoAddress = 00000000`000026b8
            
            Unwind info at fffffadf`f135e6b8, 10 bytes
              version 1, flags 3, prolog 4, codes 1
              handler routine: PassThrough!_C_specific_handler (fffffadf`f135d5de), data 6
              00: offs 4, unwind op 2, op info 4    UWOP_ALLOC_SMALL.
    2. 使用 !boxr.unwindinfo 查询详细信息,
    Code:
            kd> !boxr.unwindinfo passthrough fffffadf`f135e6b8
            _UNWIND_INFO for fffffadff135e6b8 
            Flags:
                EU
            ExceptionRoutine:
                PassThrough!_C_specific_handler (fffffadf`f135d5de)
            ScopeTable:
                Count: 6
                ScopeRecord[0]      (fffffadff135e6c8)
                    BeginAddress: 
                        PassThrough!SehTest+0x4 (fffffadf`f135d084)
                    EndAddress: 
                        PassThrough!SehTest+0x1e (fffffadf`f135d09e)
                    HandlerAddress: 
                        PassThrough!SehTest$filt$0 (fffffadf`f135d8a0)
                    JumpTarget: 
                        PassThrough!SehTest+0x1e (fffffadf`f135d09e)
                [省略中间3个 ScopeRecord 成员]
                ScopeRecord[5]      (fffffadff135e718)
                    BeginAddress: 
                        PassThrough!SehTest+0x8b (fffffadf`f135d10b)
                    EndAddress: 
                        PassThrough!SehTest+0xd7 (fffffadf`f135d157)
                    HandlerAddress: 
                        PassThrough!SehTest$filt$5 (fffffadf`f135d960)
                    JumpTarget: 
                        PassThrough!SehTest+0xd7 (fffffadf`f135d157)
    !boxr.rtfn 的用法也类似,比如:
    Code:
            kd> !box.rtfn /n passThrough @rax
            _RUNTIME_FUNCTION for fffffadff1113000 
            BeginAddress:
                PassThrough!CollidedUnwind (fffffadf`f1110020)
            EndAddress:
                PassThrough!CollidedUnwind+0x38 (fffffadf`f1110058)
            UnwindData:
                fffffadff1111688
            _UNWIND_INFO for fffffadff1111688 
            Flags:
                U
            ExceptionRoutine:
                PassThrough!_C_specific_handler (fffffadf`f11104ee)
            ScopeTable:
                Count: 1
                ScopeRecord[0]      (fffffadff1111698)
                    BeginAddress: 
                        PassThrough!CollidedUnwind+0x4 (fffffadf`f1110024)
                    EndAddress: 
                        PassThrough!CollidedUnwind+0x10 (fffffadf`f1110030)
                    HandlerAddress: 
                        PassThrough!CollidedUnwind$fin$0 (fffffadf`f1110750)
                    JumpTarget: 
                        0
    需要说明的是,我写这个扩展的目的仅仅是为了分析 x64 SEH 过程中能轻松的查看相关数据结构的详细信息,所以并没有在这个扩展上花很多时间。其代码是从 MS 例子代码的基础上增加了我需要的功能。应该有一些 BUG,但是对我来说不重要,已经满足我的需要了。源码也放在附件里,方便分析的朋友根据自己的需要进行修改。
    另外有一个疑问:我编译的 x64 wrk1.2 内核无法对 .c代码进行源码调试,对 .asm 代码倒是可以,这是为什么?我看了一下编译选项,没看出什么猫腻。有经验的朋友分享一下吧,感谢 :-)


    附录二 RtlUnwindEx 的反汇编代码和注释

    由于无法源码调试,只好把它反汇编出来加上注释……
    Code:
    VOID
                 RtlUnwindEx (
    /* rcx    */     IN PVOID pEstablisherFrame OPTIONAL,
    /* rdx    */     IN PVOID pJumpTargetIp OPTIONAL,
    /* r8     */     IN PEXCEPTION_RECORD pExceptionRecord OPTIONAL,
    /* r9     */     IN PVOID ReturnValue,
    /* rsp+28 */     IN PCONTEXT pOriginalContext,
    /* rsp+30 */     IN PUNWIND_HISTORY_TABLE pHistoryTable OPTIONAL
                     );

                 kd> uf nt!RtlUnwindEx
                 nt!RtlUnwindEx:
                 fffff800`00891e70 mov     qword ptr [rsp+20h],r9
                 fffff800`00891e75 mov     qword ptr [rsp+18h],r8
                 fffff800`00891e7a mov     qword ptr [rsp+10h],rdx
                 fffff800`00891e7f mov     rax,rsp
                 fffff800`00891e82 sub     rsp,678h
                 fffff800`00891e89 mov     qword ptr [rax-8],rbx
                 fffff800`00891e8d mov     qword ptr [rax-10h],rbp
                 fffff800`00891e91 mov     qword ptr [rax-18h],rsi
                 fffff800`00891e95 mov     rsi,qword ptr [rsp+6A0h] ; rsi = pOriginalContext
                 fffff800`00891e9d mov     qword ptr [rax-20h],rdi
                 fffff800`00891ea1 mov     qword ptr [rax-28h],r12
                 fffff800`00891ea5 mov     qword ptr [rax-40h],r15
                 fffff800`00891ea9 mov     rbp,rcx
                 fffff800`00891eac mov     r15,rdx
                 fffff800`00891eaf lea     rdx,[rsp+40h] ; rsp+40 为 l_HighLimit
                 fffff800`00891eb4 lea     rcx,[rsp+50h] ; rsp+50 为 l_LowLimit
                 fffff800`00891eb9 mov     rbx,r8
                 fffff800`00891ebc mov     rdi,rsi
                 fffff800`00891ebf lea     r12,[rax-518h] ; r12 = &l_Context
                 fffff800`00891ec6 call    nt!RtlpGetStackLimits (fffff800`00890da0)
                 fffff800`00891ecb mov     rcx,rsi
                 fffff800`00891ece call    nt!RtlCaptureContext (fffff800`008bd150)
                 fffff800`00891ed3 mov     rax,qword ptr [rsp+6A8h] ; rax = pHistoryTable
                 fffff800`00891edb test    rax,rax
    <            fffff800`00891ede je      nt!RtlUnwindEx+0x74 (fffff800`00891ee4)
    .            
    .            nt!RtlUnwindEx+0x70:
    .            fffff800`00891ee0 mov     byte ptr [rax+4],1 ; pHistoryTable->Search = UNWIND_HISTORY_TABLE_GLOBAL
    .            
    .            nt!RtlUnwindEx+0x74:
    >            fffff800`00891ee4 xor     ecx,ecx
                 fffff800`00891ee6 test    rbx,rbx ; 判断 pExceptionRecord 是否为 NULL
    <            fffff800`00891ee9 jne     nt!RtlUnwindEx+0xbc (fffff800`00891f2c)
    .            
    .            nt!RtlUnwindEx+0x7b:
    .            ; pExceptionRecord 等于 NULL
    .            fffff800`00891eeb mov     rax,qword ptr [rsi+0F8h]  ; rax = pOriginalContext->Rip
    .            fffff800`00891ef2 lea     rbx,[rsp+0C0h]            ; rbx = &l_ExceptionRecord
    .            fffff800`00891efa mov     dword ptr [rsp+0C0h],0C0000027h ; l_ExceptionRecord.ExceptionCode = STATUS_UNWIND (0xC0000027)
    .            fffff800`00891f05 mov     qword ptr [rsp+0D0h],rax  ; l_ExceptionRecord.ExceptionAddress = pOriginalContext->Rip
    .            fffff800`00891f0d mov     rax,qword ptr [rsp+6A8h]  ; rax = pHistoryTable
    .            fffff800`00891f15 mov     qword ptr [rsp+690h],rbx  ; [r8-home] = &l_ExceptionRecord ????
    .            fffff800`00891f1d mov     qword ptr [rsp+0C8h],rcx  ; l_ExceptionRecord.ExceptionRecord = NULL
    .            fffff800`00891f25 mov     dword ptr [rsp+0D8h],ecx  ; l_ExceptionRecord.NumberParameters = 0
    .            
    .            nt!RtlUnwindEx+0xbc:
    >            fffff800`00891f2c mov     rbx,qword ptr [rsp+680h]  ; rbx = &[rcx-home]
                 fffff800`00891f34 mov     esi,2 ; l_ExceptionFlags(esi) = EXCEPTION_UNWINDING (2)
                 fffff800`00891f39 mov     ecx,6 ; ecx = EXCEPTION_EXIT_UNWIND (6)
                 fffff800`00891f3e test    rbp,rbp ; 判断 pEstablisherFrame 是否为 NULL
                 fffff800`00891f41 mov     qword ptr [rsp+648h],r13 ; 保存 r13 
                 fffff800`00891f49 mov     qword ptr [rsp+640h],r14 ; 保存 r14
                 fffff800`00891f51 cmove   esi,ecx ; if (NULL == pEstablisherFrame) { l_ExceptionFlags = EXCEPTION_EXIT_UNWIND (6) }
                 fffff800`00891f54 xchg    ax,ax
                 fffff800`00891f58 xchg    ax,ax
                 fffff800`00891f5c xchg    ax,ax
                 
                 nt!RtlUnwindEx+0xf0:
               > fffff800`00891f60 mov     r13,qword ptr [rdi+0F8h] ; r13 = pOriginalContext->Rip
               . fffff800`00891f67 lea     rdx,[rsp+60h] ; rdx = &l_pImageBase
               . fffff800`00891f6c mov     r8,rax        ; r8 = pHistoryTable
               . fffff800`00891f6f mov     rcx,r13
               . fffff800`00891f72 mov     qword ptr [rsp+68h],r13
               . fffff800`00891f77 call    nt!RtlLookupFunctionEntry (fffff800`00890e60)
               .                   ; l_pFunctionEntry = RtlLookupFunctionEntry(pOriginalContext->Rip, 
               .                   ;                                           &l_pImageBase,
               .                   ;                                           pHistoryTable)
               . fffff800`00891f7c test    rax,rax ; 判断 l_pFunctionEntry (eax) 是否为 NULL
               . fffff800`00891f7f mov     r14,rax ; r14 = l_pFunctionEntry
    <          . fffff800`00891f82 je      nt!RtlUnwindEx+0x3ab (fffff800`0089221b)
    .          . 
    .          . nt!RtlUnwindEx+0x118:
    .          . fffff800`00891f88 mov     rdx,rdi ; rdx = pOriginalContext
    .          . fffff800`00891f8b mov     rcx,r12 ; rcx = &l_Context
    .          . fffff800`00891f8e call    nt!RtlpCopyContext (fffff800`00891080)
    .          .                   ; RtlpCopyContext(&l_Context, pOriginalContext)
    .          . fffff800`00891f93 mov     rdx,qword ptr [rsp+60h] ; rdx = l_pImageBase
    .          . fffff800`00891f98 mov     qword ptr [rsp+38h],0   ; _ARG_8 = 0
    .          . fffff800`00891fa1 lea     rax,[rsp+680h]          ; rax = &[rcx-home],这里被用作局部变量 l_pEstablisherFrame 空间
    .          . fffff800`00891fa9 mov     r9,r14                  ; r9 = l_pFunctionEntry
    .          . fffff800`00891fac mov     r8,r13                  ; r8 = pOriginalContext->Rip
    .          . fffff800`00891faf mov     qword ptr [rsp+30h],rax ; _ARG7 = &l_pEstablisherFrame
    .          . fffff800`00891fb4 lea     rax,[rsp+58h]           ; rax = &l_pHandlerData
    .          . fffff800`00891fb9 mov     ecx,2                   ; ecx = UNW_FLAG_UHANDLER (2)
    .          . fffff800`00891fbe mov     qword ptr [rsp+28h],rax ; _ARG_6 = &l_pHandlerData
    .          . fffff800`00891fc3 mov     qword ptr [rsp+20h],r12 ; _ARG_5 = &l_Context
    .          . fffff800`00891fc8 call    nt!RtlVirtualUnwind (fffff800`00891380)
    .          .                   ; l_pExceptionRoutine = RtlVirtualUnwind(UNW_FLAG_UHANDLER,
    .          .                   ;                                        l_pImageBase,
    .          .                   ;                                        pOriginalContext->Rip,
    .          .                   ;                                        l_pFunctionEntry,
    .          .                   ;                                        &l_Context,
    .          .                   ;                                        &l_pHandlerData,
    .          .                   ;                                        &l_pEstablisherFrame,
    .          .                   ;                                        NULL);
    .          . fffff800`00891fcd mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
    .          . fffff800`00891fd5 mov     rcx,rax                  ; rcx = l_pExceptionRoutine
    .          . fffff800`00891fd8 mov     qword ptr [rsp+48h],rax
    .          . fffff800`00891fdd test    bl,7                     ; 检查 l_pEstablisherFrame 是否对齐
    .<         . fffff800`00891fe0 jne     nt!RtlUnwindEx+0x431 (fffff800`008922a1)
    ..         . 
    ..         . nt!RtlUnwindEx+0x176:
    ..         . fffff800`00891fe6 cmp     rbx,qword ptr [rsp+50h]  ; cmp l_pEstablisherFrame, l_LowLimit
    ..<        . fffff800`00891feb jb      nt!RtlUnwindEx+0x184 (fffff800`00891ff4)
    ...        . 
    ...        . nt!RtlUnwindEx+0x17d:
    ...        . fffff800`00891fed cmp     rbx,qword ptr [rsp+40h]  ; cmp l_pEstablisherFrame, l_HighLimit
    ...<       . fffff800`00891ff2 jb      nt!RtlUnwindEx+0x1d3 (fffff800`00892043)
    ....       . 
    ....       . nt!RtlUnwindEx+0x184:
    ....       . ; 检查 l_pEstablisherFrame 是否合法
    ..>.       . fffff800`00891ff4 mov     cl,byte ptr gs:[20DEh]   ; cl = _KPCR->DpcRoutineActive
    .. .       . fffff800`00891ffc test    cl,cl                    ; 判断当前是否在执行 DPC
    .< .       . fffff800`00891ffe jne     nt!RtlUnwindEx+0x431 (fffff800`008922a1) ; 如果是在执行 DPC 则失败???
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x194:
    .. .       . fffff800`00892004 mov     rcx,qword ptr [rsp+40h] ; rcx = l_HighLimit
    .. .       . fffff800`00892009 mov     rax,qword ptr [rcx-28h] ; rax = l_KernelStackCtrl->Previous.StackBase
    .. .       . fffff800`0089200d test    rax,rax                 ; 判断 l_KernelStackCtrl->Previous.StackBase 是否为 NULL
    .< .       . fffff800`00892010 je      nt!RtlUnwindEx+0x431 (fffff800`008922a1)
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x1a6:
    .. .       . fffff800`00892016 mov     rdx,qword ptr [rcx-20h]  ; rdx = l_KernelStackCtrl->Previous.StackLimit
    .. .       . fffff800`0089201a mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
    .. .       . fffff800`00892022 cmp     rbx,rdx                  ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackLimit
    .< .       . fffff800`00892025 jb      nt!RtlUnwindEx+0x431 (fffff800`008922a1)
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x1bb:
    .. .       . fffff800`0089202b cmp     rbx,rax ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackBase
    .< .       . fffff800`0089202e jae     nt!RtlUnwindEx+0x431 (fffff800`008922a1)
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x1c4:
    .. .       . fffff800`00892034 mov     rcx,qword ptr [rsp+48h] ; rcx = l_pExceptionRoutine
    .. .       . fffff800`00892039 mov     qword ptr [rsp+50h],rdx ; l_LowLimit = l_KernelStackCtrl->Previous.StackLimit
    .. .       . fffff800`0089203e mov     qword ptr [rsp+40h],rax ; l_HighLimit = l_KernelStackCtrl->Previous.StackBase
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x1d3:
    .. >       . fffff800`00892043 test    rbp,rbp ; 判断 pEstablisherFrame 是否为 NULL
    .. <       . fffff800`00892046 je      nt!RtlUnwindEx+0x1e1 (fffff800`00892051)
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x1d8:
    .. .       . fffff800`00892048 cmp     rbp,rbx ; cmp pEstablisherFrame, l_pEstablisherFrame
    .< .       . fffff800`0089204b jb      nt!RtlUnwindEx+0x431 (fffff800`008922a1)
    .. .       . 
    .. .       . nt!RtlUnwindEx+0x1e1:
    .. >       . fffff800`00892051 test    rcx,rcx ; 判断 l_pExceptionRoutine 是否为 NULL
    ..  <      . fffff800`00892054 je      nt!RtlUnwindEx+0x39b (fffff800`0089220b)
    ..  .      . 
    ..  .      . nt!RtlUnwindEx+0x1ea:
    ..  .      . fffff800`0089205a mov     r13,qword ptr [rsp+58h] ; r13 = l_pHandlerData
    ..  .      . fffff800`0089205f mov     qword ptr [rsp+90h],r15 ; [rsp+90] = pJumpTargetIp
    ..  .      . fffff800`00892067 xor     r15d,r15d
    ..  .      . fffff800`0089206a xchg    ax,ax
    ..  .      . fffff800`0089206d xchg    ax,ax
    ..  .      . 
    ..  .      . nt!RtlUnwindEx+0x200:
    ..  .    > . fffff800`00892070 cmp     rbp,rbx ; cmp pEstablisherFrame, l_pEstablisherFrame
    ..  .<   . . fffff800`00892073 jne     nt!RtlUnwindEx+0x208 (fffff800`00892078)
    ..  ..   . . 
    ..  ..   . . nt!RtlUnwindEx+0x205:
    ..  ..   . . fffff800`00892075 or      esi,20h ; l_ExceptionFlags |= EXCEPTION_TARGET_UNWIND (0x20)
    ..  ..   . . 
    ..  ..   . . nt!RtlUnwindEx+0x208:
    ..  .>   . . fffff800`00892078 mov     r10,qword ptr [rsp+690h] ; r10 = &l_ExceptionRecord
    ..  .    . . fffff800`00892080 mov     rax,qword ptr [rsp+698h] ; rax = ReturnValue
    ..  .    . . fffff800`00892088 mov     qword ptr [rsp+0A0h],rcx ; l_DispatcherContext.LanguageHandler = l_pExceptionRoutine
    ..  .    . . fffff800`00892090 mov     dword ptr [r10+4],esi    ; l_ExceptionRecord.ExceptionFlags = l_ExceptionFlags
    ..  .    . . fffff800`00892094 mov     qword ptr [rdi+78h],rax  ; pOriginalContext->Rax = ReturnValue
    ..  .    . . fffff800`00892098 mov     rax,qword ptr [rsp+68h]  ; rax = pOriginalContext->Rip
    ..  .    . . fffff800`0089209d mov     qword ptr [rsp+70h],rax  ; l_DispatcherContext.ControlPc = pOriginalContext->Rip
    ..  .    . . fffff800`008920a2 mov     rax,qword ptr [rsp+60h]  ; rax = l_pImageBase
    ..  .    . . fffff800`008920a7 lea     r9,[rsp+70h]             ; r9 = &l_DispatcherContext
    ..  .    . . fffff800`008920ac mov     qword ptr [rsp+78h],rax  ; l_DispatcherContext.ImageBase = l_pImageBase
    ..  .    . . fffff800`008920b1 mov     rax,qword ptr [rsp+6A8h] ; rax = pHistoryTable
    ..  .    . . fffff800`008920b9 mov     r8,rdi                   ; r8 = pOriginalContext
    ..  .    . . fffff800`008920bc mov     rdx,rbx                  ; rdx = l_pEstablisherFrame
    ..  .    . . fffff800`008920bf mov     rcx,r10                  ; rcx = &l_ExceptionRecord
    ..  .    . . fffff800`008920c2 mov     qword ptr [rsp+80h],r14   ; l_DispatcherContext.FunctionEntry = l_pFunctionEntry
    ..  .    . . fffff800`008920ca mov     qword ptr [rsp+0B0h],rax  ; l_DispatcherContext.HistoryTable = pHistoryTable
    ..  .    . . fffff800`008920d2 mov     qword ptr [rsp+88h],rbx   ; l_DispatcherContext.EstablisherFrame = l_pEstablisherFrame
    ..  .    . . fffff800`008920da mov     qword ptr [rsp+98h],rdi   ; l_DispatcherContext.ContextRecord = pOriginalContext
    ..  .    . . fffff800`008920e2 mov     qword ptr [rsp+0A8h],r13  ; l_DispatcherContext.HandlerData = l_pHandlerData
    ..  .    . . fffff800`008920ea mov     dword ptr [rsp+0B8h],r15d ; l_DispatcherContext.ScopeIndex = 0
    ..  .    . . fffff800`008920f2 and     esi,0FFFFFF9Fh            ; l_ExceptionFlags &= ~(EXCEPTION_TARGET_UNWIND|EXCEPTION_COLLIDED_UNWIND)
    ..  .    . . fffff800`008920f5 call    nt!RtlpExecuteHandlerForUnwind (fffff800`008bd9d0)
    ..  .    . .                   ; RtlpExecuteHandlerForUnwind(&l_ExceptionRecord,
    ..  .    . .                   ;                             l_pEstablisherFrame,
    ..  .    . .                   ;                             pOriginalContext,
    ..  .    . .                   ;                             &l_DispatcherContext);
    ..  .    . . fffff800`008920fa dec     eax
    ..  .<   . . fffff800`008920fc je      nt!RtlUnwindEx+0x368 (fffff800`008921d8) ; 如果返回 ExceptionContinueSearch 则跳转
    ..  ..   . . 
    ..  ..   . . nt!RtlUnwindEx+0x292:
    ..  ..   . . fffff800`00892102 cmp     eax,2 ; cmp eax, ExceptionCollidedUnwind (3 - 1)
    ..  ..<  . . fffff800`00892105 jne     nt!RtlUnwindEx+0x426 (fffff800`00892296)
    ..  ...  . . 
    ..  ...  . . nt!RtlUnwindEx+0x29b:
    ..  ...  . . ; ExceptionCollidedUnwind 的情况
    ..  ...  . . fffff800`0089210b mov     r8,qword ptr [rsp+70h]   ; r8 = l_DispatcherContext.ControlPc
    ..  ...  . . fffff800`00892110 mov     r10,qword ptr [rsp+78h]  ; r10 = l_DispatcherContext.ImageBase
    ..  ...  . . fffff800`00892115 mov     rdx,qword ptr [rsp+98h]  ; rdx = l_DispatcherContext.ContextRecord
    ..  ...  . . fffff800`0089211d mov     rcx,qword ptr [rsp+6A0h] ; rcx = pOriginalContext
    ..  ...  . . fffff800`00892125 mov     r14,qword ptr [rsp+80h]  ; r14 = l_DispatcherContext.FunctionEntry
    ..  ...  . . fffff800`0089212d mov     qword ptr [rsp+68h],r8
    ..  ...  . . fffff800`00892132 mov     qword ptr [rsp+60h],r10
    ..  ...  . . fffff800`00892137 call    nt!RtlpCopyContext (fffff800`00891080)
    ..  ...  . . ;                 RtlpCopyContext(pOriginalContext, l_DispatcherContext.ContextRecord);
    ..  ...  . . fffff800`0089213c mov     rdx,rcx        ; rdx = pOriginalContext
    ..  ...  . . fffff800`0089213f mov     rdi,rcx        ; rdi = pOriginalContext
    ..  ...  . . fffff800`00892142 lea     rcx,[rsp+160h] ; rcx = &l_Context
    ..  ...  . . fffff800`0089214a lea     r12,[rsp+160h] ; r12 = &l_Context
    ..  ...  . . fffff800`00892152 call    nt!RtlpCopyContext (fffff800`00891080)
    ..  ...  . . ;                 RtlpCopyContext(&l_Context, pOriginalContext);
    ..  ...  . . fffff800`00892157 mov     qword ptr [rsp+38h],0   ; _ARG_8 = NULL
    ..  ...  . . fffff800`00892160 lea     rax,[rsp+680h]          ; rax = &l_pEstablisherFrame
    ..  ...  . . fffff800`00892168 mov     qword ptr [rsp+30h],rax ; _ARG_7 = &l_pEstablisherFrame
    ..  ...  . . fffff800`0089216d lea     rax,[rsp+58h]           ; rax = &l_pHandlerData
    ..  ...  . . fffff800`00892172 mov     r9,r14                  ; r9 = l_DispatcherContext.FunctionEntry
    ..  ...  . . fffff800`00892175 mov     qword ptr [rsp+28h],rax ; _ARG_6 = &l_pHandlerData
    ..  ...  . . fffff800`0089217a lea     rax,[rsp+160h]          ; rax = &l_Context
    ..  ...  . . fffff800`00892182 mov     rdx,r10                 ; rdx = l_DispatcherContext.ImageBase
    ..  ...  . . fffff800`00892185 xor     ecx,ecx                 ; ecx = UNW_FLAG_NHANDLER (0)
    ..  ...  . . fffff800`00892187 mov     qword ptr [rsp+20h],rax ; _ARG_5 = &l_Context
    ..  ...  . . fffff800`0089218c call    nt!RtlVirtualUnwind (fffff800`00891380)
    ..  ...  . .                   ; RtlVirtualUnwind(UNW_FLAG_NHANDLER,
    ..  ...  . .                   ;                  l_DispatcherContext.ImageBase,
    ..  ...  . .                   ;                  l_DispatcherContext.ControlPc,
    ..  ...  . .                   ;                  l_DispatcherContext.FunctionEntry,
    ..  ...  . .                   ;                  &l_Context,
    ..  ...  . .                   ;                  &l_pHandlerData,
    ..  ...  . .                   ;                  &l_pEstablisherFrame,
    ..  ...  . .                   ;                  NULL);
    ..  ...  . . fffff800`00892191 mov     rbx,qword ptr [rsp+88h]   ; rbx = l_DispatcherContext.EstablisherFrame
    ..  ...  . . fffff800`00892199 mov     rcx,qword ptr [rsp+0A0h]  ; rcx = l_DispatcherContext.LanguageHandler
    ..  ...  . . fffff800`008921a1 mov     r13,qword ptr [rsp+0A8h]  ; r13 = l_DispatcherContext.HandlerData
    ..  ...  . . fffff800`008921a9 mov     rax,qword ptr [rsp+0B0h]  ; rax = l_DispatcherContext.HistoryTable
    ..  ...  . . fffff800`008921b1 mov     r15d,dword ptr [rsp+0B8h] ; r15d = l_DispatcherContext.ScopeIndex
    ..  ...  . . fffff800`008921b9 mov     qword ptr [rsp+680h],rbx  ; l_pEstablisherFrame = l_DispatcherContext.EstablisherFrame
    ..  ...  . . fffff800`008921c1 mov     qword ptr [rsp+48h],rcx   ; l_pExceptionRoutine = l_DispatcherContext.LanguageHandler
    ..  ...  . . fffff800`008921c6 mov     qword ptr [rsp+58h],r13   ; l_pHandlerData = l_DispatcherContext.HandlerData
    ..  ...  . . fffff800`008921cb mov     qword ptr [rsp+6A8h],rax  ; pHistoryTable = l_DispatcherContext.HistoryTable
    ..  ...  . . fffff800`008921d3 or      esi,40h                   ; l_ExceptionFlags |= EXCEPTION_COLLIDED_UNWIND (40)
    ..  ...< . . fffff800`008921d6 jmp     nt!RtlUnwindEx+0x382 (fffff800`008921f2)
    ..  .... . . 
    ..  .... . . nt!RtlUnwindEx+0x368:
    ..  .>.. . . fffff800`008921d8 cmp     rbx,rbp ; cmp l_DispatcherContext.EstablisherFrame, pEstablisherFrame
    ..  . ..<. . fffff800`008921db je      nt!RtlUnwindEx+0x37d (fffff800`008921ed)
    ..  . .... . 
    ..  . .... . nt!RtlUnwindEx+0x36d:
    ..  . .... . fffff800`008921dd mov     rcx,qword ptr [rsp+48h] ; rcx = l_pExceptionRoutine
    ..  . .... . fffff800`008921e2 mov     rax,rdi                 ; rax = pOriginalContext
    ..  . .... . fffff800`008921e5 mov     rdi,r12                 ; rdi = &l_Context
    ..  . .... . fffff800`008921e8 mov     r12,rax                 ; r12 = pOriginalContext
    ..  . .v.. . fffff800`008921eb jmp     nt!RtlUnwindEx+0x382 (fffff800`008921f2)
    ..  . .... . 
    ..  . .... . nt!RtlUnwindEx+0x37d:
    ..  . ..>. . fffff800`008921ed mov     rcx,qword ptr [rsp+48h] ; rcx = l_pExceptionRoutine
    ..  . .. . . 
    ..  . .. . . nt!RtlUnwindEx+0x382:
    ..  . .> . . fffff800`008921f2 test    sil,40h  ; test sil, EXCEPTION_COLLIDED_UNWIND (40)
    ..  . .  < . fffff800`008921f6 jne     nt!RtlUnwindEx+0x200 (fffff800`00892070)
    ..  . .    . 
    ..  . .    . nt!RtlUnwindEx+0x38c:
    ..  . .    . fffff800`008921fc mov     r13,qword ptr [rsp+68h]  ; r13 = pOriginalContext->Rip
    ..  . .    . fffff800`00892201 mov     r15,qword ptr [rsp+688h] ; r15 = pJumpTargetIp
    ..  . .<   . fffff800`00892209 jmp     nt!RtlUnwindEx+0x3c7 (fffff800`00892237)
    ..  . ..   . 
    ..  . ..   . nt!RtlUnwindEx+0x39b:
    ..  > ..   . fffff800`0089220b cmp     rbx,rbp  ; cmp l_DispatcherContext.EstablisherFrame, pEstablisherFrame
    ..    .<   . fffff800`0089220e je      nt!RtlUnwindEx+0x3c7 (fffff800`00892237)
    ..    ..   . 
    ..    ..   . nt!RtlUnwindEx+0x3a0:
    ..    ..   . fffff800`00892210 mov     rax,rdi ; rax = &l_Context
    ..    ..   . fffff800`00892213 mov     rdi,r12 ; rdi = pOriginalContext
    ..    ..   . fffff800`00892216 mov     r12,rax ; r12 = &l_Context
    ..    .<   . fffff800`00892219 jmp     nt!RtlUnwindEx+0x3c7 (fffff800`00892237)
    ..    ..   . 
    ..    ..   . nt!RtlUnwindEx+0x3ab:
    >.    ..   . fffff800`0089221b mov     rcx,qword ptr [rdi+98h]  ; rcx = pOriginalContext->Rsp
    .    ..   . fffff800`00892222 mov     rax,qword ptr [rcx]      ; rax = pOriginalContext->Rsp[0]
    .    ..   . fffff800`00892225 mov     qword ptr [rdi+0F8h],rax ; pOriginalContext->Rip = pOriginalContext->Rsp[0]
    .    ..   . fffff800`0089222c lea     rax,[rcx+8]              ; rax = pOriginalContext->Rsp + 8
    .    ..   . fffff800`00892230 mov     qword ptr [rdi+98h],rax  ; pOriginalContext->Rsp = pOriginalContext->Rsp + 8 (跳过返回值)
    .    ..   .
    .    ..   . nt!RtlUnwindEx+0x3c7:
    .    .>   . fffff800`00892237 test    bl,7 ; 检查 l_DispatcherContext.EstablisherFrame 是否对齐
    .    .<   . fffff800`0089223a jne     nt!RtlUnwindEx+0x444 (fffff800`008922b4)
    .    ..   . 
    .    ..   . nt!RtlUnwindEx+0x3cc:
    .    ..   . fffff800`0089223c cmp     rbx,qword ptr [rsp+50h] ; cmp l_DispatcherContext.EstablisherFrame, l_LowLimit
    .    ..<  . fffff800`00892241 jb      nt!RtlUnwindEx+0x3da (fffff800`0089224a)
    .    ...  . 
    .    ...  . nt!RtlUnwindEx+0x3d3:
    .    ...  . fffff800`00892243 cmp     rbx,qword ptr [rsp+40h] ; cmp l_DispatcherContext.EstablisherFrame, l_HighLimit
    .    ...< . fffff800`00892248 jb      nt!RtlUnwindEx+0x414 (fffff800`00892284)
    .    .... . 
    .    .... . nt!RtlUnwindEx+0x3da:
    .    ..>. . fffff800`0089224a mov     al,byte ptr gs:[20DEh] ; al = _KPCR->DpcRoutineActive
    .    .. . . fffff800`00892252 test    al,al
    .    .. .<. fffff800`00892254 jne     nt!RtlUnwindEx+0x43c (fffff800`008922ac)
    .    .. ... 
    .    .. ... nt!RtlUnwindEx+0x3e6:
    .    .. ... fffff800`00892256 mov     rcx,qword ptr [rsp+40h] ; rcx = l_HighLimit
    .    .. ... fffff800`0089225b mov     rax,qword ptr [rcx-28h] ; rax = l_KernelStackCtrl->Previous.StackBase
    .    .. ... fffff800`0089225f test    rax,rax
    .    .. .<. fffff800`00892262 je      nt!RtlUnwindEx+0x43c (fffff800`008922ac)
    .    .. ... 
    .    .. ... nt!RtlUnwindEx+0x3f4:
    .    .. ... fffff800`00892264 mov     rdx,qword ptr [rcx-20h]  ; rdx = l_KernelStackCtrl->Previous.StackLimit
    .    .. ... fffff800`00892268 mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
    .    .. ... fffff800`00892270 cmp     rbx,rdx                  ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackLimit
    .    .< ... fffff800`00892273 jb      nt!RtlUnwindEx+0x444 (fffff800`008922b4)
    .    .. ... 
    .    .. ... nt!RtlUnwindEx+0x405:
    .    .. ... fffff800`00892275 cmp     rbx,rax ; cmp l_pEstablisherFrame, l_KernelStackCtrl->Previous.StackBase
    .    .< ... fffff800`00892278 jae     nt!RtlUnwindEx+0x444 (fffff800`008922b4)
    .    .. ... 
    .    .. ... nt!RtlUnwindEx+0x40a:
    .    .. ... fffff800`0089227a mov     qword ptr [rsp+50h],rdx ; l_LowLimit = l_KernelStackCtrl->Previous.StackLimit
    .    .. ... fffff800`0089227f mov     qword ptr [rsp+40h],rax ; l_HighLimit = l_KernelStackCtrl->Previous.StackBase
    .    .. ... 
    .    .. ... nt!RtlUnwindEx+0x414:
    .    .. >.. fffff800`00892284 cmp     rbx,rbp ; cmp l_pEstablisherFrame, pEstablisherFrame
    .    .. <.. fffff800`00892287 je      nt!RtlUnwindEx+0x449 (fffff800`008922b9)
    .    .. ... 
    .    .. ... nt!RtlUnwindEx+0x419:
    .    .. ... fffff800`00892289 mov     rax,qword ptr [rsp+6A8h] ; rax = pHistoryTable
    .    .. ..^ fffff800`00892291 jmp     nt!RtlUnwindEx+0xf0 (fffff800`00891f60)
    .    .. ..  
    .    .. ..  nt!RtlUnwindEx+0x426:
    .    >. ..  fffff800`00892296 mov     ecx,0C0000026h ; ecx = STATUS_INVALID_DISPOSITION
    .     . ..  fffff800`0089229b call    nt!RtlRaiseStatus (fffff800`00889df0)
    .     . ..  fffff800`008922a0 int     3
    .     . ..  
    .     . ..  nt!RtlUnwindEx+0x431:
    >     . ..  fffff800`008922a1 mov     ecx,0C0000028h ; ecx = STATUS_BAD_STACK
           . ..  fffff800`008922a6 call    nt!RtlRaiseStatus (fffff800`00889df0)
           . ..  fffff800`008922ab int     3
           . ..  
           . ..  nt!RtlUnwindEx+0x43c:
           . .>  fffff800`008922ac mov     rbx,qword ptr [rsp+680h] ; rbx = l_pEstablisherFrame
           . .   
           . .   nt!RtlUnwindEx+0x444:
           > .   fffff800`008922b4 cmp     rbx,rbp ; cmp l_pEstablisherFrame, pEstablisherFrame
    <        .   fffff800`008922b7 jne     nt!RtlUnwindEx+0x479 (fffff800`008922e9)
    .        .   
    .        .   nt!RtlUnwindEx+0x449:
    .        >   fffff800`008922b9 mov     rax,qword ptr [rsp+698h]  ; rax = ReturnValue
    .            fffff800`008922c1 mov     rbx,qword ptr [rsp+690h]  ; rbx = pExceptionRecord
    .            fffff800`008922c9 mov     qword ptr [rdi+78h],rax   ; pOriginalContext->Rax, ReturnValue
    .            fffff800`008922cd cmp     dword ptr [rbx],80000029h ; cmp pExceptionRecord->ExceptionCode, STATUS_UNWIND_CONSOLIDATE
    .<           fffff800`008922d3 je      nt!RtlUnwindEx+0x46c (fffff800`008922dc)
    ..           
    ..           nt!RtlUnwindEx+0x465:
    ..           fffff800`008922d5 mov     qword ptr [rdi+0F8h],r15 ; pOriginalContext->Rip, pJumpTargetIp
    ..           
    ..           nt!RtlUnwindEx+0x46c:
    .>           fffff800`008922dc mov     rdx,rbx ; rdx = pExceptionRecord
    .            fffff800`008922df mov     rcx,rdi ; rcx = pOriginalContext
    .            fffff800`008922e2 call    nt!RtlRestoreContext (fffff800`008bd290)
    .<           fffff800`008922e7 jmp     nt!RtlUnwindEx+0x4a0 (fffff800`00892310)
    ..           
    ..           nt!RtlUnwindEx+0x479:
    >.           fffff800`008922e9 cmp     r13,qword ptr [rdi+0F8h] ; pOriginalContext->Rip
    .<          fffff800`008922f0 jne     nt!RtlUnwindEx+0x48d (fffff800`008922fd)
    ..          
    ..          nt!RtlUnwindEx+0x482:
    ..          fffff800`008922f2 mov     ecx,0C00000FFh ; ecx = STATUS_BAD_FUNCTION_TABLE
    ..          fffff800`008922f7 call    nt!RtlRaiseStatus (fffff800`00889df0)
    ..          fffff800`008922fc int     3
    ..          
    ..          nt!RtlUnwindEx+0x48d:
    .>          fffff800`008922fd mov     rcx,qword ptr [rsp+690h] ; rcx = pExceptionRecord
    .           fffff800`00892305 xor     r8d,r8d                  ; r8d = 0
    .           fffff800`00892308 mov     rdx,rdi                  ; rdx = pOriginalContext
    .           fffff800`0089230b call    nt!ZwRaiseException (fffff800`008b9b80)
    .                             ; ZwRaiseException(pExceptionRecord, pOriginalContext, FALSE);
    .           
    .           nt!RtlUnwindEx+0x4a0:
    >           fffff800`00892310 mov     r15,qword ptr [rsp+638h]
                 fffff800`00892318 mov     r14,qword ptr [rsp+640h]
                 fffff800`00892320 mov     r13,qword ptr [rsp+648h]
                 fffff800`00892328 mov     r12,qword ptr [rsp+650h]
                 fffff800`00892330 mov     rdi,qword ptr [rsp+658h]
                 fffff800`00892338 mov     rsi,qword ptr [rsp+660h]
                 fffff800`00892340 mov     rbp,qword ptr [rsp+668h]
                 fffff800`00892348 mov     rbx,qword ptr [rsp+670h]
                 fffff800`00892350 add     rsp,678h
                 fffff800`00892357 ret

    参考资料
     
    详情jpg 转 rar
  • 相关阅读:
    数仓备机DN重建:快速修复你的数仓DN单点故障
    深度学习分类任务常用评估指标
    云小课 | MRS基础入门之HDFS组件介绍
    华为云数据库GaussDB(for Cassandra)揭秘第二期:内存异常增长的排查经历
    为什么vacuum后表还是继续膨胀?
    Go 自定义日志库
    Go time包
    Go 文件操作
    Go 包相关
    【程序人生】跟小伙伴们聊聊我有趣的大学生活和我那两个好基友!
  • 原文地址:https://www.cnblogs.com/kuangke/p/9397493.html
Copyright © 2020-2023  润新知