kernel/eka/compsupp/symaehabi/unwinder.c
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 0 a41df078684a
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

/* unwinder.c
 *
 * Copyright 2002-2005 ARM Limited. All rights reserved.
 *
 * Your rights to use this code are set out in the accompanying licence
 * text file LICENCE.txt (ARM contract number LEC-ELA-00080 v1.0).
 */

/* Portions copyright Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). */

/*
 * RCS $Revision: 92986 $
 * Checkin $Date: 2005-10-13 15:56:12 +0100 (Thu, 13 Oct 2005) $
 * Revising $Author: achapman $
 */

/* Language-independent unwinder implementation */

/* This source file is compiled automatically by ARM's make system into
 * multiple object files. The source regions constituting object file
 * xxx.o are delimited by ifdef xxx_c / endif directives.
 *
 * The source regions currently marked are:
 * unwinder_c
 * unwind_activity_c
 */

#ifndef __EPOC32__
#include <stddef.h>
#include <stdlib.h>
#else
#include <e32def.h>
#endif
/* Environment: */
#include "unwind_env.h"
/* Language-independent unwinder declarations: */
#include "unwinder.h"

#ifdef __EPOC32__
/* Symbian specific support */
#include "symbian_support.h"
#endif

/* Define UNWIND_ACTIVITY_DIAGNOSTICS for printed information from _Unwind_Activity */
/* Define VRS_DIAGNOSTICS for printed diagnostics about VRS operations */

#if defined(VRS_DIAGNOSTICS) || defined(UNWIND_ACTIVITY_DIAGNOSTICS)
#ifndef __EPOC32__
extern int printf(const char *, ...);
#endif
#endif

#ifdef SUPPORT_NESTED_EXCEPTIONS
extern _Unwind_Control_Block *AllocSavedUCB();
extern void FreeSavedUCB(_Unwind_Control_Block *context);
#endif

#ifdef unwinder_c

/* =========================                      ========================= */
/* ========================= Virtual register set ========================= */
/* =========================                      ========================= */

/* The approach taken by this implementation is to use the real machine
 * registers to hold all but the values of core (integer)
 * registers. Consequently the implementation must use only the core
 * registers except when manipulating the virtual register set. Non-core
 * registers are saved only on first use, so the single implementation can
 * cope with execution on processors which lack certain registers.  The
 * registers as they were at the start of the propagation must be preserved
 * over phase 1 so that the machine state is correct at the start of phase
 * 2. This requires a copy to be taken (which can be stack allocated). During
 * a stack unwind (phase 1 or phase 2), the "current" virtual register set is
 * implemented as core register values held in a data structure, and non-core
 * register values held in the registers themselves. To ensure that all
 * original register values are available at the beginning of phase 2, the
 * core registers are saved in a second structure at the start of phase 1 and
 * the non-core registers are demand-saved into another part of the data
 * structure that holds the current core registers during the phase 1 stack
 * unwind.
 */
/* Extent to which the access routines are implemented:
 * _Unwind_VRS_Get and _Unwind_VRS_Set implement only access to the core registers.
 * _Unwind_VRS_Pop implements only popping of core and vfp registers.
 * There is no support here for the Intel WMMX registers, but space is nevertheless
 * reserved in the virtual register set structure to indicate whether demand-saving
 * of those registers is required (as they are unsupported, it never is). The space
 * costs nothing as it is required for alignment.
 * The level of supported functionality is compliant with the requirements of the
 * Exceptions ABI.
 */

typedef unsigned char bool;
struct core_s  { uint32_t r[16]; };        /* core integer regs */
struct vfp_s   { uint64_t d[32]; };        /* VFP registers saved in FSTMD format */

/* Phase 1 virtual register set includes demand-save areas */
/* The phase 2 virtual register set must be a prefix of the phase 1 set */
typedef struct phase1_virtual_register_set_s {
  /* demand_save flag == 1 means save the registers in the demand-save area */
  bool demand_save_vfp_low;
  bool demand_save_vfp_high;
  bool demand_save_wmmxd;
  bool demand_save_wmmxc;
  struct core_s core;      /* current core registers */
  struct vfp_s  vfp;       /* demand-saved vfp registers */
} phase1_virtual_register_set;

/* Phase 2 virtual register set has no demand-save areas */
/* The phase 2 virtual register set must be a prefix of the phase 1 set */
/* The assembly fragments for _Unwind_RaiseException and _Unwind_Resume create
 * a phase2_virtual_register_set_s by hand so be careful.
 */
typedef struct phase2_virtual_register_set_s {
  /* demand_save flag == 1 means save the registers in the demand-save area */
  /* Always 0 in phase 2 */
  bool demand_save_vfp_low;
  bool demand_save_vfp_high;
  bool demand_save_wmmxd;
  bool demand_save_wmmxc;
  struct core_s core;      /* current core registers */
} phase2_virtual_register_set;

/* -- Helper macros for the embedded assembly */

#if defined(__TARGET_ARCH_5T)  || defined(__TARGET_ARCH_5TXM) || \
    defined(__TARGET_ARCH_5TE) || defined(__TARGET_ARCH_6) || \
    defined(__TARGET_ARCH_6T2) || defined(__TARGET_ARCH_7_A) /* || ... */
  #define ARCH_5T_OR_LATER 1
#else
  #define ARCH_5T_OR_LATER 0
#endif

#if defined(__APCS_INTERWORK) && !ARCH_5T_OR_LATER
  #define OLD_STYLE_INTERWORKING 1
#else
  #define OLD_STYLE_INTERWORKING 0
#endif

#if defined(__TARGET_ARCH_4T) || defined(__TARGET_ARCH_4TXM) || ARCH_5T_OR_LATER
  #define HAVE_BX 1
#else
  #define HAVE_BX 0
#endif

#if defined(__TARGET_ARCH_THUMBNAIL)
  #define THUMBNAIL 1
#else
  #define THUMBNAIL 0
#endif

#if HAVE_BX
  #define RET_LR bx lr
#else
  #define RET_LR mov pc,lr
#endif

/* ----- Routines: ----- */

/* ----- Helper routines, private ----- */

/* R_ARM_PREL31 is a place-relative 31-bit signed relocation.  The
 * routine takes the address of a location that was relocated by
 * R_ARM_PREL31, and returns an absolute address.
 */
static FORCEINLINE uint32_t __ARM_resolve_prel31(void *p)
{
  return (uint32_t)((((*(int32_t *)p) << 1) >> 1) + (int32_t)p);
}

/* ----- Helper routines, private but external ----- */

/* Note '%0' refers to local label '0' */
#if defined(__thumb)
#define MAYBE_SWITCH_TO_ARM_STATE SWITCH_TO_ARM_STATE
#define MAYBE_CODE16 code16
#else
#define MAYBE_SWITCH_TO_ARM_STATE /* nothing */
#define MAYBE_CODE16              /* nothing */
#endif
__asm void __ARM_Unwind_VRS_VFPpreserve_low(void *vfpp)
{
vfp_d0 CN 0;
  /* Preserve the low vfp registers in the passed memory */
#if defined(__thumb)
  macro;
  SWITCH_TO_ARM_STATE;
1
  align 4;
2
  assert (%2 - %1) = 0;
  bx pc;
  nop;
  code32;
  mend;
#endif

  MAYBE_SWITCH_TO_ARM_STATE;
  stc   p11,vfp_d0,[r0],{0x20};  /* 0xec800b20  FSTMIAD r0,{d0-d15} */
  RET_LR;
  MAYBE_CODE16;
}

__asm void __ARM_Unwind_VRS_VFPpreserve_high(void *vfpp)
{
vfp_d16 CN 0;                      /* =16 when used with stcl */
  /* Preserve the high vfp registers in the passed memory */
  MAYBE_SWITCH_TO_ARM_STATE;
  stcl  p11,vfp_d16,[r0],{0x20};  /* 0xecc00b20  FSTMIAD r0,{d16-d31} */
  RET_LR;
  MAYBE_CODE16;
}

__asm void __ARM_Unwind_VRS_VFPrestore_low(void *vfpp)
{
  /* Restore the low vfp registers from the passed memory */
vfp_d0 CN 0;
  MAYBE_SWITCH_TO_ARM_STATE;
  ldc   p11,vfp_d0,[r0],{0x20};  /* 0xec900b20  FLDMIAD r0,{d0-d15} */
  RET_LR;
  MAYBE_CODE16;
}

__asm void __ARM_Unwind_VRS_VFPrestore_high(void *vfpp)
{
  /* Restore the high vfp registers from the passed memory */
vfp_d16 CN 0;                      /* =16 when used with ldcl */
  MAYBE_SWITCH_TO_ARM_STATE;
  ldcl   p11,vfp_d16,[r0],{0x20};  /* 0xecd00b20  FLDMIAD r0,{d16-d31} */
  RET_LR;
  MAYBE_CODE16;
}


__asm NORETURNDECL void __ARM_Unwind_VRS_corerestore(void *corep)
{
  /* We rely here on corep pointing to a location in the stack,
   * as we briefly assign it to sp. This allows us to safely do
   * ldmia's which restore sp (if we use a different base register,
   * the updated sp may be used by the handler of any data abort
   * that occurs during the ldmia, and the stack gets overwritten).
   * By hypothesis this is preserve8 but the load of sp means the
   * assembler can't infer that.
   */
#if THUMBNAIL
  preserve8;
  mov.w   r13, r0;
  ldmia.w r13!,{r0-r12};
  ldr.w   r14, [r13, #4]   /* lr */
  ldr.w   r12, [r13, #4*2] /* pc */
  ldr.w   r13, [r13, #0]   /* sp */
  bx      r12
  
#else
  preserve8;
  MAYBE_SWITCH_TO_ARM_STATE;
#if OLD_STYLE_INTERWORKING
  mov   r13, r0;
  ldmia r13!,{r0-r12};
  ldr   r12,[r13, #4*2]; /* pc */
  ldmia r13,{r13-r14};
  bx    r12;
#else

  #if __ARMCC_VERSION < 300000
  mov   r13, r0;
  ldmia r13,{r0-r15};
  #else
  mov r14, r0;
  ldmia r14!, {r0-r12};
  ldr r13, [r14], #4;
  ldmia r14, {r14,r15};
  #endif

#endif
  MAYBE_CODE16;
#endif
}


/* ----- Development support ----- */

#ifdef VRS_DIAGNOSTICS
static void debug_print_vrs_vfp(uint32_t base, uint64_t *lp)
{
  int c = 0;
  int i;
  for (i = 0; i < 16; i++) {
    printf("D%-2d  0x%16.16llx    ", i + base, *lp);
    lp++;
    if (c++ == 1) {
      c = 0;
      printf("\n");
    }
  }
}


static void debug_print_vrs(_Unwind_Context *context)
{
  phase1_virtual_register_set *vrsp = (phase1_virtual_register_set *)context;
  int i;
  int c;
  printf("------------------------------------------------------------------------\n");
  c = 0;
  for (i = 0; i < 16; i++) {
    printf("r%-2d  0x%8.8x    ", i, vrsp->core.r[i]);
    if (c++ == 3) {
      c = 0;
      printf("\n");
    }
  }

  printf("-----\n");
  if (vrsp->demand_save_vfp_low == 1)
    printf("VFP low registers not saved\n");
  else
    debug_print_vrs_vfp(0, &vrsp->vfp.d[0]);
  printf("-----\n");
  if (vrsp->demand_save_vfp_high == 1)
    printf("VFP high registers not saved\n");
  else
    debug_print_vrs_vfp(16, &vrsp->vfp.d[16]);
  printf("------------------------------------------------------------------------\n");
}
#endif


/* ----- Public routines ----- */

EXPORT_C _Unwind_VRS_Result _Unwind_VRS_Set(_Unwind_Context *context,
                                            _Unwind_VRS_RegClass regclass,
                                            uint32_t regno,
                                            _Unwind_VRS_DataRepresentation representation,
                                            void *valuep)
{
  phase1_virtual_register_set *vrsp = (phase1_virtual_register_set *)context;
  switch (regclass) {
  case _UVRSC_CORE:
    {
      if (representation != _UVRSD_UINT32 || regno > 15)
        return _UVRSR_FAILED;
       vrsp->core.r[regno] = *(uint32_t *)valuep;
       return _UVRSR_OK;
    }
  case _UVRSC_VFP:
  case _UVRSC_WMMXD:
  case _UVRSC_WMMXC:
    return _UVRSR_NOT_IMPLEMENTED;
  default:
    break;
  }
  return _UVRSR_FAILED;
}


EXPORT_C _Unwind_VRS_Result _Unwind_VRS_Get(_Unwind_Context *context,
                                            _Unwind_VRS_RegClass regclass,
                                            uint32_t regno,
                                            _Unwind_VRS_DataRepresentation representation,
                                            void *valuep)
{
  phase1_virtual_register_set *vrsp = (phase1_virtual_register_set *)context;
  switch (regclass) {
  case _UVRSC_CORE:
    {
      if (representation != _UVRSD_UINT32 || regno > 15)
        return _UVRSR_FAILED;
      *(uint32_t *)valuep = vrsp->core.r[regno];
      return _UVRSR_OK;
    }
  case _UVRSC_VFP:
  case _UVRSC_WMMXD:
  case _UVRSC_WMMXC:
    return _UVRSR_NOT_IMPLEMENTED;
  default:
    break;
  }
  return _UVRSR_FAILED;
}


#define R_SP 13

EXPORT_C _Unwind_VRS_Result _Unwind_VRS_Pop(_Unwind_Context *context,
                                            _Unwind_VRS_RegClass regclass,
                                            uint32_t descriminator,
                                            _Unwind_VRS_DataRepresentation representation)
{
  phase1_virtual_register_set *vrsp = (phase1_virtual_register_set *)context;
  switch (regclass) {
  case _UVRSC_CORE:
    {
      /* If SP is included in the mask, the loaded value is used in preference to
       * the writeback value, but only on completion of the loading.
       */
      uint32_t mask, *vsp, *rp, sp_loaded;
      if (representation != _UVRSD_UINT32)
        return _UVRSR_FAILED;
      vsp = (uint32_t *)vrsp->core.r[R_SP];
      rp = (uint32_t *)&vrsp->core;
      mask = descriminator & 0xffff;
      sp_loaded = mask & (1 << R_SP);
      while (mask != 0) {
        if (mask & 1) {
#ifdef VRS_DIAGNOSTICS
          printf("VRS Pop r%d\n", rp - &vrsp->core.r[0]);
#endif
          *rp = *vsp++;
        }
        rp++;
        mask >>= 1;
      }
      if (!sp_loaded)
        vrsp->core.r[R_SP] = (uint32_t)vsp;
      return _UVRSR_OK;
    }
  case _UVRSC_VFP:
    {
      uint32_t start = descriminator >> 16;
      uint32_t count = descriminator & 0xffff;
      bool some_low = start < 16;
      bool some_high = start + count > 16;
      if ((representation != _UVRSD_VFPX && representation != _UVRSD_DOUBLE) ||
          (representation == _UVRSD_VFPX && some_high) ||
          (representation == _UVRSD_DOUBLE && start + count > 32))
        return _UVRSR_FAILED;
      if (some_low && vrsp->demand_save_vfp_low == 1) { /* Demand-save over phase 1 */
        vrsp->demand_save_vfp_low = 0;
        __ARM_Unwind_VRS_VFPpreserve_low(&vrsp->vfp.d[0]);
      }
      if (some_high && vrsp->demand_save_vfp_high == 1) { /* Demand-save over phase 1 */
        vrsp->demand_save_vfp_high = 0;
        __ARM_Unwind_VRS_VFPpreserve_high(&vrsp->vfp.d[16]);
      }
      /* Now recover from the stack into the real machine registers.
       * Note for _UVRSD_VFPX we assume FSTMX standard format 1.
       * Do this by saving the current VFP registers to a memory area,
       * moving the in-memory values into that area, and
       * restoring from the whole area.
       * Must be careful as the 64-bit values saved by FSTMX might be
       * only 32-bit aligned.
       */
      {
        struct unaligned_vfp_reg_s { uint32_t w1; uint32_t w2; };
        struct unaligned_vfp_reg_s *vsp;
        struct vfp_s temp_vfp;
        if (some_low)
          __ARM_Unwind_VRS_VFPpreserve_low(&temp_vfp.d[0]);
        if (some_high)
          __ARM_Unwind_VRS_VFPpreserve_high(&temp_vfp.d[16]);
        vsp = (struct unaligned_vfp_reg_s *)vrsp->core.r[R_SP];
        while (count--) {
          struct unaligned_vfp_reg_s *v =
            (struct unaligned_vfp_reg_s *)&temp_vfp.d[start++];
          *v = *vsp++;
#ifdef VRS_DIAGNOSTICS
          printf("VRS Pop D%d = 0x%llx\n", start - 1, temp_vfp.d[start - 1]);
#endif
        }
        vrsp->core.r[R_SP] = (uint32_t)((uint32_t *)vsp +
                                        (representation == _UVRSD_VFPX ?
                                         1 : /* +1 to skip the format word */
                                         0));
        if (some_low)
          __ARM_Unwind_VRS_VFPrestore_low(&temp_vfp.d[0]);
        if (some_high)
          __ARM_Unwind_VRS_VFPrestore_high(&temp_vfp.d[16]);
      }
      return _UVRSR_OK;
    }
  case _UVRSC_WMMXD:
  case _UVRSC_WMMXC:
    return _UVRSR_NOT_IMPLEMENTED;
  default:
    break;
  }
  return _UVRSR_FAILED;
}



/* =========================              ========================= */
/* ========================= The unwinder ========================= */
/* =========================              ========================= */


/* This implementation uses the UCB unwinder_cache as follows:
 * reserved1 is documented in the EABI as requiring initialisation to 0.
 *  It is used to manage nested simultaneous propagation. If the value is 0,
 *  the UCB is participating in no propagations. If the value is 1, the UCB
 *  is participating in one propagation. Otherwise the value is a pointer to
 *  a structure holding saved UCB state from the next propagation out.
 *  The structure used is simply a mallocated UCB.
 * reserved2 is used to preserve the call-site address over calls to a
 *  personality routine and cleanup.
 * reserved3 is used to cache the PR address.
 * reserved4 is used by the Symbian implementation to cache the ROM exeception 
 *  search table
 * reserved5 is used by the symbian implementation to cache the 
 *  TExceptionDescriptor for the executable of the 'current' frame
 */

#define NESTED_CONTEXT      unwinder_cache.reserved1
#define SAVED_CALLSITE_ADDR unwinder_cache.reserved2
#define PR_ADDR             unwinder_cache.reserved3

/* Index table entry: */

#ifndef __EPOC32__  // Symbian OS defines this in symbian_support.h
typedef struct __EIT_entry {
  uint32_t fnoffset; /* Place-relative */
  uint32_t content;
} __EIT_entry;
#endif

/* Private defines etc: */

static const uint32_t EXIDX_CANTUNWIND = 1;
static const uint32_t uint32_highbit = 0x80000000;

/* ARM C++ personality routines: */

typedef _Unwind_Reason_Code (*personality_routine)(_Unwind_State,
                                                   _Unwind_Control_Block *,
                                                   _Unwind_Context *);

WEAKDECL _Unwind_Reason_Code __aeabi_unwind_cpp_pr0(_Unwind_State state, _Unwind_Control_Block *,
                                                    _Unwind_Context *context);
IMPORT_C WEAKDECL _Unwind_Reason_Code __aeabi_unwind_cpp_pr1(_Unwind_State state, _Unwind_Control_Block *,
                                                             _Unwind_Context *context);
IMPORT_C WEAKDECL _Unwind_Reason_Code __aeabi_unwind_cpp_pr2(_Unwind_State state, _Unwind_Control_Block *,
                                                             _Unwind_Context *context);


/* Various image symbols: */

struct ExceptionTableInfo {
  uint32_t EIT_base;
  uint32_t EIT_limit;
};

#ifndef __EPOC32__
/* We define __ARM_ETInfo to allow access to some linker-generated
   names that are not legal C identifiers. __ARM_ETInfo is extern only
   because of scope limitations of the embedded assembler */
extern const struct ExceptionTableInfo __ARM_ETInfo;
#define EIT_base \
    ((const __EIT_entry *)(__ARM_ETInfo.EIT_base + (const char *)&__ARM_ETInfo))
#define EIT_limit \
    ((const __EIT_entry *)(__ARM_ETInfo.EIT_limit + (const char *)&__ARM_ETInfo))

#endif


/* ----- Index table processing ----- */

/* find_and_expand_eit_entry is a support function used in both phases to set
 * ucb.pr_cache and internal cache.
 * Call with a pointer to the ucb and the return address to look up.
 *
 * The table is contained in the half-open interval
 * [EIT_base, EIT_limit) and is an ordered array of __EIT_entrys.
 * Perform a binary search via C library routine bsearch.
 * The table contains only function start addresses (encoded as offsets), so
 * we need to special-case the end table entry in the comparison function,
 * which we do by assuming the function it describes extends to end of memory.
 * This causes us problems indirectly in that we would like to fault as
 * many attempts as possible to look up an invalid return address. There are
 * several ways an invalid return address can be obtained from a broken
 * program, such as someone corrupting the stack or broken unwind instructions
 * recovered the wrong value. It is plausible that many bad return addresses
 * will be either small integers or will point into the heap or stack, hence
 * it's desirable to get the length of that final function roughly right.
 * Here we make no attempt to do it. Code exclusively for use in toolchains
 * which define a suitable limit symbol could make use of that symbol.
 * Alternatively (QoI) a smart linker could augment the index table with a
 * dummy EXIDX_CANTUNWIND entry pointing just past the last real function.
 */

#ifndef __EPOC32__
static int EIT_comparator(const void *ck, const void *ce)
{
  uint32_t return_address = *(const uint32_t *)ck;
  const __EIT_entry *eitp = (const __EIT_entry *)ce;
  const __EIT_entry *next_eitp = eitp + 1;
  uint32_t next_fn;
  if (next_eitp != EIT_limit)
    next_fn = __ARM_resolve_prel31((void *)&next_eitp->fnoffset);
  else
    next_fn = 0xffffffffU;
  if (return_address < __ARM_resolve_prel31((void *)&eitp->fnoffset)) return -1;
  if (return_address >= next_fn) return 1;
  return 0;
}
#endif


static _Unwind_Reason_Code find_and_expand_eit_entry_V2(_Unwind_Control_Block *ucbp,
                                                     uint32_t return_address)
{
  /* Search the index table for an entry containing the specified return
   * address. Subtract the 2 from the return address, as the index table
   * contains function start addresses (a trailing noreturn BL would
   * appear to return to the first address of the next function (perhaps
   * +1 if Thumb); a leading BL would appear to return to function start
   * + instruction size (perhaps +1 if Thumb)).
   */

#ifndef __EPOC32__
  const __EIT_entry *base = EIT_base;
  size_t nelems = EIT_limit - EIT_base;
  __EIT_entry *eitp;

  return_address -= 2;

  eitp = (__EIT_entry *) bsearch(&return_address, base, nelems,
                                 sizeof(__EIT_entry), EIT_comparator);
#else
  const __EIT_entry *base = EIT_base(ucbp);
  size_t nelems = EIT_limit(ucbp) - base;
  __EIT_entry *eitp;

  return_address -= 2;

  // This must succeed on SymbianOS or else an error will have occured already.
  eitp = SearchEITV2(return_address, base, nelems);
#endif

  if (eitp == NULL) {
    /* The return address we have was not found in the EIT.
     * This breaks the scan and we have to indicate failure.
     */
    ucbp->PR_ADDR = NULL;
    DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_UNWINDER_LOOKUPFAILED);
    return _URC_FAILURE;
  }

  /* Cache the function offset */

  ucbp->pr_cache.fnstart = __ARM_resolve_prel31((void *)&eitp->fnoffset);

  /* Can this frame be unwound at all? */

  if (eitp->content == EXIDX_CANTUNWIND) {
    ucbp->PR_ADDR = NULL;
    DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_NOUNWIND);
    return _URC_FAILURE;
  }

  /* Obtain the address of the "real" __EHT_Header word */

  if (eitp->content & uint32_highbit) {
    /* It is immediate data */
    ucbp->pr_cache.ehtp = (_Unwind_EHT_Header *)&eitp->content;
    ucbp->pr_cache.additional = 1;
  } else {
    /* The content field is a 31-bit place-relative offset to an _Unwind_EHT_Entry structure */
    ucbp->pr_cache.ehtp = (_Unwind_EHT_Header *)__ARM_resolve_prel31((void *)&eitp->content);
    ucbp->pr_cache.additional = 0;
  }

  /* Discover the personality routine address */

  if (*(uint32_t *)(ucbp->pr_cache.ehtp) & uint32_highbit) {
    /* It is immediate data - compute matching pr */
    uint32_t idx = ((*(uint32_t *)(ucbp->pr_cache.ehtp)) >> 24) & 0xf;
    if (idx == 0) ucbp->PR_ADDR = (uint32_t)&__aeabi_unwind_cpp_pr0;
    else if (idx == 1) ucbp->PR_ADDR = (uint32_t)&__aeabi_unwind_cpp_pr1;
    else if (idx == 2) ucbp->PR_ADDR = (uint32_t)&__aeabi_unwind_cpp_pr2;
    else { /* Failed */
      ucbp->PR_ADDR = NULL;
      DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_TABLECORRUPT);
      return _URC_FAILURE;
    }
  } else {
    /* It's a place-relative offset to pr */
    ucbp->PR_ADDR = __ARM_resolve_prel31((void *)(ucbp->pr_cache.ehtp));
  }
  return _URC_OK;
}

static _Unwind_Reason_Code find_and_expand_eit_entry_V1(_Unwind_Control_Block *ucbp,
                                                     uint32_t return_address)
{
  /* Search the index table for an entry containing the specified return
   * address. The EIT contains function offsets relative to the base of the
   * execute region so adjust the return address accordingly.
   */

#ifndef __EPOC32__
  uint32_t return_address_offset = ADDR_TO_ER_RO_OFFSET(return_address, ucbp);
  const __EIT_entry *base = EIT_base;
  size_t nelems = EIT_limit - EIT_base;

   const __EIT_entry *eitp =
     (const __EIT_entry *) bsearch(&return_address_offset, base, nelems, 
                                   sizeof(__EIT_entry), EIT_comparator);
  if (eitp == NULL) {
    /* The return address we have was not found in the EIT.
     * This breaks the scan and we have to indicate failure.
     */
    ucbp->PR_ADDR = NULL;
    DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_UNWINDER_LOOKUPFAILED);
    return _URC_FAILURE;
  }
#else
  /* Shouldn't we subtract 2 from here just like in the V2 lookup? 
   */
  uint32_t return_address_offset = ADDR_TO_ER_RO_OFFSET(return_address, ucbp);
  const __EIT_entry *base = EIT_base(ucbp);
  size_t nelems = EIT_limit(ucbp) - base;

  // This must succeed or else an error will have occured already.
  const __EIT_entry *eitp = SearchEITV1(return_address_offset, base, nelems);

#endif


  /* Cache the function offset */

  ucbp->pr_cache.fnstart = ER_RO_OFFSET_TO_ADDR(eitp->fnoffset, ucbp);

  /* Can this frame be unwound at all? */

  if (eitp->content == EXIDX_CANTUNWIND) {
    ucbp->PR_ADDR = NULL;
    DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_NOUNWIND);
    return _URC_FAILURE;
  }

  /* Obtain the address of the "real" __EHT_Header word */
  if (eitp->content & uint32_highbit) {
    /* It is immediate data */
    ucbp->pr_cache.ehtp = (_Unwind_EHT_Header *)&eitp->content;
    ucbp->pr_cache.additional = 1;
  } else {
    /* The content field is a segment relative offset to an _Unwind_EHT_Entry structure */
    ucbp->pr_cache.ehtp = (_Unwind_EHT_Header *)ER_RO_OFFSET_TO_ADDR(eitp->content, ucbp);
    ucbp->pr_cache.additional = 0;
  }

  /* Discover the personality routine address */

  if (*(uint32_t *)(ucbp->pr_cache.ehtp) & uint32_highbit) {
    /* It is immediate data - compute matching pr */
    uint32_t idx = ((*(uint32_t *)(ucbp->pr_cache.ehtp)) >> 24) & 0xf;

    if (idx == 0) ucbp->PR_ADDR = (uint32_t)&__aeabi_unwind_cpp_pr0;
    else if (idx == 1) ucbp->PR_ADDR = (uint32_t)&__aeabi_unwind_cpp_pr1;
    else if (idx == 2) ucbp->PR_ADDR = (uint32_t)&__aeabi_unwind_cpp_pr2;
    else { /* Failed */
      ucbp->PR_ADDR = NULL;
      DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_TABLECORRUPT);
      return _URC_FAILURE;
    }
  } else {
    /* Execute region offset to PR */
    ucbp->PR_ADDR = ER_RO_OFFSET_TO_ADDR(*(uint32_t *)(ucbp->pr_cache.ehtp), ucbp);

  }
  return _URC_OK;
}

static _Unwind_Reason_Code find_and_expand_eit_entry(_Unwind_Control_Block *ucbp,
                                                     uint32_t return_address)
{
  ValidateExceptionDescriptor(return_address, ucbp);
  if (EHABI_V2(ucbp))
    return find_and_expand_eit_entry_V2(ucbp, return_address);
  else
    return find_and_expand_eit_entry_V1(ucbp, return_address);
}


/* ----- Unwinding: ----- */

/* Fwd decl */
static NORETURNDECL void unwind_next_frame(_Unwind_Control_Block *ucbp, phase2_virtual_register_set *vrsp);

/* Helper fn: If the demand_save flag in a phase1_virtual_register_set was
 * zeroed, the registers were demand-saved. This function restores from
 * the save area.
*/
static FORCEINLINE void restore_non_core_regs(phase1_virtual_register_set *vrsp)
{
  if (vrsp->demand_save_vfp_low == 0)
    __ARM_Unwind_VRS_VFPrestore_low(&vrsp->vfp.d[0]);
  if (vrsp->demand_save_vfp_high == 0)
    __ARM_Unwind_VRS_VFPrestore_high(&vrsp->vfp.d[16]);
}

/* _Unwind_RaiseException is the external entry point to begin unwinding */
__asm _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Control_Block *ucbp)
{
  extern __ARM_Unwind_RaiseException;

#if THUMBNAIL

  /* Create a phase2_virtual_register_set on the stack */
  /* Save the core registers, carefully writing the original sp value */
  /* Note we account for the pc but do not actually write it's value here */
  str.w    r14,[sp, #-8]!;
  add.w    r14, r13, #8;
  str.w    r14,[sp, #-4]!  /* pushed 3 words => 3 words */
  stmfd.w  sp!,{r0-r12};   /* pushed 13 words => 16 words */
  /* Write zeroes for the demand_save bytes so no saving occurs in phase 2 */
  mov.w    r1,#0;
  str.w    r1,[sp,#-4]!;   /* pushed 1 word => 17 words */
  mov.w    r1,sp;
  sub.w    sp,sp,#4;       /* preserve 8 byte alignment => 18 words */

  /* Now pass to C (with r0 still valid) to do the real work.
   * r0 = ucbp, r1 = phase2_virtual_register_set.
   * If we get control back, pop the stack and return preserving r0.
   */

  /* on arch 5T and later the linker will fix 'bl' => 'blx' as
     needed */
  bl.w     __ARM_Unwind_RaiseException;
  ldr.w    r14,[sp,#16*4];
  add.w    sp,sp,#18*4;
  bx lr;

#else

  MAYBE_SWITCH_TO_ARM_STATE;

  /* Create a phase2_virtual_register_set on the stack */
  /* Save the core registers, carefully writing the original sp value */
  #if __ARMCC_VERSION < 300000
  stmfd sp!,{r13-r15};  /* pushed 3 words => 3 words */
  #else
  stmdb r13, {r14,r15};
  str r13, [r13,#-3*4];
  sub r13, r13, #3*4;
  #endif
  stmfd sp!,{r0-r12};   /* pushed 13 words => 16 words */
  /* Write zeroes for the demand_save bytes so no saving occurs in phase 2 */
  mov r1,#0;
  str r1,[sp,#-4]!;     /* pushed 1 word => 17 words */
  mov r1,sp;
  sub sp,sp,#4;         /* preserve 8 byte alignment => 18 words */

  /* Now pass to C (with r0 still valid) to do the real work.
   * r0 = ucbp, r1 = phase2_virtual_register_set.
   * If we get control back, pop the stack and return preserving r0.
   */

#if OLD_STYLE_INTERWORKING
  ldr r2,Unwind_RaiseException_Offset;
  add r2,r2,pc;
  mov lr,pc;
Offset_Base
  bx    r2;
#else
  /* on arch 5T and later the linker will fix 'bl' => 'blx' as
     needed */
  bl  __ARM_Unwind_RaiseException;
#endif
  ldr r14,[sp,#16*4];
  add sp,sp,#18*4;
  RET_LR;
#if OLD_STYLE_INTERWORKING
Unwind_RaiseException_Offset dcd __ARM_Unwind_RaiseException - Offset_Base;
#endif
  MAYBE_CODE16;

#endif

#ifndef __EPOC32__
  /* Alternate symbol names for difficult symbols.
   * It is possible no functions included in the image require
   * a handler table. Therefore make only a weak reference to
   * the handler table base symbol, which may be absent.
   */
  align 4
  extern |.ARM.exidx$$Base|;
  extern |.ARM.exidx$$Limit|;
  extern |.ARM.extab$$Base| WEAKASMDECL;
  export __ARM_ETInfo;
  /* these are offsets for /ropi */
__ARM_ETInfo /* layout must match struct ExceptionTableInfo */
eit_base   dcd |.ARM.exidx$$Base|  - __ARM_ETInfo; /* index table base */
eit_limit  dcd |.ARM.exidx$$Limit| - __ARM_ETInfo; /* index table limit */
#endif
}


/* __ARM_Unwind_RaiseException performs phase 1 unwinding */

_Unwind_Reason_Code __ARM_Unwind_RaiseException(_Unwind_Control_Block *ucbp,
                                                phase2_virtual_register_set *entry_VRSp)
{
  phase1_virtual_register_set phase1_VRS;

  /* Is this a nested simultaneous propagation?
   * (see comments with _Unwind_Complete)
   */
  if (ucbp->NESTED_CONTEXT == 0) {
    /* No - this is only propagation */
    ucbp->NESTED_CONTEXT = 1;
  } else {
#ifdef SUPPORT_NESTED_EXCEPTIONS
    /* Yes - cache the state elsewhere and restore it when the propagation ends */
    /* This representation wastes space and uses malloc; do better?
     * On the other hand will it ever be used in practice?
     */
    _Unwind_Control_Block *saved_ucbp = AllocSavedUCB();
    if (ucbp == NULL) {
      DEBUGGER_BOTTLENECK(ucbp, _UASUBSYS_UNWINDER, _UAACT_ENDING, _UAARG_ENDING_UNWINDER_BUFFERFAILED);
      return _URC_FAILURE;
    }
    saved_ucbp->unwinder_cache = ucbp->unwinder_cache;
    saved_ucbp->barrier_cache = ucbp->barrier_cache;
    saved_ucbp->cleanup_cache = ucbp->cleanup_cache;
    ucbp->NESTED_CONTEXT = (uint32_t)saved_ucbp;
#else
    abort();
#endif
  }

  /* entry_VRSp contains the core registers as they were when
   * _Unwind_RaiseException was called.  Copy the call-site address to r15
   * then copy all the registers to phase1_VRS for the phase 1 stack scan.
   */

  entry_VRSp->core.r[15] = entry_VRSp->core.r[14];
  phase1_VRS.core = entry_VRSp->core;

  /* For phase 1 only ensure non-core registers are saved before use.
   * If WMMX registers are supported, initialise their flags here and
   * take appropriate action elsewhere.
   */

  phase1_VRS.demand_save_vfp_low = 1;
  phase1_VRS.demand_save_vfp_high = 1;
#ifdef __EPOC32__
  /* Set up Symbian specific caches in the _Unwind_Control_Block's 
     unwinder_cache. 
  */
  InitialiseSymbianSpecificUnwinderCache(phase1_VRS.core.r[15], ucbp);
#endif


  /* Now perform a virtual unwind until a propagation barrier is met, or
   * until something goes wrong.  If something does go wrong, we ought (I
   * suppose) to restore registers we may have destroyed.
   */

  while (1) {

    _Unwind_Reason_Code pr_result;

    /* Search the index table for the required entry.  Cache the index table
     * pointer, and obtain and cache the addresses of the "real" __EHT_Header
     * word and the personality routine.
     */

    if (find_and_expand_eit_entry(ucbp, phase1_VRS.core.r[15]) != _URC_OK) {
      restore_non_core_regs(&phase1_VRS);
      /* Debugger bottleneck fn called during lookup */
      return _URC_FAILURE;
    }

    /* Call the pr to decide what to do */

    pr_result = ((personality_routine)ucbp->PR_ADDR)(_US_VIRTUAL_UNWIND_FRAME,
                                                     ucbp,
                                                     (_Unwind_Context *)&phase1_VRS);

    if (pr_result == _URC_HANDLER_FOUND) break;
    if (pr_result == _URC_CONTINUE_UNWIND) continue;

    /* If we get here some sort of failure has occurred in the
     * pr and probably the pr returned _URC_FAILURE
     */
    restore_non_core_regs(&phase1_VRS);
    return _URC_FAILURE;
  }

  /* Propagation barrier located... restore entry register state of non-core regs */

  restore_non_core_regs(&phase1_VRS);

  /* Initiate real unwinding */
  unwind_next_frame(ucbp, entry_VRSp);
  /* Unreached, but keep compiler quiet: */
  return _URC_FAILURE;
}


/* unwind_next_frame performs phase 2 unwinding */

static NORETURNDECL void unwind_next_frame(_Unwind_Control_Block *ucbp, phase2_virtual_register_set *vrsp)
{
  while (1) {

    _Unwind_Reason_Code pr_result;

    /* Search the index table for the required entry.  Cache the index table
     * pointer, and obtain and cache the addresses of the "real" __EHT_Header
     * word and the personality routine.
     */

    if (find_and_expand_eit_entry(ucbp, vrsp->core.r[15]) != _URC_OK)
      abort();

    /* Save the call-site address and call the pr to do whatever it
     * wants to do on this new frame.
     */

    ucbp->SAVED_CALLSITE_ADDR = vrsp->core.r[15];
    pr_result = ((personality_routine)ucbp->PR_ADDR)(_US_UNWIND_FRAME_STARTING, ucbp,
                                                     (_Unwind_Context *)vrsp);

    if (pr_result == _URC_INSTALL_CONTEXT) {
      /* Upload the registers */
      __ARM_Unwind_VRS_corerestore(&vrsp->core);
    } else if (pr_result == _URC_CONTINUE_UNWIND)
      continue;
    else
      abort();
  }
}


/* _Unwind_Resume is the external entry point called after a cleanup
 * to resume unwinding. It tail-calls a helper function,
 * __ARM_Unwind_Resume, which never returns.
 */
__asm NORETURNDECL void _Unwind_Resume(_Unwind_Control_Block *ucbp)
{
  extern __ARM_Unwind_Resume;

#if THUMBNAIL

  /* Create a phase2_virtual_register_set on the stack */
  /* Save the core registers, carefully writing the original sp value */
  /* Note we account for the pc but do not actually write it's value here */
  str.w    r14,[sp, #-8]!;
  add.w    r14, r13, #8;
  str.w    r14,[sp, #-4]!    /* pushed 3 words => 3 words */
  stmfd.w  sp!,{r0-r12};     /* pushed 13 words => 16 words */
  /* Write zeroes for the demand_save bytes so no saving occurs in phase 2 */
  mov.w    r1,#0;
  str.w    r1,[sp,#-4]!;     /* pushed 1 word => 17 words */
  mov.w    r1,sp;
  sub.w    sp,sp,#4;         /* preserve 8 byte alignment => 18 words */

  /* Now pass to C (with r0 still valid) to do the real work.
   * r0 = ucbp, r1 = phase2_virtual_register_set.
   * This call never returns.
   */

  mov      pc,r2

#else

  MAYBE_SWITCH_TO_ARM_STATE;

  /* Create a phase2_virtual_register_set on the stack */
  /* Save the core registers, carefully writing the original sp value */

  #if __ARMCC_VERSION < 300000
  stmfd sp!,{r13-r15};  /* pushed 3 words => 3 words */
  #else
  stmdb r13, {r14,r15};
  str r13, [r13,#-3*4];
  sub r13, r13, #3*4;
  #endif

  stmfd sp!,{r0-r12};   /* pushed 13 words => 16 words */
  /* Write zeroes for the demand_save bytes so no saving occurs in phase 2 */
  mov r1,#0;
  str r1,[sp,#-4]!;     /* pushed 1 word => 17 words */
  mov r1,sp;
  sub sp,sp,#4;         /* preserve 8 byte alignment => 18 words */

  /* Now pass to C (with r0 still valid) to do the real work.
   * r0 = ucbp, r1 = phase2_virtual_register_set.
   * This call never returns.
   */

#ifdef __APCS_INTERWORK
  ldr r2,Unwind_Resume_Offset;
  add r2,r2,pc;
  bx    r2;
Unwind_Resume_Offset dcd __ARM_Unwind_Resume - .;
#else
  b __ARM_Unwind_Resume;
#endif
  MAYBE_CODE16;

#endif
}


/* Helper function for _Unwind_Resume */

NORETURNDECL void __ARM_Unwind_Resume(_Unwind_Control_Block *ucbp,
                                  phase2_virtual_register_set *entry_VRSp)
{
  _Unwind_Reason_Code pr_result;

  /* Recover saved state */

  entry_VRSp->core.r[15] = ucbp->SAVED_CALLSITE_ADDR;

  /* Call the cached PR and dispatch */

  pr_result = ((personality_routine)ucbp->PR_ADDR)(_US_UNWIND_FRAME_RESUME, ucbp,
                                                   (_Unwind_Context *)entry_VRSp);

  if (pr_result == _URC_INSTALL_CONTEXT) {
   /* Upload the registers */
    __ARM_Unwind_VRS_corerestore(&entry_VRSp->core);
  } else if (pr_result == _URC_CONTINUE_UNWIND)
    unwind_next_frame(ucbp, entry_VRSp);
  else
    abort();
}


/* _Unwind_Complete is called at the end of a propagation.
 * If we support multiple simultaneous propagations, restore the cached state
 * of the previous propagation here.
 */

void _Unwind_Complete(_Unwind_Control_Block *ucbp)
{
  _Unwind_Control_Block *context = (_Unwind_Control_Block *)ucbp->NESTED_CONTEXT;
  if ((uint32_t)context == 0) abort();  /* should be impossible */
  if ((uint32_t)context == 1) {
    /* This was the only ongoing propagation of this object */
    ucbp->NESTED_CONTEXT--;
    return;
  }
#ifdef SUPPORT_NESTED_EXCEPTIONS
  /* Otherwise we copy the state back from the cache structure pointed to
   * by ucbp->NESTED_CONTEXT.
   */
  /* This first one updates ucbp->NESTED_CONTEXT */
  ucbp->unwinder_cache = context->unwinder_cache;
  ucbp->barrier_cache = context->barrier_cache;
  ucbp->cleanup_cache = context->cleanup_cache;
  FreeSavedUCB(context);
#else
  abort();
#endif
}

/* _Unwind_DeleteException can be used to invoke the exception_cleanup
 * function after catching a foreign exception.
 */

void _Unwind_DeleteException(_Unwind_Control_Block *ucbp)
{
  if (ucbp->exception_cleanup != NULL)
    (ucbp->exception_cleanup)(_URC_FOREIGN_EXCEPTION_CAUGHT, ucbp);
}

#endif /* unwinder_c */
#ifdef unwind_activity_c

/* Runtime debug "bottleneck function": */
/* (not in the current Exceptions EABI document) */

void _Unwind_Activity(_Unwind_Control_Block *ucbp, uint32_t reason, uint32_t arg)
{
#ifdef UNWIND_ACTIVITY_DIAGNOSTICS
  uint32_t who = reason >> 24;
  uint32_t activity = reason & 0xffffff;
  printf("_Unwind_Activity: UCB=0x%8.8x Reason=(", (uint32_t)ucbp);
  switch (who) {
  case _UASUBSYS_UNWINDER:
    printf("unw,");
    if (activity >= 0x80)
      printf("%x) Arg=0x%8.8x\n", activity, arg);
    break;
  case _UASUBSYS_CPP:
    printf("C++,");
    if (activity >= 0x80) {
      if (activity == _UAACT_CPP_TYPEINFO)
        printf("typeinfo) Typeinfo=0x%8.8x\n", arg);
      else
        printf("%x) Arg=0x%8.8x\n", activity, arg);
    }
    break;
  default:
    printf("???,");
    if (activity >= 0x80)
      printf("%x) Arg=0x%8.8x\n", activity, arg);
    break;
  }
  if (activity < 0x80) {
    switch (activity) {
    case _UAACT_STARTING:
      printf("starting) Typeinfo=0x%8.8x\n", arg);
      break;
    case _UAACT_ENDING:
      printf("ending) Cause=%d\n", arg);
      break;
    case _UAACT_BARRIERFOUND:
      printf("barrierfound) Pad=0x%8.8x\n", arg);
      break;
    case _UAACT_PADENTRY:
      printf("padentry) Pad=0x%8.8x\n", arg);
      break;
    default:
      printf("%x) Arg=0x%8.8x\n", activity, arg);
      break;
    }
  }
#endif
}

#endif /* unwind_activity_c */