实现Personality Function时Landing Pad与Action Index均为0的清理问题咨询
我正在自行实现Personality Function,需要解析调用站点表(call site table),该表每条记录包含callsite offset、callsite length、landing pad offset、action idx四个字段。我的处理逻辑如下:
- 若抛出指令的地址小于任意调用站点地址,则终止;
- 若
landing pad offset为0,则继续展开(unwinding); - 若
action idx为0:- 若仍处于查找状态,则继续展开;
- 若处于清理状态,则将控制权转移至landing pad。
所有记录按地址从低到高顺序解析。
测试如下简单C++代码时:
void raise() { printf("Beggining of raise\n"); struct DestroyMe { volatile int payload; DestroyMe() { printf("DESTROY ME raise\n"); payload = 1; }; ~DestroyMe() { printf("I AM DESTOYED raise\n"); payload = 0; }; }; DestroyMe dm{}; printf("Before throw\n"); throw Exception(); }
Clang生成的调用站点表如下:
.Lcst_begin0: .uleb128 .Lfunc_begin0-.Lfunc_begin0 # >> Call Site 1 << .uleb128 .Ltmp0-.Lfunc_begin0 # Call between .Lfunc_begin0 and .Ltmp0 .byte 0 # has no landing pad .byte 0 # On action: cleanup .uleb128 .Ltmp0-.Lfunc_begin0 # >> Call Site 2 << .uleb128 .Ltmp1-.Ltmp0 # Call between .Ltmp0 and .Ltmp1 .uleb128 .Ltmp4-.Lfunc_begin0 # jumps to .Ltmp4 .byte 0 # On action: cleanup .uleb128 .Ltmp1-.Lfunc_begin0 # >> Call Site 3 << .uleb128 .Ltmp2-.Ltmp1 # Call between .Ltmp1 and .Ltmp2 .byte 0 # has no landing pad .byte 0 # On action: cleanup .uleb128 .Ltmp2-.Lfunc_begin0 # >> Call Site 4 << .uleb128 .Ltmp3-.Ltmp2 # Call between .Ltmp2 and .Ltmp3 .uleb128 .Ltmp4-.Lfunc_begin0 # jumps to .Ltmp4 .byte 0 # On action: cleanup .uleb128 .Ltmp3-.Lfunc_begin0 # >> Call Site 5 << .uleb128 .Lfunc_end0-.Ltmp3 # Call between .Ltmp3 and .Lfunc_end0 .byte 0 # has no landing pad .byte 0 # On action: cleanup
我有以下疑问:
- 明明存在需要调用的析构函数,为何
landing pad offset与action idx会同时为0? - 若此情况属于正常现象,应优先检查哪个字段?
- 若此情况正常,当
landing pad offset为0时,如何将控制权转移至清理逻辑?否则会直接跳转到函数开头。
注:我的逻辑与libcxxabi的实现一致,异常处理的Handler可正常工作,控制权能传递至预期的catch块,但清理逻辑的问题已困扰我数周。我参考了Itanium ABI相关文档及技术博客,但未找到答案。
问题1:为何landing pad offset与action idx同时为0?
栈上局部对象的清理是栈展开过程的固有环节,不需要依赖显式的landing pad。编译器在生成代码时,已经把析构函数的调用逻辑和栈帧的销毁步骤绑定到了栈展开流程中——当unwinder处理当前栈帧时,会自动执行这些析构逻辑,不需要通过跳转landing pad来触发。
这里的action idx=0是Itanium ABI规定的“清理动作标记”,表示当前调用站点区间内存在需要清理的对象;而landing pad offset=0则说明不需要额外的跳转,清理逻辑会直接在展开过程中被执行,不需要单独的landing pad代码块。
问题2:优先检查哪个字段?
优先检查**action idx**。action idx直接标记了当前调用站点的动作类型:0代表清理动作,非0则对应异常处理的handler逻辑。landing pad offset只是标记是否需要跳转执行额外代码,而动作类型才是决定逻辑分支的核心依据。
比如当action idx=0时,不管landing pad offset是否为0,都要先处理清理状态的逻辑,而不是先判断landing pad。
问题3:landing pad offset为0时如何触发清理?
当处于清理状态且landing pad offset=0时,不需要跳转,而是让unwinder继续执行栈展开流程即可。
编译器生成的栈帧信息(如.eh_frame段)中已经包含了当前栈帧的清理指令序列(包括局部对象的析构),Personality Function只需要向unwinder返回正确的展开指令(比如指示unwinder展开当前栈帧),unwinder会自动执行这些预定义的清理代码,完成局部对象的析构和栈帧的销毁,不需要手动跳转任何地址。
内容的提问来源于stack exchange,提问作者tntnkn

