X64 SEH的展开

本文介绍了X64架构中,结构化异常处理(SEH)的静态特性,以及如何通过PE文件中的RUNTIME_FUNCTION和UNWIND_INFO结构来处理异常。内容涉及UNWIND_CODE的解析,__C_specific_handler函数的作用,并详细解释了如何通过ScopeTable进行栈回滚。同时,文章还探讨了windbg在X64栈回溯中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++ 代码:

#include "stdafx.h"
#include <windows.h>


ULONG WINAPI FilterFunc(DWORD dwExceptionCode)
{
	return (dwExceptionCode == STATUS_INTEGER_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
}

int add1(int a,int b)
{
	int c = 0,d=0;
	__try
	{
		printf("a address=%p\n",&a);
		__try
		{
			printf("b address=%p\n", &b);
			c = a / b;
		}
		__except (FilterFunc(GetExceptionCode()))
		{
			printf("c address=%p\n", &c);
			c = a / (a + b);
		}

	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		printf("d address=%p\n", &d);
		c = c + 1;
	}

	return c;
}

#include <stdio.h> 
#include <Windows.h> 
int main()
{
	DebugBreak();
	add1(0,0);
	return 0;
}

百度云代码:SEH_Sample.zip



1 X64 的基本概念和结构体

相比于X86 在程序运行中动态构建SEH结构,X64-SEH 是静态的,其信息包含在PE文件中。下面我们首先看一下对应的结构,然后看看其提供的信息是否能够满足异常捕获以及异常处理的功能。

为异常处理和调试器支持展开数据


执行异常处理和调试支持所需的数据结构

RUNTIME_FUNCTION
typedef struct _RUNTIME_FUNCTION_ { 
    DWORD BeginAddress;                             // 函数起始地址 
    DWORD EndAddress;                                // 函数结束地址 
    DWORD UnwindInfoAddress;               // 展开信息地址看下面的_UNWIND_INFO 结构体    
}RUNTIME_FUNCTION , *_RUNTIME_FUNCTION ;
该结构在内存中必须为DWORD 对齐。所有的地址都是ImageBase 的 RVA 值。这些项已经经过排序了(按照BeginAddress升序排列),放置在PE32+ 的 .pdata节中。对于动态生成的函数,我们暂时不介绍。
UNWIND_INFO
// 
// Define unwind information flags. 
//
#define UNW_FLAG_NHANDLER 0x0 
#define UNW_FLAG_EHANDLER 0x1 
#define UNW_FLAG_UHANDLER 0x2 
#define UNW_FLAG_CHAININFO 0x4
上面四个标志依次代表:
既没有EXCEPT_FILTER也没有EXCEPT_HANDLER
有EXCEPT_FILTER 和 EXCEPT_HANDLER
有 FINALLY_HANDLER
有多个UNWIND_INFO 串联在一起,
typedef struct _UNWIND_INFO { 
    UCHAR Version : 3;                     // 版本信息,当前为1 
    UCHAR Flags : 5;                          // 对应上面的四个标志 
    UCHAR SizeOfProlog;                 // Prolog 的大小,单位是字节 
    UCHAR CountOfCodes;              // UNWIND_INFO 包含多少UNWIND_CODE结构 
    UCHAR FrameRegister : 4;   
    UCHAR FrameOffset : 4;  
    UNWIND_CODE UnwindCode[1];
// 
// unwind codes 数组后面是一个可选的DWROD 对齐的成员。此成员有两种可能,异常处理函数地址或者function table entry(flags中指定了UNW_FALGS_CHAININFO),如果是异常处理函数地址的话,它将为一个语言相关的异常处理数据 
// 
  union { 
      struct { //下面两个组成一个结构体,不是联合体,看清楚
          ULONG ExceptionHandler; 
          ULONG ExceptionData[]; 
      }; 
 
      RUNTIME_FUNCTION FunctionEntry; //如果上面的Flags指定的是 UNW_FLAG_CHAININFO,该联合体为 RUNTIME_FUNCTION
  }; 

} UNWIND_INFO, *PUNWIND_INFO;
UNWIND_INFO 结构体必须是DWORD 对齐的。
FrameRegister
如果不是0 的话,这个函数使用了帧指针,该值表示帧指针使用的非易失性寄存器的数目。与UNWIND_CODE中的成员使用相同的编码。
FrameOffset
如果FrameRegister 不为0,表示在刚建立栈帧时,应用于FP 寄存器的RSP 的缩放偏移量。实际的FP 为RSP+16*当前值,范围是0~240。这样允许将FP 寄存器指向本地动态栈帧的中间位置,然后通过更短的指令来提高代码密度(更多的 指令将可以使用8位带符号偏移形式)。
Unwind code 数组
这一系列的code 代表了prolog 中对于非易失性寄存器和RSP 寄存器的影响。由于对齐, 这个数组始终有偶数个,最后的一个成员可能未被使用。
Exception Handler
ExceptionHandler 该域为一个RVA,指向exception handler的地址,ExceptionData 指向一个类似于scopetable 的地址
如果Flags指定的是 UNW_FLAG_CHAININFO,该域为 RUNTIME_FUNCTION
UNWIND_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,     /* 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 union _UNWIND_CODE {  
    struct {  
        UBYTE CodeOffset;  
        UBYTE UnwindOp : 4;  
        UBYTE OpInfo   : 4;  
    };  
    USHORT FrameOffset;  
} UNWIND_CODE, *PUNWIND_CODE;  
UNWIND_CODE结构体用来记录在proglog 中影响非易失性寄存器和RSP寄存器的序列。每个UNWIND_CODE有上述结构。其中,分别表示本操作在prolog中的offset,Unwind操作码,操作信息。该数组的排列按照prolog中的offset的降序排列。
 
 有些展开操作代码需要本地栈帧的一个无符号偏移。这个偏移是相对于固定栈申请而言的。如果UNWIND_INFO的Frame Register 成员为0,offset 是对RSP而言的,否则,offset 是相对于在建立栈帧的时候RSP 被存储的的地方。此时需要栈帧-栈帧寄存器的偏移(16*缩放帧寄存器在UNWIND_INFO中的偏移)。如果FP 寄存器被使用,所有使用offset的unwind code必须在prolog建立栈帧之后才可以使用。


2  查找add1函数的RUNTIME_FUNCTION

 使用windbg 

0:000> .fnent ConsoleApplication3!add1
Debugger function entry 00000000`003a0fc0 for:
d:\work\temp\consoleapplication3\consoleapplication3.cpp(14)
(00000001`3f501030)   ConsoleApplication3!add1   |  (00000001`3f5010e0)   ConsoleApplication3!main
Exact matches:
    ConsoleApplication3!add1 (int, int)


BeginAddress      = 00000000`00001030
EndAddress        = 00000000`000010d4
UnwindInfoAddress = 00000000`00002670


Unwind info at 00000001`3f502670, 10 bytes
  version 1, flags 1, prolog c, codes 1
  handler routine: ConsoleApplication3!_C_specific_handler (00000001`3f501e10), data 2
  00: offs c, unwind op 2, op info 8	UWOP_ALLOC_SMALL.

根据上面的信息,结合Study_PE+,从.pdata section中找到对应的信息




2 .pdada Section中寻找add1 UNW_INFO的信息

我们知道add1的UNW_INFO 的RVA是2670,所以我们使用PE工具得到FOA




3 add1的 UNW_INFO 解析

000000001A70: 09 0C 01 00

09的二进制00

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值