异常处理机制
以Itanium C++ ABI为例子,其为类unix操作系统普遍使用的c++ABI标准
参考
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-framework
https://maskray.me/blog/2020-12-12-c++-exception-handling-abi#%E4%B8%AD%E6%96%87%E7%89%88
一.BASE ABI
所有语言和实现通用的接口;感觉写的很混乱就做个参考吧
1.符合psABI架构的处理器的运行时库需要提供的接口
_Unwind_RaiseException, |
另外,定义了两种数据类型(_Unwind_Context 和 _Unwind_Exception),用于连接调用运行时(如 C++ 运行时)与上述例程的接口。所有例程和接口的行为都如同是用 extern "C"
定义的一样,特别是这些名称不会被修饰。作为此接口的一部分,所有定义的名称都以 “Unwind“ 作为前缀。
最后,编译器会在需要异常处理的栈帧的展开描述符中存储一个语言和供应商特定的 personality 函数。展开器(unwinder)会调用 personality 函数来处理特定语言的任务,例如识别处理特定异常的栈帧。
2.Data Structures详解
Reason Codes
typedef enum { |
Exception Header
typedef void (*_Unwind_Exception_Cleanup_Fn) |
Unwind Context
3.Throwing an Exception1
查找异常处理器:先查找是否有栈帧可以处理异常(阶段 1)。
展开栈与清理:如果找到处理器,则沿着调用栈展开并执行清理操作(阶段 2)。
此函数是两阶段的结合。
_Unwind_RaiseException
是一个函数,用于抛出一个异常。它会接收一个异常对象作为参数,这个异常对象必须包含一个已设置的exception_class
字段和exception_cleanup
字段。这些字段在抛出异常之前由特定语言的运行时设置。异常对象通常是由特定语言的运行时(如 C++ 的运行时)分配和初始化的。尽管这个异常对象的结构可以根据不同的语言有所不同,但它必须包含一个
_Unwind_Exception
结构体,作为异常处理过程的基础部分。只有在发生错误时,
_Unwind_RaiseException
才会返回。
_Unwind_Reason_Code _Unwind_RaiseException |
可能遇到的部分错误码:
_URC_END_OF_STACK:展开器在阶段 1 中遇到了堆栈的末尾,但没有找到处理程序。展开运行时不会修改堆栈。在这种情况下,C++运行时通常会调用 uncaught_exception()。 |
3.Throwing an Exception2
此部分主要用于资源清理。而非异常处理,只有一个阶段,那就是栈展开,没有search操作
具体细节,用时再说
typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) |
3.1_Unwind_Resume
对异常处理的第二阶段的补充,用于恢复异常的传播,如多个catch捕捉异常
void _Unwind_Resume (struct _Unwind_Exception *exception_object); |
4.Exception Object Management
4.1_Unwind_DeleteException
这个函数会调用
exception_object
结构体中的exception_cleanup
函数指针,执行特定语言或实现的清理操作,以确保异常对象中可能占用的资源(如动态分配的内存)被正确释放。
void _Unwind_DeleteException |
5.Context Management
用于返回栈帧与寄存器信息
6. Personality Routine
对底层baseABI的封装,通过展开信息块的指针来引用。
Transferring Control to a Landing Pad:通过上下文保存的指针
_Unwind_Reason_Code (*__personality_routine) |
二 C++ABI
1. Data Structures
Caught Exception Stack
struct __cxa_eh_globals { |
C++ Exception Objects
struct __cxa_exception { |
2.过程(算法)
Throwing an Exception
1.Allocating the Exception Object
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-personality
调用:void *__cxa_allocate_exception(size_t thrown_size);
+---------------------+----------------------+ |
2.Throwing the Exception Object
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-personality
调用: void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) );This routine never returns.
1./*获得标头*/ |
3._Unwind_RaiseException
此过程是baseabi的throw的两阶段。会调用此函数
_Unwind_Reason_Code (*__personality_routine) |
phase1
会通过光标来寻找
__personality_routine
,找到则进行调用,根据返回值是否找到hander来进行下一步操作(返回,或者继续展开),若返回的话那么_Unwind_Exception
的private_2则会记住栈指针,指向具体的catch块的位置,以便于正确使用变量。
static _Unwind_Reason_Code |
phase2
**初始化光标 (
cursor
)**:
- 函数首先通过
__unw_init_local(cursor, uc)
初始化光标,以便在当前上下文中遍历堆栈帧。光标用于跟踪堆栈展开过程中的当前栈帧。遍历栈帧:
- 函数进入一个循环,调用
__unw_step(cursor)
来遍历堆栈帧。如果返回值为 0,表示已经到达堆栈的底部,函数返回_URC_END_OF_STACK
,表示堆栈展开已经完成。- 如果
__unw_step(cursor)
返回负值,表示在移动到下一个栈帧时发生了错误,函数返回_URC_FATAL_PHASE2_ERROR
,表示在第二阶段遇到致命错误。获取栈帧信息:
- 对于每个栈帧,函数调用
__unw_get_proc_info(cursor, &frameInfo)
来获取与该栈帧相关联的处理信息(如程序计数器、栈指针等)。如果获取信息失败,函数返回_URC_FATAL_PHASE2_ERROR
。**检查栈帧中的
personality routine
**:
- 如果当前栈帧有
personality routine
(即frameInfo.handler
不为 0),函数将调用它来决定如何处理该栈帧。- 如果栈指针
sp
等于exception_object->private_2
,表示这个栈帧是在第一阶段中标记为需要处理的栈帧,函数会向personality routine
传递_UA_HANDLER_FRAME
标志,指示这是一个需要处理的帧。处理
personality routine
的返回值:
personality routine
可能返回以下几种结果:
- **_URC_CONTINUE_UNWIND**:继续展开堆栈,函数将继续遍历下一个栈帧。
- **_URC_INSTALL_CONTEXT**:`personality routine` 指示需要转移控制权到异常处理器,函数调用 `__unw_resume(cursor)` 将控制权转移到处理器。如果 `__unw_resume` 返回,表示发生了错误,函数返回 `_URC_FATAL_PHASE2_ERROR`。
**错误处理**:
- 如果在遍历栈帧的过程中发生任何无法处理的错误,函数会记录错误信息并返回 `_URC_FATAL_PHASE2_ERROR`。
```C
static _Unwind_Reason_Code
unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) {
__unw_init_local(cursor, uc);
_LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p)",
(void *)exception_object);
// Walk each frame until we reach where search phase said to stop.
while (true) {
// Ask libunwind to get next frame (skip over first which is
// _Unwind_RaiseException).
int stepResult = __unw_step(cursor);
if (stepResult == 0) {
_LIBUNWIND_TRACE_UNWINDING(
"unwind_phase2(ex_ojb=%p): __unw_step() reached "
"bottom => _URC_END_OF_STACK",
(void *)exception_object);
return _URC_END_OF_STACK;
} else if (stepResult < 0) {
_LIBUNWIND_TRACE_UNWINDING(
"unwind_phase2(ex_ojb=%p): __unw_step failed => "
"_URC_FATAL_PHASE1_ERROR",
(void *)exception_object);
return _URC_FATAL_PHASE2_ERROR;
}
// Get info about this frame.
unw_word_t sp;
unw_proc_info_t frameInfo;
__unw_get_reg(cursor, UNW_REG_SP, &sp);
if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) {
_LIBUNWIND_TRACE_UNWINDING(
"unwind_phase2(ex_ojb=%p): __unw_get_proc_info "
"failed => _URC_FATAL_PHASE1_ERROR",
(void *)exception_object);
return _URC_FATAL_PHASE2_ERROR;
}
// When tracing, print state information.
if (_LIBUNWIND_TRACING_UNWINDING) {
char functionBuf[512];
const char *functionName = functionBuf;
unw_word_t offset;
if ((__unw_get_proc_name(cursor, functionBuf, sizeof(functionBuf),
&offset) != UNW_ESUCCESS) ||
(frameInfo.start_ip + offset > frameInfo.end_ip))
functionName = ".anonymous.";
_LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p): start_ip=0x%" PRIxPTR
", func=%s, sp=0x%" PRIxPTR ", lsda=0x%" PRIxPTR
", personality=0x%" PRIxPTR,
(void *)exception_object, frameInfo.start_ip,
functionName, sp, frameInfo.lsda,
frameInfo.handler);
}
// If there is a personality routine, tell it we are unwinding.
if (frameInfo.handler != 0) {
__personality_routine p =
(__personality_routine)(uintptr_t)(frameInfo.handler);
_Unwind_Action action = _UA_CLEANUP_PHASE;
if (sp == exception_object->private_2) {
// Tell personality this was the frame it marked in phase 1.
action = (_Unwind_Action)(_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME);
}
_Unwind_Reason_Code personalityResult =
(*p)(1, action, exception_object->exception_class, exception_object,
(struct _Unwind_Context *)(cursor));
switch (personalityResult) {
case _URC_CONTINUE_UNWIND:
// Continue unwinding
_LIBUNWIND_TRACE_UNWINDING(
"unwind_phase2(ex_ojb=%p): _URC_CONTINUE_UNWIND",
(void *)exception_object);
if (sp == exception_object->private_2) {
// Phase 1 said we would stop at this frame, but we did not...
_LIBUNWIND_ABORT("during phase1 personality function said it would "
"stop here, but now in phase2 it did not stop here");
}
break;
case _URC_INSTALL_CONTEXT:
_LIBUNWIND_TRACE_UNWINDING(
"unwind_phase2(ex_ojb=%p): _URC_INSTALL_CONTEXT",
(void *)exception_object);
// Personality routine says to transfer control to landing pad.
// We may get control back if landing pad calls _Unwind_Resume().
if (_LIBUNWIND_TRACING_UNWINDING) {
unw_word_t pc;
__unw_get_reg(cursor, UNW_REG_IP, &pc);
__unw_get_reg(cursor, UNW_REG_SP, &sp);
_LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p): re-entering "
"user code with ip=0x%" PRIxPTR
", sp=0x%" PRIxPTR,
(void *)exception_object, pc, sp);
}
__unw_resume(cursor);
// __unw_resume() only returns if there was an error.
return _URC_FATAL_PHASE2_ERROR;
default:
// Personality routine returned an unknown result code.
_LIBUNWIND_DEBUG_LOG("personality function returned unknown result %d",
personalityResult);
return _URC_FATAL_PHASE2_ERROR;
}
}
}
// Clean up phase did not resume at the frame that the search phase
// said it would...
return _URC_FATAL_PHASE2_ERROR;
}
4.异常处理
- 对于被search phase标记的栈帧,landing pad调用
__cxa_begin_catch
,然后执行catch block中的代码,最后调用__cxa_end_catch
销毁exception object
3.关于文件结构
此部分描述了如何识别personality routine
四 调试
以2024羊城杯为例子
异常对象的空间分配与抛出
初始全0
__cxa_allocate_exception@plt; |
搜查阶段
调用:_Unwind_RaiseException@plt |
调用三次之后,终于在调用__gxx_personality_v0时找到处理函数,private被赋值,为:0x7ffffc2572d0