- 公开视频 -> 链接点击跳转公开课程
- 博客首页 -> 链接点击跳转博客主页
目录
异常处理信息在 PE 文件中的存放
- PE异常目录: 在 PE 文件的 Data Directory 中,IMAGE_DIRECTORY_ENTRY_EXCEPTION 指向异常表,通常位于 .pdata 段。该段中包含整个模块中所有函数的异常信息条目。
- 功能: Windows x64 不在每个函数的栈中设置 SEH 帧,而是在静态数据(即异常表中)记录每个函数的异常和栈展开信息,在异常发生时,通过查询该表快速定位出当前函数对应的 RUNTIME_FUNCTION 数据,从而执行栈展开。
核心数据结构
RUNTIME_FUNCTION
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
DWORD BeginAddress; // 函数起始 RVA,相对于模块基址
DWORD EndAddress; // 函数结束 RVA
union {
DWORD UnwindInfoAddress; // 指向 UNWIND_INFO 结构的 RVA(通常)
DWORD UnwindData;
} DUMMYUNIONNAME;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
- 每个 RUNTIME_FUNCTION 条目描述一个函数的异常与栈展开信息。
- 在异常处理过程中,系统会根据错误地址在该表中查找匹配的函数范围。如果找到匹配的 BeginAddress 与 EndAddress,则根据 UnwindInfoAddress 获取对应的展开信息。
UNWIND_INFO
typedef struct _UNWIND_INFO {
UCHAR Version : 3; // 版本号(通常为1)
UCHAR Flags : 5; // 标志(如是否包含异常处理程序)
UCHAR SizeOfProlog; // 函数序言(Prolog)的字节数
UCHAR CountOfCodes; // 展开代码条目数量(数组中 UNWIND_CODE 个数)
UCHAR FrameRegister : 4; // 帧指针寄存器(如 RBP、RDI 等)
UCHAR FrameOffset : 4; // 帧指针偏移,用 16 字节单位(FP = RSP + FrameOffset * 16)
UNWIND_CODE UnwindCode[1]; // 不定长度数组,描述具体的栈展开操作
// 后续紧跟可选字段:
// 1. 如果设置了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,则紧跟有 ExceptionHandler 和 ExceptionData。
// 2. 如果设置了 UNW_FLAG_CHAININFO,则后续为一个 RUNTIME_FUNCTION 结构。
} UNWIND_INFO, *PUNWIND_INFO;
UNWIND_CODE
typedef union _UNWIND_CODE {
struct {
UBYTE CodeOffset; // 在函数中相对于 Prolog 起始处的偏移量,指定该操作的开始位置
UBYTE UnwindOp : 4; // 展开操作类型,属于 UNWIND_OP_CODES 之一
UBYTE OpInfo : 4; // 补充信息,根据不同的 UnwindOp 含义不同
};
USHORT FrameOffset; // 某些操作直接用来描述栈帧内偏移的值
} UNWIND_CODE, *PUNWIND_CODE;
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;
SCOPE_TABLE
typedef struct _SCOPE_TABLE {
DWORD Count;
struct {
DWORD BeginAddress;
DWORD EndAddress;
DWORD HandlerAddress;
DWORD JumpTarget;
} ScopeRecord[1];
} SCOPE_TABLE, *PSCOPE_TABLE;
图解
┌──────────────────────┐ ┌──────────────────────┐
│ RUNTIME_FUNCTION │ │ UNWIND_INFO │
├──────────────────────┤ ├──────────────────────┤
│ BeginAddress │ │ Version/Flags │
│ EndAddress │ │ SizeOfProlog │
│ UnwindInfoAddress |─────── | CountOfCodes │
└──────────────────────┘ │ FrameRegister/Offset │
│ UnwindCode[] │
│ ExceptionHandler ─┐
└──────────────────────┘ │
│
▼
┌──────────────────────┐
│ SCOPE_TABLE │
├──────────────────────┤
│ Count │
│ ScopeRecord[0..N] │
└──────────────────────┘
与 x86 平台 SEH 的对比
x86 SEH
- 基于链表机制,在每个函数的栈上构造 SEH 帧(链表节点)来捕获异常信息。
- 异常处理信息嵌入在函数栈区,处理过程依赖于栈上注册的异常处理例程。
x64 SEH
- 将所有异常相关信息集中存放于静态数据区(PE 文件中的异常目录),运行时通过查询定位当前函数展开信息。
- 栈展开完全依赖预编译生成的 UNWIND_INFO 和 UNWIND_CODE,由操作系统提供支持。