| /* libunwind - a platform-independent unwind library |
| Copyright (C) 2003-2005 Hewlett-Packard Co |
| Contributed by David Mosberger-Tang <davidm@hpl.hp.com> |
| |
| This file is part of libunwind. |
| |
| Permission is hereby granted, free of charge, to any person obtaining |
| a copy of this software and associated documentation files (the |
| "Software"), to deal in the Software without restriction, including |
| without limitation the rights to use, copy, modify, merge, publish, |
| distribute, sublicense, and/or sell copies of the Software, and to |
| permit persons to whom the Software is furnished to do so, subject to |
| the following conditions: |
| |
| The above copyright notice and this permission notice shall be |
| included in all copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| |
| /* Logically, we like to think of the stack as a contiguous region of |
| memory. Unfortunately, this logical view doesn't work for the |
| register backing store, because the RSE is an asynchronous engine and |
| because UNIX/Linux allow for stack-switching via sigaltstack(2). |
| Specifically, this means that any given stacked register may or may |
| not be backed up by memory in the current stack. If not, then the |
| backing memory may be found in any of the "more inner" (younger) |
| stacks. The routines in this file help manage the discontiguous |
| nature of the register backing store. The routines are completely |
| independent of UNIX/Linux, but each stack frame that switches the |
| backing store is expected to reserve 4 words for use by libunwind. For |
| example, in the Linux sigcontext, sc_fr[0] and sc_fr[1] serve this |
| purpose. */ |
| |
| #include "unwind_i.h" |
| |
| #if UNW_DEBUG |
| |
| HIDDEN const char * |
| ia64_strloc (ia64_loc_t loc) |
| { |
| static char buf[128]; |
| |
| if (IA64_IS_NULL_LOC (loc)) |
| return "<null>"; |
| |
| buf[0] = '\0'; |
| |
| if (IA64_IS_MEMSTK_NAT (loc)) |
| strcat (buf, "memstk_nat("); |
| if (IA64_IS_UC_LOC (loc)) |
| strcat (buf, "uc("); |
| if (IA64_IS_FP_LOC (loc)) |
| strcat (buf, "fp("); |
| |
| if (IA64_IS_REG_LOC (loc)) |
| sprintf (buf + strlen (buf), "%s", unw_regname (IA64_GET_REG (loc))); |
| else |
| sprintf (buf + strlen (buf), "0x%llx", |
| (unsigned long long) IA64_GET_ADDR (loc)); |
| |
| if (IA64_IS_FP_LOC (loc)) |
| strcat (buf, ")"); |
| if (IA64_IS_UC_LOC (loc)) |
| strcat (buf, ")"); |
| if (IA64_IS_MEMSTK_NAT (loc)) |
| strcat (buf, ")"); |
| |
| return buf; |
| } |
| |
| #endif /* UNW_DEBUG */ |
| |
| HIDDEN int |
| rbs_switch (struct cursor *c, |
| unw_word_t saved_bsp, unw_word_t saved_bspstore, |
| ia64_loc_t saved_rnat_loc) |
| { |
| struct rbs_area *rbs = &c->rbs_area[c->rbs_curr]; |
| unw_word_t lo, ndirty, rbs_base; |
| int ret; |
| |
| Debug (10, "(left=%u, curr=%u)\n", c->rbs_left_edge, c->rbs_curr); |
| |
| /* Calculate address "lo" at which the backing store starts: */ |
| ndirty = rse_num_regs (saved_bspstore, saved_bsp); |
| lo = rse_skip_regs (c->bsp, -ndirty); |
| |
| rbs->size = (rbs->end - lo); |
| |
| /* If the previously-recorded rbs-area is empty we don't need to |
| track it and we can simply overwrite it... */ |
| if (rbs->size) |
| { |
| Debug (10, "inner=[0x%lx-0x%lx)\n", |
| (long) (rbs->end - rbs->size), (long) rbs->end); |
| |
| c->rbs_curr = (c->rbs_curr + 1) % ARRAY_SIZE (c->rbs_area); |
| rbs = c->rbs_area + c->rbs_curr; |
| |
| if (c->rbs_curr == c->rbs_left_edge) |
| c->rbs_left_edge = (c->rbs_left_edge + 1) % ARRAY_SIZE (c->rbs_area); |
| } |
| |
| if ((ret = rbs_get_base (c, saved_bspstore, &rbs_base)) < 0) |
| return ret; |
| |
| rbs->end = saved_bspstore; |
| rbs->size = saved_bspstore - rbs_base; |
| rbs->rnat_loc = saved_rnat_loc; |
| |
| c->bsp = saved_bsp; |
| |
| Debug (10, "outer=[0x%llx-0x%llx), rnat@%s\n", (long long) rbs_base, |
| (long long) rbs->end, ia64_strloc (rbs->rnat_loc)); |
| return 0; |
| } |
| |
| HIDDEN int |
| rbs_find_stacked (struct cursor *c, unw_word_t regs_to_skip, |
| ia64_loc_t *locp, ia64_loc_t *rnat_locp) |
| { |
| unw_word_t nregs, bsp = c->bsp, curr = c->rbs_curr, n; |
| unw_word_t left_edge = c->rbs_left_edge; |
| #if UNW_DEBUG |
| int reg = 32 + regs_to_skip; |
| #endif |
| |
| while (!rbs_contains (&c->rbs_area[curr], bsp)) |
| { |
| if (curr == left_edge) |
| { |
| Debug (1, "could not find register r%d!\n", reg); |
| return -UNW_EBADREG; |
| } |
| |
| n = rse_num_regs (c->rbs_area[curr].end, bsp); |
| curr = (curr + ARRAY_SIZE (c->rbs_area) - 1) % ARRAY_SIZE (c->rbs_area); |
| bsp = rse_skip_regs (c->rbs_area[curr].end - c->rbs_area[curr].size, n); |
| } |
| |
| while (1) |
| { |
| nregs = rse_num_regs (bsp, c->rbs_area[curr].end); |
| |
| if (regs_to_skip < nregs) |
| { |
| /* found it: */ |
| unw_word_t addr; |
| |
| addr = rse_skip_regs (bsp, regs_to_skip); |
| if (locp) |
| *locp = rbs_loc (c->rbs_area + curr, addr); |
| if (rnat_locp) |
| *rnat_locp = rbs_get_rnat_loc (c->rbs_area + curr, addr); |
| return 0; |
| } |
| |
| if (curr == left_edge) |
| { |
| Debug (1, "could not find register r%d!\n", reg); |
| return -UNW_EBADREG; |
| } |
| |
| regs_to_skip -= nregs; |
| |
| curr = (curr + ARRAY_SIZE (c->rbs_area) - 1) % ARRAY_SIZE (c->rbs_area); |
| bsp = c->rbs_area[curr].end - c->rbs_area[curr].size; |
| } |
| } |
| |
| #ifdef NEED_RBS_COVER_AND_FLUSH |
| |
| static inline int |
| get_rnat (struct cursor *c, struct rbs_area *rbs, unw_word_t bsp, |
| unw_word_t *__restrict rnatp) |
| { |
| ia64_loc_t rnat_locp = rbs_get_rnat_loc (rbs, bsp); |
| |
| return ia64_get (c, rnat_locp, rnatp); |
| } |
| |
| /* Simulate the effect of "cover" followed by a "flushrs" for the |
| target-frame. However, since the target-frame's backing store |
| may not have space for the registers that got spilled onto other |
| rbs-areas, we save those registers to DIRTY_PARTITION where |
| we can then load them via a single "loadrs". |
| |
| This function returns the size of the dirty-partition that was |
| created or a negative error-code in case of error. |
| |
| Note: This does not modify the rbs_area[] structure in any way. */ |
| HIDDEN int |
| rbs_cover_and_flush (struct cursor *c, unw_word_t nregs, |
| unw_word_t *dirty_partition, unw_word_t *dirty_rnat, |
| unw_word_t *bspstore) |
| { |
| unw_word_t n, src_mask, dst_mask, bsp, *dst, src_rnat, dst_rnat = 0; |
| unw_word_t curr = c->rbs_curr, left_edge = c->rbs_left_edge; |
| struct rbs_area *rbs = c->rbs_area + curr; |
| int ret; |
| |
| bsp = c->bsp; |
| c->bsp = rse_skip_regs (bsp, nregs); |
| |
| if (likely (rbs_contains (rbs, bsp))) |
| { |
| /* at least _some_ registers are on rbs... */ |
| n = rse_num_regs (bsp, rbs->end); |
| if (likely (n >= nregs)) |
| { |
| /* common case #1: all registers are on current rbs... */ |
| /* got lucky: _all_ registers are on rbs... */ |
| ia64_loc_t rnat_loc = rbs_get_rnat_loc (rbs, c->bsp); |
| |
| *bspstore = c->bsp; |
| |
| if (IA64_IS_REG_LOC (rnat_loc)) |
| { |
| unw_word_t rnat_addr = (unw_word_t) |
| tdep_uc_addr (c->as_arg, UNW_IA64_AR_RNAT, NULL); |
| rnat_loc = IA64_LOC_ADDR (rnat_addr, 0); |
| } |
| c->loc[IA64_REG_RNAT] = rnat_loc; |
| return 0; /* all done */ |
| } |
| nregs -= n; /* account for registers already on the rbs */ |
| |
| assert (rse_skip_regs (c->bsp, -nregs) == rse_skip_regs (rbs->end, 0)); |
| } |
| else |
| /* Earlier frames also didn't get spilled; need to "loadrs" those, |
| too... */ |
| nregs += rse_num_regs (rbs->end, bsp); |
| |
| /* OK, we need to copy NREGS registers to the dirty partition. */ |
| |
| *bspstore = bsp = rbs->end; |
| c->loc[IA64_REG_RNAT] = rbs->rnat_loc; |
| assert (!IA64_IS_REG_LOC (rbs->rnat_loc)); |
| |
| dst = dirty_partition; |
| |
| while (nregs > 0) |
| { |
| if (unlikely (!rbs_contains (rbs, bsp))) |
| { |
| /* switch to next non-empty rbs-area: */ |
| do |
| { |
| if (curr == left_edge) |
| { |
| Debug (0, "rbs-underflow while flushing %lu regs, " |
| "bsp=0x%lx, dst=0x%p\n", (unsigned long) nregs, |
| (unsigned long) bsp, dst); |
| return -UNW_EBADREG; |
| } |
| |
| assert (rse_num_regs (rbs->end, bsp) == 0); |
| |
| curr = (curr + ARRAY_SIZE (c->rbs_area) - 1) |
| % ARRAY_SIZE (c->rbs_area); |
| rbs = c->rbs_area + curr; |
| bsp = rbs->end - rbs->size; |
| } |
| while (rbs->size == 0); |
| |
| if ((ret = get_rnat (c, rbs, bsp, &src_rnat)) < 0) |
| return ret; |
| } |
| |
| if (unlikely (rse_is_rnat_slot (bsp))) |
| { |
| bsp += 8; |
| if ((ret = get_rnat (c, rbs, bsp, &src_rnat)) < 0) |
| return ret; |
| } |
| if (unlikely (rse_is_rnat_slot ((unw_word_t) dst))) |
| { |
| *dst++ = dst_rnat; |
| dst_rnat = 0; |
| } |
| |
| src_mask = ((unw_word_t) 1) << rse_slot_num (bsp); |
| dst_mask = ((unw_word_t) 1) << rse_slot_num ((unw_word_t) dst); |
| |
| if (src_rnat & src_mask) |
| dst_rnat |= dst_mask; |
| else |
| dst_rnat &= ~dst_mask; |
| |
| /* copy one slot: */ |
| if ((ret = ia64_get (c, rbs_loc (rbs, bsp), dst)) < 0) |
| return ret; |
| |
| /* advance to next slot: */ |
| --nregs; |
| bsp += 8; |
| ++dst; |
| } |
| if (unlikely (rse_is_rnat_slot ((unw_word_t) dst))) |
| { |
| /* The LOADRS instruction loads "the N bytes below the current |
| BSP" but BSP can never point to an RNaT slot so if the last |
| destination word happens to be an RNaT slot, we need to write |
| that slot now. */ |
| *dst++ = dst_rnat; |
| dst_rnat = 0; |
| } |
| *dirty_rnat = dst_rnat; |
| return (char *) dst - (char *) dirty_partition; |
| } |
| |
| #endif /* !UNW_REMOTE_ONLY */ |