前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LLVM异常实现三 libunwind

LLVM异常实现三 libunwind

作者头像
AkemiHomura
发布2024-10-03 09:25:47
650
发布2024-10-03 09:25:47
举报
文章被收录于专栏:homura的博客

上期提到的__cxa_xxx相关函数的实现离不开libunwind的相关接口,libunwind专门用于平台无关的堆栈展开和错误处理,内部做了很多平台相关的兼容工作,这期我们主要来介绍一下libunwind相关接口的具体实现。

_Unwind_RaiseException

之前在throw的时候提到其中调用了_Unwind_RaiseException,这里则是libunwind的一个入口。

代码语言:javascript
复制
void
__cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *)) {
    ...
    _Unwind_RaiseException(&exception_header->unwindHeader);
		...
}

Raise Exception的过程分为两步

  1. 搜索exception所在的栈
  2. clean up,本质是跳转到landingpad进行处理

UnwindLevel1.c

代码语言:javascript
复制
/// Called by __cxa_throw.  Only returns if there is a fatal error.
_LIBUNWIND_EXPORT _Unwind_Reason_Code
_Unwind_RaiseException(_Unwind_Exception *exception_object) {
  _LIBUNWIND_TRACE_API("_Unwind_RaiseException(ex_obj=%p)",
                       (void *)exception_object);
  unw_context_t uc;
  unw_cursor_t cursor;
  __unw_getcontext(&uc);

  // Mark that this is a non-forced unwind, so _Unwind_Resume()
  // can do the right thing.
  exception_object->private_1 = 0;
  exception_object->private_2 = 0;

  // phase 1: the search phase
  _Unwind_Reason_Code phase1 = unwind_phase1(&uc, &cursor, exception_object);
  if (phase1 != _URC_NO_REASON)
    return phase1;

  // phase 2: the clean up phase
  return unwind_phase2(&uc, &cursor, exception_object);
}

出现的数据结构

libunwind.h

代码语言:javascript
复制
struct unw_context_t {
  uint64_t data[_LIBUNWIND_CONTEXT_SIZE];
};
typedef struct unw_context_t unw_context_t;

struct unw_cursor_t {
  uint64_t data[_LIBUNWIND_CURSOR_SIZE];
} LIBUNWIND_CURSOR_ALIGNMENT_ATTR;
typedef struct unw_cursor_t unw_cursor_t;

#if defined(_WIN32) && defined(__SEH__)
  #define LIBUNWIND_CURSOR_ALIGNMENT_ATTR __attribute__((__aligned__(16)))
#else
  #define LIBUNWIND_CURSOR_ALIGNMENT_ATTR
#endif

context是用于程序运行中的上下文,包括各种寄存器的值。而cursor只是用于存放cursor对象的一个空间,后面会在这个空间上构造对应的cursor对象。_LIBUNWIND_CONTEXT_SIZE和_LIBUNWIND_CURSOR_SIZE则是定义在include/__libunwind_config.h中,不同平台不一致

比如i386的定义

代码语言:javascript
复制
# if defined(__i386__)
#  define _LIBUNWIND_TARGET_I386
#  define _LIBUNWIND_CONTEXT_SIZE 8
#  define _LIBUNWIND_CURSOR_SIZE 15
#  define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86
代码语言:javascript
复制
struct _Unwind_Exception {
  _Unwind_Exception_Class exception_class;
  void (*exception_cleanup)(_Unwind_Reason_Code reason,
                            _Unwind_Exception *exc);
#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__)
  uintptr_t private_[6];
#else
  uintptr_t private_1; // non-zero means forced unwind
  uintptr_t private_2; // holds sp that phase1 found for phase2 to use
#endif
#if __SIZEOF_POINTER__ == 4
  // The implementation of _Unwind_Exception uses an attribute mode on the
  // above fields which has the side effect of causing this whole struct to
  // round up to 32 bytes in size (48 with SEH). To be more explicit, we add
  // pad fields added for binary compatibility.
  uint32_t reserved[3];
#endif
  // The Itanium ABI requires that _Unwind_Exception objects are "double-word
  // aligned".  GCC has interpreted this to mean "use the maximum useful
  // alignment for the target"; so do we.
} __attribute__((__aligned__));

typedef uint64_t _Unwind_Exception_Class;

_Unwind_Exception主要是保存了exception_class,exception_clean_up的函数指针,private值。

__unw_getcontext

实现在UnwindRegistersSave.S

根据不同的目标架构有着不同实现,但做的事情本质上是相同的,将当前程序执行的上下文(各种寄存器)临时保存起来。

i386的实现参考

代码语言:javascript
复制
#
# extern int __unw_getcontext(unw_context_t* thread_state)
#
# On entry:
#   +                       +
#   +-----------------------+
#   + thread_state pointer  +
#   +-----------------------+
#   + return address        +
#   +-----------------------+   <-- SP
#   +                       +
#
代码语言:javascript
复制
DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext)

  _LIBUNWIND_CET_ENDBR
  push  %eax
  movl  8(%esp), %eax
  movl  %ebx,  4(%eax)
  movl  %ecx,  8(%eax)
  movl  %edx, 12(%eax)
  movl  %edi, 16(%eax)
  movl  %esi, 20(%eax)
  movl  %ebp, 24(%eax)
  movl  %esp, %edx
  addl  $8, %edx
  movl  %edx, 28(%eax)  # store what sp was at call site as esp
  # skip ss
  # skip eflags
  movl  4(%esp), %edx
  movl  %edx, 40(%eax)  # store return address as eip
  # skip cs
  # skip ds
  # skip es
  # skip fs
  # skip gs
  movl  (%esp), %edx
  movl  %edx, (%eax)  # store original eax
  popl  %eax
  xorl  %eax, %eax    # return UNW_ESUCCESS
  ret

unwind_phase1

这个函数主要做了如下几件事情

  1. 初始化cursor
  2. 开始循环操作,根据cursor中的当前pc找到对应的eh_frame,从中获取return address(下次step的时候就会基于返回地址的调用者的栈)并更新cursor中的pc。
  3. 获取proc_info
  4. frame的handler不为空时,读取frame中personality,根据结果判断是否为所找的栈帧
    1. 如果是则将exception_object→private_2记录到sp中
    2. 否则继续查找
  5. 如果全部找完了那么就是_URC_NO_REASON,而不是返回错误
代码语言:javascript
复制
static _Unwind_Reason_Code
unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) {
  __unw_init_local(cursor, uc);

  // Walk each frame looking for a place to stop.
  while (true) {
    // Ask libunwind to get next frame (skip over first which is
    // _Unwind_RaiseException).
    int stepResult = __unw_step(cursor);
    if (stepResult == 0) {
          (void *)exception_object);
      return _URC_END_OF_STACK;
    } else if (stepResult < 0) {
          (void *)exception_object);
      return _URC_FATAL_PHASE1_ERROR;
    }

    // See if frame has code to run (has personality routine).
    unw_proc_info_t frameInfo;
    unw_word_t sp;
    if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) {
          (void *)exception_object);
      return _URC_FATAL_PHASE1_ERROR;
    }

    // If there is a personality routine, ask it if it will want to stop at
    // this frame.
    if (frameInfo.handler != 0) {
      _Unwind_Personality_Fn p =
          (_Unwind_Personality_Fn)(uintptr_t)(frameInfo.handler);
      _Unwind_Reason_Code personalityResult =
          (*p)(1, _UA_SEARCH_PHASE, exception_object->exception_class,
               exception_object, (struct _Unwind_Context *)(cursor));
      switch (personalityResult) {
      case _URC_HANDLER_FOUND:
        // found a catch clause or locals that need destructing in this frame
        // stop search and remember stack pointer at the frame
        __unw_get_reg(cursor, UNW_REG_SP, &sp);
        exception_object->private_2 = (uintptr_t)sp;
        return _URC_NO_REASON;

      case _URC_CONTINUE_UNWIND:
        // continue unwinding
        break;

      default:
        // something went wrong
        return _URC_FATAL_PHASE1_ERROR;
      }
    }
  }
  return _URC_NO_REASON;
}

第一阶段的personality,这个handler是从frameInfo中获取的,表明每个frame都可以有自己单独的personality,每个frame关联了一个函数,而在前面查看llvm ir的时候也是针对函数添加attribute标识personality函数。

以下是这个过程中出现的一些函数的实现

__unw_init_local

代码语言:javascript
复制
/// Create a cursor of a thread in this process given 'context' recorded by
/// __unw_getcontext().
_LIBUNWIND_HIDDEN int __unw_init_local(unw_cursor_t *cursor,
                                       unw_context_t *context) {
  _LIBUNWIND_TRACE_API("__unw_init_local(cursor=%p, context=%p)",
                       static_cast<void *>(cursor),
                       static_cast<void *>(context));
#if defined(__i386__)
# define REGISTER_KIND Registers_x86
...
#endif
  // Use "placement new" to allocate UnwindCursor in the cursor buffer.
  new (reinterpret_cast<UnwindCursor<LocalAddressSpace, REGISTER_KIND> *>(cursor))
      UnwindCursor<LocalAddressSpace, REGISTER_KIND>(
          context, LocalAddressSpace::sThisAddressSpace);
#undef REGISTER_KIND
  AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
  co->setInfoBasedOnIPRegister();

  return UNW_ESUCCESS;
}

在cursor的地址上通过placement new构造对象,所以cursor实际上是一个UnwindCursor

代码语言:javascript
复制
// libunwind does not and should not depend on C++ library which means that we
// need our own definition of inline placement new.
static void *operator new(size_t, UnwindCursor<A, R> *p) { return p; }

类型为UnwindCursor<LocalAddressSpace, Registers_x86>

UnwindCursor

这个类主要用于指向unwind过程中的各个栈帧,通过这个类提取出相关的信息。

libunwind/src/UnwindCursor.hpp

代码语言:javascript
复制
/// UnwindCursor contains all state (including all register values) during
/// an unwind.  This is normally stack allocated inside a unw_cursor_t.
template <typename A, typename R>
class UnwindCursor : public AbstractUnwindCursor{
  typedef typename A::pint_t pint_t;
public:
                      UnwindCursor(unw_context_t *context, A &as);
                      UnwindCursor(A &as, void *threadArg);
  ...
  A               &_addressSpace;
  R                _registers;
  unw_proc_info_t  _info;
  bool             _unwindInfoMissing; // 是否有调试信息
  bool             _isSignalFrame;
};

template <typename A, typename R>
UnwindCursor<A, R>::UnwindCursor(unw_context_t *context, A &as)
    : _addressSpace(as), _registers(context), _unwindInfoMissing(false),
      _isSignalFrame(false) {
  static_assert((check_fit<UnwindCursor<A, R>, unw_cursor_t>::does_fit),
                "UnwindCursor<> does not fit in unw_cursor_t");
  static_assert((alignof(UnwindCursor<A, R>) <= alignof(unw_cursor_t)),
                "UnwindCursor<> requires more alignment than unw_cursor_t");
  memset(&_info, 0, sizeof(_info));
}

先前传入的context作为这里的registers,在registers的构造函数中会拷贝context地址中的值到对应保存registers值的成员变量中。

代码语言:javascript
复制
struct unw_proc_info_t {
  unw_word_t  start_ip;         /* start address of function */
  unw_word_t  end_ip;           /* address after end of function */
  unw_word_t  lsda;             /* address of language specific data area, */
                                /*  or zero if not used */
  unw_word_t  handler;          /* personality routine, or zero if not used */
  unw_word_t  gp;               /* not used */
  unw_word_t  flags;            /* not used */
  uint32_t    format;           /* compact unwind encoding, or zero if none */
  uint32_t    unwind_info_size; /* size of DWARF unwind info, or zero if none */
  unw_word_t  unwind_info;      /* address of DWARF unwind info, or zero */
  unw_word_t  extra;            /* mach_header of mach-o image containing func */
};
typedef struct unw_proc_info_t unw_proc_info_t;

proc_info主要保存了针对某个proc的一个栈帧的一些异常处理信息。

UnwindCursor::setInfoBasedOnIPRegister

最后设置setInfoBasedOnIPRegister。这里各种平台相关的宏定义太多,我们先暂且忽略掉大部分的,先来看drawf unwind的情况。

  1. 获取了产生异常的pc地址
  2. 针对最后一条命令是throw的情况修正对应的pc
  3. 寻找unwind sections
  4. 找到后则从对应的section中解析信息填写到_info中,之后返回(getInfoFromDwarfSection)
  5. 如果没找到那么标记没有相关信息,最后返回
代码语言:javascript
复制
template <typename A, typename R>
void UnwindCursor<A, R>::setInfoBasedOnIPRegister(bool isReturnAddress) {
  pint_t pc = static_cast<pint_t>(this->getReg(UNW_REG_IP));
  // Exit early if at the top of the stack.
  if (pc == 0) {
    _unwindInfoMissing = true;
    return;
  }
  
  // If the last line of a function is a "throw" the compiler sometimes
  // emits no instructions after the call to __cxa_throw.  This means
  // the return address is actually the start of the next function.
  // To disambiguate this, back up the pc when we know it is a return
  // address.
  if (isReturnAddress)
	  --pc;
	  
  // Ask address space object to find unwind sections for this pc.
  UnwindInfoSections sects;
  if (_addressSpace.findUnwindSections(pc, sects)) {
		 ....
#if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
    // If there is dwarf unwind info, look there next.
    if (sects.dwarf_section != 0) {
      if (this->getInfoFromDwarfSection(pc, sects)) {
        // found info in dwarf, done
        return;
      }
    }
#endif
		...
  }
  
  // no unwind info, flag that we can't reliably unwind
  _unwindInfoMissing = true;
}
代码语言:javascript
复制
/// Used by findUnwindSections() to return info about needed sections.
struct UnwindInfoSections {
#if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) ||                                \
    defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) ||                              \
    defined(_LIBUNWIND_USE_DL_ITERATE_PHDR)
  // No dso_base for SEH.
  uintptr_t       dso_base;
#endif
#if defined(_LIBUNWIND_USE_DL_ITERATE_PHDR)
  size_t          text_segment_length;
#endif
#if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
  uintptr_t       dwarf_section;
  size_t          dwarf_section_length;
#endif
#if defined(_LIBUNWIND_SUPPORT_DWARF_INDEX)
  uintptr_t       dwarf_index_section;
  size_t          dwarf_index_section_length;
#endif
#if defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND)
  uintptr_t       compact_unwind_section;
  size_t          compact_unwind_section_length;
#endif
#if defined(_LIBUNWIND_ARM_EHABI)
  uintptr_t       arm_section;
  size_t          arm_section_length;
#endif
};

findUnwindSections

这里各种平台的处理都是完全不同的,实现的本质都是寻找对应的段,比如ehframe,这里选择DWARF unwind以及baremetal的情况作为参考,直接使用链接器中定义的__eh_frame_start和__eh_frame_end来获得对应的eh_frame section的长度,根据是否为空判断对应的信息是否存在,并且更新对应的UnwindInfoSections

代码语言:javascript
复制
inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr,
                                                  UnwindInfoSections &info) {
#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_LIBUNWIND_IS_BAREMETAL)
  info.dso_base = 0;
  // Bare metal is statically linked, so no need to ask the dynamic loader
  info.dwarf_section_length = (size_t)(&__eh_frame_end - &__eh_frame_start);
  info.dwarf_section =        (uintptr_t)(&__eh_frame_start);
  _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: section %p length %p",
                             (void *)info.dwarf_section, (void *)info.dwarf_section_length);
#if defined(_LIBUNWIND_SUPPORT_DWARF_INDEX)
  info.dwarf_index_section =        (uintptr_t)(&__eh_frame_hdr_start);
  info.dwarf_index_section_length = (size_t)(&__eh_frame_hdr_end - &__eh_frame_hdr_start);
  _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: index section %p length %p",
                             (void *)info.dwarf_index_section, (void *)info.dwarf_index_section_length);
#endif       
  if (info.dwarf_section_length)
    return true;   
#elif ...                                       
}

__unw_step

调用cursor的step。

这里主要是解析eh_frame中的信息,之后更新寄存器信息,包括对应的返回值地址,用于下一次step的时候根据这个返回值找到调用者的栈帧。

代码语言:javascript
复制
/// Move cursor to next frame.
_LIBUNWIND_HIDDEN int __unw_step(unw_cursor_t *cursor) {
  _LIBUNWIND_TRACE_API("__unw_step(cursor=%p)", static_cast<void *>(cursor));
  AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
  return co->step();
}

template <typename A, typename R> int UnwindCursor<A, R>::step(bool stage2) {
  (void)stage2;
  // Bottom of stack is defined is when unwind info cannot be found.
  if (_unwindInfoMissing)
    return UNW_STEP_END;

...
#elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND)
    **result = this->stepWithDwarfFDE(stage2);**
...
#endif
  }

  // update info based on new PC
  if (result == UNW_STEP_SUCCESS) {
    this->setInfoBasedOnIPRegister(true);
    if (_unwindInfoMissing)
      return UNW_STEP_END;
  }

  return result;
}

int stepWithDwarfFDE(bool stage2) {
  return DwarfInstructions<A, R>::stepWithDwarf(
      _addressSpace, (pint_t)this->getReg(UNW_REG_IP),
      (pint_t)_info.unwind_info, _registers, _isSignalFrame, stage2);
}
代码语言:javascript
复制
template <typename A, typename R>
int DwarfInstructions<A, R>::stepWithDwarf(A &addressSpace, pint_t pc,
                                           pint_t fdeStart, R &registers,
                                           bool &isSignalFrame) {
  FDE_Info fdeInfo;
  CIE_Info cieInfo;
  if (CFI_Parser<A>::decodeFDE(addressSpace, fdeStart, &fdeInfo,
                               &cieInfo) == NULL) {
    PrologInfo prolog;
    if (CFI_Parser<A>::parseFDEInstructions(addressSpace, fdeInfo, cieInfo, pc,
                                            R::getArch(), &prolog)) {
      // get pointer to cfa (architecture specific)
      pint_t cfa = getCFA(addressSpace, prolog, registers);

       // restore registers that DWARF says were saved
      R newRegisters = registers;

      // Typically, the CFA is the stack pointer at the call site in
      // the previous frame. However, there are scenarios in which this is not
      // true. For example, if we switched to a new stack. In that case, the
      // value of the previous SP might be indicated by a CFI directive.
      //
      // We set the SP here to the CFA, allowing for it to be overridden
      // by a CFI directive later on.
      newRegisters.setSP(cfa);

      pint_t returnAddress = 0;
      const int lastReg = R::lastDwarfRegNum();
      assert(static_cast<int>(CFI_Parser<A>::kMaxRegisterNumber) >= lastReg &&
             "register range too large");
      assert(lastReg >= (int)cieInfo.returnAddressRegister &&
             "register range does not contain return address register");
      for (int i = 0; i <= lastReg; ++i) {
        if (prolog.savedRegisters[i].location !=
            CFI_Parser<A>::kRegisterUnused) {
          if (registers.validFloatRegister(i))
            newRegisters.setFloatRegister(
                i, getSavedFloatRegister(addressSpace, registers, cfa,
                                         prolog.savedRegisters[i]));
          else if (registers.validVectorRegister(i))
            newRegisters.setVectorRegister(
                i, getSavedVectorRegister(addressSpace, registers, cfa,
                                          prolog.savedRegisters[i]));
          else if (i == (int)cieInfo.returnAddressRegister)
            returnAddress = getSavedRegister(addressSpace, registers, cfa,
                                             prolog.savedRegisters[i]);
          else if (registers.validRegister(i))
            newRegisters.setRegister(
                i, getSavedRegister(addressSpace, registers, cfa,
                                    prolog.savedRegisters[i]));
          else
            return UNW_EBADREG;
        } else if (i == (int)cieInfo.returnAddressRegister) {
            // Leaf function keeps the return address in register and there is no
            // explicit intructions how to restore it.
            returnAddress = registers.getRegister(cieInfo.returnAddressRegister);
        }
      }

      isSignalFrame = cieInfo.isSignalFrame;

			...

      // Return address is address after call site instruction, so setting IP to
      // that does simualates a return.
      newRegisters.setIP(returnAddress);

      // Simulate the step by replacing the register set with the new ones.
      registers = newRegisters;

      return UNW_STEP_SUCCESS;
    }
  }
  return UNW_EBADFRAME;
}

__unw_get_proc_info

将前面step过程时cursor更新的proc_info信息写入到info指针中

代码语言:javascript
复制
/// Get unwind info at cursor position in stack frame.
_LIBUNWIND_HIDDEN int __unw_get_proc_info(unw_cursor_t *cursor,
                                          unw_proc_info_t *info) {
  _LIBUNWIND_TRACE_API("__unw_get_proc_info(cursor=%p, &info=%p)",
                       static_cast<void *>(cursor), static_cast<void *>(info));
  AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
  co->getInfo(info);
  if (info->end_ip == 0)
    return UNW_ENOINFO;
  return UNW_ESUCCESS;
}
_LIBUNWIND_WEAK_ALIAS(__unw_get_proc_info, unw_get_proc_info)
代码语言:javascript
复制
template <typename A, typename R>
void UnwindCursor<A, R>::getInfo(unw_proc_info_t *info) {
  if (_unwindInfoMissing)
    memset(info, 0, sizeof(*info));
  else
    *info = _info;
}

context与cursor的联系

context和cusor两个变量的各种传递关系搞得我比较混乱,加上各种调用也比较复杂,因此我在这里整理一下这两个变量的关系。

context是一个buffer,用于保存上下文的寄存器

cursor是一个buffer,用于对象的实际构造

两个变量主要在_Unwind_RaiseException的过程中起到作用。

  1. 首先在_Unwind_RaiseException的开始读取了context到context的buffer中
  2. __unw_init_local:在每个phase的开始初始化local,即根据context的值。
    1. 将context所保存的寄存器拷贝到cursor中
    2. 内部调用setInfoBasedOnIPRegister
      1. getInfoFromDwarfSection
        1. getInfoFromFdeCie 更新info
  3. 在定位对应栈帧的时候会调用cursor.step()
    1. 内部调用stepWithDwarfFDE
      1. 更新registers。registers = newRegisters;
  4. __unw_get_proc_info: 读取cursor中的proc info,以及sp

register每次都会拷贝context中的值。也就是说第一次更新的第二次不会生效,第二次开始搜索的位置和第一次相同。

unwind_phase2

代码语言:javascript
复制
static _Unwind_Reason_Code
unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) {
  __unw_init_local(cursor, uc);

  // uc is initialized by __unw_getcontext in the parent frame. The first stack
  // frame walked is unwind_phase2.
  unsigned framesWalked = 1;
  // Walk each frame until we reach where search phase said to stop.
  while (true) {

    // Ask libunwind to get next frame (skip over first which is
    // _Unwind_RaiseException).
    int stepResult = __unw_step(cursor);
    if (stepResult == 0) {
      return _URC_END_OF_STACK;
    } else if (stepResult < 0) {
      return _URC_FATAL_PHASE2_ERROR;
    }

    // Get info about this frame.
    unw_word_t sp;
    unw_proc_info_t frameInfo;
    __unw_get_reg(cursor, UNW_REG_SP, &sp);
    if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) {
      return _URC_FATAL_PHASE2_ERROR;
    }

    ++framesWalked;
    // If there is a personality routine, tell it we are unwinding.
    if (frameInfo.handler != 0) {
      _Unwind_Personality_Fn p =
          (_Unwind_Personality_Fn)(uintptr_t)(frameInfo.handler);
      _Unwind_Action action = _UA_CLEANUP_PHASE;
      if (sp == exception_object->private_2) {
        // Tell personality this was the frame it marked in phase 1.
        action = (_Unwind_Action)(_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME);
      }
       _Unwind_Reason_Code personalityResult =
          (*p)(1, action, exception_object->exception_class, exception_object,
               (struct _Unwind_Context *)(cursor));
      switch (personalityResult) {
      case _URC_CONTINUE_UNWIND:
        // Continue unwinding
        if (sp == exception_object->private_2) {
          // Phase 1 said we would stop at this frame, but we did not...
          _LIBUNWIND_ABORT("during phase1 personality function said it would "
                           "stop here, but now in phase2 it did not stop here");
        }
        break;
      case _URC_INSTALL_CONTEXT:
        // Personality routine says to transfer control to landing pad.
        // We may get control back if landing pad calls _Unwind_Resume().
        __unw_phase2_resume(cursor, framesWalked);
        // __unw_phase2_resume() only returns if there was an error.
        return _URC_FATAL_PHASE2_ERROR;
      default:
        // Personality routine returned an unknown result code.
        return _URC_FATAL_PHASE2_ERROR;
      }
    }
  }

  // Clean up phase did not resume at the frame that the search phase
  // said it would...
  return _URC_FATAL_PHASE2_ERROR;
}
  1. 找到next_frame
  2. 获取proc_info
  3. frame的handler不为空的时候
    1. 根据private_2的情况设置不同的action,之后再执行personality操作
    2. 查看结果
      1. _URC_INSTALL_CONTEXT会执行resume,transfer control to landing pad
      2. _URC_CONTINUE_UNWIND在前一步search的时候设置了,表明应该在这个frame中停下,但实际没停,所以会炸
  4. 否则继续查找,如果全部找完了那么就是_URC_FATAL_PHASE2_ERROR

本质上是找到第一个handler不为空的frame

注意两次执行handler时的action,第一次是_UA_SEARCH_PHASE,第二次是cleanup或者cleanup | handle

代码语言:javascript
复制
typedef enum {
  _UA_SEARCH_PHASE = 1,
  _UA_CLEANUP_PHASE = 2,
  _UA_HANDLER_FRAME = 4,
  _UA_FORCE_UNWIND = 8,
  _UA_END_OF_STACK = 16 // gcc extension to C++ ABI
} _Unwind_Action;

phase2_resume

代码语言:javascript
复制
// When CET is enabled, each "call" instruction will push return address to
// CET shadow stack, each "ret" instruction will pop current CET shadow stack
// top and compare it with target address which program will return.
// In exception handing, **some stack frames will be skipped before jumping to
// landing pad and we must adjust CET shadow stack accordingly.**
// _LIBUNWIND_POP_CET_SSP is used to adjust CET shadow stack pointer and we
// directly jump to __libunwind_Registerts_x86/x86_64_jumpto instead of using
// a regular function call to avoid pushing to CET shadow stack again.
#if !defined(_LIBUNWIND_USE_CET)
#define __unw_phase2_resume(cursor, fn) __unw_resume((cursor))
#elif defined(_LIBUNWIND_TARGET_I386)
#define __unw_phase2_resume(cursor, fn)                                        \
  do {                                                                         \
    _LIBUNWIND_POP_CET_SSP((fn));                                              \
    void *cetRegContext = __libunwind_cet_get_registers((cursor));             \
    void *cetJumpAddress = __libunwind_cet_get_jump_target();                  \
    __asm__ volatile("push %%edi\n\t"                                          \
                     "sub $4, %%esp\n\t"                                       \
                     "jmp *%%edx\n\t" :: "D"(cetRegContext),                   \
                     "d"(cetJumpAddress));                                     \
  } while (0)
#elif defined(_LIBUNWIND_TARGET_X86_64)
#define __unw_phase2_resusme(cursor, fn)                                        \
  do {                                                                         \
    _LIBUNWIND_POP_CET_SSP((fn));                                            \
    void *cetRegContext = __libunwind_cet_get_registers((cursor));             \
    void *cetJumpAddress = __libunwind_cet_get_jump_target();                  \
    __asm__ volatile("jmpq *%%rdx\n\t" :: "D"(cetRegContext),                  \
                     "d"(cetJumpAddress));                                     \
  } while (0)
#endif

这里主要是跳转到对应的汇编处理代码。使用CET时会额外插入CET命令,并且调用__unw_resume。

CET:Control-flow Enforcement Technology。控制流保护

Control-flow integrity

some stack frames will be skipped before jumping to landing pad and we must adjust CET shadow stack accordingly.

代码语言:javascript
复制
/// Resume execution at cursor position (aka longjump).
_LIBUNWIND_HIDDEN int __unw_resume(unw_cursor_t *cursor) {
  _LIBUNWIND_TRACE_API("__unw_resume(cursor=%p)", static_cast<void *>(cursor));
#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
  // Inform the ASan runtime that now might be a good time to clean stuff up.
  __asan_handle_no_return();
#endif
  AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
  co->jumpto();
  return UNW_EUNSPEC;
}

template <typename A, typename R> void UnwindCursor<A, R>::jumpto() {
  _registers.jumpto();
}

void jumpto() { __libunwind_Registers_x86_jumpto(this); }

剩下的情况,根据i386和x86_64进行不同的处理

代码语言:javascript
复制
#if defined(_LIBUNWIND_USE_CET)
extern "C" void *__libunwind_cet_get_registers(unw_cursor_t *cursor) {
  AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor;
  return co->get_registers();
}
#endif

#if defined(_LIBUNWIND_TARGET_I386)
class _LIBUNWIND_HIDDEN Registers_x86;
extern "C" void __libunwind_Registers_x86_jumpto(Registers_x86 *);

#if defined(_LIBUNWIND_USE_CET)
extern "C" void *__libunwind_cet_get_jump_target() {
  return reinterpret_cast<void *>(&__libunwind_Registers_x86_jumpto);
}
#endif

最后都会跳转到这个__libunwind_Registers_x86_jumpto,下面的内容是原始的汇编代码以及我加的一些注释。

代码语言:javascript
复制
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_jumpto)
#
# extern "C" void __libunwind_Registers_x86_jumpto(Registers_x86 *);
#
# On entry:
#  +                       +
#  +-----------------------+
#  + thread_state pointer  +
#  +-----------------------+
#  + return address        +
#  +-----------------------+   <-- SP
#  +                       +

  _LIBUNWIND_CET_ENDBR
  movl   4(%esp), %eax # esp的地址+4(即this指针的值)加载到eax
  # set up eax and ret on new stack location
  movl  28(%eax), %edx # edx holds new stack pointer
		  # 28(%eax) 是this的esp,这个sp是在寻找栈帧解析dwarf的时候记录的
  subl  $8,%edx # edx -= 8
  
  --- this.esp  -->  ---
								    	    8
							       --- edx = this.esp - 8
  
  movl  %edx, 28(%eax) # 保存edx到this的eip中
  # 相当于this的esp -= 8
  movl  0(%eax), %ebx # this的eax地址的值加载到ebx
  # 原来的eax保存的是指向unwind_exception的指针,这个是在personality中设置的 
  movl  %ebx, 0(%edx) # ebx的值写到edx所在地址,也就是
  movl  40(%eax), %ebx # eip写到ebx
  movl  %ebx, 4(%edx) # 4(edx) = eip
  
	--- ebx + 8 // this.esp
	this.eax
	--- ebx + 4
	新的 eip
	--- ebx
  新开辟了一块空间,保存了eip和registers
  
  # we now have ret and eax pushed onto where new stack will be
  # restore all registers
  movl   4(%eax), %ebx
  movl   8(%eax), %ecx
  movl  12(%eax), %edx
  movl  16(%eax), %edi
  movl  20(%eax), %esi
  movl  24(%eax), %ebp
  movl  28(%eax), %esp
  # skip ss
  # skip eflags
  pop    %eax  # eax was already pushed on new stack
  pop    %ecx  # pop esp上的值到eax和ecx,ecx保存了eip,即landingpad的值
  jmp    *%ecx
  # skip cs
  # skip ds
  # skip es
  # skip fs
  # skip gs

这里ecx是landingpad的地址,是在phase2的时候personality中设置的对应的值。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024/10/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • _Unwind_RaiseException
  • 出现的数据结构
  • __unw_getcontext
  • unwind_phase1
    • __unw_init_local
      • UnwindCursor
      • UnwindCursor::setInfoBasedOnIPRegister
      • findUnwindSections
    • __unw_step
      • __unw_get_proc_info
      • context与cursor的联系
      • unwind_phase2
      • phase2_resume
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档