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

2014-11-24 11:40:51 · 作者: · 浏览: 0
前面一篇博文简单介绍了c++异常处理的流程,但在一些细节上一带而过了,比如,_Unwind_RaiseException是怎样重建函数现场的,personality routine是怎样清理栈上变量的等,这些细节涉及到很多与语言层面无关的东西,本文尝试介绍一下这些细节的具体实现。
相关的数据结构
如前所述,unwind的进行需要编译器生成一定的数据来支持,这些数据保存了与每个可能抛异常的函数相关的信息以供运行时查找,那么,编译器都保存了哪些信息呢?根据Itanium ABI的定义,主要包括以下三类:
1)unwind table,这个表记录了与函数相关的信息,共三个字段:函数的起始地址,函数的结束地址,一个info block指针。
2)unwind descriptor table, 这个列表用于描述函数中需要unwind的区域的相关信息。
3)语言相关的数据(language specific data area),用于上层语言内部的处理。
以上数据结构的描述来自Itanium ABI的标准定义,但在具体实现时,这些数据是怎么组织以及放到了哪里则是由编译器来决定的,对于GCC来说,所有与unwind相关的数据都放到了.eh_frame及.gcc_except_table这两个section里面了,而且它的格式与内容和标准的定义稍稍有些不同。
.eh_frame区域
.eh_frame的格式与.debug_frame是很相似的(不完全相同),属于DWARF标准中的一部分。所有由GCC编译生成的需要支持异常处理的程序都包含了DWARF格式的数据与字节码,这些数据与字节码的主要作用有两个:
1)描述函数调用栈的结构(layout)
2)异常发生后,指导unwinder怎么进行unwind。
DWARF的字节码功能很强大,它是图灵完备的,这意味着仅仅通过DWARF就可以做几乎任何事情。但是从数据的组织上来看,DWARF实在略显复杂晦杂,因此很少有人愿意去碰,本文也只是简单介绍其中与异常处理相关的东西。本质上来说,eh_frame像是一张表,它用于描述怎样根据程序中每一条指令来设置相应的寄存器,从而返回到当前函数的调用函数中去,它的作用可以用如下表格来形象地描述。
program counter CFA ebp  ebx eax return address
0xfff0003001 rsp+32 *(cfa-16) *(cfa-24) eax=edi *(cfa-8) 
0xfff0003002 rsp+32 *(cfa-16) eax=edi *(cfa-8)
0xfff0003003 rsp+32 *(cfa-16) *(cfa-32) eax=edi *(cfa-8
上表中,CFA(canonical frame address的缩写)表示一个基地址,用于作为当前函数中的其它地址的起始地址,使得其它地址可以用与该基地址的偏移来表示,由于这个表可能要覆盖很多程序指令,因此这个表的体积有可能是很大的,甚至比程序本身的代码量还要大。而在实际中,为了减少这个表的体积,GCC通常会对它进行压缩编码,以及尽可能减少要覆盖的指令的数量,比如,只对会抛异常的函数里的特定区域指令进行记录。
具体的实现上,eh_frame由一个CIE (Common Information Entry) 及多个 FDE (Frame Description Entry)组成,它们在内存中是连续存放的:
CIE及FDE格式的定义可以参看如下:
CIE结构:
Length Required
Extended Length Optional
CIE ID Required
Version Required
Augmentation String Required
EH Data Optional
Code Alignment Factor Required
Data Alignment Factor Required
Return Address Register Required
Augmentation Data Length Optional
Augmentation Data Optional
Initial Instructions Required
Padding
FDE结构:
Length Required
Extended Length Optional
CIE Pointer Required
PC Begin Required
PC Range Required
Augmentation Data Length Optional
Augmentation Data Optional
Call Frame Instructions Required
Padding
注意其中标注红色的字段:
1)Initial Instructions,Call Frame Instructions 这两字段里放的就是所谓的DWARF字节码,比如:DW_CFA_def_cfa R OFF,表示通过寄存器R及位移OFF来计算CFA,其功能类似于前面的表格中第二列指明的内容。
2)PC begin,PC range,这两个字段联合起来表示该FDE所能覆盖的指令的范围,eh_frame中所有的FDE最后会按照pc begin排序进行存放。
3)如果CIE中的Augmentation String中包含有字母"P",则相应的Augmentation Data中包含有指向personality routine的指针。
4)如果CIE中的Augmentation String中包含有有字母“L”,则FDE中Aumentation Data包含有language specific data的指针。
对一个elf文件通过如下命令:readelf -Wwf xxx,可以读取其中关于.eh_frame的数据,如下:
复制代码
The section .eh_frame contains:
00000000 0000001c 00000000 CIE
Version: 1
Augmentation: "zPL"
Code alignment factor: 1
Data alignment factor: -8
Return address column: 16
Augmentation data: 00 d8 09 40 00 00 00 00 00 00
DW_CFA_def_cfa: r7 ofs 8 ##以下为字节码
DW_CFA_offset: r16 at cfa-8
00000020 0000002c 00000024 FDE cie=00000000 pc=00400ac8..00400bd8
Augmentation da