mirror of
https://github.com/tobast/libunwind-eh_elf.git
synced 2024-11-29 02:07:39 +01:00
2142c20307
(Logical change 1.241)
717 lines
20 KiB
C
717 lines
20 KiB
C
/* libunwind - a platform-independent unwind library
|
|
Copyright (c) 2001-2004 Hewlett-Packard Development Company, L.P.
|
|
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 <stdlib.h>
|
|
#include <stddef.h>
|
|
|
|
#include "unwind_i.h"
|
|
|
|
#ifdef HAVE_IA64INTRIN_H
|
|
# include <ia64intrin.h>
|
|
#endif
|
|
|
|
extern unw_addr_space_t _ULia64_local_addr_space;
|
|
|
|
struct ia64_table_entry
|
|
{
|
|
uint64_t start_offset;
|
|
uint64_t end_offset;
|
|
uint64_t info_offset;
|
|
};
|
|
|
|
#ifdef UNW_LOCAL_ONLY
|
|
|
|
static inline int
|
|
is_local_addr_space (unw_addr_space_t as)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static inline int
|
|
read_mem (unw_addr_space_t as, unw_word_t addr, unw_word_t *valp, void *arg)
|
|
{
|
|
*valp = *(unw_word_t *) addr;
|
|
return 0;
|
|
}
|
|
|
|
#else /* !UNW_LOCAL_ONLY */
|
|
|
|
static inline int
|
|
is_local_addr_space (unw_addr_space_t as)
|
|
{
|
|
return as == unw_local_addr_space;
|
|
}
|
|
|
|
static inline int
|
|
read_mem (unw_addr_space_t as, unw_word_t addr, unw_word_t *valp, void *arg)
|
|
{
|
|
unw_accessors_t *a = unw_get_accessors (as);
|
|
|
|
return (*a->access_mem) (as, addr, valp, 0, arg);
|
|
}
|
|
|
|
/* Helper macro for reading an ia64_table_entry from remote memory. */
|
|
#define remote_read(addr, member) \
|
|
(*a->access_mem) (as, (addr) + offsetof (struct ia64_table_entry, \
|
|
member), &member, 0, arg)
|
|
|
|
/* Lookup an unwind-table entry in remote memory. Returns 1 if an
|
|
entry is found, 0 if no entry is found, negative if an error
|
|
occurred reading remote memory. */
|
|
static int
|
|
remote_lookup (unw_addr_space_t as,
|
|
unw_word_t table, size_t table_size, unw_word_t rel_ip,
|
|
struct ia64_table_entry *e, void *arg)
|
|
{
|
|
unw_word_t e_addr = 0, start_offset, end_offset, info_offset;
|
|
unw_accessors_t *a = unw_get_accessors (as);
|
|
unsigned long lo, hi, mid;
|
|
int ret;
|
|
|
|
/* 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_addr = table + mid * sizeof (struct ia64_table_entry);
|
|
if ((ret = remote_read (e_addr, start_offset)) < 0)
|
|
return ret;
|
|
|
|
if (rel_ip < start_offset)
|
|
hi = mid;
|
|
else
|
|
{
|
|
if ((ret = remote_read (e_addr, end_offset)) < 0)
|
|
return ret;
|
|
|
|
if (rel_ip >= end_offset)
|
|
lo = mid + 1;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
if (rel_ip < start_offset || rel_ip >= end_offset)
|
|
return 0;
|
|
e->start_offset = start_offset;
|
|
e->end_offset = end_offset;
|
|
|
|
if ((ret = remote_read (e_addr, info_offset)) < 0)
|
|
return ret;
|
|
e->info_offset = info_offset;
|
|
return 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
PROTECTED unw_word_t
|
|
_Uia64_find_dyn_list (unw_addr_space_t as, unw_dyn_info_t *di, void *arg)
|
|
{
|
|
unw_word_t hdr_addr, info_addr, hdr, directives, pers, cookie, off;
|
|
unw_word_t start_offset, end_offset, info_offset, segbase;
|
|
struct ia64_table_entry *e;
|
|
size_t table_size;
|
|
unw_word_t gp = di->gp;
|
|
int ret;
|
|
|
|
switch (di->format)
|
|
{
|
|
case UNW_INFO_FORMAT_DYNAMIC:
|
|
default:
|
|
return 0;
|
|
|
|
case UNW_INFO_FORMAT_TABLE:
|
|
e = (struct ia64_table_entry *) di->u.ti.table_data;
|
|
table_size = di->u.ti.table_len * sizeof (di->u.ti.table_data[0]);
|
|
segbase = di->u.ti.segbase;
|
|
if (table_size < sizeof (struct ia64_table_entry))
|
|
return 0;
|
|
start_offset = e[0].start_offset;
|
|
end_offset = e[0].end_offset;
|
|
info_offset = e[0].info_offset;
|
|
break;
|
|
|
|
case UNW_INFO_FORMAT_REMOTE_TABLE:
|
|
{
|
|
unw_accessors_t *a = unw_get_accessors (as);
|
|
unw_word_t e_addr = di->u.rti.table_data;
|
|
|
|
table_size = di->u.rti.table_len * sizeof (unw_word_t);
|
|
segbase = di->u.rti.segbase;
|
|
if (table_size < sizeof (struct ia64_table_entry))
|
|
return 0;
|
|
|
|
if ( (ret = remote_read (e_addr, start_offset) < 0)
|
|
|| (ret = remote_read (e_addr, end_offset) < 0)
|
|
|| (ret = remote_read (e_addr, info_offset) < 0))
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (start_offset != 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 = info_offset + segbase;
|
|
info_addr = hdr_addr + 8;
|
|
|
|
/* read the header word: */
|
|
if ((ret = read_mem (as, hdr_addr, &hdr, arg)) < 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 = read_mem (as, info_addr, &directives, arg)) < 0)
|
|
|| ((ret = read_mem (as, info_addr + 0x08, &pers, arg)) < 0)
|
|
|| ((ret = read_mem (as, info_addr + 0x10, &cookie, arg)) < 0)
|
|
|| ((ret = read_mem (as, info_addr + 0x18, &off, arg)) < 0))
|
|
return 0;
|
|
|
|
if (directives != 0 || pers != 0
|
|
|| (!as->big_endian && cookie != 0x7473696c2d6e7964ULL)
|
|
|| ( as->big_endian && cookie != 0x64796e2d6c697374ULL))
|
|
return 0;
|
|
|
|
/* OK, we ran the gauntlet and found it: */
|
|
return off + gp;
|
|
}
|
|
|
|
#endif /* !UNW_LOCAL_ONLY */
|
|
|
|
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;
|
|
}
|
|
|
|
PROTECTED int
|
|
unw_search_ia64_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;
|
|
const struct ia64_table_entry *e = NULL;
|
|
unw_word_t handler_offset, segbase = 0;
|
|
int ret;
|
|
|
|
assert ((di->format == UNW_INFO_FORMAT_TABLE
|
|
|| di->format == UNW_INFO_FORMAT_REMOTE_TABLE)
|
|
&& (ip >= di->start_ip && ip < di->end_ip));
|
|
|
|
pi->flags = 0;
|
|
pi->unwind_info = 0;
|
|
pi->handler = 0;
|
|
|
|
if (likely (di->format == UNW_INFO_FORMAT_TABLE))
|
|
{
|
|
segbase = di->u.ti.segbase;
|
|
e = lookup ((struct ia64_table_entry *) di->u.ti.table_data,
|
|
di->u.ti.table_len * sizeof (unw_word_t),
|
|
ip - segbase);
|
|
}
|
|
#ifndef UNW_LOCAL_ONLY
|
|
else
|
|
{
|
|
struct ia64_table_entry ent;
|
|
|
|
segbase = di->u.rti.segbase;
|
|
if ((ret = remote_lookup (as, di->u.rti.table_data,
|
|
di->u.rti.table_len * sizeof (unw_word_t),
|
|
ip - segbase, &ent, arg)) < 0)
|
|
return ret;
|
|
if (ret)
|
|
e = &ent;
|
|
}
|
|
#endif
|
|
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));
|
|
pi->start_ip = 0;
|
|
pi->end_ip = 0;
|
|
pi->gp = di->gp;
|
|
pi->lsda = 0;
|
|
return 0;
|
|
}
|
|
|
|
pi->start_ip = e->start_offset + segbase;
|
|
pi->end_ip = e->end_offset + segbase;
|
|
|
|
hdr_addr = e->info_offset + segbase;
|
|
info_addr = hdr_addr + 8;
|
|
|
|
/* read the header word: */
|
|
if ((ret = read_mem (as, hdr_addr, &hdr, arg)) < 0)
|
|
return ret;
|
|
|
|
if (IA64_UNW_VER (hdr) != 1)
|
|
return -UNW_EBADVERSION;
|
|
|
|
info_end_addr = info_addr + 8 * IA64_UNW_LENGTH (hdr);
|
|
|
|
if (need_unwind_info)
|
|
{
|
|
pi->unwind_info_size = 8 * IA64_UNW_LENGTH (hdr);
|
|
|
|
if (is_local_addr_space (as))
|
|
pi->unwind_info = (void *) (uintptr_t) 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)
|
|
{
|
|
if ((ret = read_mem (as, addr, wp, arg)) < 0)
|
|
{
|
|
free (pi->unwind_info);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (IA64_UNW_FLAG_EHANDLER (hdr) || IA64_UNW_FLAG_UHANDLER (hdr))
|
|
{
|
|
/* read the personality routine address (address is gp-relative): */
|
|
if ((ret = read_mem (as, info_end_addr, &handler_offset, arg)) < 0)
|
|
return ret;
|
|
Debug (4, "handler ptr @ offset=%lx, gp=%lx\n", handler_offset, di->gp);
|
|
if ((read_mem (as, handler_offset + di->gp, &pi->handler, arg)) < 0)
|
|
return ret;
|
|
}
|
|
pi->lsda = info_end_addr + 8;
|
|
pi->gp = di->gp;
|
|
pi->format = di->format;
|
|
return 0;
|
|
}
|
|
|
|
#ifndef UNW_REMOTE_ONLY
|
|
|
|
# if defined(HAVE_DL_ITERATE_PHDR)
|
|
# include <link.h>
|
|
# include <stdlib.h>
|
|
|
|
# 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
|
|
|
|
# if defined(HAVE_GETUNWIND)
|
|
extern unsigned long getunwind (void *buf, size_t len);
|
|
# else /* HAVE_GETUNWIND */
|
|
# include <unistd.h>
|
|
# include <sys/syscall.h>
|
|
# 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 (unw_dyn_info_t *di)
|
|
{
|
|
struct ia64_table_entry *ktab, *etab;
|
|
size_t size;
|
|
|
|
Debug (16, "getting kernel 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) "<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;
|
|
|
|
Debug (16, "found table `%s': [%lx-%lx) segbase=%lx len=%lu\n",
|
|
(char *) di->u.ti.name_ptr, di->start_ip, di->end_ip,
|
|
di->u.ti.segbase, di->u.ti.table_len);
|
|
return 0;
|
|
}
|
|
|
|
# ifndef UNW_LOCAL_ONLY
|
|
|
|
/* This is exported for the benefit of libunwind-ptrace.a. */
|
|
PROTECTED int
|
|
_Uia64_get_kernel_table (unw_dyn_info_t *di)
|
|
{
|
|
int ret;
|
|
|
|
if (!kernel_table.u.ti.table_data)
|
|
if ((ret = get_kernel_table (&kernel_table)) < 0)
|
|
return ret;
|
|
|
|
memcpy (di, &kernel_table, sizeof (*di));
|
|
return 0;
|
|
}
|
|
|
|
# endif /* !UNW_LOCAL_ONLY */
|
|
|
|
static inline unsigned long
|
|
current_gp (void)
|
|
{
|
|
# if defined(__GNUC__) && !defined(__INTEL_COMPILER)
|
|
register unsigned long gp __asm__("gp");
|
|
return gp;
|
|
# elif HAVE_IA64INTRIN_H
|
|
return __getReg (_IA64_REG_GP);
|
|
# else
|
|
# error Implement me.
|
|
# endif
|
|
}
|
|
|
|
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, segbase = 0;
|
|
|
|
/* 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 (16, "checking `%s' (load_base=%lx)\n",
|
|
info->dlpi_name, info->dlpi_addr);
|
|
|
|
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 (likely (p_unwind->p_vaddr >= p_text->p_vaddr
|
|
&& p_unwind->p_vaddr < p_text->p_vaddr + p_text->p_memsz))
|
|
/* normal case: unwind table is inside text segment */
|
|
segbase = p_text->p_vaddr + load_base;
|
|
else
|
|
{
|
|
/* Special case: unwind table is in some other segment; this
|
|
happens for the Linux kernel's gate DSO, for example. */
|
|
phdr = info->dlpi_phdr;
|
|
for (n = info->dlpi_phnum; --n >= 0; phdr++)
|
|
{
|
|
if (phdr->p_type == PT_LOAD && p_unwind->p_vaddr >= phdr->p_vaddr
|
|
&& p_unwind->p_vaddr < phdr->p_vaddr + phdr->p_memsz)
|
|
{
|
|
segbase = phdr->p_vaddr + load_base;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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. */
|
|
di->gp = current_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 = segbase;
|
|
|
|
Debug (16, "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;
|
|
}
|
|
|
|
# ifdef HAVE_DL_PHDR_REMOVALS_COUNTER
|
|
|
|
static inline int
|
|
validate_cache (unw_addr_space_t as)
|
|
{
|
|
/* Note: we don't need to serialize here with respect to
|
|
dl_iterate_phdr() because if somebody were to remove an object
|
|
that is required to complete the unwind on whose behalf we're
|
|
validating the cache here, we'd be hosed anyhow. What we're
|
|
guarding against here is the case where library FOO gets mapped,
|
|
unwind info for FOO gets cached, FOO gets unmapped, BAR gets
|
|
mapped in the place where FOO was and then we unwind across a
|
|
function in FOO. Since no thread can execute in BAR before FOO
|
|
has been removed, we are guaranteed that
|
|
dl_phdr_removals_counter() would have been incremented before we
|
|
get here. */
|
|
unsigned long long removals = dl_phdr_removals_counter ();
|
|
|
|
if (removals == as->shared_object_removals)
|
|
return 1;
|
|
|
|
as->shared_object_removals = removals;
|
|
unw_flush_cache (as, 0, 0);
|
|
return -1;
|
|
}
|
|
|
|
# else /* !HAVE_DL_PHDR_REMOVALS_COUNTER */
|
|
|
|
/* Check whether any phdrs have been removed since we last flushed the
|
|
cache. If so we flush the cache and return -1, if not, we do
|
|
nothing and return 1. */
|
|
|
|
static int
|
|
check_callback (struct dl_phdr_info *info, size_t size, void *ptr)
|
|
{
|
|
# ifdef HAVE_STRUCT_DL_PHDR_INFO_DLPI_SUBS
|
|
unw_addr_space_t as = ptr;
|
|
|
|
if (size <
|
|
offsetof (struct dl_phdr_info, dlpi_subs) + sizeof (info->dlpi_subs))
|
|
/* It would be safer to flush the cache here, but that would
|
|
disable caching for older libc's which would be incompatible
|
|
with the behavior of older versions of libunwind so we return 1
|
|
instead and hope nobody runs into stale cache info... */
|
|
return 1;
|
|
|
|
if (info->dlpi_subs == as->shared_object_removals)
|
|
return 1;
|
|
|
|
as->shared_object_removals = info->dlpi_subs;
|
|
unw_flush_cache (as, 0, 0);
|
|
return -1; /* indicate that there were removals */
|
|
# else
|
|
return 1;
|
|
# endif
|
|
}
|
|
|
|
static inline int
|
|
validate_cache (unw_addr_space_t as)
|
|
{
|
|
sigset_t saved_sigmask;
|
|
int ret;
|
|
|
|
sigprocmask (SIG_SETMASK, &unwi_full_sigmask, &saved_sigmask);
|
|
ret = dl_iterate_phdr (check_callback, as);
|
|
sigprocmask (SIG_SETMASK, &saved_sigmask, NULL);
|
|
return ret;
|
|
}
|
|
|
|
# endif /* HAVE_DL_PHDR_REMOVALS_COUNTER */
|
|
|
|
# elif defined(HAVE_DLMODINFO)
|
|
/* Support for HP-UX-style dlmodinfo() */
|
|
# include <dlfcn.h>
|
|
|
|
static inline int
|
|
validate_cache (unw_addr_space_t as)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
# endif /* !HAVE_DLMODINFO */
|
|
|
|
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)
|
|
{
|
|
# if defined(HAVE_DL_ITERATE_PHDR)
|
|
unw_dyn_info_t di, *dip = &di;
|
|
sigset_t saved_sigmask;
|
|
int ret;
|
|
|
|
di.u.ti.segbase = ip; /* this is cheap... */
|
|
|
|
sigprocmask (SIG_SETMASK, &unwi_full_sigmask, &saved_sigmask);
|
|
ret = dl_iterate_phdr (callback, &di);
|
|
sigprocmask (SIG_SETMASK, &saved_sigmask, NULL);
|
|
|
|
if (ret <= 0)
|
|
{
|
|
if (!kernel_table.u.ti.table_data)
|
|
{
|
|
if ((ret = get_kernel_table (&kernel_table)) < 0)
|
|
return ret;
|
|
}
|
|
if (ip < kernel_table.start_ip || ip >= kernel_table.end_ip)
|
|
return -UNW_ENOINFO;
|
|
dip = &kernel_table;
|
|
}
|
|
# elif defined(HAVE_DLMODINFO)
|
|
# define UNWIND_TBL_32BIT 0x8000000000000000
|
|
struct load_module_desc lmd;
|
|
unw_dyn_info_t di, *dip = &di;
|
|
struct unwind_header
|
|
{
|
|
uint64_t header_version;
|
|
uint64_t start_offset;
|
|
uint64_t end_offset;
|
|
}
|
|
*uhdr;
|
|
|
|
if (!dlmodinfo (ip, &lmd, sizeof (lmd), NULL, 0, 0))
|
|
return -UNW_ENOINFO;
|
|
|
|
di.format = UNW_INFO_FORMAT_TABLE;
|
|
di.start_ip = lmd.text_base;
|
|
di.end_ip = lmd.text_base + lmd.text_size;
|
|
di.gp = lmd.linkage_ptr;
|
|
di.u.ti.name_ptr = 0; /* no obvious table-name available */
|
|
di.u.ti.segbase = lmd.text_base;
|
|
|
|
uhdr = (struct unwind_header *) lmd.unwind_base;
|
|
|
|
if ((uhdr->header_version & ~UNWIND_TBL_32BIT) != 1
|
|
&& (uhdr->header_version & ~UNWIND_TBL_32BIT) != 2)
|
|
{
|
|
Debug (1, "encountered unknown unwind header version %ld\n",
|
|
(long) (uhdr->header_version & ~UNWIND_TBL_32BIT));
|
|
return -UNW_EBADVERSION;
|
|
}
|
|
if (uhdr->header_version & UNWIND_TBL_32BIT)
|
|
{
|
|
Debug (1, "32-bit unwind tables are not supported yet\n");
|
|
return -UNW_EINVAL;
|
|
}
|
|
|
|
di.u.ti.table_data = (unw_word_t *) (di.u.ti.segbase + uhdr->start_offset);
|
|
di.u.ti.table_len = ((uhdr->end_offset - uhdr->start_offset)
|
|
/ sizeof (unw_word_t));
|
|
|
|
Debug (16, "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);
|
|
# endif
|
|
|
|
/* now search the table: */
|
|
return tdep_search_unwind_table (as, ip, dip, pi, need_unwind_info, arg);
|
|
}
|
|
|
|
/* Returns 1 if the cache is up-to-date or -1 if the cache contained
|
|
stale data and had to be flushed. */
|
|
|
|
HIDDEN int
|
|
ia64_local_validate_cache (unw_addr_space_t as, void *arg)
|
|
{
|
|
return validate_cache (as);
|
|
}
|
|
|
|
#endif /* !UNW_REMOTE_ONLY */
|