1.基础语法 try:可能出现异常的代码,使用throw抛出指定类型的异常。
throw:抛出异常语句。
catch:捕获指定类型的异常,并尝试做出处理。可以定义多个用于捕获各种类型的异常。
基本类型异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> double division (int a, int b) { if (b == 0 ) throw "Division by zero condition." ; else return a / b; }int main () { try { std::cout << division (1 , 0 ) << std::endl; } catch (const char * msg) { std::cout << msg << std::endl; } catch (...) { std::cout << "exception catched." << std::endl; } return 0 ; }
C++标准异常
自定义异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <exception> class MyException : public std::exception {public : const char * what () const throw () { return "MyException." ; } };int main () { try { throw MyException (); } catch (MyException& e) { std::cout << e.what () << std::endl; } catch (std::exception& e) { std::cout << e.what () << std::endl; } return 0 ; }
2.x86异常处理 2.1 异常栈帧结构图
2.2 结构定义 2.2.1 FuncInfo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct FuncInfo { DWORD magicNumber; int maxState; UnwindMapEntry* pUnwindMap; DWORD nTryBlocks; TryBlockMapEntry* pTryBlockMap; DWORD nIPMapEntries; void * pIPtoStateMap; ESTypeList* pESTypeList; int EHFlags; };
2.2.2 UnwindMapEntry 1 2 3 4 5 struct UnwindMapEntry { int toState; void (*action)(); };
2.2.3 TryBlockMapEntry 1 2 3 4 5 6 7 8 9 struct TryBlockMapEntry { int tryLow; int tryHigh; int catchHigh; int nCatches; HandlerType* pHandlerArray; };
2.2.4 HandlerType 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct HandlerType { DWORD adjectives; TypeDescriptor* pType; int dispCatchObj; void * addressOfHandler; };
2.2.5 ESTypeList 1 2 3 4 5 6 7 8 struct ESTypeList {int nCount; HandlerType* pTypeArray; };
2.2.6 TypeDescriptor 1 2 3 4 5 6 7 8 9 10 11 struct TypeDescriptor { const void * pVFTable; void * spare; char name[0 ]; };
2.3 throw结构定义 2.3.1 ThrowInfo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct ThrowInfo { DWORD attributes; void (*pmfnUnwind)(); int (*pForwardCompat)(); CatchableTypeArray* pCatchableTypeArray; }; struct CatchableTypeArray { int nCatchableTypes; CatchableType* arrayOfCatchableTypes[0 ]; };
2.3.2 CatchableTypeArray 1 2 3 4 5 6 struct CatchableTypeArray { int nCatchableTypes; CatchableType* arrayOfCatchableTypes[0 ]; };
2.3.3 CatchableType 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 struct CatchableType { DWORD properties; TypeDescriptor* pType; PMD thisDisplacement; int sizeOrOffset; void (*copyFunction)(); }; struct PMD { int mdisp; int pdisp; int vdisp; };
3. 示例分析 3.1 try/catch块分析 函数头部:
异常处理函数体:
函数信息结构内容:
可以看出main函数中有2个栈展开结构,1一个try块。
其中,栈展开信息内容如下:
try块信息如下:
可以看出该try块对应2个catch异常处理。
pHandlerArray信息如下:
其中,catch_code1和catch_code2就是编写的catch块内的异常处理代码,如下所示:
3.2 回调注册分析–对象类型异常 __CxxFrameHandler3函数将参数向下传递。
__InternalCxxFrameHandler函数主要是对一些信息进行校验,然后调用FindHandler函数查找try/catch块。
FindHandler函数中使用三层循环对try块的范围进行校验,并将符合要求的try与catch进行类型比对,比对成功调用CatchIt完成异常处理。
__TypeMatch函数中使用strcmp比较了RTTI的name字符串。
CatchIt函数中主要是处理异常对象、执行栈展开、执行catch块。
其中栈展开调用了如下API:
之后循环调用UnwindMapEntry.action完成局部对象的析构。
最终执行catch块代码,完成异常处理。
在catch块代码执行完后,会将eax修改为catch块结束位置,返回给上一层函数。
最终由JumpToContinuation函数跳转到catch块结束位置完成异常处理。
3.3 throw过程分析 异常对象抛出位置,可以看到ThrowInfo作为参数传递。如果抛出的是对象类型的异常,这里会调用它的构造函数。
相关结构信息如下:
根据上述信息可以看出抛出的异常类型为 基本类型 ,可以被char*、void*类型的catch块捕获。
在__CxxThrowException函数中调用API向内核抛出异常。
之后就是内核对异常的处理,实现方式由系统决定。
4.x64异常处理 x64中几乎每个函数都存在一个RUNTIME_FUNCTION结构(除了不操作rsp、没有异常处理的函数、不会调用其他函数),保存在pe文件的.pdata段。从这个结构出发可以找到UNWIND_INFO,最终找到FuncInfo结构,需要注意的是x64下指针位置都是RVA,需要加上ImageBase来定位真实地址。
4.1 结构定义 4.1.1 RUNTIME_FUNCTION 1 2 3 4 5 6 typedef struct _RUNTIME_FUNCTION { ULONG BeginAddress; ULONG EndAddress; ULONG UnwindData; } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
4.1.2 UNWIND_INFO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef struct _UNWIND_INFO { UCHAR Version : 3 ; UCHAR Flags : 5 ; UCHAR SizeOfProlog; UCHAR CountOfCodes; UCHAR FrameRegister : 4 ; UCHAR FrameOffset : 4 ; UNWIND_CODE UnwindCode[1 ]; ULONG FunctionEntry; ULONG ExceptionData; } UNWIND_INFO, *PUNWIND_INFO; Flag: 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)
4.1.3 UNWIND_CODE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef union _UNWIND_CODE { struct { UCHAR CodeOffset; UCHAR UnwindOp : 4 ; UCHAR OpInfo : 4 ; }; USHORT FrameOffset; } UNWIND_CODE, *PUNWIND_CODE;typedef enum _UNWIND_OP_CODES { UWOP_PUSH_NONVOL = 0 , UWOP_ALLOC_LARGE, UWOP_ALLOC_SMALL, UWOP_SET_FPREG, UWOP_SAVE_NONVOL, UWOP_SAVE_NONVOL_FAR, UWOP_SPARE_CODE1, UWOP_SPARE_CODE2, UWOP_SAVE_XMM128, UWOP_SAVE_XMM128_FAR, UWOP_PUSH_MACHFRAME } UNWIND_OP_CODES, *PUNWIND_OP_CODES;
4.1.3 FuncInfo变化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct FuncInfo { DWORD magicNumber; int maxState; UnwindMapEntry* pUnwindMap; DWORD nTryBlocks; TryBlockMapEntry* pTryBlockMap; DWORD nIPMapEntries; void * pIPtoStateMap; UnWindMapEntry* pUnWindMapEntry; ESTypeList* pESTypeList; int EHFlags; };
4.1.4 IptoStateMapEntry 1 2 3 4 struct IptoStateMapEntry { ULONG __Ip; ULONG State; };
4.2 结构关系图 x86应用程序中,使用栈空间的一个变量标 识try块的状态索引,在x64中不再使用该变量,而是通过产生异常的 地址(RIP)查询IP状态映射表来获取try块的状态索引,结构如下图所示:
4.3 示例分析 查看main函数的交叉引用可以定位到它的RUNTIME_FUNCTION结构,如下图所示:
根据RUNTIME_FUNCTION的第三个成员定位到UNWIND_INFO结构,如下图所示:
UNWIND_INFO结构信息如下:
根据rva定位到FuncInfo结构,如下图所示:
进而定位到TryBlockMapEntry数组以及HandlerType数组,得到try1的索引为0~0,如下图所示:
最终定位到catch块地址,如下图所示:
根据FuncInfo定位IP状态表,如下图所示:
根据try1的索引00可以在IP表中确定边界为0x1400A8BC90x1400A8BEB,验证如下:
catch1的范围为0x1401DC1B40x1401DC1ED,catch2的范围为0x1401DC2100x1401DC249,如下图所示:
5. 总结
基于Windows SEH实现,在函数头部构造异常链,以及异常处理回调函数的注册。
异常处理结构信息在编译阶段生成好。
throw调用了Windows API来完成异常的抛出。
类型识别基于RTTI实现,本质就是编译器生成的类型字符串,使用strcmp比对来寻找合适的catch进行执行。
栈展开机制是在当前函数中找不到catch时,需要向上层函数寻找时被触发,展开过程需要调用局部对象的析构函数。