gcc的异常处理机制

一、序言
这个异常处理可以说是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]#

上一篇:CSAPP第4章家庭作业参考答案


下一篇:图解CPU执行一段程序