x64 上的结构化异常处理和 C++ 异常处理编码约定和行为的概述。 有关异常处理的一般信息,请参阅 Microsoft C++中的异常处理。
用于异常处理和调试器支持的展开数据
在处理异常时,要恢复非易失性寄存器,需要对非叶函数使用静态数据进行注释。 此数据通常称为“函数展开信息”,描述如何在任意指令中正确展开函数。 此数据存储为 pdata(过程数据),后者又引用 xdata(异常处理数据)。
函数展开信息由若干数据结构组成,下面将对其进行介绍。
有关支持 Intel APX(高级性能扩展)的展开信息,请参阅 “展开 V3 预览版规范”。
RUNTIME_FUNCTION 结构
基于表格的异常处理要求所有分配堆栈空间或调用其他函数的函数(例如,非叶函数)都拥有一个表项。 函数表条目的格式为:
| 大小 | 值 |
|---|---|
| ULONG | 函数起始地址 |
| ULONG | 函数结束地址 |
| ULONG | 解开信息地址 |
RUNTIME_FUNCTION 结构必须在内存中按 DWORD 对齐。 所有地址都相对于映像,也就是说,它们是相对于包含函数表条目的映像起始地址的 32 位偏移。 这些条目已经过排序,并被放入 PE32+ 映像的 .pdata 节中。 对于动态生成的函数 [JIT 编译器],支持这些函数的运行时必须使用 RtlInstallFunctionTableCallback 或 RtlAddFunctionTable 向操作系统提供此信息。 否则会导致不可靠的异常处理和调试进程。
UNWIND_INFO 结构
展开数据信息结构记录函数对栈指针的影响,以及非易失性寄存器在栈上的保存位置:
| 大小 | 值 |
|---|---|
| UBYTE:3 | 版本 |
| UBYTE:5 | 旗帜 |
| UBYTE | prolog 的大小 |
| UBYTE | 展开代码的计数 |
| UBYTE:4 | 帧寄存器 |
| UBYTE:4 | 帧寄存器偏移(缩放因子) |
| USHORT * n | 展开代码数组 |
| 变量 | 可以采用下面的形式 (1) 或 (2) |
(1) 异常处理程序
| 大小 | 值 |
|---|---|
| ULONG | 异常处理程序的地址 |
| 变量 | 特定于语言的处理程序数据(可选) |
(2) 链式展开信息
| 大小 | 值 |
|---|---|
| ULONG | 函数起始地址 |
| ULONG | 函数结束地址 |
| ULONG | 解开信息地址 |
UNWIND_INFO 结构必须在内存中按 DWORD 对齐。 下面是每个字段的含义:
版本
展开数据的版本号,当前为 1。
标记
当前定义了三个标志:
标志 说明 UNW_FLAG_EHANDLER该函数具有一个异常处理程序,操作系统调用该处理程序来检查异常的状态并可能处理它。 C __try子句等语言功能注册此类处理程序。UNW_FLAG_UHANDLER该函数具有在展开堆栈时操作系统调用的终止处理程序。 此处理程序可以在异常安全代码中释放函数分配的资源。 本地 C++ 对象析构函数和 C __finally子句等语言功能注册此类终止处理程序。UNW_FLAG_CHAININFO此展开信息结构不是该过程的主要结构。 相反,链式展开信息条目是前一个 RUNTIME_FUNCTION条目的内容。 有关信息,请参阅链式展开信息结构。 如果设置了此标志,则必须清除UNW_FLAG_EHANDLER和UNW_FLAG_UHANDLER标志。 此外,帧寄存器和固定堆栈分配字段必须具有与主展开信息中相同的值。prolog 的大小
函数 prolog 的长度(以字节为单位)。
展开代码的数量
展开代码数组中的槽数。 某些展开代码(如
UWOP_SAVE_NONVOL)需要数组中的多个槽。帧寄存器
如果为非零,则函数使用帧指针(FP),并且此字段是用作帧指针的非易失性寄存器的数量,对节点的操作信息字段
UNWIND_CODE使用相同的编码。帧寄存器偏移(缩放的)
此字段表示
RSP寄存器值与所选帧指针(FP)寄存器值之间经缩放的偏移量。 所选 FP 寄存器设置为RSP+ 16 * 此数字,这意味着可以使用从 0 到 240 的偏移量。 此偏移量会使 FP 寄存器指向动态栈帧中局部栈分配区域的中部,从而通过使用更短的指令获得更高的代码密度。 (也就是说,更多指令代码可以使用 8 位有符号偏移形式。)展开代码数组
用于说明序言代码对非易失性寄存器和
RSP的影响的项目数组。 有关各个项的含义,请参阅 有关展开操作代码 的部分。 为了保持适当的数据对齐方式,此数组始终包含偶数条目,最终条目可能未使用。 在这种情况下,数组的长度比展开代码字段计数所指示的长度多一个。异常处理程序的地址
如果标志
UNW_FLAG_CHAININFO未设置,且标志UNW_FLAG_EHANDLER或UNW_FLAG_UHANDLER之一已设置,则这是一个指向该函数的特定语言异常处理程序或终止处理程序的映像相对指针。语言特定的处理程序数据
函数的异常处理程序中与语言相关的数据。 此数据的格式未指定,完全由所使用的特定异常处理程序确定。
链式展开信息
如果设置了标志
UNW_FLAG_CHAININFO,则UNWIND_INFO结构以三个UWORD结束。 这些UWORD表示的是链式展开函数的RUNTIME_FUNCTION信息。
UNWIND_CODE 结构
使用展开代码数组记录序言代码中影响非易失寄存器和 RSP 的操作顺序。 每个代码项都具有以下格式:
| 大小 | 值 |
|---|---|
| UBYTE | prolog 中的偏移 |
| UBYTE:4 | 展开操作代码 |
| UBYTE:4 | 操作信息 |
数组按 prolog 中偏移的降序排序。
prolog 中的偏移
执行此操作的指令结尾的偏移(相对于 prolog 的开头)加 1(即下一个指令开头的偏移)。
展开操作代码
某些操作代码需要对本地堆栈帧中的值进行无符号偏移。 此偏移相对于开头(即固定堆栈分配的最低地址)。 如果 UNWIND_INFO 中的帧寄存器字段为零,则该偏移量是相对于 RSP 的。 如果 Frame Register 字段为非零,则此偏移量来自建立 FP 寄存器时所在的位置 RSP 。 它等于 FP 寄存器的值减去 FP 寄存器偏移量(16 * UNWIND_INFO 中按比例缩放后的帧寄存器偏移量)。 如果使用 FP 寄存器,则必须仅在 prolog 中建立 FP 寄存器之后,才能使用任何采用偏移的展开代码。
对于除 UWOP_SAVE_XMM128 和 UWOP_SAVE_XMM128_FAR 之外的所有操作码,偏移始终为 8 的倍数,因为所有相关堆栈值都存储在 8 字节边界上(堆栈本身始终为 16 字节对齐)。 对于采用短偏移量(小于 512K)的操作码,该代码对应节点中的最后一个 USHORT 保存的是偏移量除以 8 后的值。 对于采用长偏移量(512K <= 偏移量 < 4GB)的操作码,此代码最后两个 USHORT 节点存放该偏移量(采用小端格式)。
对于操作码 UWOP_SAVE_XMM128 和 UWOP_SAVE_XMM128_FAR,偏移量始终是 16 的倍数,因为所有 128 位 XMM 操作都必须在 16 字节对齐的内存中进行。 因此,将比例因子 16 用于 UWOP_SAVE_XMM128,允许小于 1M 的偏移。
展开操作代码可以是以下值之一:
UWOP_PUSH_NONVOL(0) 1 个节点将非易失性整数寄存器压栈,并将
RSP减 8。 操作信息是寄存器的编号。 由于对 epilog 的约束,UWOP_PUSH_NONVOL展开代码必须首先出现在 prolog 中,并相应地最后出现在展开代码数组中。 此相对排序适用于除UWOP_PUSH_MACHFRAME以外的其他所有展开代码。UWOP_ALLOC_LARGE(1) 2 或 3 个节点在堆栈上分配一个大块区域。 有两种形式。 如果操作信息等于 0,则分配的大小除以 8 的值会记录在下一个槽中,允许分配最大为 512K - 8。 如果操作信息等于 1,则分配的未缩放大小会以 little-endian 格式记录在接下来的两个字段中,允许的分配最大为 4GB 减去 8 字节。
UWOP_ALLOC_SMALL(2) 1 个节点在堆栈上分配小型区域。 分配的大小是操作信息字段 * 8 + 8,允许分配为 8 到 128 个字节。
堆栈分配的展开代码应始终使用可能的最短编码:
分配大小 展开代码 8 到 128 个字节 UWOP_ALLOC_SMALL136 到 512K-8 个字节 UWOP_ALLOC_LARGE,操作信息 = 0512K 至 4G - 8 字节 UWOP_ALLOC_LARGE操作信息 = 1UWOP_SET_FPREG(3) 1 个节点通过将该寄存器设置为当前
RSP的某个偏移量,设立帧指针寄存器。 偏移量等于 * 16 中的UNWIND_INFOFrame Register 偏移量(缩放)字段,允许偏移量从 0 到 240。 使用偏移可以建立指向固定堆栈分配中间的帧指针,允许更多访问使用短指令形式,从而帮助优化代码密度。 操作信息字段是保留字段,不应使用。UWOP_SAVE_NONVOL(4) 2 个节点使用 MOV(而不是 PUSH)将非易失性整数寄存器保存在堆栈上。 此代码主要用于收缩包装,其中非易失性寄存器会保存到堆栈中之前分配的位置。 操作信息是寄存器的编号。 以 8 为比例的堆栈偏移会记录在下一个展开操作代码槽中,如上面的备注中所述。
UWOP_SAVE_NONVOL_FAR(5) 3 个节点使用 MOV(而不是 PUSH),通过长偏移将非易失性整数寄存器保存在堆栈上。 此代码主要用于收缩包装,其中非易失性寄存器会保存到堆栈中之前分配的位置。 操作信息是寄存器的编号。 未缩放的堆栈偏移记录在接下来的两个展开操作码槽中,如上面的备注中所述。
UWOP_SAVE_XMM128(8) 2 个节点在堆栈上保存非易失性
XMM寄存器的所有 128 位。 操作信息是寄存器的编号。 乘以 16 的堆栈偏移量记录在下一个位置中。UWOP_SAVE_XMM128_FAR(9) 3 个节点使用长偏移量将一个非易失性
XMM寄存器的全部 128 位保存到堆栈上。 操作信息是寄存器的编号。 未缩放的堆栈偏移量会记录在接下来的两个插槽中。UWOP_PUSH_MACHFRAME(10) 1 个节点压入计算机帧。 此展开代码记录硬件中断或异常的影响。 它有两种形式。 值为 0 表示硬件已将如下所示的栈帧压入栈中:
位置 值 RSP+32SSRSP+24旧 RSPRSP+16EFLAGSRSP+8CSRSPRIP值为 1,表示硬件已将如下这样的帧压入栈中:
位置 值 RSP+40SSRSP+32旧 RSPRSP+24EFLAGSRSP+16CSRSP+8RIPRSP错误代码 此展开代码始终出现在虚拟 prolog 中,后者实际上不会执行,而是出现在中断例程的实际入口点之前,它的存在只是为了提供一个位置来模拟计算机帧的压入。
UWOP_PUSH_MACHFRAME会记录该模拟,这表示计算机已在概念上执行此操作:从堆栈顶部弹出
RIP回信地址到 Temp按下
SS推送旧版
RSP按下
EFLAGS按下
CS推送Temp
压入错误代码(如果操作信息等于 1)
模拟的
UWOP_PUSH_MACHFRAME操作会将RSP减少 40(如果操作信息为 0)或 48(如果操作信息为 1)。
操作信息
操作信息位的含义取决于操作代码。 若要对常规用途(整数)寄存器进行编码,请使用此映射:
| bit | 注册 |
|---|---|
| 0 | RAX |
| 1 | RCX |
| 2 | RDX |
| 3 | RBX |
| 4 | RSP |
| 5 | RBP |
| 6 | RSI |
| 7 | RDI |
| 8 到 15 |
R8 至 R15 |
链式展开信息结构
如果设置了 UNW_FLAG_CHAININFO 标志,则展开信息结构是次要结构,而共享的异常处理程序/链式信息地址字段中包含主展开信息。 此示例代码检索主要展开信息,假定 unwindInfo 它是具有 UNW_FLAG_CHAININFO 标志集的结构。
PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);
链式信息在两种情况下非常有用。 首先,它可用于非连续代码段。 通过使用链式信息,可以减小所需展开信息的大小,因为不必重复包含主展开信息中的展开代码数组。
还可以使用链式信息对易失性寄存器保存进行分组。 编译器可能会推迟保存某些易失性寄存器,直到离开函数入口序言之后才进行保存。 可以通过在分组代码之前为函数部分提供主要展开信息来记录它们,然后使用非零大小的 prolog 设置链接信息,其中链接信息中的展开代码反映非易失性寄存器的保存。 在这种情况下,展开码都为 UWOP_SAVE_NONVOL。 不支持这样的分组:使用 PUSH 保存非易失性寄存器,或者通过额外的固定栈分配修改 RSP 寄存器。
将 UNW_FLAG_CHAININFO 设置好的 UNWIND_INFO 项可以包含一个 RUNTIME_FUNCTION 条目,而该条目的 UNWIND_INFO 项也设置了 UNW_FLAG_CHAININFO,这有时称为 多重收缩包装。 最后,链式展开信息指针最终会到达某个 UNWIND_INFO 项,其 UNW_FLAG_CHAININFO 已清除。 此项是主要 UNWIND_INFO 项,它指向实际过程入口点。
解开程序
展开代码数组按降序排序。 发生异常时,操作系统会将完整的上下文存储在上下文记录中。 然后调用异常调度逻辑,这会重复执行以下步骤来查找异常处理程序:
使用存储在上下文记录中的当前
RIP,搜索描述当前函数的RUNTIME_FUNCTION表项(对于链式UNWIND_INFO表项,则为函数的一部分)。如果搜索找不到函数表条目,则假定代码是叶函数的一部分,并
RSP直接处理返回指针。 [RSP] 处的返回指针存储在已更新的上下文中,将模拟的RSP增加 8,然后重复步骤 1。如果搜索找到了函数表项,
RIP可能位于三个区域之一:a)尾声代码中,b)序言代码中,或 c)可能受异常处理程序涵盖的代码中。案例 a) 如果
RIP位于 epilog 中,则控件将离开函数。 此函数不能有与此异常关联的异常处理程序。 尾声代码的作用必须继续计算出调用方函数的上下文。 若要确定RIP是否位于尾声代码中,将检查从RIP开始的代码流。 如果该代码流与合法尾声代码的末尾部分匹配,则说明它位于尾声代码中。 尾声代码的其余部分将被模拟,并且在处理每条指令时更新上下文记录。 此处理完成之后,会重复步骤 1。- 情况 b)如果
RIP位于序言部分中,则控制流尚未进入该函数。 此函数不能有与该异常关联的异常处理程序。 必须先撤销函数序言的作用,才能计算调用方函数的上下文信息。 如果从函数起始位置到RIP的距离小于或等于展开信息中编码的 prolog 大小,则RIP位于 prolog 中。 展开器通过展开代码数组向前扫描第一个条目,其偏移量小于或等于从函数开始的偏移量RIP,然后撤消展开代码数组中所有剩余项的效果。 然后重复步骤 1。
- 情况 b)如果
案例 c) 如果
RIP不是 prolog 或 epilog 中,并且函数具有异常处理程序(UNW_FLAG_EHANDLER已设置),则调用特定于语言的处理程序。 处理程序会扫描其数据,并根据需要调用筛选器函数。 特定于语言的处理程序可以返回已处理异常或继续搜索。 它还可以直接启动展开。
如果特定于语言的处理程序返回已处理状态,则使用原始上下文记录继续执行。
如果没有特定语言的处理程序,或者该处理程序返回“继续搜索”状态,则必须将上下文记录回退到调用方的状态。 展开器撤消展开代码数组中每个元素的效果。 然后重复步骤 1。
当涉及链式展开信息时,依然会遵循这些基本步骤。 唯一的区别是:在遍历展开代码数组以撤销函数序言的影响时,一旦遍历到该数组末尾,就会链接到父级展开信息,并遍历其中的整个展开代码数组。 这种链接会持续进行,直到到达不带 UNW_CHAINED_INFO 标志的展开信息,然后完成对其展开代码数组的遍历。
最小的展开数据集是 8 个字节。 此类集合表示某个函数仅分配了 128 字节或更少的栈空间,并且可能保存了一个非易失性寄存器。 它也是不带展开代码的零长度 prolog 的链式展开信息结构大小。
特定于语言的处理程序
该 UNWIND_INFO 结构在设置了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER 标志之一时,提供特定于语言的处理程序的相对地址。 如前一节所述,搜索异常处理程序时,或在展开调用栈的过程中,会调用语言特定的处理程序。 处理程序使用此原型:
typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
IN PEXCEPTION_RECORD ExceptionRecord,
IN ULONG64 EstablisherFrame,
IN OUT PCONTEXT ContextRecord,
IN OUT PDISPATCHER_CONTEXT DispatcherContext
);
ExceptionRecord 提供指向具有标准 Win64 定义的异常记录的指针。
EstablisherFrame 是此函数的固定堆栈分配的基址。
ContextRecord 指向引发异常时的异常上下文(在异常处理程序情况下)或当前“展开”上下文(在终止处理程序情况下)。
DispatcherContext 指向此函数的调度程序上下文。 它具有以下定义:
typedef struct _DISPATCHER_CONTEXT {
ULONG64 ControlPc;
ULONG64 ImageBase;
PRUNTIME_FUNCTION FunctionEntry;
ULONG64 EstablisherFrame;
ULONG64 TargetIp;
PCONTEXT ContextRecord;
PEXCEPTION_ROUTINE LanguageHandler;
PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;
ControlPc 是此函数中的值 RIP 。 此值可以是异常地址,也可以是控制离开建立函数的位置处的地址。
RIP用于确定控制流是否位于此函数内的某个受保护结构中,例如,用于__try/__except的__try块,或用于__try/的__finally块。
ImageBase 是包含此函数的模块的映像基数(加载地址)。 函数条目和展开信息中使用的 32 位偏移量应加到 ImageBase 上,以获得最终地址。
FunctionEntry 提供一个指向 RUNTIME_FUNCTION 函数条目的指针,该条目保存了此函数的函数地址和解展开信息地址,这些地址都相对于映像基址。
EstablisherFrame 是此函数的固定堆栈分配的基址。
TargetIp 提供一个可选的指令地址,用于指定展开后的继续执行地址。 如果未指定 EstablisherFrame,则忽略此地址。
ContextRecord 指向异常上下文,供系统异常调度/展开代码使用。
LanguageHandler 指向所调用的特定于语言的语言处理程序例程。
HandlerData 指向该函数的特定于编程语言的处理器数据。
MASM 的展开帮助程序
若要编写正确的程序集例程,请使用一组伪操作以及实际的程序集指令。 这些伪操作会生成相应的 .pdata 和 .xdata。 此外,可使用一组宏来简化这些伪操作在最常见用法中的使用。
原始伪操作
| 伪操作 | 说明 |
|---|---|
| PROC FRAME [:ehandler] | 使 MASM 在 .pdata 中生成函数表条目,并在 .xdata 中生成用于函数结构化异常处理展开行为的展开信息。 如果ehandler存在,则该过程在.xdata中作为特定语言处理程序输入。使用 FRAME 属性时,请遵循一个 .ENDPROLOG 指令。 如果函数是叶函数(如 函数类型中定义),则不需要 FRAME 属性,这些伪操作的其余部分也是不必要的。 |
| .PUSHREG register | 基于序言中的当前偏移量,为指定的寄存器编号生成一个 UWOP_PUSH_NONVOL 展开代码项。仅将它与非易失性整数寄存器一起使用。 对于将易失性寄存器压栈的情况,请改用 .ALLOCSTACK 8。 |
| .SETFRAME 寄存器, 偏移量 | 使用指定的寄存器和偏移值,在展开信息中填充帧寄存器字段和对应的偏移量。 偏移必须是 16 的倍数,并且小于或等于 240。 此指令还会使用当前的序言偏移量,为指定寄存器生成一个 UWOP_SET_FPREG 展开代码项。 |
| .ALLOCSTACK 大小 | 为序言中的当前偏移量生成一个指定大小的UWOP_ALLOC_SMALL或UWOP_ALLOC_LARGE。size 操作数必须是 8 的倍数。 |
| .SAVEREG 寄存器, 偏移量 | 使用当前序言偏移量,为指定的寄存器和偏移量生成一个 UWOP_SAVE_NONVOL 或 UWOP_SAVE_NONVOL_FAR 展开代码条目。 MASM 会选择最高效的编码。offset 必须为正,且是 8 的倍数。 偏移量是相对于过程帧的基址而言的,该基址通常位于 RSP 中;或者,如果使用帧指针,则相对于未缩放的帧指针。 |
| .SAVEXMM128 寄存器, 偏移量 | 使用当前序言偏移量,为指定的 XMM 寄存器和偏移量生成一个 UWOP_SAVE_XMM128 或 UWOP_SAVE_XMM128_FAR 展开代码条目。 MASM 会选择最高效的编码。offset 必须为正,并且是 16 的倍数。 偏移量是相对于过程栈帧的基址而言的,该基址通常位于 RSP中;如果使用帧指针,则该基址为未缩放的帧指针。 |
| .PUSHFRAME [代码] | 生成一个 UWOP_PUSH_MACHFRAME 展开代码项。 如果指定可选的 code,展开代码条目的修饰符为 1。 否则修饰符为 0。 |
| .ENDPROLOG | 指示序言声明的结尾。 必须出现在函数的前 255 个字节中。 |
下面是一个示例函数 prolog,其中包含大多数操作码的正确用法:
sample PROC FRAME
db 048h; emit a REX prefix, to enable hot-patching
push rbp
.pushreg rbp
sub rsp, 040h
.allocstack 040h
lea rbp, [rsp+020h]
.setframe rbp, 020h
movdqa [rbp], xmm7
.savexmm128 xmm7, 020h ;the offset is from the base of the frame
;not the scaled offset of the frame
mov [rbp+018h], rsi
.savereg rsi, 038h
mov [rsp+010h], rdi
.savereg rdi, 010h ; you can still use RSP as the base of the frame
; or any other register you choose
.endprolog
; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer
sub rsp, 060h
; we can unwind from the next AV because of the frame pointer
mov rax, 0
mov rax, [rax] ; AV!
; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5
movdqa xmm7, [rbp]
mov rsi, [rbp+018h]
mov rdi, [rbp-010h]
; Here's the official epilog
lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
pop rbp
ret
sample ENDP
有关 epilog 示例的详细信息,请参阅 x64 prolog 和 epilog 中的 Epilog 代码。
MASM 宏
若要简化原始伪操作的使用,请使用ksamd64.inc中定义的一组宏。 这些宏可帮助你创建典型的过程序幕和尾声。
| 宏 | 说明 |
|---|---|
| alloc_stack(n) | 分配一个 n 字节的堆栈帧(使用 sub rsp, n),并生成相应的展开信息(.allocstack n) |
| save_reg reg、 loc | 将非易失性寄存器 reg 保存到堆栈中偏移 RSPloc 处,并发出相应的展开信息 (.savereg reg, loc) |
| push_reg reg | 将非易失性寄存器 reg 压入堆栈,并生成相应的展开信息(.pushreg reg) |
| rex_push_regreg | 使用 2 字节的 push 指令将非易失性寄存器压入栈中保存,并生成相应的展开信息(.pushreg reg)。 在函数中,如果 `push` 是第一个指令,则使用此宏,以确保函数可进行热修补。 |
| save_xmm128 reg、 loc | 将非易失性 XMM 寄存器 reg 保存在堆栈上偏移 RSPloc 处,并生成相应的展开信息(.savexmm128 reg, loc) |
| set_frame reg, offset | 将帧寄存器 reg 设置为 RSP + 偏移量(使用 mov 或 lea),并生成相应的展开信息(.set_frame reg, offset) |
| push_eflags | 使用 pushfq 指令推送 eflags,并发出适当的展开信息(.alloc_stack 8) |
下面是一个正确使用这些宏的函数前导代码示例:
sampleFrame struct
Fill dq ?; fill to 8 mod 16
SavedRdi dq ?; Saved Register RDI
SavedRsi dq ?; Saved Register RSI
sampleFrame ends
sample2 PROC FRAME
alloc_stack(sizeof sampleFrame)
save_reg rdi, sampleFrame.SavedRdi
save_reg rsi, sampleFrame.SavedRsi
.end_prolog
; function body
mov rsi, sampleFrame.SavedRsi[rsp]
mov rdi, sampleFrame.SavedRdi[rsp]
; Here's the official epilog
add rsp, (sizeof sampleFrame)
ret
sample2 ENDP
展开 C 语言中的数据定义
下面是展开数据的 C 语言描述:
typedef enum _UNWIND_OP_CODES {
UWOP_PUSH_NONVOL = 0, /* info == register number */
UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */
UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */
UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */
UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;
typedef unsigned char UBYTE;
typedef union _UNWIND_CODE {
struct {
UBYTE CodeOffset;
UBYTE UnwindOp : 4;
UBYTE OpInfo : 4;
};
USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
#define UNW_FLAG_EHANDLER 0x01
#define UNW_FLAG_UHANDLER 0x02
#define UNW_FLAG_CHAININFO 0x04
typedef struct _UNWIND_INFO {
UBYTE Version : 3;
UBYTE Flags : 5;
UBYTE SizeOfProlog;
UBYTE CountOfCodes;
UBYTE FrameRegister : 4;
UBYTE FrameOffset : 4;
UNWIND_CODE UnwindCode[1];
/* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
* union {
* OPTIONAL ULONG ExceptionHandler;
* OPTIONAL ULONG FunctionEntry;
* };
* OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;
typedef struct _RUNTIME_FUNCTION {
ULONG BeginAddress;
ULONG EndAddress;
ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
#define GetUnwindCodeEntry(info, index) \
((info)->UnwindCode[index])
#define GetLanguageSpecificDataPtr(info) \
((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))
#define GetExceptionHandler(base, info) \
((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))
#define GetChainedFunctionEntry(base, info) \
((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))
#define GetExceptionDataPtr(info) \
((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))