【技术研究】浅析C++异常处理机制

TEC

Posted by Corax on November 21, 2023

浅析C++异常处理机制

1.引言

https://www.cnblogs.com/catch/p/3604516.html https://www.cnblogs.com/catch/p/3619379.html https://www.cnblogs.com/-citywall123/p/12901301.html https://bbs.kanxue.com/thread-271891.htm

什么是异常处理

就是处理程序当中的错误,其中包括,除数为0,年龄输入一个负数等等不对劲的事情。那么怎么处理呢,这就涉及到异常处理机制。

初识异常处理机制

当抛出异常的时候,会现在所在区域查找是否存在catch块对异常进行处理,如果有就直接处理掉。如果没有就不断沿着调用链向上进行栈展开找到能处理该异常的catch块。

2.栈展开

当异常抛出后如果没有被catch,就要沿着调用链一直向上抛,知道走完整个调用链,如果都没有找到,就会调用std::terminate(),把程序给abort掉,就是终止掉。

如果找到了相应的catch,就会进入这个代码块执行相应的操作,这个代码块有个专有名字叫做Landing pad

而从抛出异常到执行Landing pad的这个整个过程就是stack unwind。

这个栈展开的过程包括两个阶段。

  1. 往上找Landing pad
  2. 没有找到就abort,找到了就几下Landing pad位置,然后回到抛出异常的函数的位置,一帧一帧地清理调用链上各个函数内部的局部变量,知道Landing pad所在函数。

如果本身调用链如下

image-20231122202057334

那如果func1抛出了一个错误,那就会从func1一级一级往上面找

image-20231122202153146

所以其实栈展开也可以说是一个函数调用的逆过程。

这个过程如何实现呢

是由一个专门用来栈展开地库来进行的,intel上,属于itanum abi接口的一部分,在任何语言上都可以实现。GCC 就基于这个接口来实现 c++ 的异常处理。

Itanium C++ ABI

_Unwind_RaiseException,
_Unwind_Resume,
_Unwind_DeleteException,
_Unwind_GetGR,
_Unwind_SetGR,
_Unwind_GetIP,
_Unwind_SetIP,
_Unwind_GetRegionStart,
_Unwind_GetLanguageSpecificData,
_Unwind_ForcedUnwind

其中

_Unwind_RaiseException

用来栈展开。用户执行throw的时候调用,主要功能就是从当前的函数开始,对调用链的每个函数调用一个personality routine函数(personality routine这个函数简单来讲就是干两件事情

  1. 检查当前函数有没有可以处理当前异常的catch
  2. 清理栈上的局部变量

可以反应过来

这和栈展开的两个阶段一一对应。

image-20231122203628734

image-20231122203655183

可以将栈展开由 personality routine来完成。

两个阶段具体到调用链上的函数来讲,每个函数在进行栈展开的时候都会进行两次被personality routine遍历。

下面是_Unwind_RaiseException() 内部的大概实现,伪代码

第一个就是在通过personality routine找catch,第二个是personality routine进行栈局部变量清理

_Unwind_RaiseException(exception)
{
    bool found = false;
    while (1)
     {
         // 建立上个函数的上下文
         context = build_context();
         if (!context) break;
         found = personality_routine(exception, context, SEARCH);
         if (found or reach the end) break;
     }

    while (found)
    {
        context = build_context();
        if (!context) break;
        personality_routine(exception, context, UNWIND);
        if (reach_catch_function) break;
    }
}

同样再介绍一下ABI的函数使用到的其他结构体

struct _Unwind_Context;

struct _Unwind_Exception {
  uint64     exception_class;
  _Unwind_Exception_Cleanup_Fn exception_cleanup;
  uint64     private_1;
  uint64     private_2;
};
struct _Unwind_Context
{
  void *reg[DWARF_FRAME_REGISTERS+1];
  void *cfa;
  void *ra;
  void *lsda;
  struct dwarf_eh_bases bases;
  _Unwind_Word args_size;
};

_Unwind_Context ,对调用者完全透明,表示程序运行时的上下文。有一些寄存器的值,函数返回地址等等。

C++ ABI

在写下throw xxx的时候,编译器会分配一个数据结构来表示这个异常。具体结构如下

struct __cxa_exception { 
  std::type_info *    exceptionType;
  void (*exceptionDestructor) (void *); 
  unexpected_handler    unexpectedHandler;
  terminate_handler    terminateHandler;
  __cxa_exception *    nextException;

  int     handlerCount;
  int     handlerSwitchValue;
  const char *     actionRecord;
  const char *     languageSpecificData;
  void *     catchTemp;
  void *     adjustedPtr;

  _Unwind_Exception    unwindHeader;
};

AI如是说道


    exceptionType 和 exceptionDestructor 用于存储异常对象的类型信息和析构函数,以便在处理异常时进行清理操作。
    unexpectedHandler 和 terminateHandler 用于指定未捕获异常和无法处理异常时的处理函数。
    nextException 可以被用来链接多个异常对象,以便创建链式异常机制。
    handlerCount、 handlerSwitchValue 和 actionRecord 用于描述异常处理程序的跳转表信息。
    languageSpecificData 用于存储特定于语言的异常数据,例如 C++ 中的虚拟继承表。
    catchTemp 用于在异常处理程序中访问异常对象的副本。
    adjustedPtr 表示实际存储异常对象的位置,用于支持数据对齐。
    unwindHeader 是一个 _Unwind_Exception 结构体,包含了 unwind 头信息,用于支持 C++ 异常处理机制中的堆栈展开(stack unwinding)。

总的来说,这个结构体封装了 C++ 异常处理机制中的异常对象,包含了异常类型信息、处理程序、跳转表和 unwind 头等重要成员。这些信息在异常处理过程中起到了关键作用,帮助 C++ 语言实现了灵活和可靠的异常处理机制。

讲的非常好啊,要注意最后一个变量_Unwind_Exception unwindHeader,是接口内部用的结构体,对此

image-20231122205445328

image-20231122205451688

当我们throw一个异常的时候,会有这么一个结构题

image-20231122205525330

其中,_cxa_exception 就是头部,exception_obj 则是 “throw xxx” 中的 xxx,这两部分在内存中是连续的。

异常对象由函数 __cxa_allocate_exception() 进行创建,最后由 __cxa_free_exception() 进行销毁。当我们在程序里执行了抛出异常后,编译器为我们做了如下的事情:

  1. 调用 __cxa_allocate_exception 函数,分配一个异常对象。
  2. 调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化。
  3. __cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() (作为接口使用)从而开始 unwind。
  4. _Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine。
  5. 该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理。这里再次调用
  6. _Unwind_RaiseException() 将控制权转到相应的catch代码。
  7. unwind 完成,用户代码继续执行。

从源码来看栈展开

image-20231122205740867

是从这里throw开始的对吧

看源码

extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
{
   PROBE2 (throw, obj, tinfo);

   // Definitely a primary.
   __cxa_refcounted_exception *header = __get_refcounted_exception_header_from_obj (obj);
   header->referenceCount = 1;
   header->exc.exceptionType = tinfo;
   header->exc.exceptionDestructor = dest;
   header->exc.unexpectedHandler = std::get_unexpected ();
   header->exc.terminateHandler = std::get_terminate ();
   __GXX_INIT_PRIMARY_EXCEPTION_CLASS(header->exc.unwindHeader.exception_class);
   header->exc.unwindHeader.exception_cleanup = __gxx_exception_cleanup;

   #ifdef _GLIBCXX_SJLJ_EXCEPTIONS
   _Unwind_SjLj_RaiseException (&header->exc.unwindHeader);
   #else
   _Unwind_RaiseException (&header->exc.unwindHeader);
   #endif

   // Some sort of unwinding error. Note that terminate is a handler.
   __cxa_begin_catch (&header->exc.unwindHeader);
   std::terminate ();
}

可以看到这里调用了_Unwind_RaiseException,栈展开正式开始

image-20231122205916980

下面是_Unwind_RaiseException源码

/* Raise an exception, passing along the given exception object.  */

_Unwind_Reason_Code
_Unwind_RaiseException(struct _Unwind_Exception *exc)
{
  struct _Unwind_Context this_context, cur_context;
  _Unwind_Reason_Code code;

  uw_init_context (&this_context);
  cur_context = this_context;

  /* Phase 1: Search.  Unwind the stack, calling the personality routine
     with the _UA_SEARCH_PHASE flag set.  Do not modify the stack yet.  */
  while (1)
    {
      _Unwind_FrameState fs;

      code = uw_frame_state_for (&cur_context, &fs);

      if (code == _URC_END_OF_STACK)
    /* Hit end of stack with no handler found.  */
    return _URC_END_OF_STACK;

      if (code != _URC_NO_REASON)
    /* Some error encountered.  Ususally the unwinder doesn't
       diagnose these and merely crashes.  */
    return _URC_FATAL_PHASE1_ERROR;

      /* Unwind successful.  Run the personality routine, if any.  */
      if (fs.personality)
    {
      code = (*fs.personality) (1, _UA_SEARCH_PHASE, exc->exception_class,
                    exc, &cur_context);
      if (code == _URC_HANDLER_FOUND)
        break;
      else if (code != _URC_CONTINUE_UNWIND)
        return _URC_FATAL_PHASE1_ERROR;
    }

      uw_update_context (&cur_context, &fs);
    }

  /* Indicate to _Unwind_Resume and associated subroutines that this
     is not a forced unwind.  Further, note where we found a handler.  */
  exc->private_1 = 0;
  exc->private_2 = uw_identify_context (&cur_context);

  cur_context = this_context;
  code = _Unwind_RaiseException_Phase2 (exc, &cur_context);
  if (code != _URC_INSTALL_CONTEXT)
    return code;

  uw_install_context (&this_context, &cur_context);
}


static _Unwind_Reason_Code
_Unwind_RaiseException_Phase2(struct _Unwind_Exception *exc,
                  struct _Unwind_Context *context)
{
  _Unwind_Reason_Code code;

  while (1)
    {
      _Unwind_FrameState fs;
      int match_handler;

      code = uw_frame_state_for (context, &fs);

      /* Identify when we've reached the designated handler context.  */
      match_handler = (uw_identify_context (context) == exc->private_2
               ? _UA_HANDLER_FRAME : 0);

      if (code != _URC_NO_REASON)
    /* Some error encountered.  Usually the unwinder doesn't
       diagnose these and merely crashes.  */
      return _URC_FATAL_PHASE2_ERROR;

      /* Unwind successful.  Run the personality routine, if any.  */
      if (fs.personality)
      {
        code = (*fs.personality) (1, _UA_CLEANUP_PHASE | match_handler,
                    exc->exception_class, exc, context);
        if (code == _URC_INSTALL_CONTEXT)
          break;
        if (code != _URC_CONTINUE_UNWIND) 
          return _URC_FATAL_PHASE2_ERROR;
      }

      /* Don't let us unwind past the handler context.  */
      if (match_handler)
         abort ();

      uw_update_context (context, &fs);
    }

  return code;
}

上面正好就是两个阶段,一个对应找,一个对应清。

下面来深入看看

3.相关数据结构

栈展开需要一定数据去支持,这些数据保存了与每个可能抛异常的函数相关的信息供运行时查找

主要三类

1)unwind table,这个表记录了与函数相关的信息,共三个字段:函数的起始地址,函数的结束地址,一个 info block 指针。

2)unwind descriptor table,这个列表用于描述函数中需要 unwind 的区域的相关信息。

3)语言相关的数据(language specific data area),用于上层语言内部的处理。

对GCC来讲所有unwind相关数据放在了 .eh_frame 及 .gcc_except_table 这两个 section 里面了

4.eh_frame区域

属于DWARF标准的一部分,这些数据两个作用

1)描述函数调用栈的结构(layout)

2)异常发生后,指导 unwinder 怎么进行 unwind。

其中DWARF的功能很强大,在ezbyte那题里面研究领教过了。

eh_frame 像是一张表,它用于描述怎样根据程序中某一条指令来设置相应的寄存器,从而返回到当前函数的调用函数中去,它的作用可以用如下表格来形地描述。

image-20231122212614996

这里的CFA就像一个基址一样,用它来做运算。

具体是向上,eh_frame 由一个CIE和多个FDE组成

CIE (Common Information Entry)和FDE (Frame Description Entry)

image-20231122212752938

CIE格式如下

image-20231122212806412

FDE如下

image-20231122212821497

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,可以读

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 data:     00 00 00 00 00 00 00 00
  #以下为字节码
  DW_CFA_advance_loc: 1 to 00400ac9
  DW_CFA_def_cfa_offset: 16
  DW_CFA_offset: r6 at cfa-16
  DW_CFA_advance_loc: 3 to 00400acc
  DW_CFA_def_cfa_reg: r6
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop

对于GCC编译的程序来讲,CIE,FDE都是unwind 过程中恢复现场时所依赖的全部东西。这里所说的恢复现场指的是恢复调用当前函数的函数的现场,比如,func1 调用 func2,然后我们可以在 func2 里通过查询 CIE,FDE 恢复 func1 的现场。CIE,FDE 存在于每一个需要处理异常的 ELF 文件中,当异常发生时,runtime 根据当前 PC 值调用 dl_iterate_phdr() 函数就可以把当前程序所加载的所有模块轮询一遍,从而找到该 PC 所在模块的 eh_frame。

等于说,这里面存着我们要进行栈展开的信息。

根据eh找到CIE,再找到FDE

static _Unwind_Reason_Code
uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs)
{
  struct dwarf_fde *fde;
  struct dwarf_cie *cie;
  const unsigned char *aug, *insn, *end;

  memset (fs, 0, sizeof (*fs));
  context->args_size = 0;
  context->lsda = 0;

  // 根据context查找FDE。
  fde = _Unwind_Find_FDE (context->ra - 1, &context->bases);
  if (fde == NULL)
    {
      /* Couldn't find frame unwind info for this function.  Try a
     target-specific fallback mechanism.  This will necessarily
     not provide a personality routine or LSDA.  */
#ifdef MD_FALLBACK_FRAME_STATE_FOR
      MD_FALLBACK_FRAME_STATE_FOR (context, fs, success);
      return _URC_END_OF_STACK;
    success:
      return _URC_NO_REASON;
#else
      return _URC_END_OF_STACK;
#endif
    }

  fs->pc = context->bases.func;

  // 获取对应的CIE.
  cie = get_cie (fde);

  // 提取出CIE中的信息,如personality routine的地址。
  insn = extract_cie_info (cie, context, fs);
  if (insn == NULL)
    /* CIE contained unknown augmentation.  */
    return _URC_FATAL_PHASE1_ERROR;

  /* First decode all the insns in the CIE.  */
  end = (unsigned char *) next_fde ((struct dwarf_fde *) cie);

  // 执行dwarf字节码,从而恢复相应的寄存器的值。
  execute_cfa_program (insn, end, context, fs);

 // 定位到fde的相关数据
  /* Locate augmentation for the fde.  */
  aug = (unsigned char *) fde + sizeof (*fde);
  aug += 2 * size_of_encoded_value (fs->fde_encoding);
  insn = NULL;
  if (fs->saw_z)
    {
      _Unwind_Word i;
      aug = read_uleb128 (aug, &i);
      insn = aug + i;
    }

  // 读取language specific data的指针
  if (fs->lsda_encoding != DW_EH_PE_omit)
    aug = read_encoded_value (context, fs->lsda_encoding, aug,
                  (_Unwind_Ptr *) &context->lsda);

  /* Then the insns in the FDE up to our target PC.  */
  if (insn == NULL)
    insn = aug;
  end = (unsigned char *) next_fde (fde);

  // 执行FDE中的字节码。
  execute_cfa_program (insn, end, context, fs);

  return _URC_NO_REASON;
}

5.实现Personality routine

1)检查当前函数是否有相应的 catch 语句。

2)清理当前函数中的局部变量。

而这两个功能在运行时没法完成,必须要在编译时建立相关的数据进行协助。这些与抛异常的函数具体相关的信息全部放在 .gcc_except_table 区域里去了,这些信息会作为Itanium ABI 接口中所谓的 language specific data 在 unwinder 与 c++ ABI 之间传递。

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中这两部分分别通过正负下标来进行索引:

image-20231122214311753

6.Landing pad

指的是能够 catch 当前异常的 catch 语句。这个说法其实不确切,准确来说,landing pad 指的是 unwinder 之外的“用户代码”

1)用于 catch 相应的 exception,对于一个函数来说,如果该函数中有 catch 语句,且能够处理当前的异常,则该 catch 就是 landing pad。

2)如果当前函数没有 catch 或者 catch 不能处理当前 exception,则意味着异常还要从当前函数继续往上抛,因而 unwind 当前函数时有可能要进行相应的清理,此时这些清理局部变量的代码就是 landing pad。

#include <iostream>
#include <stddef.h>
using namespace std;

class cs
{
    public:

        explicit cs(int i) :i_(i) { cout << "cs constructor:" << i << endl; }
        ~cs() { cout << "cs destructor:" << i_ << endl; }

    private:

        int i_;
};

void test_func3()
{
    cs c(33);
    cs c2(332);

    throw 3;

    cs c3(333);
    cout << "test func3" << endl;
}

void test_func3_2()
{
    cs c(32);
    cs c2(322);

    test_func3();

    cs c3(323);

    test_func3();
}

void test_func2()
{
    cs c(22);

    cout << "test func2" << endl;
    try
    {
        test_func3_2();

        cs c2(222);
    }
    catch (int)
    {
        cout << "catch 2" << endl;
    }
}

void test_func1()
{
    cout << "test func1" << endl;
    try
    {
        test_func2();
    }
    catch (...)
    {
        cout << "catch 1" << endl;
    }
}

int main()
{
    test_func1();
    return 0;
}

对于函数 test_func3_2() 来说,当 test_func3() 抛出异常后,在 unwind 的第二阶段,我们知道 test_func3_2() 中的局部变量 c 及 c2 是需要清理的,而 c3 则不用,那么编译器是怎么生成代码来完成这件事情的呢?当异常发生时,运行时是没有办法知道当前哪些变量是需要清理的,因为这个原因编译器在生成代码的时候,在函数的末尾设置了多个出口,使得当异常发生时,可以直接跳到某一段代码就能清理相应的局部变量,我们看看 test_func3_2() 编译后生成的对应的汇编代码:

看一下如何清理的

void test_func3_2()
{
  400ca4:    55                     push   %rbp
  400ca5:    48 89 e5               mov    %rsp,%rbp
  400ca8:    53                     push   %rbx
  400ca9:    48 83 ec 48            sub    $0x48,%rsp
    cs c(32);
  400cad:    48 8d 7d e0            lea    0xffffffffffffffe0(%rbp),%rdi
  400cb1:    be 20 00 00 00         mov    $0x20,%esi
  400cb6:    e8 9f 02 00 00         callq  400f5a <_ZN2csC1Ei>
    cs c2(322);
  400cbb:    48 8d 7d d0            lea    0xffffffffffffffd0(%rbp),%rdi
  400cbf:    be 42 01 00 00         mov    $0x142,%esi
  400cc4:    e8 91 02 00 00         callq  400f5a <_ZN2csC1Ei>

    test_func3();
  400cc9:    e8 5a ff ff ff         callq  400c28 <_Z10test_func3v>

    cs c3(323);
  400cce:    48 8d 7d c0            lea    0xffffffffffffffc0(%rbp),%rdi
  400cd2:    be 43 01 00 00         mov    $0x143,%esi
  400cd7:    e8 7e 02 00 00         callq  400f5a <_ZN2csC1Ei>

    test_func3();
  400cdc:    e8 47 ff ff ff         callq  400c28 <_Z10test_func3v>
  400ce1:    eb 17                  jmp    400cfa <_Z12test_func3_2v+0x56>
  400ce3:    48 89 45 b8            mov    %rax,0xffffffffffffffb8(%rbp)
  400ce7:    48 8b 5d b8            mov    0xffffffffffffffb8(%rbp),%rbx
  400ceb:    48 8d 7d c0            lea    0xffffffffffffffc0(%rbp),%rdi #c3的this指针
  400cef:    e8 2e 02 00 00         callq  400f22 <_ZN2csD1Ev>
  400cf4:    48 89 5d b8            mov    %rbx,0xffffffffffffffb8(%rbp)
  400cf8:    eb 0f                  jmp    400d09 <_Z12test_func3_2v+0x65>
  400cfa:    48 8d 7d c0            lea    0xffffffffffffffc0(%rbp),%rdi #c3的this指针
  400cfe:    e8 1f 02 00 00         callq  400f22 <_ZN2csD1Ev>
  400d03:    eb 17                  jmp    400d1c <_Z12test_func3_2v+0x78>
  400d05:    48 89 45 b8            mov    %rax,0xffffffffffffffb8(%rbp)
  400d09:    48 8b 5d b8            mov    0xffffffffffffffb8(%rbp),%rbx
  400d0d:    48 8d 7d d0            lea    0xffffffffffffffd0(%rbp),%rdi #c2的this指针
  400d11:    e8 0c 02 00 00         callq  400f22 <_ZN2csD1Ev>
  400d16:    48 89 5d b8            mov    %rbx,0xffffffffffffffb8(%rbp)
  400d1a:    eb 0f                  jmp    400d2b <_Z12test_func3_2v+0x87> 
  400d1c:    48 8d 7d d0            lea    0xffffffffffffffd0(%rbp),%rdi #c2的this指针
  400d20:    e8 fd 01 00 00         callq  400f22 <_ZN2csD1Ev>
  400d25:    eb 1e                  jmp    400d45 <_Z12test_func3_2v+0xa1>
  400d27:    48 89 45 b8            mov    %rax,0xffffffffffffffb8(%rbp)
  400d2b:    48 8b 5d b8            mov    0xffffffffffffffb8(%rbp),%rbx
  400d2f:    48 8d 7d e0            lea    0xffffffffffffffe0(%rbp),%rdi #c的this指针
  400d33:    e8 ea 01 00 00         callq  400f22 <_ZN2csD1Ev>
  400d38:    48 89 5d b8            mov    %rbx,0xffffffffffffffb8(%rbp)
  400d3c:    48 8b 7d b8            mov    0xffffffffffffffb8(%rbp),%rdi
  400d40:    e8 b3 fc ff ff         callq  4009f8 <_Unwind_Resume@plt>  #c的this指针
  400d45:    48 8d 7d e0            lea    0xffffffffffffffe0(%rbp),%rdi
  400d49:    e8 d4 01 00 00         callq  400f22 <_ZN2csD1Ev>
}
  400d4e:    48 83 c4 48            add    $0x48,%rsp
  400d52:    5b                     pop    %rbx
  400d53:    c9                     leaveq 
  400d54:    c3                     retq   
  400d55:    90                     nop

其实是在控制程序流程。

7.关于DWARF

就拿那道ezbyte距离好了

00000040 0000000000000094 00000044 FDE cie=00000000 pc=0000000000404bf5..0000000000404c21
  DW_CFA_advance_loc: 5 to 0000000000404bfa
  DW_CFA_def_cfa_offset: 16
  DW_CFA_offset: r6 (rbp) at cfa-16
  DW_CFA_advance_loc: 3 to 0000000000404bfd
  DW_CFA_def_cfa_register: r6 (rbp)
  DW_CFA_val_expression: r12 (r12) (DW_OP_constu: 2616514329260088143; DW_OP_constu: 1237891274917891239; DW_OP_constu: 1892739; DW_OP_breg12 (r12): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_constu: 8502251781212277489; DW_OP_constu: 1209847170981118947; DW_OP_constu: 8971237; DW_OP_breg13 (r13): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_or; DW_OP_constu: 2451795628338718684; DW_OP_constu: 1098791727398412397; DW_OP_constu: 1512312; DW_OP_breg14 (r14): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_or; DW_OP_constu: 8722213363631027234; DW_OP_constu: 1890878197237214971; DW_OP_constu: 9123704; DW_OP_breg15 (r15): 0; DW_OP_plus; DW_OP_xor; DW_OP_xor; DW_OP_or)
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop

详细的文档

dwarfstd.org/doc/DWARF5.pdf

在以后做相关的东西的时候,把他当作虚拟机来看就好了。深入的以后再研究。