CPP 异常处理机制初探
字数 2241 2025-08-27 12:33:48
C++ 异常处理机制深入解析
1. C++ 异常处理概述
C++异常处理机制主要解决两个核心问题:
- 异常匹配:根据抛出的异常找到合适的异常处理代码(匹配对应类型的catch块)
- 栈回滚:当抛出异常的函数无法处理异常时,需要:
- 清理当前栈帧上的对象(可能需要执行cleanup块)
- 回退栈帧到上一层函数
- 递归向上搜索直到找到匹配的catch块或栈为空
示例代码分析
#include <iostream>
#include <exception>
using namespace std;
struct ExceptionA : public exception {
ExceptionA(int a, int b) : a(a), b(b){}
int a, b;
};
class Strobj {
public:
Strobj(char* a) {
str_ = new char[strlen(a) + 1];
strcpy(str_, a);
}
~Strobj() { delete[] str_; }
char* str_;
};
Strobj doThrow(bool doth) {
Strobj oops("123456");
if(doth) throw ExceptionA(1, 2);
return oops;
}
int main() {
try {
Strobj a = doThrow(true);
} catch(ExceptionA& e) {
cout << "ExceptionA caught" << endl;
}
}
2. Itanium C++ ABI 异常处理框架
GNUC++实现的是Itanium C++ ABI异常处理接口,由两部分组成:
- libunwind:通用的异常处理库
- libc++:专注于C++异常处理的模块,提供
personality函数
异常处理流程
分为两个阶段:
-
搜索阶段(Search Phase):
- 以
_UA_SEARCH_PHASE标志调用personality函数 - 从当前PC开始,逐帧向上搜索
- 如果找不到匹配的handler,调用terminate()
- 如果找到,进入清理阶段
- 以
-
清理阶段(Cleanup Phase):
- 以
_UA_CLEANUP_PHASE标志调用personality函数 - 真正回滚栈帧,执行cleanup块
- 回滚到有匹配handler的函数,跳转到catch块
- 以
3. 异常对象数据结构
异常对象的内存布局:
| __cxa_exception | _Unwind_Exception | thrown object |
关键结构体
struct _Unwind_Exception {
uint64 exception_class; // "CLNGC++\0"
_Unwind_Exception_Cleanup_Fn exception_cleanup;
uint64 private_1;
uint64 private_2;
};
struct __cxa_exception {
std::type_info* exceptionType;
void (*exceptionDestructor)(void*);
__cxa_exception* nextException;
int handlerCount;
int handlerSwitchValue;
const char* actionRecord;
const char* languageSpecificData;
void* catchTemp;
void* adjustedPtr;
_Unwind_Exception unwindHeader;
};
异常对象创建过程:
__cxa_allocate_exception分配sizeof(__cxa_exception) + sizeof(异常类)内存- 在前部初始化
__cxa_exception对象 - 在后部构造异常类实例
4. 异常处理帧(Windows平台)
Windows下EXE文件的异常处理信息:
- 存储在
.pdata段中的RUNTIME_FUNCTION表 - 每个函数对应一个
UNWIND_INFO结构体,包含:- 函数中的try块信息
- 异常处理需要的cleanup块和catch块
- 函数序言操作(用于栈回滚)
UNWIND_INFO关键成员
- Exception Handler:包含两个重要成员:
Address of exception handler:personality函数地址(如__gxx_personality_seh0)Language-specific handler data:异常处理相关数据
5. Language-specific handler data解析
__gxx_personality_seh0是解析异常处理数据结构的关键函数,其调用链:
__gxx_personality_seh0 → _GCC_specific_handler → __gxx_personality_imp → scan_eh_tab
scan_eh_tab解析过程
-
获取LSDA指针:
uint8_t* lsda = (uint8_t*)_Unwind_GetLanguageSpecificData(context); -
解析LSDA头部:
lpStartEncoding:landing pad起始编码方式ttypeEncoding:类型信息编码方式callSiteEncoding:调用点表编码方式classInfoOffset:类信息表偏移量
-
解析callSiteTable:
- 每个try块对应一个表项,包含:
start:try块起始地址length:try块长度landingPad:landing pad地址actionEntry:action表项偏移量
- 每个try块对应一个表项,包含:
-
解析actionEntry链表:
- 每个actionEntry包含:
ttypeIndex:类型索引(SLEB128格式)actionOffset:下一action项偏移量
- 链表结构:如
0x40710F → 0x40710D → 0x40710B → 0x407109
- 每个actionEntry包含:
-
类型匹配检查:
- 对每个
ttypeIndex,通过classInfo表找到对应的typeinfo - 检查
catchType->can_catch(excpType, adjustedPtr)
- 对每个
classInfo表解析
- 倒序存储,第一项在最高地址
- 每项编码方式由
ttypeEncoding指定 - 通过
ttypeIndex查表得到对应的typeinfo地址
6. 异常处理返回机制
当异常处理找到匹配的catch块后:
-
设置寄存器:
rax:指向异常对象的unwind_exception成员rdx:catch块的ttypeIndex(类编号)
-
关键代码:
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0), reinterpret_cast<uintptr_t>(unwind_exception)); _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), static_cast<uintptr_t>(results.ttypeIndex)); _Unwind_SetIP(context, results.landingPad); -
控制流跳转到catch块起始地址
7. 关键数据结构总结
| 数据结构 | 描述 |
|---|---|
RUNTIME_FUNCTION |
函数异常处理信息入口 |
UNWIND_INFO |
函数异常处理详细信息 |
LSDA |
语言特定数据区域 |
callSiteTable |
try块信息表 |
actionEntry |
catch块处理动作链表 |
classInfo |
类型信息表 |
8. 逆向分析要点
- 定位
.pdata段和RUNTIME_FUNCTION表 - 分析
UNWIND_INFO结构:- personality函数地址
- LSDA指针
- 解析LSDA:
- callSiteTable → try块范围
- actionEntry → catch块处理逻辑
- classInfo → 异常类型信息
- 跟踪
__cxa_throw到catch块的跳转过程:- rax/rdx寄存器设置
- landing pad地址计算