C++异常处理:unwinding库析构局部对象的替代函数入口点是什么?
在C++异常处理的栈展开(unwinding)场景中,用于析构局部对象的**替代函数入口点(alternative function entry point)**是编译器为函数生成的特殊执行入口——它并非函数的常规起始地址,而是栈展开机制在异常发生时直接跳转到的代码位置,负责执行局部对象析构等清理逻辑,之后继续完成栈展开流程。
示例C++代码
class C { public: C(){} ~C(){} }; void g() { throw std::exception(); } void f() { C c; g(); } int main() { return 0; }
生成的汇编代码及疑问标注
00000000004044e0 <f()>: 4044e0: 48 83 ec 18 subq $24, %rsp 4044e4: 48 8d 7c 24 10 leaq 16(%rsp), %rdi 4044e9: e8 52 00 00 00 callq 0x404540 <C::C()> 4044ee: e8 6d 00 00 00 callq 0x404560 <g()> 4044f3: e9 00 00 00 00 jmp 0x4044f8 <f()+0x18> 4044f8: 48 8d 7c 24 10 leaq 16(%rsp), %rdi 4044fd: e8 4e 00 00 00 callq 0x404550 <C::~C()> 404502: 48 83 c4 18 addq $24, %rsp 404506: c3 retq ; 这是替代函数入口点吗?unwinding机制(__cxa_throw())如何到达此处? ; 另外,当被__cxa_throw()调用时,%rax似乎指向_Unwind_Exception结构体的实例, ; 该实例被保存在栈上,随后用作_Unwind_resume的第一个参数? ; 还有unwinding机制设置的%edx(推测为int类型)在此栈帧中未被使用,它的作用是什么? 404507: 48 89 c1 movq %rax, %rcx 40450a: 89 d0 movl %edx, %eax 40450c: 48 89 4c 24 08 movq %rcx, 8(%rsp) 404511: 89 44 24 04 movl %eax, 4(%rsp) 404515: 48 8d 7c 24 10 leaq 16(%rsp), %rdi 40451a: e8 31 00 00 00 callq 0x404550 <C::~C()> 40451f: 48 8b 7c 24 08 movq 8(%rsp), %rdi 404524: e8 37 51 0a 00 callq 0x4a9660 <_Unwind_Resume> 404529: 0f 1f 80 00 00 00 00 nopl (%rax)
LLVM文档相关内容
invoke指令在异常发生后继续执行的位置被称为landing pad(着陆垫)。LLVM landing pad在概念上是alternative function entry points(替代函数入口点),会传入异常结构引用和类型信息索引作为参数。landing pad会保存异常结构引用,然后选择与异常对象类型信息对应的catch块。
LLVM的‘landingpad’指令用于向后端传递关于landing pad的信息。对于C++,landingpad指令返回一个指针和整数对,分别对应异常结构的指针和选择器值。
landingpad指令会在父函数的属性列表中查找该try/catch序列要使用的personality function(个性函数)的引用。该指令包含cleanup、catch和filter子句的列表。
详细解释
1. 替代函数入口点的本质
你标注的404507开始的代码段,就是f()的替代函数入口点,对应LLVM文档中的landing pad(着陆垫)。它是编译器为f()生成的特殊执行分支:
- 函数的常规入口是
4044e0,负责正常流程的执行(分配栈、构造对象、调用g()、析构对象返回); - 而
404507是异常展开时的专属入口,仅在g()抛出异常、栈展开触发时被执行。
2. 栈展开机制如何到达此处
当g()中的throw触发后,C++运行时库的__cxa_throw会调用unwind库的_Unwind_RaiseException,后者会:
- 遍历当前调用栈,读取每个函数的栈帧异常处理信息(EH Frame);
- 发现
f()存在需要在异常时清理的局部对象(C c),找到对应的cleanup landing pad地址(即404507); - 直接跳转到该地址,而非按照函数正常流程执行。
3. %rax与%edx的作用
这两个寄存器是LLVM landingpad指令的标准返回值传递载体:
- %rax:指向
_Unwind_Exception结构体实例,该结构体保存了异常的核心信息。代码中把它存入栈上,之后传递给_Unwind_Resume,用于继续向上执行栈展开; - %edx:是异常类型选择器值,用于在有
catch块的场景中匹配异常类型。由于f()没有catch块,仅需要执行cleanup,所以这个值在这里没有被使用,但它是unwind机制传递的标准参数。
4. 整个异常处理流程
- 正常执行:
f()从4044e0进入,分配栈空间→构造C→调用g()→若g()正常返回,跳转到4044f8析构C→返回; - 异常发生:
g()抛出异常→unwind机制找到f()的landing pad(404507)→跳转到此处保存异常信息→析构C→调用_Unwind_Resume继续向上展开栈,直到找到匹配的catch块或终止程序。
内容的提问来源于stack exchange,提问作者alex35833

