一、序言
这个异常处理可以说是C++语法中一个重要的组成部分,但是在使用中很少有人认真的研究过它的实现,甚至在工程中使用这个工具的人都很少,加上我之前对于C++的使用也是大概在windows下使用了两年,所以一些基本的问题我也不是很熟悉。但是对于windows下的结构化异常处理方法,在Matt pietreck之前的一篇文章中有深入的说明(在google里面搜索seh,第一篇应该就是那篇文章),这也是我最早觉得非常有技术含量的文章。当时的Matt在borland工作,好像是搞调试器的,例如当时著名的SoftICE工具,bound checker之类的,所以在Microsoft Journal上的Under the hood 专栏中时常有惊艳的文章。
之后在linux下开发,主要接触的并不是语言本身的问题,大多是Linux系统级别的一些环境级别的问题,而且使用的是C语言开发,对于C++的很多特性并不是有很深入系统的了解,例如模板、构造函数的执行时机等一些比较细节的东西,但是对于C++的整个内存布局,在之前应该是CodeProject网站上对于windows下动态类型识别的功能有比较详细的描述,所以对于C++的内存布局,动态类型识别,虚函数实现等相对比较简单的功能印象依然深刻。
可是gcc下的异常处理机制在很多的文章中很少有描述,至少说我们使用Google搜索gcc的异常实现很少找到相关的说明文档,即使有文档我们也可以看到文档里的内容语焉不详,根本没有达到代码级的分析,很多都是数据结构的分析,这些分析对于了解大致原理来进行一些YY来说是可以满足条件的,但是对于工程中问题的追查没有太大实际意义,因为在实际应用过程中,问题并不是知道原理就可以的,而是需要精确到程序实现的每一行代码。
二、基础知识
在C++中,异常处理就是使用try catch及 throw三个关键字来实现,对于try,在语法分析阶段被消化掉,在汇编代码中,try的位置没有而外的代码体现,而只是在附加的exception_table中有相应的结构定界体现,表示这个结构中的代码是受保护的代码,并且如果它有对应的catch,那么catch处有相应的action table结构。
与此相反,对于throw和catch语句,它们在最终生成的汇编代码中有自己的体现,其中throw被编译器在生成代码的时候就转换成了一个函数,函数的名称叫做__cxx_throw,有些同学如果使用到过gdb 的catch throw可能会有印象,事实上在捕捉throw的时候gdb也只是简单的在这个函数的地方打上一个断点而已。如果你的可执行文件是通过动态链接生成的,那么在程序开始还没有启动的时候执行catch throw的时候gdb会提示这个函数没有定义,所以你手动执行b __cxx_throw即可,此时gdb把它作为一个普通的延迟断点,在gcc动态库加载的时候断点即可生效。
对于catch同样是翻译成一组函数。这里是一个比较有意思的现象,就是在编译器生成代码的时候,它直接生成一些自己也不一定定义的函数调用,然后在链接的时候让用户或者其他的模块来定义,这样就解耦了语义分析后代码生成和辅助功能实现的关系。这种方法在linux系统的其它地方下也有实现,例如gcc thread local 变量中对于外部定义变量的引用、gcc中对于重载new函数的调用,在汇编中对于__gcc_personality_v0的调用,在gcc的时候它都不知道这些函数的实现,但是依然勇敢的生成了对这些函数的调用,如果在链接的时候没有链接他们相应的定义模块,就会出现链接错误。
三、异常处理的基本思路和基本结构
1、基本实现思路
异常处理包括两个主要的部分,一个是栈帧展开,一个是异常执行。这两个是一种独立的实现机制,实现的关联性并非必然。所谓的栈帧展开不仅这里需要使用,在我们日常使用的gdb中,这个栈帧展开也是一个非常基本的功能。在gdb的堆栈回溯中,经常需要在bt之后通过frame找到对应的某一层堆栈位置,进而查看该层中局部变量的定义。大家不要认为这个展开就是直接的调用链回溯之后设置变量的值,问题的难点在于对于系统来说它一组寄存器状态可以放入CPU中,而回溯到指定层的栈帧不仅只是就该栈帧(ebp和esp),还包括了其他的通用寄存器(eax,ebx,esi,edi)等这些寄存器的内容,因为在栈帧的特殊点,变量的计算需要使用寄存器来实现。
另一个personality的实现则是语言特有的一个内容,它涉及到异常的识别和执行。这个异常的识别和展开是在gcc下就有两种实现,一种是通过setjmp+longjmp的实现方式,就是通过两个函数在每个try的开始执行setjmp操作,把所有的寄存器数值都保存起来,当异常发生之后,在全局结构中找到这个保存的跳转点位置,然后通过longjmp跳转过去,这个中实现方法在gcc中使用的personality叫做__gcc_personality_sj0,和 现在成用的__gcc_personality_v0相对应。
而__gcc_personality_v0胜出的原因在于这种实现对于代码的正常执行流来说影响极小,它只是在额外程序section中生成特定的数据结构来表示源代码中的异常结构,在可执行代码中并不需要因为有异常而执行跳转点的保存。但是这种实现的缺点就是不太直观,很多人并不理解相关的辅助数据结构,所以成为了认知的“黑洞”。
gcc到底是使用哪种personality是在gcc可执行文件生成的时候就已经确定的,一旦代码生成,就不能在编译阶段选择在两种实现中切换,这个特性在编译时确定。
2、堆栈展开数据结构
栈帧展开在可执行文件的eh_frame节中中体现。正如之前所说的,这个结构和调试信息中的debug_frame信息是相似的,使用的都是dwarf格式的文件结构,但是两者有一个重要的区别,debug结构的frame在strip之后就不再包含调试信息,而且调试信息默认是不会加载的内存中的,只是放在内存中,当调试器需要的时候从硬盘上读取数据。
在eh_frame中包含的是一个一个的fde结构,每个fde结构描述了一个函数堆栈的栈帧信息,包含了最为基本的一个函数的其实地址、长度以及不同的指令回复需要的字节码,也就是byte code。
例如下面是一个完整的fde结构
[root@Harry exception]# readelf --debug-dump=frames *.o
Contents of the .eh_frame section:
00000000 00000018 00000000 CIE
Version: 1
Augmentation: "zPR"
Code alignment factor: 1
Data alignment factor: -4
Return address column: 8
Augmentation data: 00 00 00 00 00 1b
DW_CFA_def_cfa: r4 (esp) ofs 4
DW_CFA_offset: r8 (eip) at cfa-4
0000001c 00000018 00000020 FDE cie=00000000 pc=00000000..0000003e cie表示cie的位置,PC表示该fde覆盖的范围,开始的第一项应该是该节的起始地址。
DW_CFA_advance_loc: 1 to 00000001
DW_CFA_def_cfa_offset: 8
DW_CFA_advance_loc: 2 to 00000003
DW_CFA_offset: r5 (ebp) at cfa-8
DW_CFA_def_cfa_register: r5 (ebp)
DW_CFA_nop
DW_CFA_nop
DW_CFA_nop
00000038 0000001c 00000000 CIE
Version: 1
Augmentation: "zPLR"
Code alignment factor: 1
Data alignment factor: -4
Return address column: 8
Augmentation data: 00 00 00 00 00 00 1b
DW_CFA_def_cfa: r4 (esp) ofs 4
DW_CFA_offset: r8 (eip) at cfa-4
DW_CFA_nop
DW_CFA_nop
00000058 00000020 00000024 FDE cie=00000038 pc=00000000..00000063
Augmentation data: 00 00 00 00
DW_CFA_advance_loc: 1 to 00000001
DW_CFA_def_cfa_offset: 8
DW_CFA_advance_loc: 2 to 00000003
DW_CFA_offset: r5 (ebp) at cfa-8
DW_CFA_def_cfa_register: r5 (ebp)
DW_CFA_advance_loc: 20 to 00000017
DW_CFA_offset: r3 (ebx) at cfa-16
DW_CFA_offset: r6 (esi) at cfa-12
DW_CFA_nop
DW_CFA_nop
0000007c 0000003c 00000048 FDE cie=00000038 pc=00000063..000000c4
Augmentation data: 0c 00 00 00 这里有一些扩展字段,这些字段也就是额外的L(LSDA的数据结构位置)
DW_CFA_advance_loc: 1 to 00000064
DW_CFA_def_cfa_offset: 8
DW_CFA_advance_loc: 2 to 00000066
DW_CFA_offset: r5 (ebp) at cfa-8
DW_CFA_def_cfa_register: r5 (ebp)
DW_CFA_advance_loc: 8 to 0000006e
DW_CFA_expression: r3 (ebx) (DW_OP_breg5: 0; DW_OP_const1s: -16; DW_OP_and; DW_OP_const1s: -8; DW_OP_plus)
DW_CFA_expression: r6 (esi) (DW_OP_breg5: 0; DW_OP_const1s: -16; DW_OP_and; DW_OP_const1s: -4; DW_OP_plus)
DW_CFA_advance_loc1: 81 to 000000bf
DW_CFA_restore: r3 (ebx)
DW_CFA_advance_loc: 1 to 000000c0
DW_CFA_restore: r6 (esi)
DW_CFA_advance_loc: 2 to 000000c2
DW_CFA_def_cfa_register: r4 (esp)
DW_CFA_advance_loc: 1 to 000000c3
DW_CFA_restore: r5 (ebp)
DW_CFA_def_cfa_offset: 4
最后的nop是为了长度对其而添加的内容,其中的advance_loc则表示了PC指针的动态变化,所以真正的字节码解析器(byte code interpreter)需要将这些loc指令转换为绝对地址,也就是再次细化一个函数内的结构信息(函数外信息则在FDE的开始PC位置表示)。这里也说明了代码涉及的一个重要原则,就是对不同的逻辑单位定义对应的专有结构。
3、cie和pde的关系
cie是一个编码格式、fde内容,对齐大小、返回地址寄存器等信息的存储单位,这个信息通常来说变化不大,不同的fde明显可以共享这些信息,所以这些cie被独立出来,不同结构的FDE可以共享这些信息,例如上面的两个结构中,可能包含有两种不同的结构,他们的一个明显区别就在于时候包含了L字符,而这个字符的意义在代码中的解析为
gcc-4.1.0\gcc\unwind-dw2-fde.c
/* Return the FDE pointer encoding from the CIE. */
/* ??? This is a subset of extract_cie_info from unwind-dw2.c. */
static int
get_cie_encoding (const struct dwarf_cie *cie)
{
const unsigned char *aug, *p;
_Unwind_Ptr dummy;
_Unwind_Word utmp;
_Unwind_Sword stmp;
aug = cie->augmentation;
if (aug[0] != 'z')
return DW_EH_PE_absptr;
p = aug + strlen ((const char *)aug) + 1; /* Skip the augmentation string. */
p = read_uleb128 (p, &utmp); /* Skip code alignment. */
p = read_sleb128 (p, &stmp); /* Skip data alignment. */
if (cie->version == 1) /* Skip return address column. */
p++;
else
p = read_uleb128 (p, &utmp);
aug++; /* Skip 'z' */
p = read_uleb128 (p, &utmp); /* Skip augmentation length. */
while (1)
{
/* This is what we're looking for. */
if (*aug == 'R')
return *p;
/* Personality encoding and pointer. */
else if (*aug == 'P')
{
/* ??? Avoid dereferencing indirect pointers, since we're
faking the base address. Gotta keep DW_EH_PE_aligned
intact, however. */
p = read_encoded_value_with_base (*p & 0x7F, 0, p + 1, &dummy);
}
/* LSDA encoding. */
else if (*aug == 'L')
p++;
/* Otherwise end of string, or unknown augmentation. */
else
return DW_EH_PE_absptr;
aug++;
}
}
4、LSDA及gcc_except_table
这个结构是异常特有结构,它不仅包含了try+catch的结构,而且包含了cleanup的信息,所谓的cleanup就是当一个函数调用的更为底层的函数抛出了异常时,自己已经执行过构造函数的对象需要执行这些对象的析构函数,析构之后再返回到上层执行展开或者析构。用例子来展示一下:
1 struct nvd
2 {
3 ~nvd() {int i = 0;};
4 };
5 int foo()
6 {
7 int bar();
8 nvd n;
9 bar();
10 }
在这个例子中,如果bar中抛出了异常,那么局部变量n的析构函数需要被执行,执行之后返回到foo的上一层,继续判断是执行cleanup还是执行catch中的某一个动作。在gcc的异常管理结构中,在刚才说明的fde中,大家应该记得在CIE的开始有一个编码方式'L',它表示了一个LSDA(Language Specification Data Area)结构,而这个结构就引导了整个异常处理结构的开始。这个LSDA在之前的FDE的augmentation 中有描述,所以可以找到这些字段。这些字段包括了Type Info信息,也就是catch中指明的不同类型的变量,这些类型是匹配一个异常语句是否匹配被执行的关键,该函数中try结构或者说包含有析构函数的block的起始位置和结束位置call site,当call site内发生异常时需要跳转到的位置 landing pad信息。当然也不是说每个函数都有LSDA,有些不会抛出异常的函数(例如一些没有任何调用的叶子函数)就不需要LSDA,这也是为什么我们在上面的例子中看到两种不同的CIE的原因,因为一种类型的函数是叶子函数,这个属性在之后的例子展示中还有说明,所以大家注意一下。
gcc-4.1.0\libstdc++-v3\libsupc++\eh_personality.cc
PERSONALITY_FUNCTION (_Unwind_State state,
struct _Unwind_Exception* ue_header,
struct _Unwind_Context* context)
language_specific_data = (const unsigned char *)
_Unwind_GetLanguageSpecificData (context);
void *
_Unwind_GetLanguageSpecificData (struct _Unwind_Context *context)
{
return context->lsda;
}
而这个LSDA的初始化位置在
static _Unwind_Reason_Code
uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs)
if (fs->lsda_encoding != DW_EH_PE_omit)
{
_Unwind_Ptr lsda;
aug = read_encoded_value (context, fs->lsda_encoding, aug, &lsda);
context->lsda = (void *) lsda;
}
5、LSDA的解析
gcc-4.1.0\gcc\unwind-c.c
typedef struct
{
_Unwind_Ptr Start; tryblock的起始地址
_Unwind_Ptr LPStart; 结束地址。
_Unwind_Ptr ttype_base;
const unsigned char *TType; catch(type)中type信息布局,同样是将相同结构的信息放在一起组成数组结构,便于代码中通过自然循环遍历。
const unsigned char *action_table;一个try对应的一组catch结构列表,多个catch并列在一起可能组成catch的table。
unsigned char ttype_encoding;
unsigned char call_site_encoding;
} lsda_header_info;
static const unsigned char *
parse_lsda_header (struct _Unwind_Context *context, const unsigned char *p,
lsda_header_info *info)
{
_Unwind_Word tmp;
unsigned char lpstart_encoding;
info->Start = (context ? _Unwind_GetRegionStart (context) : 0);
/* Find @LPStart, the base to which landing pad offsets are relative. */
lpstart_encoding = *p++;
if (lpstart_encoding != DW_EH_PE_omit)
p = read_encoded_value (context, lpstart_encoding, p, &info->LPStart);
else
info->LPStart = info->Start;
/* Find @TType, the base of the handler and exception spec type data. */
info->ttype_encoding = *p++;
if (info->ttype_encoding != DW_EH_PE_omit)
{
p = read_uleb128 (p, &tmp);
info->TType = p + tmp;
}
else
info->TType = 0;
/* The encoding and length of the call-site table; the action table
immediately follows. */
info->call_site_encoding = *p++;
p = read_uleb128 (p, &tmp);
info->action_table = p + tmp;
return p;
}
6、personality处理及gcc_except_table
注意的是,堆栈的展开是在上层由eh_frame完成的,它不涉及异常处理相关逻辑,事实上eh_frame是和gdb的堆栈信息非常接近的,所以它并不是专门为C++的异常处理程序设定的,异常的特有结构是在except_table中设定的。而这个结构的解析和使用又是由具体的personality函数完成
gcc-4.1.0\libstdc++-v3\libsupc++\eh_personality.cc
extern "C" _Unwind_Reason_Code
#ifdef __ARM_EABI_UNWINDER__
PERSONALITY_FUNCTION (_Unwind_State state,
struct _Unwind_Exception* ue_header,
struct _Unwind_Context* context)
#else
PERSONALITY_FUNCTION (int version,
_Unwind_Action actions,
_Unwind_Exception_Class exception_class,
struct _Unwind_Exception *ue_header,
struct _Unwind_Context *context)
#endif
{
enum found_handler_type
{
found_nothing,
found_terminate,
found_cleanup,
found_handler
} found_type;
lsda_header_info info;
const unsigned char *language_specific_data;
const unsigned char *action_record;
const unsigned char *p;
_Unwind_Ptr landing_pad, ip;
int handler_switch_value;
void* thrown_ptr = ue_header + 1;
bool foreign_exception;
__cxa_exception* xh = __get_exception_header_from_ue(ue_header);
// Interface version check.
if (version != 1)
return _URC_FATAL_PHASE1_ERROR;
foreign_exception = !__is_gxx_exception_class(exception_class);
// Shortcut for phase 2 found handler for domestic exception.
if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME)
&& !foreign_exception)
{
restore_caught_exception(ue_header, handler_switch_value,
language_specific_data, landing_pad);
found_type = (landing_pad == 0 ? found_terminate : found_handler);
goto install_context;
}
language_specific_data = (const unsigned char *)
_Unwind_GetLanguageSpecificData (context);
// If no LSDA, then there are no handlers or cleanups.
if (! language_specific_data)
CONTINUE_UNWINDING; 如果没有LSDA,则将异常直接透传到更上层,本层不处理,也不出错。
// Parse the LSDA header.
p = parse_lsda_header (context, language_specific_data, &info);
info.ttype_base = base_of_encoded_value (info.ttype_encoding, context);
ip = _Unwind_GetIP (context) - 1;
landing_pad = 0;
action_record = 0;
handler_switch_value = 0;
遍历actiontable,也就是一个function中所有的可能出错的block结构的范围,其中的cs_start和cs_len分表表示这些结构的开始和长度,其中cs为call site的缩写。
// Search the call-site table for the action associated with this IP.
while (p < info.action_table)
{
_Unwind_Ptr cs_start, cs_len, cs_lp;
_Unwind_Word cs_action;
// Note that all call-site encodings are "absolute" displacements.
p = read_encoded_value (0, info.call_site_encoding, p, &cs_start); action对应的call site的开始
p = read_encoded_value (0, info.call_site_encoding, p, &cs_len); call site的长度
p = read_encoded_value (0, info.call_site_encoding, p, &cs_lp); landing pad的位置,
p = read_uleb128 (p, &cs_action); 对应的catch blocks的开始位置。
// The table is sorted, so if we've passed the ip, stop.
if (ip < info.Start + cs_start)
p = info.action_table;
else if (ip < info.Start + cs_start + cs_len)
{
if (cs_lp)
landing_pad = info.LPStart + cs_lp;如果landing pad的地址非空,说明存在cleanup或者action结构。
if (cs_action) 存在action,说明存在catch结构。
action_record = info.action_table + cs_action - 1;
goto found_something;
}
}
执行到这里,说明函数包含有LSDA,但是当前异常地址不在任何一个call site范围内,此时程序将会被结束,及出现了未被处理的异常,此时将会终止掉程序的执行,下面的注释也就是这个意思。。
// If ip is not present in the table, call terminate. This is for
// a destructor inside a cleanup, or a library routine the compiler
// was not expecting to throw.
found_type = found_terminate;
goto do_something;
found_something:
if (landing_pad == 0)
{
// If ip is present, and has a null landing pad, there are
// no cleanups or handlers to be run.
found_type = found_nothing;
}
else if (action_record == 0)
{
// If ip is present, has a non-null landing pad, and a null
// action table offset, then there are only cleanups present.
// Cleanups use a zero switch value, as set above.
found_type = found_cleanup;
}
else
{
// Otherwise we have a catch handler or exception specification.
_Unwind_Sword ar_filter, ar_disp;
const std::type_info* catch_type;
_throw_typet* throw_type;
bool saw_cleanup = false;
bool saw_handler = false;
// During forced unwinding, we only run cleanups. With a foreign
// exception class, there's no exception type.
// ??? What to do about GNU Java and GNU Ada exceptions.
if ((actions & _UA_FORCE_UNWIND)
|| foreign_exception)
throw_type = 0;
else
#ifdef __ARM_EABI_UNWINDER__
throw_type = ue_header;
#else
throw_type = xh->exceptionType;
#endif
while (1)遍历所有的catch blocks,调用每个不同的catch的filter结构,进行动态类型匹配,找到第一个匹配的类型之后返回。
{
p = action_record;
p = read_sleb128 (p, &ar_filter);
read_sleb128 (p, &ar_disp);
if (ar_filter == 0)
{
// Zero filter values are cleanups.
saw_cleanup = true;
}
else if (ar_filter > 0)
{
// Positive filter values are handlers.
catch_type = get_ttype_entry (&info, ar_filter);
// Null catch type is a catch-all handler; we can catch foreign 对应于catch(...)语句
// exceptions with this. Otherwise we must match types.
if (! catch_type
|| (throw_type
&& get_adjusted_ptr (catch_type, throw_type,
&thrown_ptr)))
{
saw_handler = true;
break;
}
}
else
{
// Negative filter values are exception specifications.
// ??? How do foreign exceptions fit in? As far as I can
// see we can't match because there's no __cxa_exception
// object to stuff bits in for __cxa_call_unexpected to use.
// Allow them iff the exception spec is non-empty. I.e.
// a throw() specification results in __unexpected.
if (throw_type
? ! check_exception_spec (&info, throw_type, thrown_ptr, 动态类型识别及匹配。
ar_filter)
: empty_exception_spec (&info, ar_filter))
{
saw_handler = true;
break;
}
}
if (ar_disp == 0)
break;
action_record = p + ar_disp;
}
if (saw_handler)
{
handler_switch_value = ar_filter;
found_type = found_handler;
}
else
found_type = (saw_cleanup ? found_cleanup : found_nothing);
}
do_something:
if (found_type == found_nothing)
CONTINUE_UNWINDING;如果没有找到处理函数,则继续展开,这里包含了所有catch的block中均不匹配的情况也在这里返回。
if (actions & _UA_SEARCH_PHASE)
{
if (found_type == found_cleanup)
CONTINUE_UNWINDING;
// For domestic exceptions, we cache data from phase 1 for phase 2.
if (!foreign_exception)
{
save_caught_exception(ue_header, context, thrown_ptr,
handler_switch_value, language_specific_data,
landing_pad, action_record);
}
return _URC_HANDLER_FOUND; search阶段,terminate也走该流程。
}
install_context:
// We can't use any of the cxa routines with foreign exceptions,
// because they all expect ue_header to be a struct __cxa_exception.
// So in that case, call terminate or unexpected directly.
if ((actions & _UA_FORCE_UNWIND)
|| foreign_exception)
{
if (found_type == found_terminate)
std::terminate ();
else if (handler_switch_value < 0)
{
try
{ std::unexpected (); }
catch(...)
{ std::terminate (); }
}
}
else
{
if (found_type == found_terminate)
__cxa_call_terminate(ue_header);
// Cache the TType base value for __cxa_call_unexpected, as we won't
// have an _Unwind_Context then.
if (handler_switch_value < 0)
{
parse_lsda_header (context, language_specific_data, &info);
#ifdef __ARM_EABI_UNWINDER__
const _Unwind_Word* e;
_Unwind_Word n;
e = ((const _Unwind_Word*) info.TType) - handler_switch_value - 1;
// Count the number of rtti objects.
n = 0;
while (e[n] != 0)
n++;
// Count.
ue_header->barrier_cache.bitpattern[1] = n;
// Base (obsolete)
ue_header->barrier_cache.bitpattern[2] = 0;
// Stride.
ue_header->barrier_cache.bitpattern[3] = 4;
// List head.
ue_header->barrier_cache.bitpattern[4] = (_Unwind_Word) e;
#else
xh->catchTemp = base_of_encoded_value (info.ttype_encoding, context);
#endif
}
}
/* For targets with pointers smaller than the word size, we must extend the
pointer, and this extension is target dependent. */
_Unwind_SetGR (context, __builtin_eh_return_data_regno (0),
__builtin_extend_pointer (ue_header));
_Unwind_SetGR (context, __builtin_eh_return_data_regno (1),
handler_switch_value);
_Unwind_SetIP (context, landing_pad); 调整IP位置,也就是landing pad位置,开始跳转。
#ifdef __ARM_EABI_UNWINDER__
if (found_type == found_cleanup)
__cxa_begin_cleanup(ue_header);
#endif
return _URC_INSTALL_CONTEXT;
}
7、一个异常处理函数的例子
[root@Harry exception]# cat nestteddest.cpp
#include <stdio.h>
struct nvdest
{
~nvdest() {printf("destructing \n");}
};
int foo()
{
nvdest outter;
extern int bar();
{
nvdest nvdest;
try
{
bar();
}
catch(int &var)
{
printf("gotcha\n");
}
}
}
int baz()
{
nvdest outer;
{
nvdest inner;
extern int bay();
bay();
}
printf("I am a marker\n");
}
[root@Harry exception]# cat nestteddest.cpp
#include <stdio.h>
struct nvdest
{
~nvdest() {printf("destructing \n");}
};
int foo()
{
nvdest outter;
extern int bar();
{
nvdest nvdest;
try
{
bar();
}
catch(int &var)
{
printf("gotcha\n");
}
}
}
int baz()
{
nvdest outer;
{
nvdest inner;
extern int bay();
bay();
}
printf("I am a marker\n");
}
[root@Harry exception]# gcc -S -dA -fverbose-asm nestteddest.cpp
[root@Harry exception]# cat nestteddest.s
.file "nestteddest.cpp"
# GNU C++ (GCC) version 4.4.2 20091027 (Red Hat 4.4.2-7) (i686-redhat-linux)
# compiled by GNU C version 4.4.2 20091027 (Red Hat 4.4.2-7), GMP version 4.3.1, MPFR version 2.4.1.
# GGC heuristics: --param ggc-min-expand=98 --param ggc-min-heapsize=128396
# options passed: -D_GNU_SOURCE nestteddest.cpp -mtune=generic -march=i686
# -fverbose-asm
# options enabled: -falign-loops -fargument-alias -fauto-inc-dec
# -fbranch-count-reg -fcommon -fdwarf2-cfi-asm -fearly-inlining
# -feliminate-unused-debug-types -fexceptions -ffunction-cse -fgcse-lm
# -fident -finline-functions-called-once -fira-share-save-slots
# -fira-share-spill-slots -fivopts -fkeep-static-consts
# -fleading-underscore -fmath-errno -fmerge-debug-strings
# -fmove-loop-invariants -fpcc-struct-return -fpeephole -fsched-interblock
# -fsched-spec -fsched-stalled-insns-dep -fsigned-zeros
# -fsplit-ivs-in-unroller -ftrapping-math -ftree-coalesce-vars
# -ftree-cselim -ftree-loop-im -ftree-loop-ivcanon -ftree-loop-optimize
# -ftree-parallelize-loops= -ftree-reassoc -ftree-scev-cprop
# -ftree-switch-conversion -ftree-vect-loop-version -funit-at-a-time
# -fvect-cost-model -fverbose-asm -fzero-initialized-in-bss -m32 -m80387
# -m96bit-long-double -maccumulate-outgoing-args -malign-stringops
# -mfancy-math-387 -mfp-ret-in-387 -mglibc -mieee-fp -mno-red-zone
# -mno-sse4 -mpush-args -msahf -mtls-direct-seg-refs
# Compiler executable checksum: 1654075adcfd832dfb7b0208272c8238
.section .rodata
.LC0:
.string "destructing "
.section .text._ZN6nvdestD1Ev,"axG",@progbits,_ZN6nvdestD1Ev,comdat
.align 2
.weak _ZN6nvdestD1Ev
.type _ZN6nvdestD1Ev, @function
_ZN6nvdestD1Ev:
.LFB2:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
# basic block 2
pushl %ebp #
.cfi_def_cfa_offset 8
movl %esp, %ebp #,
.cfi_offset 5, -8
.cfi_def_cfa_register 5
subl $24, %esp #,
movl $.LC0, (%esp) #,
call puts #
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size _ZN6nvdestD1Ev, .-_ZN6nvdestD1Ev
.section .rodata
.LC1:
.string "gotcha"
.globl _Unwind_Resume
.text
.globl _Z3foov
.type _Z3foov, @function
_Z3foov:
.LFB3:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
.cfi_lsda 0x0,.LLSDA3
# basic block 2
pushl %ebp #
.cfi_def_cfa_offset 8
movl %esp, %ebp #,
.cfi_offset 5, -8
.cfi_def_cfa_register 5
pushl %esi #
pushl %ebx #
subl $32, %esp #,
.LEHB0:
.cfi_offset 3, -16
.cfi_offset 6, -12
call _Z3barv #
.LEHE0:
# basic block 3
jmp .L5 #
.L15:
# basic block 4
cmpl $1, %edx #, tmp68
jne .L10 #,
.L6:
# basic block 5
movl %eax, (%esp) # tmp67,
call __cxa_begin_catch #
movl %eax, -12(%ebp) # var.0, var
movl $.LC1, (%esp) #,
.LEHB1:
call puts #
.LEHE1:
# basic block 6
.LEHB2:
call __cxa_end_catch #
.LEHE2:
# basic block 7
jmp .L5 #
.L14:
# basic block 8
.L8:
movl %edx, %ebx # tmp68, save_filt.6
movl %eax, %esi # tmp67, save_eptr.5
call __cxa_end_catch #
movl %esi, %eax # save_eptr.5, tmp67
movl %ebx, %edx # save_filt.6, tmp68
jmp .L10 #
.L5:
# basic block 9
leal -14(%ebp), %eax #, tmp69
movl %eax, (%esp) # tmp69,
.LEHB3:
call _ZN6nvdestD1Ev #
.LEHE3:
# basic block 10
jmp .L18 #
.L16:
# basic block 11
.L10:
# basic block 12
movl %edx, %ebx # tmp68, save_filt.8
movl %eax, %esi # tmp67, save_eptr.7
leal -14(%ebp), %eax #, tmp70
movl %eax, (%esp) # tmp70,
call _ZN6nvdestD1Ev #
movl %esi, %eax # save_eptr.7, tmp67
movl %ebx, %edx # save_filt.8, tmp68
jmp .L11 #
.L18:
# basic block 13
leal -13(%ebp), %eax #, tmp71
movl %eax, (%esp) # tmp71,
.LEHB4:
call _ZN6nvdestD1Ev #
.LEHE4:
addl $32, %esp #,
popl %ebx #
.cfi_remember_state
.cfi_restore 3
popl %esi #
.cfi_restore 6
popl %ebp #
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.L17:
# basic block 14
.L11:
# basic block 15
.cfi_restore_state
movl %edx, %ebx # tmp68, save_filt.10
movl %eax, %esi # tmp67, save_eptr.9
leal -13(%ebp), %eax #, tmp72
movl %eax, (%esp) # tmp72,
call _ZN6nvdestD1Ev #
movl %esi, %eax # save_eptr.9, tmp67
movl %ebx, %edx # save_filt.10, tmp68
movl %eax, (%esp) # tmp67,
.LEHB5:
call _Unwind_Resume #
.LEHE5:
.cfi_endproc
.LFE3:
.size _Z3foov, .-_Z3foov
.globl __gxx_personality_v0
.section .gcc_except_table,"a",@progbits
.align 4
.LLSDA3:
.byte 0xff # @LPStart format (omit)
.byte 0x0 # @TType format (absolute)
.uleb128 .LLSDATT3-.LLSDATTD3 # @TType base offset
.LLSDATTD3:
.byte 0x1 # call-site format (uleb128)
.uleb128 .LLSDACSE3-.LLSDACSB3 # Call-site table length
.LLSDACSB3:
.uleb128 .LEHB0-.LFB3 # region 0 start
.uleb128 .LEHE0-.LEHB0 # length
.uleb128 .L15-.LFB3 # landing pad
.uleb128 0x3 # action
.uleb128 .LEHB1-.LFB3 # region 1 start
.uleb128 .LEHE1-.LEHB1 # length
.uleb128 .L14-.LFB3 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB2-.LFB3 # region 2 start
.uleb128 .LEHE2-.LEHB2 # length
.uleb128 .L16-.LFB3 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB3-.LFB3 # region 3 start
.uleb128 .LEHE3-.LEHB3 # length
.uleb128 .L17-.LFB3 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB4-.LFB3 # region 4 start
.uleb128 .LEHE4-.LEHB4 # length
.uleb128 0x0 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB5-.LFB3 # region 5 start
.uleb128 .LEHE5-.LEHB5 # length
.uleb128 0x0 # landing pad
.uleb128 0x0 # action
.LLSDACSE3:
.byte 0x0 # Action record table
.byte 0x0
.byte 0x1
.byte 0x7d
.align 4
.long _ZTIi
.LLSDATT3:
.text
.section .rodata
.LC2:
.string "I am a marker"
.text
.globl _Z3bazv
.type _Z3bazv, @function
_Z3bazv:
.LFB4:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
.cfi_lsda 0x0,.LLSDA4
# basic block 2
pushl %ebp #
.cfi_def_cfa_offset 8
movl %esp, %ebp #,
.cfi_offset 5, -8
.cfi_def_cfa_register 5
pushl %esi #
pushl %ebx #
subl $32, %esp #,
.LEHB6:
.cfi_offset 3, -16
.cfi_offset 6, -12
call _Z3bayv #
.LEHE6: 从下面表格知道,该区间对应的landing pad为L24,即对于bay的调用在try的区间内。
# basic block 3
leal -10(%ebp), %eax #, tmp63
movl %eax, (%esp) # tmp63,
.LEHB7:
call _ZN6nvdestD1Ev #局部变量inner的析构也在try的保护范围内。
.LEHE7:
# basic block 4
jmp .L26 #
.L24:
# basic block 5
.L21:
movl %edx, %ebx # tmp64, save_filt.2
movl %eax, %esi # tmp65, save_eptr.1
leal -10(%ebp), %eax #, tmp66
movl %eax, (%esp) # tmp66,
call _ZN6nvdestD1Ev #
movl %esi, %eax # save_eptr.1, tmp65
movl %ebx, %edx # save_filt.2, tmp64
jmp .L22 # bay函数如果抛出异常,跳转到该block执行,从L21开始,此处跳转到L22,再次执行外层变量的析构函数,这里有一点非常重要,那就是这个cleanup函数本身不在任何的call site范围内,所以在cleanup阶段析构函数如果再次出现异常,将会在personality函数中执行到有LSDA,但是异常PC不再任何一个call site的情况,此时也就是满足了handler为terminate的条件,此时整个进程被结束。这样就是为什么一些书上说不要在析构函数中抛出异常的原因:cleanup阶段抛出的异常对程序是致命的,没有人能捕获,或者说是二次异常问题
.L26:
# basic block 6
movl $.LC2, (%esp) #,
.LEHB8:
call puts #
.LEHE8:
# basic block 7
leal -9(%ebp), %eax #, tmp67
movl %eax, (%esp) # tmp67,
.LEHB9:
call _ZN6nvdestD1Ev #
.LEHE9:
addl $32, %esp #,
popl %ebx #
.cfi_remember_state
.cfi_restore 3
popl %esi #
.cfi_restore 6
popl %ebp #
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.L25:
# basic block 8
.L22:
# basic block 9
.cfi_restore_state
movl %edx, %ebx # tmp64, save_filt.4
movl %eax, %esi # tmp65, save_eptr.3
leal -9(%ebp), %eax #, tmp68
movl %eax, (%esp) # tmp68,
call _ZN6nvdestD1Ev #
movl %esi, %eax # save_eptr.3, tmp65
movl %ebx, %edx # save_filt.4, tmp64
movl %eax, (%esp) # tmp65,
.LEHB10:
call _Unwind_Resume #
.LEHE10:
.cfi_endproc
.LFE4:
.size _Z3bazv, .-_Z3bazv
.section .gcc_except_table
.LLSDA4:
.byte 0xff # @LPStart format (omit)
.byte 0xff # @TType format (omit)
.byte 0x1 # call-site format (uleb128)
.uleb128 .LLSDACSE4-.LLSDACSB4 # Call-site table length
.LLSDACSB4:
.uleb128 .LEHB6-.LFB4 # region 0 start
.uleb128 .LEHE6-.LEHB6 # length
.uleb128 .L24-.LFB4 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB7-.LFB4 # region 1 start 对于局部变量inner的析构受保护,跳转到25处。
.uleb128 .LEHE7-.LEHB7 # length
.uleb128 .L25-.LFB4 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB8-.LFB4 # region 2 start
.uleb128 .LEHE8-.LEHB8 # length
.uleb128 .L25-.LFB4 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB9-.LFB4 # region 3 start
.uleb128 .LEHE9-.LEHB9 # length
.uleb128 0x0 # landing pad
.uleb128 0x0 # action
.uleb128 .LEHB10-.LFB4 # region 4 start
.uleb128 .LEHE10-.LEHB10 # length
.uleb128 0x0 # landing pad
.uleb128 0x0 # action
.LLSDACSE4:
.text
.ident "GCC: (GNU) 4.4.2 20091027 (Red Hat 4.4.2-7)"
.section .note.GNU-stack,"",@progbits
[root@Harry exception]#