1  /* DWARF2 EH unwinding support for IA64 VMS.
       2     Copyright (C) 2005-2023 Free Software Foundation, Inc.
       3  
       4     This file is part of GCC.
       5  
       6     GCC is free software; you can redistribute it and/or modify it
       7     under the terms of the GNU General Public License as published
       8     by the Free Software Foundation; either version 3, or (at your
       9     option) any later version.
      10  
      11     GCC is distributed in the hope that it will be useful, but WITHOUT
      12     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
      13     or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
      14     License for more details.
      15  
      16     Under Section 7 of GPL version 3, you are granted additional
      17     permissions described in the GCC Runtime Library Exception, version
      18     3.1, as published by the Free Software Foundation.
      19  
      20     You should have received a copy of the GNU General Public License and
      21     a copy of the GCC Runtime Library Exception along with this program;
      22     see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
      23     <http://www.gnu.org/licenses/>.  */
      24  
      25  #define __NEW_STARLET
      26  #include <libicb.h>
      27  #include <chfdef.h>
      28  #include <lib_c/chfctxdef.h>
      29  #include <lib_c/intstkdef.h>
      30  
      31  #include <stdio.h>
      32  #include <string.h>
      33  
      34  #define UNW_IVMS_MODE(HEADER) (((HEADER) >> 44) & 0x3L)
      35  #define MD_UNW_COMPATIBLE_PERSONALITY_P(HEADER) (!UNW_IVMS_MODE (HEADER))
      36  
      37  #define DYN$C_SSENTRY 66
      38  /* ??? would rather get the proper header file.  */
      39  
      40  #define MD_FALLBACK_FRAME_STATE_FOR ia64_vms_fallback_frame_state
      41  
      42  extern INVO_CONTEXT_BLK * LIB$I64_CREATE_INVO_CONTEXT (void);
      43  
      44  extern int LIB$I64_IS_EXC_DISPATCH_FRAME (void *);
      45  extern int LIB$I64_IS_AST_DISPATCH_FRAME (void *);
      46  
      47  extern int LIB$I64_INIT_INVO_CONTEXT (INVO_CONTEXT_BLK *, int, int);
      48  extern int LIB$I64_GET_CURR_INVO_CONTEXT (INVO_CONTEXT_BLK *);
      49  extern int LIB$I64_GET_PREV_INVO_CONTEXT (INVO_CONTEXT_BLK *);
      50  
      51  typedef unsigned int uint;
      52  typedef unsigned __int64 uw_reg;
      53  typedef uw_reg * uw_loc;
      54  
      55  typedef char fp_reg[16];
      56  
      57  #define DENOTES_VMS_DISPATCHER_FRAME(icb) \
      58  (LIB$I64_IS_EXC_DISPATCH_FRAME (&(icb)->libicb$ih_pc))
      59  
      60  #define DENOTES_BOTTOM_OF_STACK(icb) ((icb)->libicb$v_bottom_of_stack)
      61  
      62  #define FAIL_IF(COND) \
      63     do { if (COND) { context->rp = 0; return _URC_END_OF_STACK; } } while (0)
      64  /* Clearing context->rp is required to prevent the ia64 gcc unwinder from
      65     attempting to keep on walking the call chain.  */
      66  
      67  static int
      68  ia64_vms_fallback_frame_state (struct _Unwind_Context *context,
      69  			       _Unwind_FrameState *fs)
      70  {
      71    int i, status;
      72  
      73    INVO_CONTEXT_BLK local_icb;
      74    INVO_CONTEXT_BLK *icb = &local_icb;
      75      
      76    CHFCTX * chfctx;
      77    CHF$MECH_ARRAY * chfmech;
      78    CHF64$SIGNAL_ARRAY *chfsig64;
      79    INTSTK * intstk;
      80  
      81    static int eh_debug = -1;
      82    int try_bs_copy = 0;
      83    /* Non zero to attempt copy of alternate backing store contents for
      84       dirty partition in interrupted context. ??? Alpha code, only activated
      85       on specific request via specific bit in EH_DEBUG.  */
      86  
      87    if (eh_debug == -1)
      88      {
      89        char * EH_DEBUG = getenv ("EH_DEBUG");
      90        const uint try_bs_copy_mask = (1 << 16);
      91  
      92        eh_debug = EH_DEBUG ? atoi (EH_DEBUG) : 0;
      93        
      94        /* Fetch and clear the try_bs_copy bit.  */
      95        try_bs_copy = (uint)eh_debug & try_bs_copy_mask;
      96        eh_debug &= ~try_bs_copy_mask;
      97      }
      98  
      99    /* We're called to attempt unwinding through a frame for which no unwind
     100       info is available, typical of an operating system exception dispatcher
     101       frame.  The code below knows how to handle this case, and only this one,
     102       returning a failure code if it finds it is not in this situation.
     103  
     104       Note that we're called from deep down in the exception propagation call
     105       chain, possibly below an exception dispatcher but for a frame above it
     106       like some os entry point.  */
     107  
     108    if (eh_debug)
     109      printf ("FALLBACK - ctxt->rp=0x%lx, sp=0x%lx, psp=0x%lx, bsp=0x%lx\n",
     110  	    context->rp, context->sp, context->psp, context->bsp);
     111  
     112    /* Step 0 :
     113       -------------------------------------------------------------------------
     114       VMS-unwind up until we reach a VMS dispatcher frame corresponding to the
     115       context we are trying to unwind through. Fail if get past this context or
     116       if we reach the bottom of stack along the way.
     117       -------------------------------------------------------------------------
     118    */
     119  
     120    status = LIB$I64_INIT_INVO_CONTEXT (icb, LIBICB$K_INVO_CONTEXT_VERSION, 0);
     121    FAIL_IF (status == 0);
     122  
     123    status = LIB$I64_GET_CURR_INVO_CONTEXT (icb);
     124  
     125    /* Beware: we might be unwinding through nested condition handlers, so the
     126       dispatcher frame we seek might not be the first one on the way up.  Loop
     127       thus.  */     
     128    do {
     129      
     130      /* Seek the next dispatcher frame up the "current" point.  Stop if we
     131         either get past the target context or hit the bottom-of-stack along
     132         the way.  */
     133      status = LIB$I64_GET_PREV_INVO_CONTEXT (icb);
     134      FAIL_IF (status == 0);
     135      FAIL_IF ((uw_reg)icb->libicb$ih_sp > (uw_reg)context->psp
     136  	     || DENOTES_BOTTOM_OF_STACK (icb));
     137      
     138      if (eh_debug)
     139        printf ("frame%s sp @ 0x%llx, pc @ 0x%llx bsp=0x%llx\n",
     140  	      DENOTES_VMS_DISPATCHER_FRAME (icb) ? " (dispatcher)" : "",
     141  	      icb->libicb$ih_sp, icb->libicb$ih_pc, icb->libicb$ih_bsp);
     142  
     143      /* Continue until the target frame is found.  */
     144    } while ((uw_reg)icb->libicb$ih_bsp != (uw_reg)context->bsp);
     145  
     146    /* If this is not a dispatcher frame, this is certainly a frame for a leaf
     147       subprogram.  Use default unwind information.  */
     148    if (! DENOTES_VMS_DISPATCHER_FRAME (icb))
     149      return _URC_END_OF_STACK;
     150  
     151    /* At this point, we know we are really trying to unwind past an exception
     152       dispatcher frame, and have it described in ICB.  Proceed.  */
     153  
     154    /* Step 1 :
     155       ------------------------------------------------------------------------
     156       We have the VMS dispatcher frame ICB handy and know we are trying to
     157       unwind past it.  Fetch pointers to useful datastructures from there, then
     158       unwind one step further up to the interrupted user context from which
     159       some required values will be easily accessible.
     160       ------------------------------------------------------------------------
     161    */
     162  
     163    chfctx = icb->libicb$ph_chfctx_addr;
     164    FAIL_IF (chfctx == 0);
     165    
     166    chfmech = (CHF$MECH_ARRAY *)chfctx->chfctx$q_mcharglst;
     167    FAIL_IF (chfmech == 0);
     168  
     169    chfsig64 = (CHF64$SIGNAL_ARRAY *)chfmech->chf$ph_mch_sig64_addr;
     170    FAIL_IF (chfsig64 == 0);
     171   
     172    intstk = (INTSTK *)chfmech->chf$q_mch_esf_addr;
     173    FAIL_IF (intstk == 0 || intstk->intstk$b_subtype == DYN$C_SSENTRY);
     174  
     175    status = LIB$I64_GET_PREV_INVO_CONTEXT (icb);
     176    FAIL_IF (status == 0);
     177  
     178    if (eh_debug)
     179      printf ("User frame, "
     180  	    "chfmech @ 0x%p, chfsig64 @ 0x%p, intstk @ 0x%p\n",
     181  	    chfmech, chfsig64, intstk);
     182  
     183    /* Step 2 :
     184       ------------------------------------------------------------------------
     185       Point the GCC context locations/values required for further unwinding at
     186       their corresponding locations/values in the datastructures at hand.
     187       ------------------------------------------------------------------------
     188    */
     189  
     190    /* Static General Register locations, including scratch registers in case
     191       the unwinder needs to refer to a value stored in one of them.  */
     192    {
     193      uw_reg * ctxregs = (uw_reg *)&intstk->intstk$q_regbase;
     194  
     195      for (i = 2; i <= 3; i++)
     196        context->ireg[i - 2].loc = (uw_loc)&ctxregs[i];
     197      for (i = 8; i <= 11; i++)
     198        context->ireg[i - 2].loc = (uw_loc)&ctxregs[i];
     199      for (i = 14; i <= 31; i++)
     200        context->ireg[i - 2].loc = (uw_loc)&ctxregs[i];
     201    }
     202  
     203    /* Static Floating Point Register locations, as available from the
     204       mechargs array, which happens to include all the to be preserved
     205       ones + others.  */
     206    {
     207      fp_reg * ctxregs;
     208  
     209      ctxregs = (fp_reg *)&chfmech->chf$fh_mch_savf2;
     210      for (i = 2; i <= 5 ; i++)
     211        context->fr_loc[i - 2] = (uw_loc)&ctxregs[i - 2];
     212  
     213      ctxregs = (fp_reg *)&chfmech->chf$fh_mch_savf12;
     214      for (i = 12; i <= 31 ; i++)
     215        context->fr_loc[i - 2] = (uw_loc)&ctxregs[i - 12];
     216    }
     217  
     218    /* Relevant application register locations.  */
     219  
     220    context->fpsr_loc = (uw_loc)&intstk->intstk$q_fpsr;
     221    context->lc_loc   = (uw_loc)&intstk->intstk$q_lc;
     222    context->unat_loc = (uw_loc)&intstk->intstk$q_unat;
     223  
     224    /* Branch register locations.  */
     225    
     226    {
     227      uw_reg * ctxregs = (uw_reg *)&intstk->intstk$q_b0;
     228  
     229      for (i = 0; i < 8; i++)
     230        context->br_loc[i] = (uw_loc)&ctxregs[i];
     231    }
     232  
     233    /* Necessary register values.  */
     234  
     235    /* ??? Still unclear if we need to account for possible flushes to an
     236       alternate backing store (maybe the unwinding performed above did the
     237       trick already) and how this would be handled.  Blind alpha tentative
     238       below for experimentation purposes in malfunctioning cases.  */
     239    {
     240      uw_reg q_bsp      = (uw_reg) intstk->intstk$q_bsp;
     241      uw_reg q_bspstore = (uw_reg) intstk->intstk$q_bspstore;
     242      uw_reg q_bspbase  = (uw_reg) intstk->intstk$q_bspbase;
     243      uw_reg ih_bspbase = (uw_reg) icb->libicb$ih_bspbase;
     244      
     245      if (eh_debug)
     246        printf ("q_bspstore = 0x%lx, q_bsp = 0x%lx, q_bspbase = 0x%lx\n"
     247  	      "ih_bspbase = 0x%lx\n",
     248  	      q_bspstore, q_bsp, q_bspbase, ih_bspbase);
     249  
     250      /* We witness many situations where q_bspbase is set while ih_bspbase is
     251         null, and every attempt made with q_bspbase badly failed while doing
     252         nothing resulted in proper behavior.  */
     253      if (q_bspstore < q_bsp && ih_bspbase && try_bs_copy)
     254        {
     255  	uw_reg dirty_size = q_bsp - q_bspstore;
     256  	uw_reg q_rnat = (uw_reg) intstk->intstk$q_rnat;
     257  
     258  	if (eh_debug)
     259  	  printf ("Attempting an alternate backing store copy ...\n");
     260  
     261  	ia64_copy_rbs
     262  	  (context, q_bspstore, ih_bspbase, dirty_size, q_rnat);
     263  	/* Not clear if these are the proper arguments here.  This is what
     264  	   looked the closest to what is performed in the Linux case.  */
     265        }
     266      
     267    }
     268  
     269    context->bsp = (uw_reg)intstk->intstk$q_bsp;
     270    fs->no_reg_stack_frame = 1;
     271  
     272    context->pr  = (uw_reg)intstk->intstk$q_preds;
     273    context->gp  = (uw_reg)intstk->intstk$q_gp;
     274  
     275    /* We're directly setting up the "context" for a VMS exception handler.
     276       The "previous SP" for it is the SP upon the handler's entry, that is
     277       the SP at the condition/interruption/exception point.  */  
     278    context->psp = (uw_reg)icb->libicb$ih_sp;
     279  
     280    /* Previous Frame State location.  What eventually ends up in pfs_loc is
     281       installed with ar.pfs = pfs_loc; br.ret; so setup to target intstk->q_ifs
     282       to have the interrupted context restored and not that of its caller if
     283       we happen to have a handler in the interrupted context itself.  */
     284    fs->curr.reg[UNW_REG_PFS].where = UNW_WHERE_PSPREL;
     285    fs->curr.reg[UNW_REG_PFS].val
     286      = (uw_reg)&intstk->intstk$q_ifs - (uw_reg)context->psp;
     287    fs->curr.reg[UNW_REG_PFS].when = -1;
     288  
     289    /* If we need to unwind further up, past the interrupted context, we need to
     290       hand out the interrupted context's pfs, still.  */
     291    context->signal_pfs_loc = (uw_loc) &intstk->intstk$q_pfs;
     292  
     293    /* Finally, rules for RP .  */
     294    {
     295      uw_reg * post_sigarray
     296        = (uw_reg *)chfsig64 + 1 + chfsig64->chf64$l_sig_args;
     297  
     298      uw_reg * ih_pc_loc = post_sigarray - 2;
     299  
     300      fs->curr.reg[UNW_REG_RP].where = UNW_WHERE_PSPREL;
     301      fs->curr.reg[UNW_REG_RP].val
     302        = (uw_reg)ih_pc_loc - (uw_reg)context->psp;
     303      fs->curr.reg[UNW_REG_RP].when = -1;
     304    }
     305  
     306    return _URC_NO_REASON;
     307  }
     308