Patchwork [Alpha] Implement EH fallback routine on Tru64

login
register
mail settings
Submitter Eric Botcazou
Date Sept. 10, 2010, 6:27 p.m.
Message ID <201009102027.22957.ebotcazou@adacore.com>
Download mbox | patch
Permalink /patch/64441/
State New
Headers show

Comments

Eric Botcazou - Sept. 10, 2010, 6:27 p.m.
This implements the MD_UNWIND_SUPPORT on Tru64 and thus fixes the couple of 
Ada testcases gnat.dg/null_pointer_deref[12].adb on the platform.  It also 
contains a step-by-step tutorial on how to implement this support.

Tested on alphaev56-dec-osf5.1, OK for mainline?

2010-09-10  Olivier Hainque  <hainque@adacore.com>
            Jose Ruiz  <ruiz@adacore.com>

	* config/alpha/osf5.h (MD_UNWIND_SUPPORT): Likewise.
	* config/alpha/osf5-unwind.h: New file.
Richard Henderson - Sept. 14, 2010, 11:12 p.m.
The patch is ok for mainline, but...

> +   (gdb) x /i $ra-4
> +   <__sigtramp+160>: jsr     ra,(a3),0x3ff800d0ed4 <_fpdata+36468>
> +
> +   # Look at the code around that return address, and eventually observe a
> +   # significantly large chunk of *constant* code right before the call:
> +
> +   (gdb) x /10i  $ra-44
> +   <__sigtramp+120>: lda     gp,-27988(gp)
> +   <__sigtramp+124>: ldq     at,-18968(gp)
> +   <__sigtramp+128>: lda     t0,-1
> +   <__sigtramp+132>: stq     t0,0(at)
> +   <__sigtramp+136>: ldq     at,-18960(gp)
> +   <__sigtramp+140>: ldl     t1,8(at)
> +   <__sigtramp+144>: ldq     at,-18960(gp)
> +   <__sigtramp+148>: stl     t1,12(at)
> +   <__sigtramp+152>: ldq     at,-18960(gp)
> +   <__sigtramp+156>: stl     t0,8(at)

Normally one looks at the code *following* the return.
It should be something like a half-dozen insns 
concluding with a syscall PALcall.

> +   On this platform, the third handler's argument is a pointer to a structure
> +   describing this context (struct sigcontext *). We unfortunately have no
> +   direct way to transfer this value here, so a couple of tricks are required
> +   to compute it.

If I remember correctly, $16 will contain the sigcontext
structure for the syscall.  It should be relatively easy
to pick out this value by a trivial amount of code
interpretation.  I suspect it'll be just an LDA insn
with RC=16 and RB=SP.

What you have is probably just as good in practice, but
isn't the way I would have gone about finding the info.


r~
Eric Botcazou - Sept. 15, 2010, 7:11 a.m.
> The patch is ok for mainline, but...

Thanks.

> Normally one looks at the code *following* the return.
> It should be something like a half-dozen insns
> concluding with a syscall PALcall.

Do you mean the code following the call to the user handler within the system 
handler?  Why would this matter?  I think that all the EH fallback routines 
contributed by AdaCore follow this same pattern.

> If I remember correctly, $16 will contain the sigcontext
> structure for the syscall.  It should be relatively easy
> to pick out this value by a trivial amount of code
> interpretation.  I suspect it'll be just an LDA insn
> with RC=16 and RB=SP.

OK, thanks for the hint.  This code was written a long time ago though and 
proved quite robust so I don't think we want to tweak it at this point.
Richard Henderson - Sept. 15, 2010, 3:47 p.m.
On 09/15/2010 12:11 AM, Eric Botcazou wrote:
> This code was written a long time ago though and 
> proved quite robust so I don't think we want to tweak it at this point.

Sure.  I've an idea that the system code will never change
at this point in Tru64's life cycle.  ;-)


r~

Patch

Index: config/alpha/osf5-unwind.h
===================================================================
--- config/alpha/osf5-unwind.h	(revision 0)
+++ config/alpha/osf5-unwind.h	(revision 0)
@@ -0,0 +1,329 @@ 
+/* DWARF2 EH unwinding support for Alpha Tru64.
+   Copyright (C) 2010 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* This file implements the MD_FALLBACK_FRAME_STATE_FOR macro, triggered when
+   the GCC table based unwinding process hits a frame for which no unwind info
+   has been registered. This typically occurs when raising an exception from a
+   signal handler, because the handler is actually called from the OS kernel.
+
+   The basic idea is to detect that we are indeed trying to unwind past a
+   signal handler and to fill out the GCC internal unwinding structures for
+   the OS kernel frame as if it had been directly called from the interrupted
+   context.
+
+   This is all assuming that the code to set the handler asked the kernel to
+   pass a pointer to such context information.  */
+
+/* --------------------------------------------------------------------------
+   -- Basic principles of operation:
+   --------------------------------------------------------------------------
+
+   1/ We first need a way to detect if we are trying to unwind past a signal
+      handler.
+
+   The typical method that is used on most platforms is to look at the code
+   around the return address we have and check if it matches the OS code
+   calling a handler.  To determine what this code is expected to be, get a
+   breakpoint into a real signal handler and look at the code around the
+   return address.  Depending on the library versions the pattern of the
+   signal handler is different; this is the reason why we check against more
+   than one pattern.
+
+   On this target, the return address is right after the call and every
+   instruction is 4 bytes long.  For the simple case of a null dereference in
+   a single-threaded app, it went like:
+
+   # Check that we indeed have something we expect: the instruction right
+   # before the return address is within a __sigtramp function and is a call.
+
+   [... run gdb and break at the signal handler entry ...]
+
+   (gdb) x /i $ra-4
+   <__sigtramp+160>: jsr     ra,(a3),0x3ff800d0ed4 <_fpdata+36468>
+
+   # Look at the code around that return address, and eventually observe a
+   # significantly large chunk of *constant* code right before the call:
+
+   (gdb) x /10i  $ra-44
+   <__sigtramp+120>: lda     gp,-27988(gp)
+   <__sigtramp+124>: ldq     at,-18968(gp)
+   <__sigtramp+128>: lda     t0,-1
+   <__sigtramp+132>: stq     t0,0(at)
+   <__sigtramp+136>: ldq     at,-18960(gp)
+   <__sigtramp+140>: ldl     t1,8(at)
+   <__sigtramp+144>: ldq     at,-18960(gp)
+   <__sigtramp+148>: stl     t1,12(at)
+   <__sigtramp+152>: ldq     at,-18960(gp)
+   <__sigtramp+156>: stl     t0,8(at)
+
+   # The hexadecimal equivalent that we will have to match is:
+
+   (gdb) x /10x  $ra-44
+   <__sigtramp+120>: 0x23bd92ac    0xa79db5e8    0x203fffff   0xb43c0000
+   <__sigtramp+136>: 0xa79db5f0    0xa05c0008    0xa79db5f0   0xb05c000c
+   <__sigtramp+152>: 0xa79db5f0    0xb03c0008
+
+   The problem observed on this target with this approach is that although
+   we found a constant set of instruction patterns there were some
+   gp-related offsets that made the machine code to differ from one
+   installation to another.  This problem could have been overcome by masking
+   these offsets, but we found that it would be simpler and more efficient to
+   check whether the return address was part of a signal handler, by comparing
+   it against some expected code offset from __sigtramp.
+
+   # Check that we indeed have something we expect: the instruction
+   # right before the return address is within a __sigtramp
+   # function and is a call. We also need to obtain the offset
+   # between the return address and the start address of __sigtramp.
+
+   [... run gdb and break at the signal handler entry ...]
+
+   (gdb) x /2i $ra-4
+   <__sigtramp+160>: jsr     ra,(a3),0x3ff800d0ed4 <_fpdata+36468>
+   <__sigtramp+164>: ldah    gp,16381(ra)
+
+   (gdb) p (long)$ra - (long)&__sigtramp
+   $2 = 164
+
+   --------------------------------------------------------------------------
+
+   2/ Once we know we are going through a signal handler, we need a way to
+      retrieve information about the interrupted run-time context.
+
+   On this platform, the third handler's argument is a pointer to a structure
+   describing this context (struct sigcontext *). We unfortunately have no
+   direct way to transfer this value here, so a couple of tricks are required
+   to compute it.
+
+   As documented at least in some header files (e.g. sys/machine/context.h),
+   the structure the handler gets a pointer to is located on the stack.  As of
+   today, while writing this macro, we have unfortunately not been able to
+   find a detailed description of the full stack layout at handler entry time,
+   so we'll have to resort to empirism :)
+
+   When unwinding here, we have the handler's CFA at hand, as part of the
+   current unwinding context which is one of our arguments.  We presume that
+   for each call to a signal handler by the same kernel routine, the context's
+   structure location on the stack is always at the same offset from the
+   handler's CFA, and we compute that offset from bare observation:
+
+   For the simple case of a bare null dereference in a single-threaded app,
+   computing the offset was done using GNAT like this:
+
+   # Break on the first handler's instruction, before the prologue to have the
+   # CFA in $sp, and get there:
+
+   (gdb) b *&__gnat_error_handler
+   Breakpoint 1 at 0x120016090: file init.c, line 378.
+
+   (gdb) r
+   Program received signal SIGSEGV, Segmentation fault.
+
+   (gdb) c
+   Breakpoint 1, __gnat_error_handler (sig=..., sip=..., context=...)
+
+   # The displayed argument value are meaningless because we stopped before
+   # their final "homing". We know they are passed through $a0, $a1 and $a2
+   # from the ABI, though, so ...
+
+   # Observe that $sp and the context pointer are in the same (stack) area,
+   # and compute the offset:
+
+   (gdb) p /x $sp
+   $2 = 0x11fffbc80
+
+   (gdb) p /x $a2
+   $3 = 0x11fffbcf8
+
+   (gdb) p /x (long)$a2 - (long)$sp
+   $4 = 0x78
+
+   --------------------------------------------------------------------------
+
+   3/ Once we know we are unwinding through a signal handler and have the
+      address of the structure describing the interrupted context at hand, we
+      have to fill the internal frame-state/unwind-context structures properly
+      to allow the unwinding process to proceed.
+
+   Roughly, we are provided with an *unwinding* CONTEXT, describing the state
+   of some point P in the call chain we are unwinding through.  The macro we
+   implement has to fill a "frame state" structure FS that describe the P's
+   caller state, by way of *rules* to compute its CFA, return address, and
+   **saved** registers *locations*. 
+
+   For the case we are going to deal with, the caller is some kernel code
+   calling a signal handler, and:
+
+   o The saved registers are all in the interrupted run-time context,
+
+   o The CFA is the stack pointer value when the kernel code is entered, that
+     is, the stack pointer value at the interruption point, also part of the
+     interrupted run-time context.
+
+   o We want the return address to appear as the address of the active
+     instruction at the interruption point, so that the unwinder proceeds as
+     if the interruption had been a regular call.  This address is also part
+     of the interrupted run-time context.
+
+   --
+
+   Also, note that there is an important difference between the return address
+   we need to claim for the kernel frame and the value of the return address
+   register at the interruption point.
+
+   The latter might be required to be able to unwind past the interrupted
+   routine, for instance if it is interrupted before saving the incoming
+   register value in its own frame, which may typically happen during stack
+   probes for stack-checking purposes.
+
+   It is then essential that the rules stated to locate the kernel frame
+   return address don't clobber the rules describing where is saved the return
+   address register at the interruption point, so some scratch register state
+   entry should be used for the former. We have DWARF_ALT_FRAME_RETURN_COLUMN
+   at hand exactly for that purpose.
+
+   --------------------------------------------------------------------------
+
+   4/ Depending on the context (single-threaded or multi-threaded app, ...),
+   the code calling the handler and the handler-cfa to interrupted-context
+   offset might change, so we use a simple generic data structure to track
+   the possible variants.  */
+
+/* This is the structure to wrap information about each possible sighandler
+   caller we may have to identify.  */
+
+typedef struct {
+  /* Expected return address when being called from a sighandler.  */
+  void *ra_value;
+
+  /* Offset to get to the sigcontext structure from the handler's CFA
+     when the pattern matches.  */
+  int cfa_to_context_offset;
+
+} sighandler_call_t;
+
+/* Helper macro for MD_FALLBACK_FRAME_STATE_FOR below.
+
+   Look at RA to see if it matches within a sighandler caller.
+   Set SIGCTX to the corresponding sigcontext structure (computed from
+   CFA) if it does, or to 0 otherwise.  */
+
+#define COMPUTE_SIGCONTEXT_FOR(RA,CFA,SIGCTX)				    \
+do {									    \
+  /* Define and register the applicable patterns.  */			    \
+  extern void __sigtramp (void);					    \
+									    \
+  sighandler_call_t sighandler_calls [] = {				    \
+    {__sigtramp + 164, 0x78}						    \
+  };									    \
+									    \
+  int n_patterns_to_match						    \
+    = sizeof (sighandler_calls) / sizeof (sighandler_call_t);		    \
+									    \
+  int pn;  /* pattern number  */					    \
+									    \
+  int match = 0;  /* Did last pattern match ?  */			    \
+									    \
+  /* Try to match each pattern in turn.  */				    \
+  for (pn = 0; !match && pn < n_patterns_to_match; pn ++)		    \
+    match = ((RA) == sighandler_calls[pn].ra_value);			    \
+									    \
+  (SIGCTX) = (struct sigcontext *)					    \
+    (match ? ((CFA) + sighandler_calls[pn - 1].cfa_to_context_offset) : 0); \
+} while (0);
+
+#include <sys/context_t.h>
+
+#define REG_SP  30  /* hard reg for stack pointer */
+#define REG_RA  26  /* hard reg for return address */
+
+#define MD_FALLBACK_FRAME_STATE_FOR alpha_fallback_frame_state
+
+static _Unwind_Reason_Code
+alpha_fallback_frame_state (struct _Unwind_Context *context,
+			    _Unwind_FrameState *fs)
+{
+  /* Return address and CFA of the frame we're attempting to unwind through,
+     possibly a signal handler.  */
+  void *ctx_ra  = (void *)context->ra;
+  void *ctx_cfa = (void *)context->cfa;
+
+  /* CFA of the intermediate abstract kernel frame between the interrupted
+     code and the signal handler, if we're indeed unwinding through a signal
+     handler.  */
+  void *k_cfa;
+
+  /* Pointer to the sigcontext structure pushed by the kernel when we're
+     unwinding through a signal handler.  */
+  struct sigcontext *sigctx;
+  int i;
+
+  COMPUTE_SIGCONTEXT_FOR (ctx_ra, ctx_cfa, sigctx);
+
+  if (sigctx == 0)
+    return _URC_END_OF_STACK;
+
+  /* The kernel frame's CFA is exactly the stack pointer value at the
+     interruption point.  */
+  k_cfa = (void *) sigctx->sc_regs [REG_SP];
+
+  /* State the rules to compute the CFA we have the value of: use the
+     previous CFA and offset by the difference between the two.  See
+     uw_update_context_1 for the supporting details.  */
+  fs->regs.cfa_how = CFA_REG_OFFSET;
+  fs->regs.cfa_reg = __builtin_dwarf_sp_column ();
+  fs->regs.cfa_offset = k_cfa - ctx_cfa;
+
+  /* Fill the internal frame_state structure with information stating
+     where each register of interest in the saved context can be found
+     from the CFA.  */
+
+  /* The general registers are in sigctx->sc_regs.  Leave out r31, which
+     is read-as-zero. It makes no sense restoring it, and we are going to
+     use the state entry for the kernel return address rule below.
+
+     This loop must cover at least all the callee-saved registers, and
+     we just don't bother specializing the set here.  */
+  for (i = 0; i <= 30; i ++)
+    {
+      fs->regs.reg[i].how = REG_SAVED_OFFSET;
+      fs->regs.reg[i].loc.offset
+	= (void *) &sigctx->sc_regs[i] - (void *) k_cfa;
+    }
+
+  /* Ditto for the floating point registers in sigctx->sc_fpregs.  */
+  for (i = 0; i <= 31; i ++)
+    {
+      fs->regs.reg[32+i].how = REG_SAVED_OFFSET;
+      fs->regs.reg[32+i].loc.offset
+	= (void *) &sigctx->sc_fpregs[i] - (void *) k_cfa;
+    }
+
+  /* State the rules to find the kernel's code "return address", which
+     is the address of the active instruction when the signal was caught,
+     in sigctx->sc_pc. Use DWARF_ALT_FRAME_RETURN_COLUMN since the return
+     address register is a general register and should be left alone.  */
+  fs->retaddr_column = DWARF_ALT_FRAME_RETURN_COLUMN;
+  fs->regs.reg[DWARF_ALT_FRAME_RETURN_COLUMN].how = REG_SAVED_OFFSET;
+  fs->regs.reg[DWARF_ALT_FRAME_RETURN_COLUMN].loc.offset
+    = (void *) &sigctx->sc_pc - (void *) k_cfa;
+  fs->signal_frame = 1;
+
+  return _URC_NO_REASON;
+}
Index: config/alpha/osf5.h
===================================================================
--- config/alpha/osf5.h	(revision 163921)
+++ config/alpha/osf5.h	(working copy)
@@ -274,3 +274,5 @@  __enable_execute_stack (void *addr)
 
 /* Handle #pragma extern_prefix.  */
 #define TARGET_HANDLE_PRAGMA_EXTERN_PREFIX 1
+
+#define MD_UNWIND_SUPPORT "config/alpha/osf5-unwind.h"