| /* libunwind - a platform-independent unwind library |
| Copyright (C) 2001-2004 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. */ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "unwind_i.h" |
| |
| /* forward declaration: */ |
| static int create_state_record_for (struct cursor *c, |
| struct ia64_state_record *sr, |
| unw_word_t ip); |
| |
| typedef unsigned long unw_word; |
| |
| #define alloc_reg_state() (mempool_alloc (&unw.reg_state_pool)) |
| #define free_reg_state(rs) (mempool_free (&unw.reg_state_pool, rs)) |
| #define alloc_labeled_state() (mempool_alloc (&unw.labeled_state_pool)) |
| #define free_labeled_state(s) (mempool_free (&unw.labeled_state_pool, s)) |
| |
| /* Routines to manipulate the state stack. */ |
| |
| static inline void |
| push (struct ia64_state_record *sr) |
| { |
| struct ia64_reg_state *rs; |
| |
| rs = alloc_reg_state (); |
| if (!rs) |
| { |
| print_error ("libunwind: cannot stack reg state!\n"); |
| return; |
| } |
| memcpy (rs, &sr->curr, sizeof (*rs)); |
| sr->curr.next = rs; |
| } |
| |
| static void |
| pop (struct ia64_state_record *sr) |
| { |
| struct ia64_reg_state *rs = sr->curr.next; |
| |
| if (!rs) |
| { |
| print_error ("libunwind: stack underflow!\n"); |
| return; |
| } |
| memcpy (&sr->curr, rs, sizeof (*rs)); |
| free_reg_state (rs); |
| } |
| |
| /* Make a copy of the state stack. Non-recursive to avoid stack overflows. */ |
| static struct ia64_reg_state * |
| dup_state_stack (struct ia64_reg_state *rs) |
| { |
| struct ia64_reg_state *copy, *prev = NULL, *first = NULL; |
| |
| while (rs) |
| { |
| copy = alloc_reg_state (); |
| if (!copy) |
| { |
| print_error ("unwind.dup_state_stack: out of memory\n"); |
| return NULL; |
| } |
| memcpy (copy, rs, sizeof (*copy)); |
| if (first) |
| prev->next = copy; |
| else |
| first = copy; |
| rs = rs->next; |
| prev = copy; |
| } |
| return first; |
| } |
| |
| /* Free all stacked register states (but not RS itself). */ |
| static void |
| free_state_stack (struct ia64_reg_state *rs) |
| { |
| struct ia64_reg_state *p, *next; |
| |
| for (p = rs->next; p != NULL; p = next) |
| { |
| next = p->next; |
| free_reg_state (p); |
| } |
| rs->next = NULL; |
| } |
| |
| /* Unwind decoder routines */ |
| |
| static enum ia64_pregnum __attribute__ ((const)) |
| decode_abreg (unsigned char abreg, int memory) |
| { |
| switch (abreg) |
| { |
| case 0x04 ... 0x07: |
| return IA64_REG_R4 + (abreg - 0x04); |
| case 0x22 ... 0x25: |
| return IA64_REG_F2 + (abreg - 0x22); |
| case 0x30 ... 0x3f: |
| return IA64_REG_F16 + (abreg - 0x30); |
| case 0x41 ... 0x45: |
| return IA64_REG_B1 + (abreg - 0x41); |
| case 0x60: |
| return IA64_REG_PR; |
| case 0x61: |
| return IA64_REG_PSP; |
| case 0x62: |
| return memory ? IA64_REG_PRI_UNAT_MEM : IA64_REG_PRI_UNAT_GR; |
| case 0x63: |
| return IA64_REG_IP; |
| case 0x64: |
| return IA64_REG_BSP; |
| case 0x65: |
| return IA64_REG_BSPSTORE; |
| case 0x66: |
| return IA64_REG_RNAT; |
| case 0x67: |
| return IA64_REG_UNAT; |
| case 0x68: |
| return IA64_REG_FPSR; |
| case 0x69: |
| return IA64_REG_PFS; |
| case 0x6a: |
| return IA64_REG_LC; |
| default: |
| break; |
| } |
| dprintf ("libunwind: bad abreg=0x%x\n", abreg); |
| return IA64_REG_LC; |
| } |
| |
| static void |
| set_reg (struct ia64_reg_info *reg, enum ia64_where where, int when, |
| unsigned long val) |
| { |
| reg->val = val; |
| reg->where = where; |
| if (reg->when == IA64_WHEN_NEVER) |
| reg->when = when; |
| } |
| |
| static void |
| alloc_spill_area (unsigned long *offp, unsigned long regsize, |
| struct ia64_reg_info *lo, struct ia64_reg_info *hi) |
| { |
| struct ia64_reg_info *reg; |
| |
| for (reg = hi; reg >= lo; --reg) |
| { |
| if (reg->where == IA64_WHERE_SPILL_HOME) |
| { |
| reg->where = IA64_WHERE_PSPREL; |
| *offp -= regsize; |
| reg->val = *offp; |
| } |
| } |
| } |
| |
| static inline void |
| spill_next_when (struct ia64_reg_info **regp, struct ia64_reg_info *lim, |
| unw_word t) |
| { |
| struct ia64_reg_info *reg; |
| |
| for (reg = *regp; reg <= lim; ++reg) |
| { |
| if (reg->where == IA64_WHERE_SPILL_HOME) |
| { |
| reg->when = t; |
| *regp = reg + 1; |
| return; |
| } |
| } |
| dprintf ("libunwind: excess spill!\n"); |
| } |
| |
| static inline void |
| finish_prologue (struct ia64_state_record *sr) |
| { |
| struct ia64_reg_info *reg; |
| unsigned long off; |
| int i; |
| |
| /* First, resolve implicit register save locations (see Section |
| "11.4.2.3 Rules for Using Unwind Descriptors", rule 3). */ |
| for (i = 0; i < (int) NELEMS (unw.save_order); ++i) |
| { |
| reg = sr->curr.reg + unw.save_order[i]; |
| if (reg->where == IA64_WHERE_GR_SAVE) |
| { |
| reg->where = IA64_WHERE_GR; |
| reg->val = sr->gr_save_loc++; |
| } |
| } |
| |
| /* Next, compute when the fp, general, and branch registers get |
| saved. This must come before alloc_spill_area() because we need |
| to know which registers are spilled to their home locations. */ |
| |
| if (sr->imask) |
| { |
| unsigned char kind, mask = 0, *cp = sr->imask; |
| unsigned long t; |
| static const unsigned char limit[3] = |
| { |
| IA64_REG_F31, IA64_REG_R7, IA64_REG_B5 |
| }; |
| struct ia64_reg_info *(regs[3]); |
| |
| regs[0] = sr->curr.reg + IA64_REG_F2; |
| regs[1] = sr->curr.reg + IA64_REG_R4; |
| regs[2] = sr->curr.reg + IA64_REG_B1; |
| |
| for (t = 0; (int) t < sr->region_len; ++t) |
| { |
| if ((t & 3) == 0) |
| mask = *cp++; |
| kind = (mask >> 2 * (3 - (t & 3))) & 3; |
| if (kind > 0) |
| spill_next_when (®s[kind - 1], sr->curr.reg + limit[kind - 1], |
| sr->region_start + t); |
| } |
| } |
| |
| /* Next, lay out the memory stack spill area. */ |
| |
| if (sr->any_spills) |
| { |
| off = sr->spill_offset; |
| alloc_spill_area (&off, 16, sr->curr.reg + IA64_REG_F2, |
| sr->curr.reg + IA64_REG_F31); |
| alloc_spill_area (&off, 8, sr->curr.reg + IA64_REG_B1, |
| sr->curr.reg + IA64_REG_B5); |
| alloc_spill_area (&off, 8, sr->curr.reg + IA64_REG_R4, |
| sr->curr.reg + IA64_REG_R7); |
| } |
| } |
| |
| /* Region header descriptors. */ |
| |
| static void |
| desc_prologue (int body, unw_word rlen, unsigned char mask, |
| unsigned char grsave, struct ia64_state_record *sr) |
| { |
| int i, region_start; |
| |
| if (!(sr->in_body || sr->first_region)) |
| finish_prologue (sr); |
| sr->first_region = 0; |
| |
| /* check if we're done: */ |
| if (sr->when_target < sr->region_start + sr->region_len) |
| { |
| sr->done = 1; |
| return; |
| } |
| |
| region_start = sr->region_start + sr->region_len; |
| |
| for (i = 0; i < sr->epilogue_count; ++i) |
| pop (sr); |
| sr->epilogue_count = 0; |
| sr->when_sp_restored = IA64_WHEN_NEVER; |
| |
| sr->region_start = region_start; |
| sr->region_len = rlen; |
| sr->in_body = body; |
| |
| if (!body) |
| { |
| push (sr); |
| |
| if (mask) |
| for (i = 0; i < 4; ++i) |
| { |
| if (mask & 0x8) |
| set_reg (sr->curr.reg + unw.save_order[i], IA64_WHERE_GR, |
| sr->region_start + sr->region_len - 1, grsave++); |
| mask <<= 1; |
| } |
| sr->gr_save_loc = grsave; |
| sr->any_spills = 0; |
| sr->imask = 0; |
| sr->spill_offset = 0x10; /* default to psp+16 */ |
| } |
| } |
| |
| /* Prologue descriptors. */ |
| |
| static inline void |
| desc_abi (unsigned char abi, unsigned char context, |
| struct ia64_state_record *sr) |
| { |
| sr->abi_marker = (abi << 8) | context; |
| } |
| |
| static inline void |
| desc_br_gr (unsigned char brmask, unsigned char gr, |
| struct ia64_state_record *sr) |
| { |
| int i; |
| |
| for (i = 0; i < 5; ++i) |
| { |
| if (brmask & 1) |
| set_reg (sr->curr.reg + IA64_REG_B1 + i, IA64_WHERE_GR, |
| sr->region_start + sr->region_len - 1, gr++); |
| brmask >>= 1; |
| } |
| } |
| |
| static inline void |
| desc_br_mem (unsigned char brmask, struct ia64_state_record *sr) |
| { |
| int i; |
| |
| for (i = 0; i < 5; ++i) |
| { |
| if (brmask & 1) |
| { |
| set_reg (sr->curr.reg + IA64_REG_B1 + i, IA64_WHERE_SPILL_HOME, |
| sr->region_start + sr->region_len - 1, 0); |
| sr->any_spills = 1; |
| } |
| brmask >>= 1; |
| } |
| } |
| |
| static inline void |
| desc_frgr_mem (unsigned char grmask, unw_word frmask, |
| struct ia64_state_record *sr) |
| { |
| int i; |
| |
| for (i = 0; i < 4; ++i) |
| { |
| if ((grmask & 1) != 0) |
| { |
| set_reg (sr->curr.reg + IA64_REG_R4 + i, IA64_WHERE_SPILL_HOME, |
| sr->region_start + sr->region_len - 1, 0); |
| sr->any_spills = 1; |
| } |
| grmask >>= 1; |
| } |
| for (i = 0; i < 20; ++i) |
| { |
| if ((frmask & 1) != 0) |
| { |
| int base = (i < 4) ? IA64_REG_F2 : IA64_REG_F16 - 4; |
| set_reg (sr->curr.reg + base + i, IA64_WHERE_SPILL_HOME, |
| sr->region_start + sr->region_len - 1, 0); |
| sr->any_spills = 1; |
| } |
| frmask >>= 1; |
| } |
| } |
| |
| static inline void |
| desc_fr_mem (unsigned char frmask, struct ia64_state_record *sr) |
| { |
| int i; |
| |
| for (i = 0; i < 4; ++i) |
| { |
| if ((frmask & 1) != 0) |
| { |
| set_reg (sr->curr.reg + IA64_REG_F2 + i, IA64_WHERE_SPILL_HOME, |
| sr->region_start + sr->region_len - 1, 0); |
| sr->any_spills = 1; |
| } |
| frmask >>= 1; |
| } |
| } |
| |
| static inline void |
| desc_gr_gr (unsigned char grmask, unsigned char gr, |
| struct ia64_state_record *sr) |
| { |
| int i; |
| |
| for (i = 0; i < 4; ++i) |
| { |
| if ((grmask & 1) != 0) |
| set_reg (sr->curr.reg + IA64_REG_R4 + i, IA64_WHERE_GR, |
| sr->region_start + sr->region_len - 1, gr++); |
| grmask >>= 1; |
| } |
| } |
| |
| static inline void |
| desc_gr_mem (unsigned char grmask, struct ia64_state_record *sr) |
| { |
| int i; |
| |
| for (i = 0; i < 4; ++i) |
| { |
| if ((grmask & 1) != 0) |
| { |
| set_reg (sr->curr.reg + IA64_REG_R4 + i, IA64_WHERE_SPILL_HOME, |
| sr->region_start + sr->region_len - 1, 0); |
| sr->any_spills = 1; |
| } |
| grmask >>= 1; |
| } |
| } |
| |
| static inline void |
| desc_mem_stack_f (unw_word t, unw_word size, struct ia64_state_record *sr) |
| { |
| set_reg (sr->curr.reg + IA64_REG_PSP, IA64_WHERE_NONE, |
| sr->region_start + MIN ((int) t, sr->region_len - 1), 16 * size); |
| } |
| |
| static inline void |
| desc_mem_stack_v (unw_word t, struct ia64_state_record *sr) |
| { |
| sr->curr.reg[IA64_REG_PSP].when = |
| sr->region_start + MIN ((int) t, sr->region_len - 1); |
| } |
| |
| static inline void |
| desc_reg_gr (unsigned char reg, unsigned char dst, |
| struct ia64_state_record *sr) |
| { |
| set_reg (sr->curr.reg + reg, IA64_WHERE_GR, |
| sr->region_start + sr->region_len - 1, dst); |
| } |
| |
| static inline void |
| desc_reg_psprel (unsigned char reg, unw_word pspoff, |
| struct ia64_state_record *sr) |
| { |
| set_reg (sr->curr.reg + reg, IA64_WHERE_PSPREL, |
| sr->region_start + sr->region_len - 1, 0x10 - 4 * pspoff); |
| } |
| |
| static inline void |
| desc_reg_sprel (unsigned char reg, unw_word spoff, |
| struct ia64_state_record *sr) |
| { |
| set_reg (sr->curr.reg + reg, IA64_WHERE_SPREL, |
| sr->region_start + sr->region_len - 1, 4 * spoff); |
| } |
| |
| static inline void |
| desc_rp_br (unsigned char dst, struct ia64_state_record *sr) |
| { |
| sr->return_link_reg = dst; |
| } |
| |
| static inline void |
| desc_reg_when (unsigned char regnum, unw_word t, struct ia64_state_record *sr) |
| { |
| struct ia64_reg_info *reg = sr->curr.reg + regnum; |
| |
| if (reg->where == IA64_WHERE_NONE) |
| reg->where = IA64_WHERE_GR_SAVE; |
| reg->when = sr->region_start + MIN ((int) t, sr->region_len - 1); |
| } |
| |
| static inline void |
| desc_spill_base (unw_word pspoff, struct ia64_state_record *sr) |
| { |
| sr->spill_offset = 0x10 - 4 * pspoff; |
| } |
| |
| static inline unsigned char * |
| desc_spill_mask (unsigned char *imaskp, struct ia64_state_record *sr) |
| { |
| sr->imask = imaskp; |
| return imaskp + (2 * sr->region_len + 7) / 8; |
| } |
| |
| /* Body descriptors. */ |
| |
| static inline void |
| desc_epilogue (unw_word t, unw_word ecount, struct ia64_state_record *sr) |
| { |
| sr->when_sp_restored = sr->region_start + sr->region_len - 1 - t; |
| sr->epilogue_count = ecount + 1; |
| } |
| |
| static inline void |
| desc_copy_state (unw_word label, struct ia64_state_record *sr) |
| { |
| struct ia64_labeled_state *ls; |
| |
| for (ls = sr->labeled_states; ls; ls = ls->next) |
| { |
| if (ls->label == label) |
| { |
| free_state_stack (&sr->curr); |
| memcpy (&sr->curr, &ls->saved_state, sizeof (sr->curr)); |
| sr->curr.next = dup_state_stack (ls->saved_state.next); |
| return; |
| } |
| } |
| print_error ("libunwind: failed to find labeled state\n"); |
| } |
| |
| static inline void |
| desc_label_state (unw_word label, struct ia64_state_record *sr) |
| { |
| struct ia64_labeled_state *ls; |
| |
| ls = alloc_labeled_state (); |
| if (!ls) |
| { |
| print_error ("unwind.desc_label_state(): out of memory\n"); |
| return; |
| } |
| ls->label = label; |
| memcpy (&ls->saved_state, &sr->curr, sizeof (ls->saved_state)); |
| ls->saved_state.next = dup_state_stack (sr->curr.next); |
| |
| /* insert into list of labeled states: */ |
| ls->next = sr->labeled_states; |
| sr->labeled_states = ls; |
| } |
| |
| /* General descriptors. */ |
| |
| static inline int |
| desc_is_active (unsigned char qp, unw_word t, struct ia64_state_record *sr) |
| { |
| if (sr->when_target <= sr->region_start + MIN ((int) t, sr->region_len - 1)) |
| return 0; |
| if (qp > 0) |
| { |
| if ((sr->pr_val & ((unw_word_t) 1 << qp)) == 0) |
| return 0; |
| sr->pr_mask |= ((unw_word_t) 1 << qp); |
| } |
| return 1; |
| } |
| |
| static inline void |
| desc_restore_p (unsigned char qp, unw_word t, unsigned char abreg, |
| struct ia64_state_record *sr) |
| { |
| struct ia64_reg_info *r; |
| |
| if (!desc_is_active (qp, t, sr)) |
| return; |
| |
| r = sr->curr.reg + decode_abreg (abreg, 0); |
| r->where = IA64_WHERE_NONE; |
| r->when = IA64_WHEN_NEVER; |
| r->val = 0; |
| } |
| |
| static inline void |
| desc_spill_reg_p (unsigned char qp, unw_word t, unsigned char abreg, |
| unsigned char x, unsigned char ytreg, |
| struct ia64_state_record *sr) |
| { |
| enum ia64_where where = IA64_WHERE_GR; |
| struct ia64_reg_info *r; |
| |
| if (!desc_is_active (qp, t, sr)) |
| return; |
| |
| if (x) |
| where = IA64_WHERE_BR; |
| else if (ytreg & 0x80) |
| where = IA64_WHERE_FR; |
| |
| r = sr->curr.reg + decode_abreg (abreg, 0); |
| r->where = where; |
| r->when = sr->region_start + MIN ((int) t, sr->region_len - 1); |
| r->val = (ytreg & 0x7f); |
| } |
| |
| static inline void |
| desc_spill_psprel_p (unsigned char qp, unw_word t, unsigned char abreg, |
| unw_word pspoff, struct ia64_state_record *sr) |
| { |
| struct ia64_reg_info *r; |
| |
| if (!desc_is_active (qp, t, sr)) |
| return; |
| |
| r = sr->curr.reg + decode_abreg (abreg, 1); |
| r->where = IA64_WHERE_PSPREL; |
| r->when = sr->region_start + MIN ((int) t, sr->region_len - 1); |
| r->val = 0x10 - 4 * pspoff; |
| } |
| |
| static inline void |
| desc_spill_sprel_p (unsigned char qp, unw_word t, unsigned char abreg, |
| unw_word spoff, struct ia64_state_record *sr) |
| { |
| struct ia64_reg_info *r; |
| |
| if (!desc_is_active (qp, t, sr)) |
| return; |
| |
| r = sr->curr.reg + decode_abreg (abreg, 1); |
| r->where = IA64_WHERE_SPREL; |
| r->when = sr->region_start + MIN ((int) t, sr->region_len - 1); |
| r->val = 4 * spoff; |
| } |
| |
| #define UNW_DEC_BAD_CODE(code) \ |
| print_error ("libunwind: unknown code encountered\n") |
| |
| /* Register names. */ |
| #define UNW_REG_BSP IA64_REG_BSP |
| #define UNW_REG_BSPSTORE IA64_REG_BSPSTORE |
| #define UNW_REG_FPSR IA64_REG_FPSR |
| #define UNW_REG_LC IA64_REG_LC |
| #define UNW_REG_PFS IA64_REG_PFS |
| #define UNW_REG_PR IA64_REG_PR |
| #define UNW_REG_RNAT IA64_REG_RNAT |
| #define UNW_REG_PSP IA64_REG_PSP |
| #define UNW_REG_RP IA64_REG_IP |
| #define UNW_REG_UNAT IA64_REG_UNAT |
| |
| /* Region headers. */ |
| #define UNW_DEC_PROLOGUE_GR(fmt,r,m,gr,arg) desc_prologue(0,r,m,gr,arg) |
| #define UNW_DEC_PROLOGUE(fmt,b,r,arg) desc_prologue(b,r,0,32,arg) |
| |
| /* Prologue descriptors. */ |
| #define UNW_DEC_ABI(fmt,a,c,arg) desc_abi(a,c,arg) |
| #define UNW_DEC_BR_GR(fmt,b,g,arg) desc_br_gr(b,g,arg) |
| #define UNW_DEC_BR_MEM(fmt,b,arg) desc_br_mem(b,arg) |
| #define UNW_DEC_FRGR_MEM(fmt,g,f,arg) desc_frgr_mem(g,f,arg) |
| #define UNW_DEC_FR_MEM(fmt,f,arg) desc_fr_mem(f,arg) |
| #define UNW_DEC_GR_GR(fmt,m,g,arg) desc_gr_gr(m,g,arg) |
| #define UNW_DEC_GR_MEM(fmt,m,arg) desc_gr_mem(m,arg) |
| #define UNW_DEC_MEM_STACK_F(fmt,t,s,arg) desc_mem_stack_f(t,s,arg) |
| #define UNW_DEC_MEM_STACK_V(fmt,t,arg) desc_mem_stack_v(t,arg) |
| #define UNW_DEC_REG_GR(fmt,r,d,arg) desc_reg_gr(r,d,arg) |
| #define UNW_DEC_REG_PSPREL(fmt,r,o,arg) desc_reg_psprel(r,o,arg) |
| #define UNW_DEC_REG_SPREL(fmt,r,o,arg) desc_reg_sprel(r,o,arg) |
| #define UNW_DEC_REG_WHEN(fmt,r,t,arg) desc_reg_when(r,t,arg) |
| #define UNW_DEC_PRIUNAT_WHEN_GR(fmt,t,arg) \ |
| desc_reg_when(IA64_REG_PRI_UNAT_GR,t,arg) |
| #define UNW_DEC_PRIUNAT_WHEN_MEM(fmt,t,arg) \ |
| desc_reg_when(IA64_REG_PRI_UNAT_MEM,t,arg) |
| #define UNW_DEC_PRIUNAT_GR(fmt,r,arg) \ |
| desc_reg_gr(IA64_REG_PRI_UNAT_GR,r,arg) |
| #define UNW_DEC_PRIUNAT_PSPREL(fmt,o,arg) \ |
| desc_reg_psprel(IA64_REG_PRI_UNAT_MEM,o,arg) |
| #define UNW_DEC_PRIUNAT_SPREL(fmt,o,arg) \ |
| desc_reg_sprel(IA64_REG_PRI_UNAT_MEM,o,arg) |
| #define UNW_DEC_RP_BR(fmt,d,arg) desc_rp_br(d,arg) |
| #define UNW_DEC_SPILL_BASE(fmt,o,arg) desc_spill_base(o,arg) |
| #define UNW_DEC_SPILL_MASK(fmt,m,arg) (m = desc_spill_mask(m,arg)) |
| |
| /* Body descriptors. */ |
| #define UNW_DEC_EPILOGUE(fmt,t,c,arg) desc_epilogue(t,c,arg) |
| #define UNW_DEC_COPY_STATE(fmt,l,arg) desc_copy_state(l,arg) |
| #define UNW_DEC_LABEL_STATE(fmt,l,arg) desc_label_state(l,arg) |
| |
| /* General unwind descriptors. */ |
| #define UNW_DEC_SPILL_REG_P(f,p,t,a,x,y,arg) desc_spill_reg_p(p,t,a,x,y,arg) |
| #define UNW_DEC_SPILL_REG(f,t,a,x,y,arg) desc_spill_reg_p(0,t,a,x,y,arg) |
| #define UNW_DEC_SPILL_PSPREL_P(f,p,t,a,o,arg) \ |
| desc_spill_psprel_p(p,t,a,o,arg) |
| #define UNW_DEC_SPILL_PSPREL(f,t,a,o,arg) \ |
| desc_spill_psprel_p(0,t,a,o,arg) |
| #define UNW_DEC_SPILL_SPREL_P(f,p,t,a,o,arg) desc_spill_sprel_p(p,t,a,o,arg) |
| #define UNW_DEC_SPILL_SPREL(f,t,a,o,arg) desc_spill_sprel_p(0,t,a,o,arg) |
| #define UNW_DEC_RESTORE_P(f,p,t,a,arg) desc_restore_p(p,t,a,arg) |
| #define UNW_DEC_RESTORE(f,t,a,arg) desc_restore_p(0,t,a,arg) |
| |
| #include "unwind_decoder.h" |
| |
| /* parse dynamic unwind info */ |
| |
| static struct ia64_reg_info * |
| lookup_preg (int regnum, int memory, struct ia64_state_record *sr) |
| { |
| int preg; |
| |
| switch (regnum) |
| { |
| case UNW_IA64_AR_BSP: preg = IA64_REG_BSP; break; |
| case UNW_IA64_AR_BSPSTORE: preg = IA64_REG_BSPSTORE; break; |
| case UNW_IA64_AR_FPSR: preg = IA64_REG_FPSR; break; |
| case UNW_IA64_AR_LC: preg = IA64_REG_LC; break; |
| case UNW_IA64_AR_PFS: preg = IA64_REG_PFS; break; |
| case UNW_IA64_AR_RNAT: preg = IA64_REG_RNAT; break; |
| case UNW_IA64_AR_UNAT: preg = IA64_REG_UNAT; break; |
| case UNW_IA64_BR + 0: preg = IA64_REG_IP; break; |
| case UNW_IA64_PR: preg = IA64_REG_PR; break; |
| case UNW_IA64_SP: preg = IA64_REG_PSP; break; |
| |
| case UNW_IA64_NAT: |
| if (memory) |
| preg = IA64_REG_PRI_UNAT_MEM; |
| else |
| preg = IA64_REG_PRI_UNAT_GR; |
| break; |
| |
| case UNW_IA64_GR + 4 ... UNW_IA64_GR + 7: |
| preg = IA64_REG_R4 + (regnum - (UNW_IA64_GR + 4)); |
| break; |
| |
| case UNW_IA64_BR + 1 ... UNW_IA64_BR + 5: |
| preg = IA64_REG_B1 + (regnum - UNW_IA64_BR); |
| break; |
| |
| case UNW_IA64_FR + 2 ... UNW_IA64_FR + 5: |
| preg = IA64_REG_F2 + (regnum - (UNW_IA64_FR + 2)); |
| break; |
| |
| case UNW_IA64_FR + 16 ... UNW_IA64_FR + 31: |
| preg = IA64_REG_F16 + (regnum - (UNW_IA64_FR + 16)); |
| break; |
| |
| default: |
| dprintf ("%s: invalid register number %d\n", __FUNCTION__, regnum); |
| return NULL; |
| } |
| return sr->curr.reg + preg; |
| } |
| |
| /* An alias directive inside a region of length RLEN is interpreted to |
| mean that the region behaves exactly like the first RLEN |
| instructions at the aliased IP. RLEN=0 implies that the current |
| state matches exactly that of the aliased IP. */ |
| |
| static int |
| desc_alias (unw_dyn_op_t *op, struct cursor *c, struct ia64_state_record *sr) |
| { |
| struct ia64_state_record orig_sr = *sr; |
| int i, ret, when, rlen = sr->region_len; |
| unw_word_t new_ip; |
| |
| when = MIN(sr->when_target, rlen - 1); |
| new_ip = op->val + ((when / 3) * 16 + (when % 3)); |
| |
| if ((ret = ia64_fetch_proc_info (c, new_ip, 1)) < 0) |
| return ret; |
| |
| if ((ret = create_state_record_for (c, sr, new_ip)) < 0) |
| return ret; |
| |
| sr->first_region = orig_sr.first_region; |
| sr->done = 0; |
| sr->any_spills |= orig_sr.any_spills; |
| sr->in_body = orig_sr.in_body; |
| sr->region_start = orig_sr.region_start; |
| sr->region_len = orig_sr.region_len; |
| if (sr->when_sp_restored != IA64_WHEN_NEVER) |
| sr->when_sp_restored = op->when + MIN (orig_sr.when_sp_restored, rlen - 1); |
| sr->epilogue_count = orig_sr.epilogue_count; |
| sr->when_target = orig_sr.when_target; |
| |
| for (i = 0; i < IA64_NUM_PREGS; ++i) |
| if (sr->curr.reg[i].when != IA64_WHEN_NEVER) |
| sr->curr.reg[i].when = op->when + MIN (sr->curr.reg[i].when, rlen - 1); |
| |
| ia64_free_state_record (sr); |
| sr->labeled_states = orig_sr.labeled_states; |
| sr->curr.next = orig_sr.curr.next; |
| return 0; |
| } |
| |
| static inline int |
| parse_dynamic (struct cursor *c, struct ia64_state_record *sr) |
| { |
| unw_dyn_info_t *di = c->pi.unwind_info; |
| unw_dyn_proc_info_t *proc = &di->u.pi; |
| unw_dyn_region_info_t *r; |
| struct ia64_reg_info *ri; |
| enum ia64_where where; |
| int32_t when, len; |
| unw_dyn_op_t *op; |
| unw_word_t val; |
| int memory, ret; |
| int8_t qp; |
| |
| for (r = proc->regions; r; r = r->next) |
| { |
| len = r->insn_count; |
| if (len < 0) |
| { |
| if (r->next) |
| { |
| Debug (1, "negative region length allowed in last region only!"); |
| return -UNW_EINVAL; |
| } |
| len = -len; |
| /* hack old region info to set the start where we need it: */ |
| sr->region_start = (di->end_ip - di->start_ip) / 0x10 * 3 - len; |
| sr->region_len = 0; |
| } |
| /* all regions are treated as prologue regions: */ |
| desc_prologue (0, len, 0, 0, sr); |
| |
| if (sr->done) |
| return 0; |
| |
| for (op = r->op; op < r->op + r->op_count; ++op) |
| { |
| when = op->when; |
| val = op->val; |
| qp = op->qp; |
| |
| if (!desc_is_active (qp, when, sr)) |
| continue; |
| |
| when = sr->region_start + MIN ((int) when, sr->region_len - 1); |
| |
| switch (op->tag) |
| { |
| case UNW_DYN_SAVE_REG: |
| memory = 0; |
| if ((unsigned) (val - UNW_IA64_GR) < 128) |
| where = IA64_WHERE_GR; |
| else if ((unsigned) (val - UNW_IA64_FR) < 128) |
| where = IA64_WHERE_FR; |
| else if ((unsigned) (val - UNW_IA64_BR) < 8) |
| where = IA64_WHERE_BR; |
| else |
| { |
| dprintf ("%s: can't save to register number %d\n", |
| __FUNCTION__, (int) op->reg); |
| return -UNW_EBADREG; |
| } |
| /* fall through */ |
| update_reg_info: |
| ri = lookup_preg (op->reg, memory, sr); |
| if (!ri) |
| return -UNW_EBADREG; |
| ri->where = where; |
| ri->when = when; |
| ri->val = val; |
| break; |
| |
| case UNW_DYN_SPILL_FP_REL: |
| memory = 1; |
| where = IA64_WHERE_PSPREL; |
| val = 0x10 - val; |
| goto update_reg_info; |
| |
| case UNW_DYN_SPILL_SP_REL: |
| memory = 1; |
| where = IA64_WHERE_SPREL; |
| goto update_reg_info; |
| |
| case UNW_DYN_ADD: |
| if (op->reg == UNW_IA64_SP) |
| { |
| if (val & 0xf) |
| { |
| dprintf ("%s: frame-size %ld not an integer " |
| "multiple of 16\n", |
| __FUNCTION__, (long) op->val); |
| return -UNW_EINVAL; |
| } |
| desc_mem_stack_f (when, -((int64_t) val / 16), sr); |
| } |
| else |
| { |
| dprintf ("%s: can only ADD to stack-pointer\n", |
| __FUNCTION__); |
| return -UNW_EBADREG; |
| } |
| break; |
| |
| case UNW_DYN_POP_FRAMES: |
| sr->when_sp_restored = when; |
| sr->epilogue_count = op->val; |
| break; |
| |
| case UNW_DYN_LABEL_STATE: |
| desc_label_state (op->val, sr); |
| break; |
| |
| case UNW_DYN_COPY_STATE: |
| desc_copy_state (op->val, sr); |
| break; |
| |
| case UNW_DYN_ALIAS: |
| if ((ret = desc_alias (op, c, sr)) < 0) |
| return ret; |
| |
| case UNW_DYN_STOP: |
| goto end_of_ops; |
| } |
| } |
| end_of_ops: |
| ; |
| } |
| return 0; |
| } |
| |
| |
| HIDDEN int |
| ia64_fetch_proc_info (struct cursor *c, unw_word_t ip, int need_unwind_info) |
| { |
| int ret, dynamic = 1; |
| |
| if (c->pi_valid && !need_unwind_info) |
| return 0; |
| |
| /* check dynamic info first --- it overrides everything else */ |
| ret = unwi_find_dynamic_proc_info (c->as, ip, &c->pi, need_unwind_info, |
| c->as_arg); |
| if (ret == -UNW_ENOINFO) |
| { |
| dynamic = 0; |
| ret = ia64_find_proc_info (c, ip, need_unwind_info); |
| } |
| |
| c->pi_valid = 1; |
| c->pi_is_dynamic = dynamic; |
| return ret; |
| } |
| |
| static inline void |
| put_unwind_info (struct cursor *c, unw_proc_info_t *pi) |
| { |
| if (!c->pi_valid) |
| return; |
| |
| if (c->pi_is_dynamic) |
| unwi_put_dynamic_unwind_info (c->as, pi, c->as_arg); |
| else |
| ia64_put_unwind_info (c, pi); |
| } |
| |
| static int |
| create_state_record_for (struct cursor *c, struct ia64_state_record *sr, |
| unw_word_t ip) |
| { |
| unw_word_t predicates = c->pr; |
| struct ia64_reg_info *r; |
| uint8_t *dp, *desc_end; |
| int ret; |
| |
| assert (c->pi_valid); |
| |
| /* build state record */ |
| memset (sr, 0, sizeof (*sr)); |
| for (r = sr->curr.reg; r < sr->curr.reg + IA64_NUM_PREGS; ++r) |
| r->when = IA64_WHEN_NEVER; |
| sr->pr_val = predicates; |
| sr->first_region = 1; |
| |
| if (!c->pi.unwind_info) |
| { |
| /* No info, return default unwinder (leaf proc, no mem stack, no |
| saved regs), rp in b0, pfs in ar.pfs. */ |
| Debug (1, "no unwind info for ip=0x%lx (gp=%lx)\n", |
| (long) ip, (long) c->pi.gp); |
| sr->curr.reg[IA64_REG_IP].where = IA64_WHERE_BR; |
| sr->curr.reg[IA64_REG_IP].when = -1; |
| sr->curr.reg[IA64_REG_IP].val = 0; |
| goto out; |
| } |
| |
| sr->when_target = (3 * ((ip & ~(unw_word_t) 0xf) - c->pi.start_ip) / 16 |
| + (ip & 0xf)); |
| |
| switch (c->pi.format) |
| { |
| case UNW_INFO_FORMAT_TABLE: |
| case UNW_INFO_FORMAT_REMOTE_TABLE: |
| dp = c->pi.unwind_info; |
| desc_end = dp + c->pi.unwind_info_size; |
| while (!sr->done && dp < desc_end) |
| dp = unw_decode (dp, sr->in_body, sr); |
| ret = 0; |
| break; |
| |
| case UNW_INFO_FORMAT_DYNAMIC: |
| ret = parse_dynamic (c, sr); |
| break; |
| |
| default: |
| ret = -UNW_EINVAL; |
| } |
| |
| put_unwind_info (c, &c->pi); |
| |
| if (ret < 0) |
| return ret; |
| |
| if (sr->when_target > sr->when_sp_restored) |
| { |
| /* sp has been restored and all values on the memory stack below |
| psp also have been restored. */ |
| sr->curr.reg[IA64_REG_PSP].val = 0; |
| sr->curr.reg[IA64_REG_PSP].where = IA64_WHERE_NONE; |
| sr->curr.reg[IA64_REG_PSP].when = IA64_WHEN_NEVER; |
| for (r = sr->curr.reg; r < sr->curr.reg + IA64_NUM_PREGS; ++r) |
| if ((r->where == IA64_WHERE_PSPREL && r->val <= 0x10) |
| || r->where == IA64_WHERE_SPREL) |
| { |
| r->val = 0; |
| r->where = IA64_WHERE_NONE; |
| r->when = IA64_WHEN_NEVER; |
| } |
| } |
| |
| /* If RP did't get saved, generate entry for the return link |
| register. */ |
| if (sr->curr.reg[IA64_REG_IP].when >= sr->when_target) |
| { |
| sr->curr.reg[IA64_REG_IP].where = IA64_WHERE_BR; |
| sr->curr.reg[IA64_REG_IP].when = -1; |
| sr->curr.reg[IA64_REG_IP].val = sr->return_link_reg; |
| } |
| |
| if (sr->when_target > sr->curr.reg[IA64_REG_BSP].when |
| && sr->when_target > sr->curr.reg[IA64_REG_BSPSTORE].when |
| && sr->when_target > sr->curr.reg[IA64_REG_RNAT].when) |
| { |
| Debug (8, "func 0x%lx may switch the register-backing-store\n", |
| c->pi.start_ip); |
| c->pi.flags |= UNW_PI_FLAG_IA64_RBS_SWITCH; |
| } |
| out: |
| #if UNW_DEBUG |
| if (unwi_debug_level > 0) |
| { |
| dprintf ("libunwind: state record for func 0x%lx, t=%u (flags=0x%lx):\n", |
| (long) c->pi.start_ip, sr->when_target, (long) c->pi.flags); |
| for (r = sr->curr.reg; r < sr->curr.reg + IA64_NUM_PREGS; ++r) |
| { |
| if (r->where != IA64_WHERE_NONE || r->when != IA64_WHEN_NEVER) |
| { |
| dprintf (" %s <- ", unw.preg_name[r - sr->curr.reg]); |
| switch (r->where) |
| { |
| case IA64_WHERE_GR: |
| dprintf ("r%lu", (long) r->val); |
| break; |
| case IA64_WHERE_FR: |
| dprintf ("f%lu", (long) r->val); |
| break; |
| case IA64_WHERE_BR: |
| dprintf ("b%lu", (long) r->val); |
| break; |
| case IA64_WHERE_SPREL: |
| dprintf ("[sp+0x%lx]", (long) r->val); |
| break; |
| case IA64_WHERE_PSPREL: |
| dprintf ("[psp+0x%lx]", (long) r->val); |
| break; |
| case IA64_WHERE_NONE: |
| dprintf ("%s+0x%lx", |
| unw.preg_name[r - sr->curr.reg], (long) r->val); |
| break; |
| default: |
| dprintf ("BADWHERE(%d)", r->where); |
| break; |
| } |
| dprintf ("\t\t%d\n", r->when); |
| } |
| } |
| } |
| #endif |
| return 0; |
| } |
| |
| /* The proc-info must be valid for IP before this routine can be |
| called. */ |
| HIDDEN int |
| ia64_create_state_record (struct cursor *c, struct ia64_state_record *sr) |
| { |
| return create_state_record_for (c, sr, c->ip); |
| } |
| |
| HIDDEN int |
| ia64_free_state_record (struct ia64_state_record *sr) |
| { |
| struct ia64_labeled_state *ls, *next; |
| |
| /* free labeled register states & stack: */ |
| |
| for (ls = sr->labeled_states; ls; ls = next) |
| { |
| next = ls->next; |
| free_state_stack (&ls->saved_state); |
| free_labeled_state (ls); |
| } |
| free_state_stack (&sr->curr); |
| |
| return 0; |
| } |
| |
| HIDDEN int |
| ia64_make_proc_info (struct cursor *c) |
| { |
| if (c->as->caching_policy == UNW_CACHE_NONE |
| || ia64_get_cached_proc_info (c) < 0) |
| /* Lookup it up the slow way... */ |
| return ia64_fetch_proc_info (c, c->ip, 0); |
| return 0; |
| } |