diff --git a/src/ia64/tables-ia64.c b/src/ia64/tables-ia64.c index e69de29b..7779198c 100644 --- a/src/ia64/tables-ia64.c +++ b/src/ia64/tables-ia64.c @@ -0,0 +1,379 @@ +/* libunwind - a platform-independent unwind library + Copyright (C) 2001-2002 Hewlett-Packard Co + Contributed by David Mosberger-Tang + +This file is part of libunwind. */ + +#include +#include + +#include "unwind_i.h" + +struct ia64_table_entry + { + uint64_t start_offset; + uint64_t end_offset; + uint64_t info_offset; + }; + +static inline const struct ia64_table_entry * +lookup (struct ia64_table_entry *table, size_t table_size, unw_word_t rel_ip) +{ + const struct ia64_table_entry *e = 0; + unsigned long lo, hi, mid; + + /* do a binary search for right entry: */ + for (lo = 0, hi = table_size / sizeof (struct ia64_table_entry); lo < hi;) + { + mid = (lo + hi) / 2; + e = table + mid; + if (rel_ip < e->start_offset) + hi = mid; + else if (rel_ip >= e->end_offset) + lo = mid + 1; + else + break; + } + if (rel_ip < e->start_offset || rel_ip >= e->end_offset) + return NULL; + return e; +} + +static inline int +is_local_addr_space (unw_addr_space_t as) +{ + extern unw_addr_space_t _ULia64_local_addr_space; + + return (as == _Uia64_local_addr_space +#ifndef UNW_REMOTE_ONLY + || as == _ULia64_local_addr_space +#endif + ); +} + +HIDDEN int +tdep_search_unwind_table (unw_addr_space_t as, unw_word_t ip, + unw_dyn_info_t *di, + unw_proc_info_t *pi, int need_unwind_info, void *arg) +{ + unw_word_t addr, hdr_addr, info_addr, info_end_addr, hdr, *wp; + unw_word_t handler_offset; + const struct ia64_table_entry *e; + unw_accessors_t *a = unw_get_accessors (as); + int ret; + + assert (di->format == UNW_INFO_FORMAT_TABLE + && (ip >= di->start_ip && ip < di->end_ip)); + + e = lookup ((struct ia64_table_entry *) di->u.ti.table_data, + di->u.ti.table_len * sizeof (unw_word_t), ip - di->u.ti.segbase); + if (!e) + { + /* IP is inside this table's range, but there is no explicit + unwind info => use default conventions (i.e., this is NOT an + error). */ + memset (pi, 0, sizeof (*pi)); + return 0; + } + + pi->start_ip = e->start_offset + di->u.ti.segbase; + pi->end_ip = e->end_offset + di->u.ti.segbase; + + hdr_addr = e->info_offset + di->u.ti.segbase; + info_addr = hdr_addr + 8; + + /* read the header word: */ + ret = (*a->access_mem) (as, hdr_addr, &hdr, 0, arg); + if (ret < 0) + return ret; + + if (IA64_UNW_VER (hdr) != 1) + return -UNW_EBADVERSION; + + info_end_addr = info_addr + 8 * IA64_UNW_LENGTH (hdr); + + pi->unwind_info = 0; + if (need_unwind_info) + { + pi->unwind_info_size = 8 * IA64_UNW_LENGTH (hdr); + + if (is_local_addr_space (as)) + pi->unwind_info = (void *) info_addr; + else + { + /* Internalize unwind info. Note: since we're doing this + only for non-local address spaces, there is no + signal-safety issue and it is OK to use malloc()/free(). */ + pi->unwind_info = malloc (8 * IA64_UNW_LENGTH (hdr)); + if (!pi->unwind_info) + return -UNW_ENOMEM; + + wp = (unw_word_t *) pi->unwind_info; + for (addr = info_addr; addr < info_end_addr; addr += 8, ++wp) + { + ret = (*a->access_mem) (as, addr, wp, 0, arg); + if (ret < 0) + { + free (pi->unwind_info); + return ret; + } + } + } + } + + pi->handler = 0; + if (IA64_UNW_FLAG_EHANDLER (hdr) || IA64_UNW_FLAG_UHANDLER (hdr)) + { + /* read the personality routine address (address is gp-relative): */ + ret = (*a->access_mem) (as, info_end_addr + 8, &handler_offset, 0, arg); + if (ret < 0) + return ret; + pi->handler = handler_offset + di->gp; + } + pi->lsda = info_end_addr + 16; + pi->gp = di->gp; + pi->flags = 0; + pi->format = di->format; + return 0; +} + +HIDDEN void +tdep_put_unwind_info (unw_addr_space_t as, unw_proc_info_t *pi, void *arg) +{ + if (!pi->unwind_info) + return; + + if (!is_local_addr_space (as)) + { + free (pi->unwind_info); + pi->unwind_info = NULL; + } +} + +unw_word_t +_Uia64_find_dyn_list (unw_addr_space_t as, void *table, size_t table_size, + unw_word_t segbase, void *arg) +{ + unw_word_t hdr_addr, info_addr, hdr, directives, pers, cookie, off; + unw_accessors_t *a = unw_get_accessors (as); + struct ia64_table_entry *tab = table; + int ret; + + if (table_size < sizeof (struct ia64_table_entry)) + return 0; + + if (tab[0].start_offset != tab[0].end_offset) + /* dyn-list entry cover a zero-length "procedure" and should be + first entry (note: technically a binary could contain code + below the segment base, but this doesn't happen for normal + binaries and certainly doesn't happen when libunwind is a + separate shared object. For weird cases, the application may + have to provide its own (slower) version of this routine. */ + return 0; + + hdr_addr = tab[0].info_offset + segbase; + info_addr = hdr_addr + 8; + + /* read the header word: */ + ret = (*a->access_mem) (as, hdr_addr, &hdr, 0, arg); + if (ret < 0) + return ret; + + if (IA64_UNW_VER (hdr) != 1 + || IA64_UNW_FLAG_EHANDLER (hdr) || IA64_UNW_FLAG_UHANDLER (hdr)) + /* dyn-list entry must be version 1 and doesn't have ehandler + or uhandler */ + return 0; + + if (IA64_UNW_LENGTH (hdr) != 1) + /* dyn-list entry must consist of a single word of NOP directives */ + return 0; + + if (((ret = (*a->access_mem) (as, info_addr, &directives, 0, arg)) < 0) + || ((ret = (*a->access_mem) (as, info_addr + 0x08, &pers, 0, arg)) < 0) + || ((ret = (*a->access_mem) (as, info_addr + 0x10, &cookie, 0, arg)) < 0) + || ((ret = (*a->access_mem) (as, info_addr + 0x10, &off, 0, arg)) < 0)) + return 0; + + if (directives != 0 || pers != 0 + || (!as->big_endian && cookie != 0x7473696c2d6e7964) + || ( as->big_endian && cookie != 0x64796e2d6c697374)) + return 0; + + /* OK, we ran the gauntlet and found it: */ + return off + segbase; +} + +#ifndef UNW_REMOTE_ONLY + +#include +#include +#include + +#if __GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 2) \ + || (__GLIBC__ == 2 && __GLIBC_MINOR__ == 2 && !defined(DT_CONFIG)) +# error You need GLIBC 2.2.4 or later on IA-64 Linux +#endif + +#ifdef HAVE_GETUNWIND + +extern unsigned long getunwind (void *buf, size_t len); + +#else /* HAVE_GETUNWIND */ + +#include +#include + +# ifndef __NR_getunwind +# define __NR_getunwind 1215 +# endif + +static unsigned long +getunwind (void *buf, size_t len) +{ + return syscall (SYS_getunwind, buf, len); +} + +#endif /* HAVE_GETUNWIND */ + +static unw_dyn_info_t kernel_table; + +static int +get_kernel_table (void *ptr) +{ + struct ia64_table_entry *ktab, *etab; + unw_dyn_info_t *di = ptr; + size_t size; + + debug (100, "unwind: checking kernel unwind table"); + + size = getunwind (NULL, 0); + ktab = sos_alloc (size); + if (!ktab) + { + dprintf (__FILE__".%s: failed to allocate %Zu bytes", + __FUNCTION__, size); + return -UNW_ENOMEM; + } + getunwind (ktab, size); + + /* Determine length of kernel's unwind table & relocate its entries. */ + for (etab = ktab; etab->start_offset; ++etab) + etab->info_offset += (uint64_t) ktab; + + 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) ""; + 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; + + debug (100, "unwind: found table `%s': segbase=%lx, len=%lu, gp=%lx\n", + (char *) di->u.ti.name_ptr, di->u.ti.segbase, di->u.ti.table_len, + di->gp); + return 0; +} + +static int +callback (struct dl_phdr_info *info, size_t size, void *ptr) +{ + unw_dyn_info_t *di = ptr; + const Elf64_Phdr *phdr, *p_unwind, *p_dynamic, *p_text; + long n; + Elf64_Addr load_base; + + /* Make sure struct dl_phdr_info is at least as big as we need. */ + if (size < offsetof (struct dl_phdr_info, dlpi_phnum) + + sizeof (info->dlpi_phnum)) + return -1; + + debug (100, "unwind: checking `%s'\n", info->dlpi_name); + + phdr = info->dlpi_phdr; + load_base = info->dlpi_addr; + p_text = NULL; + p_unwind = NULL; + p_dynamic = NULL; + + /* See if PC falls into one of the loaded segments. Find the unwind + segment at the same time. */ + for (n = info->dlpi_phnum; --n >= 0; phdr++) + { + if (phdr->p_type == PT_LOAD) + { + Elf64_Addr vaddr = phdr->p_vaddr + load_base; + if (di->u.ti.segbase >= vaddr + && di->u.ti.segbase < vaddr + phdr->p_memsz) + p_text = phdr; + } + else if (phdr->p_type == PT_IA_64_UNWIND) + p_unwind = phdr; + else if (phdr->p_type == PT_DYNAMIC) + p_dynamic = phdr; + } + if (!p_text || !p_unwind) + return 0; + + if (p_dynamic) + { + /* For dynamicly linked executables and shared libraries, + DT_PLTGOT is the gp value for that object. */ + Elf64_Dyn *dyn = (Elf64_Dyn *)(p_dynamic->p_vaddr + load_base); + for (; dyn->d_tag != DT_NULL ; dyn++) + if (dyn->d_tag == DT_PLTGOT) + { + /* On IA-64, _DYNAMIC is writable and GLIBC has relocated it. */ + di->gp = dyn->d_un.d_ptr; + break; + } + } + else + { + /* Otherwise this is a static executable with no _DYNAMIC. + The gp is constant program-wide. */ + register unsigned long gp __asm__("gp"); + di->gp = gp; + } + di->format = UNW_INFO_FORMAT_TABLE; + di->start_ip = p_text->p_vaddr + load_base; + di->end_ip = p_text->p_vaddr + load_base + p_text->p_memsz; + di->u.ti.name_ptr = (unw_word_t) info->dlpi_name; + di->u.ti.table_data = (void *) (p_unwind->p_vaddr + load_base); + di->u.ti.table_len = p_unwind->p_memsz / sizeof (unw_word_t); + di->u.ti.segbase = p_text->p_vaddr + load_base; + + debug (100, "unwind: found table `%s': segbase=%lx, len=%lu, gp=%lx, " + "table_data=%p\n", (char *) di->u.ti.name_ptr, di->u.ti.segbase, + di->u.ti.table_len, di->gp, di->u.ti.table_data); + return 1; +} + +HIDDEN int +tdep_find_proc_info (unw_addr_space_t as, unw_word_t ip, + unw_proc_info_t *pi, int need_unwind_info, void *arg) +{ + unw_dyn_info_t di, *dip = &di; + int ret; + + di.u.ti.segbase = ip; /* this is cheap... */ + + if (dl_iterate_phdr (callback, &di) <= 0) + { + if (!kernel_table.u.ti.table_data) + { + ret = get_kernel_table (&kernel_table); + if (ret < 0) + return ret; + } + if (ip < kernel_table.start_ip || ip >= kernel_table.end_ip) + return -UNW_ENOINFO; + dip = &kernel_table; + } + + /* now search the table: */ + return tdep_search_unwind_table (as, ip, dip, pi, need_unwind_info, arg); +} + +#endif /* !UNW_REMOTE_ONLY */