c++ 异常处理(2)(三)

2014-11-24 11:40:51 · 作者: · 浏览: 5
/ 返回地址。
void *lsda;// 该函数对应的language specific data,如果存在的话。
struct dwarf_eh_bases bases;
_Unwind_Word args_size;
};
复制代码
实现Personality routine
Peronality routine 的作用主要有两个:
1)检查当前函数是否有相应的catch语句。
2)清理当前函数中的局部变量。
十分不巧,这两件事情仅仅依靠运行时也是没法完成的,必须依靠编译器在编译时建立起相关的数据进行协助。对GCC来说,这些与抛异常的函数具体相关的信息全部放在.gcc_except_table区域里去了,这些信息会作为Itanium ABI接口中所谓的language specific data在unwinder 与c++ ABI之间传递,根据前面的介绍,我们知道在FDE中保存有指向language specific data的指针,因此unwinder在重建现场的时候就已经把这些数据读取了出来,c++的ABI只要调用_Unwind_GetLanguageSpecificData()就可以得到指向该数据的指针。
关于GCC下language specific data的格式,在网上几乎找不到什么权威的文档,我只在llvm的官网上找到一个相关的链接,这个文档对gcc_except_table作了很详细的说明,我对比了一下GCC 源码里的personality routine的相关实现,发现两者还是有些许出入,因此本文接下来的介绍主要基于对GCC相关源码的个人解读。
下图来源于网络,展示了gcc_except_table及language specific data 的格式:
由上图所示,LSDA主要由一个表头,后面紧跟着三张表组成。
1.LSDA Header:
该表头主要用来保存接下来三张表的相关信息,如编码,及表的位移等,该表头主要包含六个域:
1)Landing pad起始地址的编码方式,长度为一个字节。
2)landing pad 起始地址,这是可选的,只有当前面指明的编码方式不等于DW_EH_PE_omit时,这个字段才存在,此时读取这个字段就需要根据前面指定的编码方式进行读取,长度不固定。
如果这个字段不存在,则landing pad的起始地址需要通过调用_Unwind_GetRegionStart()来获得,得到其实就是当前模块加载的起始地址,这是最常见的形式。
3)type table的编码方式,长度为一个字节。
4)type table的位移,类型为unsigned LEB128,这个字段是可选的,只有3)中编码方式不等于DW_EH_PE_omit时,这个才存在。
5)call site table的编码方式,长度为一个字节。
6)call site table 的长度,一个unsigned LEB128的值。
2.call site table
LSDA表头之后紧跟着的是call site table,该表用于记录程序中哪些指令有可能会抛异常,表中每条记录共有4个字段:
1)可能会抛异常的指令的地址,该地址是距Landing pad起始地址的偏移,编码方式由LSDA表头中第一个字段指明。
2)可能抛异常的指令的区域长度,该字段与1)一起表示一系列连续的指令,编码方式与1)相同。
3)用于处理上述指令的Landing pad的位移,这个值如果为0则表示不存在相应的landing pad。
4)指明要采取哪些action,这是一个unsigned LEB128的值,该值减1后作为下标获取action table中相应记录。
call site table中的记录按第一个字段也就是指令起始地址进行排序存放,因此unwind的时候可以加快对该表的搜索,unwind时,如果当前pc的值不在call site table覆盖的范围内的话,搜索就会返回,然后就调用std::terminate()结束程序,这通常来说是不正常的行为。
如果在call site table中有对应的处理,但landing pad的地址却是0的话,表明当前函数既不存在catch语句,也不需要清理局部变量,这是一种正常情况,unwinder应该继续向上unwind,而如果landing pad不为0,则表明该函数中有catch语句,但是这些catch能否处理抛出的异常则还要结合action字段,到type table中去进一步加以判断:
1)如果action字段为0,则表明当前函数没有catch语句,但有局部变量需要清理。
2)如果action字段不为0,则表明当前函数中存在catch语句,又因为catch是可能存在多个的,怎么知道哪个能够catch当前的异常呢?因此需要去检查action table中的表项。
3. Action table
action table中每一条记录是一个二元组,表示一个catch语句所对应的异常,或者表示当前函数所允许抛出的异常(exception specification),该列表每条记录包含两个字段:
1)filter type,这是一个unsigned LEB128的数值,用于指向type table中的记录,该值有可能是负数。
2)指向下一个action table中的下一条记录,这是当函数中有多个catch或exception specification 有多个时,将各个action 记录链接起来。
4. Type Table
type table中存放的是异常类型的指针:
std::type_info* type_tables[];
这个表被分成两部分,一部分是各个catch所对应的异常的类型,另一部分是该函数允许抛出的异常类型:
void func() throw(int, string)
{
}
type table中这两部分分别通过正负下标来进行索引:
有了如上这些数据,personality routine只需要根据当前的pc值及当前的异常类型,不断在上述表中查找,最后就能找到当前函数是否有landing pad,如果有则返回_URC_INSTALL_CONTEXT,指示unwinder跳过去执行相应的代码。
什么是Landing pad
在前面一篇博文里,我们简单提到了Landing pad:指的是能够catch当前异常的catch语句。这个说法其实不确切。
准确来说,landing pad指的是unwinder之外的“用户代码”:
1)用于catch相应的exception,或清理当前函数局部变量的代码。对于一个函数来说,如果