CPP 异常处理机制初探
字数 2241 2025-08-27 12:33:48

C++ 异常处理机制深入解析

1. C++ 异常处理概述

C++异常处理机制主要解决两个核心问题:

  1. 异常匹配:根据抛出的异常找到合适的异常处理代码(匹配对应类型的catch块)
  2. 栈回滚:当抛出异常的函数无法处理异常时,需要:
    • 清理当前栈帧上的对象(可能需要执行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异常处理接口,由两部分组成:

  1. libunwind:通用的异常处理库
  2. libc++:专注于C++异常处理的模块,提供personality函数

异常处理流程

分为两个阶段:

  1. 搜索阶段(Search Phase):

    • _UA_SEARCH_PHASE标志调用personality函数
    • 从当前PC开始,逐帧向上搜索
    • 如果找不到匹配的handler,调用terminate()
    • 如果找到,进入清理阶段
  2. 清理阶段(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;
};

异常对象创建过程:

  1. __cxa_allocate_exception分配sizeof(__cxa_exception) + sizeof(异常类)内存
  2. 在前部初始化__cxa_exception对象
  3. 在后部构造异常类实例

4. 异常处理帧(Windows平台)

Windows下EXE文件的异常处理信息:

  • 存储在.pdata段中的RUNTIME_FUNCTION
  • 每个函数对应一个UNWIND_INFO结构体,包含:
    • 函数中的try块信息
    • 异常处理需要的cleanup块和catch块
    • 函数序言操作(用于栈回滚)

UNWIND_INFO关键成员

  • Exception Handler:包含两个重要成员:
    1. Address of exception handler:personality函数地址(如__gxx_personality_seh0
    2. 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解析过程

  1. 获取LSDA指针

    uint8_t* lsda = (uint8_t*)_Unwind_GetLanguageSpecificData(context);
    
  2. 解析LSDA头部

    • lpStartEncoding:landing pad起始编码方式
    • ttypeEncoding:类型信息编码方式
    • callSiteEncoding:调用点表编码方式
    • classInfoOffset:类信息表偏移量
  3. 解析callSiteTable

    • 每个try块对应一个表项,包含:
      • start:try块起始地址
      • length:try块长度
      • landingPad:landing pad地址
      • actionEntry:action表项偏移量
  4. 解析actionEntry链表

    • 每个actionEntry包含:
      • ttypeIndex:类型索引(SLEB128格式)
      • actionOffset:下一action项偏移量
    • 链表结构:如0x40710F → 0x40710D → 0x40710B → 0x407109
  5. 类型匹配检查

    • 对每个ttypeIndex,通过classInfo表找到对应的typeinfo
    • 检查catchType->can_catch(excpType, adjustedPtr)

classInfo表解析

  • 倒序存储,第一项在最高地址
  • 每项编码方式由ttypeEncoding指定
  • 通过ttypeIndex查表得到对应的typeinfo地址

6. 异常处理返回机制

当异常处理找到匹配的catch块后:

  1. 设置寄存器:

    • rax:指向异常对象的unwind_exception成员
    • rdx:catch块的ttypeIndex(类编号)
  2. 关键代码:

    _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);
    
  3. 控制流跳转到catch块起始地址

7. 关键数据结构总结

数据结构 描述
RUNTIME_FUNCTION 函数异常处理信息入口
UNWIND_INFO 函数异常处理详细信息
LSDA 语言特定数据区域
callSiteTable try块信息表
actionEntry catch块处理动作链表
classInfo 类型信息表

8. 逆向分析要点

  1. 定位.pdata段和RUNTIME_FUNCTION
  2. 分析UNWIND_INFO结构:
    • personality函数地址
    • LSDA指针
  3. 解析LSDA:
    • callSiteTable → try块范围
    • actionEntry → catch块处理逻辑
    • classInfo → 异常类型信息
  4. 跟踪__cxa_throw到catch块的跳转过程:
    • rax/rdx寄存器设置
    • landing pad地址计算
C++ 异常处理机制深入解析 1. C++ 异常处理概述 C++异常处理机制主要解决两个核心问题: 异常匹配 :根据抛出的异常找到合适的异常处理代码(匹配对应类型的catch块) 栈回滚 :当抛出异常的函数无法处理异常时,需要: 清理当前栈帧上的对象(可能需要执行cleanup块) 回退栈帧到上一层函数 递归向上搜索直到找到匹配的catch块或栈为空 示例代码分析 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_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 是解析异常处理数据结构的关键函数,其调用链: scan_ eh_ tab解析过程 获取LSDA指针 : 解析LSDA头部 : lpStartEncoding :landing pad起始编码方式 ttypeEncoding :类型信息编码方式 callSiteEncoding :调用点表编码方式 classInfoOffset :类信息表偏移量 解析callSiteTable : 每个try块对应一个表项,包含: start :try块起始地址 length :try块长度 landingPad :landing pad地址 actionEntry :action表项偏移量 解析actionEntry链表 : 每个actionEntry包含: ttypeIndex :类型索引(SLEB128格式) actionOffset :下一action项偏移量 链表结构:如 0x40710F → 0x40710D → 0x40710B → 0x407109 类型匹配检查 : 对每个 ttypeIndex ,通过 classInfo 表找到对应的 typeinfo 检查 catchType->can_catch(excpType, adjustedPtr) classInfo表解析 倒序存储,第一项在最高地址 每项编码方式由 ttypeEncoding 指定 通过 ttypeIndex 查表得到对应的 typeinfo 地址 6. 异常处理返回机制 当异常处理找到匹配的catch块后: 设置寄存器: rax :指向异常对象的 unwind_exception 成员 rdx :catch块的 ttypeIndex (类编号) 关键代码: 控制流跳转到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地址计算