This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] powerpc dwarf2 unwinder fallback


This patch corrects some problems with the unwind fallback code on
powerpc-linux and powerpc64-linux.

1) Floating point and vector registers were not being restored.  OK, so
who uses fprs and vmx inside signal handlers?  It can't be very common
to have explicit use, but gcc generates code that uses fp and vector
regs to move blocks of memory around.

2) The 64-bit rt_sigreturn code used to access saved registers by an
offset from pc.  This works when the trampoline is on the stack, but new
linux kernels will use a vdso with the trampoline no longer on the
stack.  I think the only reason that an offset from pc was used was to
support linux-2.4.19 and linux-2.4.20, the first two released linux
kernels that supported ppc64.  These kernels used a different stack
layout than later kernels.  Since people might still be running these
old kernels, I've kept code to support them, and added extra code to
support the vdso.

3) I've removed gcc_sigcontext because it only bore a passing semblance
to the real structure, and merged it with gcc_ucontext.  get_sigcontext
becomes get_regs too, because we are really only interested in regs.

Tested with powerpc-linux and powerpc64-linux bootstrap and regression
check.  Also glibc "make check" both with and without vdso (but using
the equivalent code with a gcc-3.4 branch compiler to avoid glibc build
problems with gcc-4.0).  OK to install?

	* config/rs6000/linux-unwind.h (struct gcc_vregs): New.
	(struct gcc_regs): Rename from gcc_pt_regs.  Add more regs.
	(struct gcc_sigcontext): Delete.  Merge contents to..
	(struct gcc_ucontext): ..here.
	(get_sigcontext): Delete.
	(get_regs): New function, like get_sigcontext but return regs ptr.
	64-bit version finds regs from r1 to support vdso.
	(ppc_linux_aux_vector): New function.
	(ppc_fallback_frame_state): Modify for get_regs.  Restore fprs
	and vector regs.

Index: gcc/config/rs6000/linux-unwind.h
===================================================================
RCS file: /cvs/gcc/gcc/gcc/config/rs6000/linux-unwind.h,v
retrieving revision 1.2
diff -u -p -r1.2 linux-unwind.h
--- gcc/config/rs6000/linux-unwind.h	15 Sep 2004 11:43:31 -0000	1.2
+++ gcc/config/rs6000/linux-unwind.h	29 Jan 2005 07:17:56 -0000
@@ -24,7 +24,22 @@
    these structs elsewhere;  Many fields are missing, particularly
    from the end of the structures.  */
 
-struct gcc_pt_regs
+struct gcc_vregs
+{
+  __attribute__ ((vector_size (16))) int vr[32];
+#ifdef __powerpc64__
+  unsigned int pad1[3];
+  unsigned int vscr;
+  unsigned int vsave;
+  unsigned int pad2[3];
+#else
+  unsigned int vsave;
+  unsigned int pad[2];
+  unsigned int vscr;
+#endif
+};
+
+struct gcc_regs
 {
   unsigned long gpr[32];
   unsigned long nip;
@@ -34,22 +49,32 @@ struct gcc_pt_regs
   unsigned long link;
   unsigned long xer;
   unsigned long ccr;
-};
-
-struct gcc_sigcontext
-{
-  unsigned long	pad[7];
-  struct gcc_pt_regs *regs;
+  unsigned long softe;
+  unsigned long trap;
+  unsigned long dar;
+  unsigned long dsisr;
+  unsigned long result;
+  unsigned long pad1[4];
+  double fpr[32];
+  unsigned int pad2;
+  unsigned int fpscr;
+#ifdef __powerpc64__
+  struct gcc_vregs *vp;
+#else
+  unsigned int pad3[2];
+#endif
+  struct gcc_vregs vregs;
 };
 
 struct gcc_ucontext
 {
 #ifdef __powerpc64__
-  unsigned long pad[21];
+  unsigned long pad[28];
 #else
-  unsigned long pad[5];
+  unsigned long pad[12];
 #endif
-  struct gcc_sigcontext uc_mcontext;
+  struct gcc_regs *regs;
+  struct gcc_regs rsave;
 };
 
 #ifdef __powerpc64__
@@ -77,34 +102,54 @@ frob_update_context (struct _Unwind_Cont
 }
 
 /* If PC is at a sigreturn trampoline, return a pointer to the
-   sigcontext.  Otherwise return NULL.  */
+   regs.  Otherwise return NULL.  */
 
-static struct gcc_sigcontext *
-get_sigcontext (struct _Unwind_Context *context)
+static struct gcc_regs *
+get_regs (struct _Unwind_Context *context)
 {
   const unsigned char *pc = context->ra;
 
   /* addi r1, r1, 128; li r0, 0x0077; sc  (sigreturn) */
   /* addi r1, r1, 128; li r0, 0x00AC; sc  (rt_sigreturn) */
-  if (*(unsigned int *) (pc+0) != 0x38210000 + SIGNAL_FRAMESIZE
-      || *(unsigned int *) (pc+8) != 0x44000002)
+  if (*(unsigned int *) (pc + 0) != 0x38210000 + SIGNAL_FRAMESIZE
+      || *(unsigned int *) (pc + 8) != 0x44000002)
     return NULL;
-  if (*(unsigned int *) (pc+4) == 0x38000077)
+  if (*(unsigned int *) (pc + 4) == 0x38000077)
     {
       struct sigframe {
 	char gap[SIGNAL_FRAMESIZE];
-	struct gcc_sigcontext sigctx;
-      } *rt_ = context->cfa;
-      return &rt_->sigctx;
+	unsigned long pad[7];
+	struct gcc_regs *regs;
+      } *frame = (struct sigframe *) context->cfa;
+      return frame->regs;
     }
-  else if (*(unsigned int *) (pc+4) == 0x380000AC)
+  else if (*(unsigned int *) (pc + 4) == 0x380000AC)
     {
-      struct rt_sigframe {
+      /* This works for 2.4 kernels, but not for 2.6 kernels with vdso
+	 because pc isn't pointing into the stack.  Can be removed when
+	 no one is running 2.4.19 or 2.4.20, the first two ppc64
+	 kernels released.  */
+      struct rt_sigframe_24 {
 	int tramp[6];
 	void *pinfo;
 	struct gcc_ucontext *puc;
-      } *rt_ = (struct rt_sigframe *) pc;
-      return &rt_->puc->uc_mcontext;
+      } *frame24 = (struct rt_sigframe_24 *) pc;
+
+      /* Test for magic value in *puc of vdso.  */
+      if ((long) frame24->puc != -21 * 8)
+	return frame24->puc->regs;
+      else
+	{
+	  struct rt_sigframe {
+	    char gap[SIGNAL_FRAMESIZE];
+	    struct gcc_ucontext uc;
+	    unsigned long pad[2];
+	    int tramp[6];
+	    void *pinfo;
+	    struct gcc_ucontext *puc;
+	  } *frame = (struct rt_sigframe *) context->cfa;
+	  return frame->uc.regs;
+	}
     }
   return NULL;
 }
@@ -113,8 +158,8 @@ get_sigcontext (struct _Unwind_Context *
 
 enum { SIGNAL_FRAMESIZE = 64 };
 
-static struct gcc_sigcontext *
-get_sigcontext (struct _Unwind_Context *context)
+static struct gcc_regs *
+get_regs (struct _Unwind_Context *context)
 {
   const unsigned char *pc = context->ra;
 
@@ -122,31 +167,64 @@ get_sigcontext (struct _Unwind_Context *
   /* li r0, 0x0077; sc  (sigreturn new)  */
   /* li r0, 0x6666; sc  (rt_sigreturn old)  */
   /* li r0, 0x00AC; sc  (rt_sigreturn new)  */
-  if (*(unsigned int *) (pc+4) != 0x44000002)
+  if (*(unsigned int *) (pc + 4) != 0x44000002)
     return NULL;
-  if (*(unsigned int *) (pc+0) == 0x38007777
-      || *(unsigned int *) (pc+0) == 0x38000077)
+  if (*(unsigned int *) (pc + 0) == 0x38007777
+      || *(unsigned int *) (pc + 0) == 0x38000077)
     {
       struct sigframe {
 	char gap[SIGNAL_FRAMESIZE];
-	struct gcc_sigcontext sigctx;
-      } *rt_ = context->cfa;
-      return &rt_->sigctx;
+	unsigned long pad[7];
+	struct gcc_regs *regs;
+      } *frame = (struct sigframe *) context->cfa;
+      return frame->regs;
     }
-  else if (*(unsigned int *) (pc+0) == 0x38006666
-	   || *(unsigned int *) (pc+0) == 0x380000AC)
+  else if (*(unsigned int *) (pc + 0) == 0x38006666
+	   || *(unsigned int *) (pc + 0) == 0x380000AC)
     {
       struct rt_sigframe {
 	char gap[SIGNAL_FRAMESIZE + 16];
 	char siginfo[128];
 	struct gcc_ucontext uc;
-      } *rt_ = context->cfa;
-      return &rt_->uc.uc_mcontext;
+      } *frame = (struct rt_sigframe *) context->cfa;
+      return frame->uc.regs;
     }
   return NULL;
 }
 #endif
 
+/* Find an entry in the process auxilliary vector.  The canonical way to
+   test for VMX is to look at AT_HWCAP.  */
+
+static long
+ppc_linux_aux_vector (long which)
+{
+  /* __libc_stack_end holds the original stack passed to a process.  */
+  extern long *__libc_stack_end;
+  long argc;
+  char **argv;
+  char **envp;
+  struct auxv
+  {
+    long a_type;
+    long a_val;
+  } *auxp;
+
+  /* The Linux kernel puts argc first on the stack.  */
+  argc = __libc_stack_end[0];
+  /* Followed by argv, NULL terminated.  */
+  argv = (char **) __libc_stack_end + 1;
+  /* Followed by environment string pointers, NULL terminated. */
+  envp = argv + argc + 1;
+  while (*envp++)
+    continue;
+  /* Followed by the aux vector, zero terminated.  */
+  for (auxp = (struct auxv *) envp; auxp->a_type != 0; ++auxp)
+    if (auxp->a_type == which)
+      return auxp->a_val;
+  return 0;
+}
+
 /* Do code reading to identify a signal frame, and set the frame
    state data appropriately.  See unwind-dw2.c for the structs.  */
 
@@ -156,14 +234,15 @@ static _Unwind_Reason_Code
 ppc_fallback_frame_state (struct _Unwind_Context *context,
 			  _Unwind_FrameState *fs)
 {
-  struct gcc_sigcontext *sc = get_sigcontext (context);
+  static long hwcap = 0;
+  struct gcc_regs *regs = get_regs (context);
   long new_cfa;
   int i;
 
-  if (sc == NULL)
+  if (regs == NULL)
     return _URC_END_OF_STACK;
 
-  new_cfa = sc->regs->gpr[STACK_POINTER_REGNUM];
+  new_cfa = regs->gpr[STACK_POINTER_REGNUM];
   fs->cfa_how = CFA_REG_OFFSET;
   fs->cfa_reg = STACK_POINTER_REGNUM;
   fs->cfa_offset = new_cfa - (long) context->cfa;
@@ -172,21 +251,65 @@ ppc_fallback_frame_state (struct _Unwind
     if (i != STACK_POINTER_REGNUM)
       {
 	fs->regs.reg[i].how = REG_SAVED_OFFSET;
-	fs->regs.reg[i].loc.offset
-	  = (long)&(sc->regs->gpr[i]) - new_cfa;
+	fs->regs.reg[i].loc.offset = (long) &regs->gpr[i] - new_cfa;
       }
 
   fs->regs.reg[CR2_REGNO].how = REG_SAVED_OFFSET;
-  fs->regs.reg[CR2_REGNO].loc.offset
-    = (long)&(sc->regs->ccr) - new_cfa;
+  fs->regs.reg[CR2_REGNO].loc.offset = (long) &regs->ccr - new_cfa;
 
   fs->regs.reg[LINK_REGISTER_REGNUM].how = REG_SAVED_OFFSET;
-  fs->regs.reg[LINK_REGISTER_REGNUM].loc.offset
-    = (long)&(sc->regs->link) - new_cfa;
+  fs->regs.reg[LINK_REGISTER_REGNUM].loc.offset = (long) &regs->link - new_cfa;
 
   fs->regs.reg[ARG_POINTER_REGNUM].how = REG_SAVED_OFFSET;
-  fs->regs.reg[ARG_POINTER_REGNUM].loc.offset
-    = (long)&(sc->regs->nip) - new_cfa;
+  fs->regs.reg[ARG_POINTER_REGNUM].loc.offset = (long) &regs->nip - new_cfa;
   fs->retaddr_column = ARG_POINTER_REGNUM;
+
+  if (hwcap == 0)
+    {
+      hwcap = ppc_linux_aux_vector (16);
+      /* These will already be set if we found AT_HWCAP.  A non-zero
+	 value stops us looking again if for some reason we couldn't
+	 find AT_HWCAP.  */
+#ifdef __powerpc64__
+      hwcap |= 0xc0000000;
+#else
+      hwcap |= 0x80000000;
+#endif
+    }
+
+  /* If we have a FPU...  */
+  if (hwcap & 0x08000000)
+    for (i = 0; i < 32; i++)
+      {
+	fs->regs.reg[i + 32].how = REG_SAVED_OFFSET;
+	fs->regs.reg[i + 32].loc.offset = (long) &regs->fpr[i] - new_cfa;
+      }
+
+  /* If we have a VMX unit...  */
+  if (hwcap & 0x10000000)
+    {
+      struct gcc_vregs *vregs;
+#ifdef __powerpc64__
+      vregs = regs->vp;
+#else
+      vregs = &regs->vregs;
+#endif
+      if (regs->msr & (1 << 25))
+	{
+	  for (i = 0; i < 32; i++)
+	    {
+	      fs->regs.reg[i + FIRST_ALTIVEC_REGNO].how = REG_SAVED_OFFSET;
+	      fs->regs.reg[i + FIRST_ALTIVEC_REGNO].loc.offset
+		= (long) &vregs[i] - new_cfa;
+	    }
+
+	  fs->regs.reg[VSCR_REGNO].how = REG_SAVED_OFFSET;
+	  fs->regs.reg[VSCR_REGNO].loc.offset = (long) &vregs->vscr - new_cfa;
+	}
+
+      fs->regs.reg[VRSAVE_REGNO].how = REG_SAVED_OFFSET;
+      fs->regs.reg[VRSAVE_REGNO].loc.offset = (long) &vregs->vsave - new_cfa;
+    }
+
   return _URC_NO_REASON;
 }

-- 
Alan Modra
IBM OzLabs - Linux Technology Centre


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]