/* 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 . */ /* 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 #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; }