前几天在Hopper的pesudo code里面看到这样一句,
r0 = *__gxx_personality_sj0;
当时没明白是做什么的,今天正好有空,搬来了梯子请出Google老师。
- 它与Exception Handling相关
在Google之后,我发现了一个stackoverflow上的问题[1],鉴于该问题中提到的编译器给出的错误提示,
out/kernel.o:(.eh_frame+0x11):undefined reference to '__gxx_personality_v0'
可以推断出__gxx_personality和.eh_frame是有关的。
那么什么是.eh_frame呢?
根据Ervin在他博客[2]中收集的信息,我们可以知道.eh_frame是处理Exception Handling的,于是果断写一段代码测试之。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { throw exception(); }
在MachOView中,我们可以看到的确是生成了.eh_frame这个section(如图T1-MachOView),但不幸的是在Hopper中并没有找到__gxx_personality相关的内容(如图T1-Hopper)。
修改一下程序,
#include <iostream> using namespace std; int main(int argc, char *argv[]) { try { throw exception(); } catch (const std::exception& e) { } }
编译后在Hopper中验证(如图T2-Hopper),的确找到了__gxx_personality。
也就是说,只要存在try {} catch ()结构或者是有throw都会生成.eh_frame。但当且仅当throw位于try block内部时,编译器才会使用到__gxx_personality。
如果有兴趣可以读一下.eh_frame section的格式细节[3]
- __gxx_personality
刚才确认了__gxx_personality是用于Exception Handling的,那么它是怎么工作的呢,或者说,它的作用是什么呢?
既然提到了Exception Handling,那就少不了stack unwinding。先来对stack unwinding做一个简短的介绍吧。
stack unwinding的其中一个作用就是每当离开一个{ }时,将{ }的local var清空的过程(不包含使用new运算符生成的变量),比如调用destructor,这个过程与这些local var的constructor过程是反向的。如下图。
可以看到
Constructor f1 Constructor f2 Constructor f3 Constructor f4 Deconstructor f4 Deconstructor f3 exception
f4和f3在离开try block之后就被析构了,而f1和f2没能被delete,内存泄漏了。如下图。
那么要怎么做才能保证不造成内存泄漏呢?稍后介绍,现在先让我们回到__gxx_personality的问题上来。
根据NCTU上的一份记录[4],我们可以将__gxx_personality和C personality routine等同起来。__gxx_personality的前缀gxx表明它是C++专属。
Personality routine主要做什么呢?
1)检查当前函数是否有相应的catch语句。
2)清理当前函数中的局部变量。
在unwind.h中可以看到Personality routine需要的参数有五个,
typedef _Unwind_Reason_Code(*_Unwind_Personality_Fn) (int, _Unwind_Action, _Unwind_Exception_Class, struct _Unwind_Exception *, struct _Unwind_Context *); typedef _Unwind_Personality_Fn __personality_routine;
在刚才NCTU的记录中,iant给出了详细的stack unwinding步骤[5],
而要做这两件事还需要编译器在编译时分析出的数据进行协助,而这些信息位于.gcc_except_table section里,至于unwinder和personality routine具体是怎么干的,可以参考twoon的博文《c++ 异常处理(2)》[6]。
说了这么多,要怎么处理刚才有可能的泄漏呢?
其实很简单,只需要利用一下cleanup这个__attribute__就行,修改后的程序如下,
#include <iostream> #include <string> using namespace std; class A { public: A(string name) : name(name) { printf("Constructor %s\n", name.c_str()); } ~A() { printf("Deconstructor %s\n", name.c_str()); } private: string name; }; void clean(A ** x) { delete *x; printf("delete\n"); } void mayThrow() { printf("throw!\n"); throw exception(); } int main(int argc, char *argv[]) { try { A * obj1 __attribute__((cleanup(clean))) = new A("f1"); A * obj2 __attribute__((cleanup(clean))) = new A("f2"); A obj3("f3"); A obj4("f4"); // 可能会抛出异常, 但是不会发生泄漏 // 由于我们已经给obj1, obj2附上了cleanup的属性 // 我们将在clean函数里面进行delete, 此处不再需要delete mayThrow(); } catch (exception e) { printf("Exception\n"); } printf("No leakage\n"); }
- 总结
__gxx_personality就是Personality routine在C++下的实现,它是一个函数指针。
它的主要作用就是在Exception Handling时,清除当前stack frame上的local var,并且配合.gcc_except_table里的数据重构上一个stack frame。
大致的过程正如CesarB在stackoverflow上的答案[7]中所写,
Most of it is hidden within __cxa_throw, which must:
- Walk the stack with the help of the exception tables until it finds a handler for that exception.
- Unwind the stack until it gets to that handler.
- Actually call the handler.
- References
[1]: What is __gxx_personality_v0 for?
[2]: .eh_frame的一些资料
[3]: .eh_frame sections
[4]: richi-2011-12-16.txt
[5]: .gcc_except_table
[6]: c++ 异常处理(2)
[7]: CesarB's anwser
- Extended
[1]: Deep Wizardry: Stack Unwinding
[2]: How a C++ compiler implements exception handling
[3]: Itanium C++ ABI: Exception Handling