diff options
Diffstat (limited to 'gdb/ia64-tdep.c')
-rw-r--r-- | gdb/ia64-tdep.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/gdb/ia64-tdep.c b/gdb/ia64-tdep.c index 4d0f8a8ec89..d3c86fda506 100644 --- a/gdb/ia64-tdep.c +++ b/gdb/ia64-tdep.c @@ -36,8 +36,14 @@ #include "objfiles.h" #include "elf/common.h" /* for DT_PLTGOT value */ #include "elf-bfd.h" +#include "elf.h" /* for PT_IA64_UNWIND value */ #include "dis-asm.h" +#ifdef HAVE_LIBUNWIND_IA64_H +#include "libunwind-frame.h" +#include "libunwind-ia64.h" +#endif + /* Hook for determining the global pointer when calling functions in the inferior under AIX. The initialization code in ia64-aix-nat.c sets this hook to the address of a function which will find the @@ -87,6 +93,7 @@ typedef enum instruction_type /* FIXME: These extern declarations should go in ia64-tdep.h. */ extern CORE_ADDR ia64_linux_sigcontext_register_address (CORE_ADDR, int); extern CORE_ADDR ia64_aix_sigcontext_register_address (CORE_ADDR, int); +extern unsigned long ia64_linux_getunwind_table (void *, size_t); static gdbarch_init_ftype ia64_gdbarch_init; @@ -2099,6 +2106,614 @@ static const struct frame_base ia64_frame_base = ia64_frame_base_address }; +#ifdef HAVE_LIBUNWIND_IA64_H + +struct ia64_unwind_table_entry + { + unw_word_t start_offset; + unw_word_t end_offset; + unw_word_t info_offset; + }; + +static __inline__ uint64_t +ia64_rse_slot_num (uint64_t addr) +{ + return (addr >> 3) & 0x3f; +} + +/* Skip over a designated number of registers in the backing + store, remembering every 64th position is for NAT. */ +static __inline__ uint64_t +ia64_rse_skip_regs (uint64_t addr, long num_regs) +{ + long delta = ia64_rse_slot_num(addr) + num_regs; + + if (num_regs < 0) + delta -= 0x3e; + return addr + ((num_regs + delta/0x3f) << 3); +} + +/* Gdb libunwind-frame callback function to convert from an ia64 gdb register + number to a libunwind register number. */ +static int +ia64_gdb2uw_regnum (int regnum) +{ + if (regnum == sp_regnum) + return UNW_IA64_SP; + else if (regnum == IA64_BSP_REGNUM) + return UNW_IA64_BSP; + else if ((unsigned) (regnum - IA64_GR0_REGNUM) < 128) + return UNW_IA64_GR + (regnum - IA64_GR0_REGNUM); + else if ((unsigned) (regnum - V32_REGNUM) < 95) + return UNW_IA64_GR + 32 + (regnum - V32_REGNUM); + else if ((unsigned) (regnum - IA64_FR0_REGNUM) < 128) + return UNW_IA64_FR + (regnum - IA64_FR0_REGNUM); + else if ((unsigned) (regnum - IA64_PR0_REGNUM) < 64) + return -1; + else if ((unsigned) (regnum - IA64_BR0_REGNUM) < 8) + return UNW_IA64_BR + (regnum - IA64_BR0_REGNUM); + else if (regnum == IA64_PR_REGNUM) + return UNW_IA64_PR; + else if (regnum == IA64_IP_REGNUM) + return UNW_REG_IP; + else if (regnum == IA64_CFM_REGNUM) + return UNW_IA64_CFM; + else if ((unsigned) (regnum - IA64_AR0_REGNUM) < 128) + return UNW_IA64_AR + (regnum - IA64_AR0_REGNUM); + else if ((unsigned) (regnum - IA64_NAT0_REGNUM) < 128) + return UNW_IA64_NAT + (regnum - IA64_NAT0_REGNUM); + else + return -1; +} + +/* Gdb libunwind-frame callback function to convert from a libunwind register + number to a ia64 gdb register number. */ +static int +ia64_uw2gdb_regnum (int uw_regnum) +{ + if (uw_regnum == UNW_IA64_SP) + return sp_regnum; + else if (uw_regnum == UNW_IA64_BSP) + return IA64_BSP_REGNUM; + else if ((unsigned) (uw_regnum - UNW_IA64_GR) < 32) + return IA64_GR0_REGNUM + (uw_regnum - UNW_IA64_GR); + else if ((unsigned) (uw_regnum - UNW_IA64_GR) < 128) + return V32_REGNUM + (uw_regnum - (IA64_GR0_REGNUM + 32)); + else if ((unsigned) (uw_regnum - UNW_IA64_FR) < 128) + return IA64_FR0_REGNUM + (uw_regnum - UNW_IA64_FR); + else if ((unsigned) (uw_regnum - UNW_IA64_BR) < 8) + return IA64_BR0_REGNUM + (uw_regnum - UNW_IA64_BR); + else if (uw_regnum == UNW_IA64_PR) + return IA64_PR_REGNUM; + else if (uw_regnum == UNW_REG_IP) + return IA64_IP_REGNUM; + else if (uw_regnum == UNW_IA64_CFM) + return IA64_CFM_REGNUM; + else if ((unsigned) (uw_regnum - UNW_IA64_AR) < 128) + return IA64_AR0_REGNUM + (uw_regnum - UNW_IA64_AR); + else if ((unsigned) (uw_regnum - UNW_IA64_NAT) < 128) + return IA64_NAT0_REGNUM + (uw_regnum - UNW_IA64_NAT); + else + return -1; +} + +/* Gdb libunwind-frame callback function to reveal if register is a float + register or not. */ +static int +ia64_is_fpreg (int uw_regnum) +{ + return unw_is_fpreg (uw_regnum); +} + +/* Libunwind callback accessor function for general registers. */ +static int +ia64_access_reg (unw_addr_space_t as, unw_regnum_t uw_regnum, unw_word_t *val, + int write, void *arg) +{ + int regnum = ia64_uw2gdb_regnum (uw_regnum); + unw_word_t bsp, sof, sol, cfm, psr, ip; + struct frame_info *next_frame = arg; + long new_sof, old_sof; + char buf[MAX_REGISTER_SIZE]; + + if (write) + { + if (regnum < 0) + /* ignore writes to pseudo-registers such as UNW_IA64_PROC_STARTI. */ + return 0; + + switch (uw_regnum) + { + case UNW_REG_IP: + ia64_write_pc (*val, inferior_ptid); + break; + + case UNW_IA64_AR_BSPSTORE: + write_register (IA64_BSP_REGNUM, *val); + break; + + case UNW_IA64_AR_BSP: + case UNW_IA64_BSP: + /* Account for the fact that ptrace() expects bsp to point + after the current register frame. */ + cfm = read_register (IA64_CFM_REGNUM); + sof = (cfm & 0x7f); + bsp = ia64_rse_skip_regs (*val, sof); + write_register (IA64_BSP_REGNUM, bsp); + break; + + case UNW_IA64_CFM: + /* If we change CFM, we need to adjust ptrace's notion of + bsp accordingly, so that the real bsp remains + unchanged. */ + bsp = read_register (IA64_BSP_REGNUM); + cfm = read_register (IA64_CFM_REGNUM); + old_sof = (cfm & 0x7f); + new_sof = (*val & 0x7f); + if (old_sof != new_sof) + { + bsp = ia64_rse_skip_regs (bsp, -old_sof + new_sof); + write_register (IA64_BSP_REGNUM, bsp); + } + write_register (IA64_CFM_REGNUM, *val); + break; + + default: + write_register (regnum, *val); + break; + } + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, + " access_reg: to cache: %4s=%016lx\n", + (((unsigned) regnum <= IA64_NAT127_REGNUM) + ? ia64_register_names[regnum] : "r??"), *val); + } + else + { + switch (uw_regnum) + { + case UNW_REG_IP: + /* Libunwind expects to see the pc value which means the slot number + from the psr must be merged with the ip word address. */ + frame_unwind_register (next_frame, IA64_IP_REGNUM, buf); + ip = extract_unsigned_integer (buf, 8); + frame_unwind_register (next_frame, IA64_PSR_REGNUM, buf); + psr = extract_unsigned_integer (buf, 8); + *val = ip | ((psr >> 41) & 0x3); + break; + + case UNW_IA64_AR_BSP: + /* Libunwind expects to see the beginning of the current register + frame so we must account for the fact that ptrace() will return a value + for bsp that points *after* the current register frame. */ + frame_unwind_register (next_frame, IA64_BSP_REGNUM, buf); + bsp = extract_unsigned_integer (buf, 8); + frame_unwind_register (next_frame, IA64_CFM_REGNUM, buf); + cfm = extract_unsigned_integer (buf, 8); + sof = (cfm & 0x7f); + *val = ia64_rse_skip_regs (bsp, -sof); + break; + + case UNW_IA64_AR_BSPSTORE: + /* Libunwind wants bspstore to be after the current register frame. + This is what ptrace() and gdb treats as the regular bsp value. */ + frame_unwind_register (next_frame, IA64_BSP_REGNUM, buf); + *val = extract_unsigned_integer (buf, 8); + break; + + default: + /* For all other registers, just unwind the value directly. */ + frame_unwind_register (next_frame, regnum, buf); + *val = extract_unsigned_integer (buf, 8); + break; + } + + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, + " access_reg: from cache: %4s=%016lx\n", + (((unsigned) regnum <= IA64_NAT127_REGNUM) + ? ia64_register_names[regnum] : "r??"), *val); + } + return 0; +} + +/* Libunwind callback accessor function for floating-point registers. */ +static int +ia64_access_fpreg (unw_addr_space_t as, unw_regnum_t uw_regnum, unw_fpreg_t *val, + int write, void *arg) +{ + int regnum = ia64_uw2gdb_regnum (uw_regnum); + + if (write) + regcache_cooked_write (current_regcache, regnum, (char *) val); + else + regcache_cooked_read (current_regcache, regnum, (char *) val); + return 0; +} + +/* Libunwind callback accessor function for accessing memory. */ +static int +ia64_access_mem (unw_addr_space_t as, + unw_word_t addr, unw_word_t *val, + int write, void *arg) +{ + /* XXX do we need to normalize byte-order here? */ + if (write) + return target_write_memory (addr, (char *) val, sizeof (unw_word_t)); + else + return target_read_memory (addr, (char *) val, sizeof (unw_word_t)); +} + +/* Call low-level function to access the kernel unwind table. */ +static int +getunwind_table (void *buf, size_t len) +{ + LONGEST x; + x = target_read_partial (¤t_target, TARGET_OBJECT_UNWIND_TABLE, NULL, + buf, 0, len); + + return (int)x; +} + +/* Get the kernel unwind table. */ +static int +get_kernel_table (unw_word_t ip, unw_dyn_info_t *di) +{ + size_t size; + struct ia64_table_entry + { + uint64_t start_offset; + uint64_t end_offset; + uint64_t info_offset; + }; + static struct ia64_table_entry *ktab = NULL, *etab; + + if (!ktab) + { + size = getunwind_table (NULL, 0); + if ((int)size < 0) + return -UNW_ENOINFO; + ktab = xmalloc (size); + getunwind_table (ktab, size); + + /* Determine length of kernel's unwind table and relocate + it's entries. */ + for (etab = ktab; etab->start_offset; ++etab) + etab->info_offset += (uint64_t) ktab; + } + + if (ip < ktab[0].start_offset || ip >= etab[-1].end_offset) + return -UNW_ENOINFO; + + di->format = UNW_INFO_FORMAT_TABLE; + di->gp = 0; + di->start_ip = ktab[0].start_offset; + di->end_ip = etab[-1].end_offset; + di->u.ti.name_ptr = (unw_word_t) "<kernel>"; + di->u.ti.segbase = 0; + di->u.ti.table_len = ((char *) etab - (char *) ktab) / sizeof (unw_word_t); + di->u.ti.table_data = (unw_word_t *) ktab; + + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, "get_kernel_table: found table `%s': " + "segbase=%lx, length=%lu, gp=%lx\n", + (char *) di->u.ti.name_ptr, di->u.ti.segbase, + di->u.ti.table_len, di->gp); + return 0; +} + +/* Find the unwind table entry for a specified address. */ +static int +ia64_find_unwind_table (struct objfile *objfile, unw_word_t ip, + unw_dyn_info_t *dip, void **buf) +{ + Elf_Internal_Phdr *phdr, *p_text = NULL, *p_unwind = NULL; + Elf_Internal_Ehdr *ehdr; + unw_word_t segbase = 0; + CORE_ADDR load_base; + bfd *bfd; + int i; + + bfd = objfile->obfd; + + ehdr = elf_tdata (bfd)->elf_header; + phdr = elf_tdata (bfd)->phdr; + + load_base = ANOFFSET (objfile->section_offsets, SECT_OFF_TEXT (objfile)); + + for (i = 0; i < ehdr->e_phnum; ++i) + { + switch (phdr[i].p_type) + { + case PT_LOAD: + if ((unw_word_t) (ip - load_base - phdr[i].p_vaddr) + < phdr[i].p_memsz) + p_text = phdr + i; + break; + + case PT_IA_64_UNWIND: + p_unwind = phdr + i; + break; + + default: + break; + } + } + + if (!p_text || !p_unwind + /* Verify that the segment that contains the IP also contains + the static unwind table. If not, we are dealing with + runtime-generated code, for which we have no info here. */ + || (p_unwind->p_vaddr - p_text->p_vaddr) >= p_text->p_memsz) + return -UNW_ENOINFO; + + segbase = p_text->p_vaddr + load_base; + + dip->start_ip = segbase; + dip->end_ip = dip->start_ip + p_text->p_memsz; + dip->gp = FIND_GLOBAL_POINTER (ip); + dip->format = UNW_INFO_FORMAT_TABLE; + dip->u.ti.name_ptr = (unw_word_t) bfd_get_filename (bfd); + dip->u.ti.segbase = segbase; + dip->u.ti.table_len = p_unwind->p_memsz / sizeof (unw_word_t); + + /* The following can happen in corner cases where dynamically + generated code falls into the same page that contains the + data-segment and the page-offset of the code is within the first + page of the executable. */ + if (ip < dip->start_ip || ip >= dip->end_ip) + return -UNW_ENOINFO; + + /* Read in the libunwind table. */ + *buf = xmalloc (p_unwind->p_memsz); + target_read_memory (p_unwind->p_vaddr + load_base, (char *)(*buf), p_unwind->p_memsz); + + dip->u.ti.table_data = (unw_word_t *)(*buf); + + return 0; +} + +/* Libunwind callback accessor function to acquire procedure unwind-info. */ +static int +ia64_find_proc_info_x (unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi, + int need_unwind_info, void *arg) +{ + struct obj_section *sec = find_pc_section (ip); + unw_dyn_info_t di; + int ret; + void *buf = NULL; + + if (!sec) + { + /* XXX This only works if the host and the target architecture are + both ia64 and if the have (more or less) the same kernel + version. */ + if (get_kernel_table (ip, &di) < 0) + return -UNW_ENOINFO; + } + else + { + ret = ia64_find_unwind_table (sec->objfile, ip, &di, &buf); + if (ret < 0) + return ret; + } + + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, "acquire_unwind_info: %lx -> " + "(name=`%s',segbase=%lx,start=%lx,end=%lx,gp=%lx," + "length=%lu,data=%p)\n", ip, (char *)di.u.ti.name_ptr, + di.u.ti.segbase, di.start_ip, di.end_ip, + di.gp, di.u.ti.table_len, di.u.ti.table_data); + + ret = libunwind_search_unwind_table (&as, ip, &di, pi, need_unwind_info, arg); + + /* We no longer need the dyn info storage so free it. */ + xfree (buf); + + return ret; +} + +/* Libunwind callback accessor function for cleanup. */ +static void +ia64_put_unwind_info (unw_addr_space_t as, + unw_proc_info_t *pip, void *arg) +{ + /* Nothing required for now. */ +} + +/* Libunwind callback accessor function to get head of the dynamic + unwind-info registration list. */ +static int +ia64_get_dyn_info_list (unw_addr_space_t as, + unw_word_t *dilap, void *arg) +{ + struct obj_section *text_sec; + struct objfile *objfile; + unw_word_t ip, addr; + unw_dyn_info_t di; + int ret; + + if (!libunwind_is_initialized ()) + return -UNW_ENOINFO; + + for (objfile = object_files; objfile; objfile = objfile->next) + { + void *buf = NULL; + + text_sec = objfile->sections + SECT_OFF_TEXT (objfile); + ip = text_sec->addr; + ret = ia64_find_unwind_table (objfile, ip, &di, &buf); + if (ret >= 0) + { + addr = libunwind_find_dyn_list (as, di.u.ti.table_data, + (di.u.ti.table_len + * sizeof (di.u.ti.table_data[0])), + di.u.ti.segbase, di.gp, arg); + /* We no longer need the dyn info storage so free it. */ + xfree (buf); + + if (addr) + { + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, + "dynamic unwind table in objfile %s " + "at %lx (gp=%lx)\n", + bfd_get_filename (objfile->obfd), + addr, di.gp); + *dilap = addr; + return 0; + } + } + } + return -UNW_ENOINFO; +} + + +/* Frame interface functions for libunwind. */ + +static void +ia64_libunwind_frame_this_id (struct frame_info *next_frame, void **this_cache, + struct frame_id *this_id) +{ + char buf[8]; + CORE_ADDR bsp; + struct frame_id id; + + libunwind_frame_this_id (next_frame, this_cache, &id); + + /* We must add the bsp as the special address for frame comparison purposes. */ + frame_unwind_register (next_frame, IA64_BSP_REGNUM, buf); + bsp = extract_unsigned_integer (buf, 8); + + (*this_id) = frame_id_build_special (id.stack_addr, id.code_addr, bsp); + + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, + "libunwind frame id: code %lx, stack %lx, special %lx, next_frame %p\n", + id.code_addr, id.stack_addr, bsp, next_frame); +} + +static void +ia64_libunwind_frame_prev_register (struct frame_info *next_frame, + void **this_cache, + int regnum, int *optimizedp, + enum lval_type *lvalp, CORE_ADDR *addrp, + int *realnump, void *valuep) +{ + int reg = regnum; + + if (VP0_REGNUM <= regnum && regnum <= VP63_REGNUM) + reg = IA64_PR_REGNUM; + else if (IA64_NAT0_REGNUM <= regnum && regnum <= IA64_NAT127_REGNUM) + reg = IA64_UNAT_REGNUM; + + /* Let libunwind do most of the work. */ + libunwind_frame_prev_register (next_frame, this_cache, reg, + optimizedp, lvalp, addrp, realnump, valuep); + + if (VP0_REGNUM <= regnum && regnum <= VP63_REGNUM) + { + ULONGEST prN_val; + + if (VP16_REGNUM <= regnum && regnum <= VP63_REGNUM) + { + int rrb_pr = 0; + ULONGEST cfm; + unsigned char buf[MAX_REGISTER_SIZE]; + + /* Fetch predicate register rename base from current frame + marker for this frame. */ + frame_unwind_register (next_frame, IA64_CFM_REGNUM, buf); + cfm = extract_unsigned_integer (buf, 8); + rrb_pr = (cfm >> 32) & 0x3f; + + /* Adjust the register number to account for register rotation. */ + regnum = VP16_REGNUM + + ((regnum - VP16_REGNUM) + rrb_pr) % 48; + } + prN_val = extract_bit_field ((unsigned char *) valuep, + regnum - VP0_REGNUM, 1); + store_unsigned_integer (valuep, register_size (current_gdbarch, regnum), prN_val); + } + else if (IA64_NAT0_REGNUM <= regnum && regnum <= IA64_NAT127_REGNUM) + { + ULONGEST unatN_val; + + unatN_val = extract_bit_field ((unsigned char *) valuep, + regnum - IA64_NAT0_REGNUM, 1); + store_unsigned_integer (valuep, register_size (current_gdbarch, regnum), + unatN_val); + } + else if (regnum == IA64_BSP_REGNUM) + { + char cfm_valuep[MAX_REGISTER_SIZE]; + int cfm_optim; + int cfm_realnum; + enum lval_type cfm_lval; + CORE_ADDR cfm_addr; + CORE_ADDR bsp, prev_cfm, prev_bsp; + + /* We want to calculate the previous bsp as the end of the previous register stack frame. + This corresponds to what the hardware bsp register will be if we pop the frame + back which is why we might have been called. We know that libunwind will pass us back + the beginning of the current frame so we should just add sof to it. */ + prev_bsp = extract_unsigned_integer (valuep, 8); + libunwind_frame_prev_register (next_frame, this_cache, IA64_CFM_REGNUM, + &cfm_optim, &cfm_lval, &cfm_addr, &cfm_realnum, cfm_valuep); + prev_cfm = extract_unsigned_integer (cfm_valuep, 8); + prev_bsp = rse_address_add (prev_bsp, (prev_cfm & 0x7f)); + + store_unsigned_integer (valuep, register_size (current_gdbarch, regnum), + prev_bsp); + } + + if (gdbarch_debug >= 1) + fprintf_unfiltered (gdb_stdlog, + "libunwind prev register <%s> is %lx\n", + (((unsigned) regnum <= IA64_NAT127_REGNUM) + ? ia64_register_names[regnum] : "r??"), extract_unsigned_integer (valuep, 8)); +} + +static const struct frame_unwind ia64_libunwind_frame_unwind = +{ + NORMAL_FRAME, + ia64_libunwind_frame_this_id, + ia64_libunwind_frame_prev_register +}; + +static const struct frame_unwind * +ia64_libunwind_frame_sniffer (struct frame_info *next_frame) +{ + if (libunwind_is_initialized () && libunwind_frame_sniffer (next_frame)) + return &ia64_libunwind_frame_unwind; + + return NULL; +} + +/* Set of libunwind callback acccessor functions. */ +static unw_accessors_t ia64_unw_accessors = +{ + ia64_find_proc_info_x, + ia64_put_unwind_info, + ia64_get_dyn_info_list, + ia64_access_mem, + ia64_access_reg, + ia64_access_fpreg, + /* resume */ + /* get_proc_name */ +}; + +/* Set of ia64 gdb libunwind-frame callbacks and data for generic libunwind-frame code to use. */ +static struct libunwind_descr ia64_libunwind_descr = +{ + ia64_gdb2uw_regnum, + ia64_uw2gdb_regnum, + ia64_is_fpreg, + &ia64_unw_accessors, +}; + +#endif /* HAVE_LIBUNWIND_IA64_H */ + /* Should we use EXTRACT_STRUCT_VALUE_ADDRESS instead of EXTRACT_RETURN_VALUE? GCC_P is true if compiled with gcc and TYPE is the type (which is known to be struct, union or array). */ @@ -2795,6 +3410,10 @@ ia64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) set_gdbarch_unwind_pc (gdbarch, ia64_unwind_pc); frame_unwind_append_sniffer (gdbarch, ia64_sigtramp_frame_sniffer); +#ifdef HAVE_LIBUNWIND_IA64_H + frame_unwind_append_sniffer (gdbarch, ia64_libunwind_frame_sniffer); + libunwind_frame_set_descr (gdbarch, &ia64_libunwind_descr); +#endif frame_unwind_append_sniffer (gdbarch, ia64_frame_sniffer); frame_base_set_default (gdbarch, &ia64_frame_base); |