diff options
author | Ulrich Drepper <drepper@redhat.com> | 2005-07-26 05:00:05 +0000 |
---|---|---|
committer | Ulrich Drepper <drepper@redhat.com> | 2005-07-26 05:00:05 +0000 |
commit | b08d5a8fb42f4586d756068065186b5af7e48dad (patch) | |
tree | 9f05f86be7877ed461b4dc05f53b29ea4fc0d2a1 /libdwfl | |
download | elfutils-b08d5a8fb42f4586d756068065186b5af7e48dad.tar.gz |
Adjust for monotone.
Diffstat (limited to 'libdwfl')
40 files changed, 6107 insertions, 0 deletions
diff --git a/libdwfl/.cvsignore b/libdwfl/.cvsignore new file mode 100644 index 00000000..70845e08 --- /dev/null +++ b/libdwfl/.cvsignore @@ -0,0 +1 @@ +Makefile.in diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog new file mode 100644 index 00000000..a4b8732b --- /dev/null +++ b/libdwfl/ChangeLog @@ -0,0 +1,13 @@ +2005-07-23 Ulrich Drepper <drepper@redhat.com> + + * Makefile.am: Fix rules to allow building with mudflap. + +2005-07-21 Roland McGrath <roland@redhat.com> + + * Makefile.am (noinst_HEADERS): Add loc2c.c. + + * test2.c (main): Check sscanf result to quiet warning. + +2005-07-20 Roland McGrath <roland@redhat.com> + + * libdwfl-branch merged, creating this direcotry. diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am new file mode 100644 index 00000000..de4507f2 --- /dev/null +++ b/libdwfl/Makefile.am @@ -0,0 +1,114 @@ +## Makefile.am for libdwfl library subdirectory in elfutils. +## +## Process this file with automake to create Makefile.in +## +## Copyright (C) 2005 Red Hat, Inc. +## +## This program is Open Source software; you can redistribute it and/or +## modify it under the terms of the Open Software License version 1.0 as +## published by the Open Source Initiative. +## +## You should have received a copy of the Open Software License along +## with this program; if not, you may obtain a copy of the Open Software +## License version 1.0 from http://www.opensource.org/licenses/osl.php or +## by writing the Open Source Initiative c/o Lawrence Rosen, Esq., +## 3001 King Ranch Road, Ukiah, CA 95482. +## +DEFS = -D_GNU_SOURCE -DHAVE_CONFIG_H +if MUDFLAP +AM_CFLAGS = -fmudflap +else +AM_CFLAGS = +endif +AM_CFLAGS += -Wall -Werror -Wshadow -Wunused -Wformat=2 -Wextra -std=gnu99 +INCLUDES = -I. -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ + -I$(srcdir)/../libdw -I.. -I$(srcdir)/../lib +VERSION = 1 + +noinst_PROGRAMS = ptest test2 + +test2_SOURCES = test2.c loc2c.c + +lib_LIBRARIES = libdwfl.a +if !MUDFLAP +noinst_LIBRARIES = libdwfl_pic.a +noinst_PROGRAMS += $(noinst_LIBRARIES:_pic.a=.so) +endif + +euincludedir = ${includedir}/elfutils +euinclude_HEADERS = libdwfl.h + +libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c \ + dwfl_module.c dwfl_report_elf.c relocate.c \ + dwfl_module_info.c dwfl_getmodules.c \ + dwfl_module_getdwarf.c dwfl_getdwarf.c \ + argp-std.c find-debuginfo.c \ + linux-kernel-modules.c linux-proc-maps.c \ + dwfl_addrmodule.c dwfl_addrdwarf.c \ + cu.c dwfl_module_nextcu.c dwfl_nextcu.c dwfl_cumodule.c \ + dwfl_module_addrdie.c dwfl_addrdie.c \ + lines.c dwfl_lineinfo.c dwfl_linemodule.c \ + dwfl_module_getsrc.c dwfl_getsrc.c \ + dwfl_module_getsrc_file.c \ + elf-from-memory.c + + +if MUDFLAP +libdwfl = libdwfl.a $(libdw) $(libebl) $(libelf) $(libeu) +libdw = ../libdw/libdw.a +libelf = ../libelf/libelf.a +libmudflap = -lmudflap +else +libdwfl = libdwfl.so +libdw = ../libdw/libdw.so +libelf = ../libelf/libelf.so +endif +libebl = ../libebl/libebl.a +libeu = ../lib/libeu.a + + +if !MUDFLAP +libdwfl_pic_a_SOURCES = +am_libdwfl_pic_a_OBJECTS = $(libdwfl_a_SOURCES:.c=.os) + +libdwfl_so_SOURCES = +libdwfl_LIBS = $(libeu) $(libdw) $(libebl) $(libelf) +libdwfl_so_LDADD = -ldl +libdwfl.so: libdwfl_pic.a $(srcdir)/libdwfl.map $(libdwfl_LIBS) + $(CC) -shared -o $@ -Wl,--whole-archive,$<,--no-whole-archive \ + -Wl,--version-script,$(srcdir)/libdwfl.map,--no-undefined \ + -Wl,--soname,$@.$(VERSION),-z,defs \ + $(libdwfl_LIBS) $(libdwfl_so_LDADD) + if readelf -d $@ | fgrep -q TEXTREL; then exit 1; fi + ln -fs $@ $@.$(VERSION) + + +%.os: %.c %.o + if $(COMPILE) -c -o $@ -fpic -DPIC -DSHARED -MT $@ -MD -MP \ + -MF "$(DEPDIR)/$*.Tpo" `test -f '$<' || echo '$(srcdir)/'`$<; \ + then cat "$(DEPDIR)/$*.Tpo" >> "$(DEPDIR)/$*.Po"; \ + rm -f "$(DEPDIR)/$*.Tpo"; \ + else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; \ + fi + +install: install-am libdwfl.so + $(mkinstalldirs) $(DESTDIR)$(libdir) + $(INSTALL_PROGRAM) libdwfl.so $(DESTDIR)$(libdir)/libdwfl-$(PACKAGE_VERSION).so + ln -fs libdwfl-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdwfl.so.$(VERSION) + ln -fs libdwfl.so.$(VERSION) $(DESTDIR)$(libdir)/libdwfl.so + +uninstall: uninstall-am + rm -f $(DESTDIR)$(libdir)/libdwfl-$(PACKAGE_VERSION).so + rm -f $(DESTDIR)$(libdir)/libdwfl.so.$(VERSION) + rm -f $(DESTDIR)$(libdir)/libdwfl.so + rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils +endif + +noinst_HEADERS = libdwflP.h loc2c.h + +EXTRA_DIST = libdwfl.map + +CLEANFILES = $(am_libdwfl_pic_a_OBJECTS) + +ptest_LDADD = $(libdwfl) $(libdw) $(libmudflap) +test2_LDADD = $(libdwfl) $(libdw) $(libmudflap) diff --git a/libdwfl/argp-std.c b/libdwfl/argp-std.c new file mode 100644 index 00000000..ebddcfb1 --- /dev/null +++ b/libdwfl/argp-std.c @@ -0,0 +1,164 @@ +/* Standard argp argument parsers for tools using libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include <argp.h> +#include <stdlib.h> +#include <assert.h> +#include <libintl.h> + +/* gettext helper macros. */ +#define _(Str) dgettext ("elfutils", Str) + + +#define OPT_DEBUGINFO 0x100 + +static const struct argp_option options[] = +{ + { NULL, 0, NULL, 0, N_("Input Selection:"), 0 }, + { "executable", 'e', "FILE", 0, N_("Find addresses in FILE"), 0 }, + { "pid", 'p', "PID", 0, + N_("Find addresses in files mapped into process PID"), 0 }, + { "kernel", 'k', NULL, 0, N_("Find addresses in the running kernel"), 0 }, + { "debuginfo-path", OPT_DEBUGINFO, "PATH", 0, + N_("Search path for separate debuginfo files"), 0 }, + { NULL, 0, NULL, 0, NULL, 0 } +}; + +static char *debuginfo_path; + +static const Dwfl_Callbacks proc_callbacks = + { + .find_debuginfo = INTUSE(dwfl_standard_find_debuginfo), + .debuginfo_path = &debuginfo_path, + + .find_elf = INTUSE(dwfl_linux_proc_find_elf), + }; + +static const Dwfl_Callbacks kernel_callbacks = + { + .find_debuginfo = INTUSE(dwfl_standard_find_debuginfo), + .debuginfo_path = &debuginfo_path, + + .find_elf = INTUSE(dwfl_linux_kernel_find_elf), + .section_address = INTUSE(dwfl_linux_kernel_module_section_address), + }; + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + inline void failure (int errnum, const char *msg) + { + if (errnum == -1) + argp_failure (state, EXIT_FAILURE, 0, "%s: %s", + msg, INTUSE(dwfl_errmsg) (-1)); + else + argp_failure (state, EXIT_FAILURE, errnum, "%s", msg); + } + inline error_t fail (int errnum, const char *msg) + { + failure (errnum, msg); + return errnum == -1 ? EIO : errnum; + } + + switch (key) + { + case OPT_DEBUGINFO: + debuginfo_path = arg; + break; + + case 'e': + if (state->hook == NULL) + { + Dwfl *dwfl = INTUSE(dwfl_begin) (&proc_callbacks); + if (INTUSE(dwfl_report_elf) (dwfl, "", arg, -1, 0) == NULL) + return fail (-1, arg); + state->hook = dwfl; + } + else + { + toomany: + argp_error (state, "%s", _("only one -e, -p, or -k option allowed")); + return EINVAL; + } + break; + + case 'p': + if (state->hook == NULL) + { + Dwfl *dwfl = INTUSE(dwfl_begin) (&proc_callbacks); + int result = INTUSE(dwfl_linux_proc_report) (dwfl, atoi (arg)); + if (result != 0) + return fail (result, arg); + state->hook = dwfl; + } + else + goto toomany; + break; + + case 'k': + if (state->hook == NULL) + { + Dwfl *dwfl = INTUSE(dwfl_begin) (&kernel_callbacks); + int result = INTUSE(dwfl_linux_kernel_report_kernel) (dwfl); + if (result != 0) + return fail (result, _("cannot load kernel symbols")); + result = INTUSE(dwfl_linux_kernel_report_modules) (dwfl); + if (result != 0) + /* Non-fatal to have no modules since we do have the kernel. */ + failure (result, _("cannot find kernel modules")); + state->hook = dwfl; + } + else + goto toomany; + break; + + case ARGP_KEY_SUCCESS: + { + Dwfl *dwfl = state->hook; + + if (dwfl == NULL) + { + /* Default if no -e, -p, or -k, is "-e a.out". */ + arg = "a.out"; + dwfl = INTUSE(dwfl_begin) (&proc_callbacks); + if (INTUSE(dwfl_report_elf) (dwfl, "", arg, -1, 0) == NULL) + return fail (-1, arg); + state->hook = dwfl; + } + + /* One of the three flavors has done dwfl_begin and some reporting + if we got here. Tie up the Dwfl and return it to the caller of + argp_parse. */ + + int result = INTUSE(dwfl_report_end) (dwfl, NULL, NULL); + assert (result == 0); + + *(Dwfl **) state->input = dwfl; + } + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const struct argp libdwfl_argp = + { .options = options, .parser = parse_opt }; + +const struct argp * +dwfl_standard_argp (void) +{ + return &libdwfl_argp; +} diff --git a/libdwfl/cu.c b/libdwfl/cu.c new file mode 100644 index 00000000..44207097 --- /dev/null +++ b/libdwfl/cu.c @@ -0,0 +1,275 @@ +/* Keeping track of DWARF compilation units in libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include "../libdw/libdwP.h" +#include "../libdw/memory-access.h" +#include <search.h> + + +static inline Dwarf_Arange * +dwar (Dwfl_Module *mod, unsigned int idx) +{ + return &mod->dw->aranges->info[mod->aranges[idx].arange]; +} + + +static Dwfl_Error +addrarange (Dwfl_Module *mod, Dwarf_Addr addr, struct dwfl_arange **arange) +{ + if (mod->aranges == NULL) + { + Dwarf_Aranges *dwaranges; + if (dwarf_getaranges (mod->dw, &dwaranges, NULL) != 0) + return DWFL_E_LIBDW; + + struct dwfl_arange *aranges = malloc (dwaranges->naranges + * sizeof *aranges); + if (unlikely (aranges == NULL)) + return DWFL_E_NOMEM; + + /* libdw has sorted its list by address, which is how we want it. + But the sorted list is full of not-quite-contiguous runs pointing + to the same CU. We don't care about the little gaps inside the + module, we'll consider them part of the surrounding CU anyway. + Collect our own array with just one record for each run of ranges + pointing to one CU. */ + + size_t naranges = 0; + Dwarf_Off lastcu = 0; + for (size_t i = 0; i < dwaranges->naranges; ++i) + if (i == 0 || dwaranges->info[i].offset != lastcu) + { + aranges[naranges].arange = i; + aranges[naranges].cu = NULL; + ++naranges; + lastcu = dwaranges->info[i].offset; + } + + /* Store the final array, which is probably much smaller than before. */ + mod->naranges = naranges; + mod->aranges = (realloc (aranges, naranges * sizeof aranges[0]) + ?: aranges); + mod->lazycu += naranges; + } + + /* The address must be inside the module to begin with. */ + addr -= mod->debug.bias; + + /* The ranges are sorted by address, so we can use binary search. */ + size_t l = 0, u = mod->naranges; + while (l < u) + { + size_t idx = (l + u) / 2; + Dwarf_Addr start = dwar (mod, idx)->addr; + if (addr < start) + { + u = idx; + continue; + } + else if (addr > start) + { + if (idx + 1 < mod->naranges) + { + if (addr >= dwar (mod, idx + 1)->addr) + { + l = idx + 1; + continue; + } + } + else + { + /* It might be in the last range. */ + const Dwarf_Arange *last + = &mod->dw->aranges->info[mod->dw->aranges->naranges - 1]; + if (addr > last->addr + last->length) + break; + } + } + + *arange = &mod->aranges[idx]; + return DWFL_E_NOERROR; + } + + return DWFL_E_ADDR_OUTOFRANGE; +} + + +static void +nofree (void *arg) +{ + struct dwfl_cu *cu = arg; + if (cu == (void *) -1l) + return; + + assert (cu->mod->lazycu == 0); +} + +/* One reason fewer to keep the lazy lookup table for CUs. */ +static inline void +less_lazy (Dwfl_Module *mod) +{ + if (--mod->lazycu > 0) + return; + + /* We know about all the CUs now, we don't need this table. */ + tdestroy (mod->lazy_cu_root, nofree); + mod->lazy_cu_root = NULL; +} + +static inline Dwarf_Off +cudie_offset (const struct dwfl_cu *cu) +{ + return cu->die.cu->start + 3 * cu->die.cu->offset_size - 4 + 3; +} + +static int +compare_cukey (const void *a, const void *b) +{ + return cudie_offset (a) - cudie_offset (b); +} + +/* Intern the CU if necessary. */ +static Dwfl_Error +intern_cu (Dwfl_Module *mod, Dwarf_Off cuoff, struct dwfl_cu **result) +{ + struct Dwarf_CU dwkey; + struct dwfl_cu key; + key.die.cu = &dwkey; + dwkey.offset_size = 0; + dwkey.start = cuoff - (3 * 0 - 4 + 3); + struct dwfl_cu **found = tsearch (&key, &mod->lazy_cu_root, &compare_cukey); + if (unlikely (found == NULL)) + return DWFL_E_NOMEM; + + if (*found == &key || *found == NULL) + { + if (unlikely (cuoff + 4 >= mod->dw->sectiondata[IDX_debug_info]->d_size)) + { + /* This is the EOF marker. Now we have interned all the CUs. + One increment in MOD->lazycu counts not having hit EOF yet. */ + *found = (void *) -1l; + less_lazy (mod); + } + else + { + /* This is a new entry, meaning we haven't looked at this CU. */ + + *found = NULL; + + struct dwfl_cu *cu = malloc (sizeof *cu); + if (unlikely (cu == NULL)) + return DWFL_E_NOMEM; + + cu->mod = mod; + cu->next = NULL; + cu->lines = NULL; + + /* XXX use non-searching lookup */ + Dwarf_Die *die = dwarf_offdie (mod->dw, cuoff, &cu->die); + if (die == NULL) + return DWFL_E_LIBDW; + assert (die == &cu->die); + + struct dwfl_cu **newvec = realloc (mod->cu, ((mod->ncu + 1) + * sizeof (mod->cu[0]))); + if (newvec == NULL) + { + free (cu); + return DWFL_E_NOMEM; + } + mod->cu = newvec; + + mod->cu[mod->ncu++] = cu; + if (cu->die.cu->start == 0) + mod->first_cu = cu; + + *found = cu; + } + } + + *result = *found; + return DWFL_E_NOERROR; +} + + +/* Traverse all the CUs in the module. */ + +Dwfl_Error +internal_function_def +__libdwfl_nextcu (Dwfl_Module *mod, struct dwfl_cu *lastcu, + struct dwfl_cu **cu) +{ + Dwarf_Off cuoff; + struct dwfl_cu **nextp; + + if (lastcu == NULL) + { + /* Start the traversal. */ + cuoff = 0; + nextp = &mod->first_cu; + } + else + { + /* Continue following LASTCU. */ + cuoff = lastcu->die.cu->end; + nextp = &lastcu->next; + } + + if (*nextp == NULL) + { + size_t cuhdrsz; + Dwarf_Off nextoff; + if (dwarf_nextcu (mod->dw, cuoff, &nextoff, &cuhdrsz, + NULL, NULL, NULL) != 0) + return DWFL_E_LIBDW; + + Dwfl_Error result = intern_cu (mod, cuoff + cuhdrsz, nextp); + if (result != DWFL_E_NOERROR) + return result; + + if ((*nextp)->next == NULL && nextoff == (Dwarf_Off) -1l) + (*nextp)->next = (void *) -1l; + } + + *cu = *nextp == (void *) -1l ? NULL : *nextp; + return DWFL_E_NOERROR; +} + + +/* Intern the CU arange points to, if necessary. */ + +static Dwfl_Error +arangecu (Dwfl_Module *mod, struct dwfl_arange *arange, struct dwfl_cu **cu) +{ + if (arange->cu == NULL) + { + const Dwarf_Arange *dwarange = &mod->dw->aranges->info[arange->arange]; + Dwfl_Error result = intern_cu (mod, dwarange->offset, &arange->cu); + if (result != DWFL_E_NOERROR) + return result; + assert (arange->cu != NULL && arange->cu != (void *) -1l); + less_lazy (mod); /* Each arange with null ->cu counts once. */ + } + + *cu = arange->cu; + return DWFL_E_NOERROR; +} + +Dwfl_Error +internal_function_def +__libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, struct dwfl_cu **cu) +{ + struct dwfl_arange *arange; + return addrarange (mod, addr, &arange) ?: arangecu (mod, arange, cu); +} diff --git a/libdwfl/dwfl_addrdie.c b/libdwfl/dwfl_addrdie.c new file mode 100644 index 00000000..4aba9c3e --- /dev/null +++ b/libdwfl/dwfl_addrdie.c @@ -0,0 +1,21 @@ +/* Fetch CU DIE from address. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwarf_Die * +dwfl_addrdie (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Addr *bias) +{ + return INTUSE(dwfl_module_addrdie) (INTUSE(dwfl_addrmodule) (dwfl, addr), + addr, bias); +} diff --git a/libdwfl/dwfl_addrdwarf.c b/libdwfl/dwfl_addrdwarf.c new file mode 100644 index 00000000..33eaec62 --- /dev/null +++ b/libdwfl/dwfl_addrdwarf.c @@ -0,0 +1,22 @@ +/* Fetch libdw handle from address. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwarf * +dwfl_addrdwarf (Dwfl *dwfl, Dwarf_Addr address, Dwarf_Addr *bias) +{ + return INTUSE(dwfl_module_getdwarf) (INTUSE(dwfl_addrmodule) (dwfl, address), + bias); +} +INTDEF (dwfl_addrdwarf) diff --git a/libdwfl/dwfl_addrmodule.c b/libdwfl/dwfl_addrmodule.c new file mode 100644 index 00000000..69aab57c --- /dev/null +++ b/libdwfl/dwfl_addrmodule.c @@ -0,0 +1,38 @@ +/* Find module containing address. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwfl_Module * +dwfl_addrmodule (Dwfl *dwfl, Dwarf_Addr address) +{ + if (dwfl == NULL) + return NULL; + + /* Do binary search on the array indexed by module load address. */ + size_t l = 0, u = dwfl->nmodules; + while (l < u) + { + size_t idx = (l + u) / 2; + Dwfl_Module *m = dwfl->modules[idx]; + if (address < m->low_addr) + u = idx; + else if (address >= m->high_addr) + l = idx + 1; + else + return m; + } + + return NULL; +} +INTDEF (dwfl_addrmodule) diff --git a/libdwfl/dwfl_begin.c b/libdwfl/dwfl_begin.c new file mode 100644 index 00000000..e7130a4a --- /dev/null +++ b/libdwfl/dwfl_begin.c @@ -0,0 +1,33 @@ +/* Set up a session using libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwfl * +dwfl_begin (const Dwfl_Callbacks *callbacks) +{ + if (elf_version (EV_CURRENT) == EV_NONE) + { + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + + Dwfl *dwfl = calloc (1, sizeof *dwfl); + if (dwfl == NULL) + __libdwfl_seterrno (DWFL_E_NOMEM); + else + dwfl->callbacks = callbacks; + + return dwfl; +} +INTDEF (dwfl_begin) diff --git a/libdwfl/dwfl_cumodule.c b/libdwfl/dwfl_cumodule.c new file mode 100644 index 00000000..69b5c0fb --- /dev/null +++ b/libdwfl/dwfl_cumodule.c @@ -0,0 +1,21 @@ +/* Find the module for a CU DIE previously returned by libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwfl_Module * +dwfl_cumodule (Dwarf_Die *cudie) +{ + struct dwfl_cu *cu = (struct dwfl_cu *) cudie; + return cu->mod; +} diff --git a/libdwfl/dwfl_end.c b/libdwfl/dwfl_end.c new file mode 100644 index 00000000..b2836d30 --- /dev/null +++ b/libdwfl/dwfl_end.c @@ -0,0 +1,26 @@ +/* Finish a session using libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +void +dwfl_end (Dwfl *dwfl) +{ + if (dwfl != NULL) + { + for (size_t i = 0; i < dwfl->nmodules; ++i) + if (dwfl->modules[i] != NULL) + __libdwfl_module_free (dwfl->modules[i]); + free (dwfl->modules); + } +} diff --git a/libdwfl/dwfl_error.c b/libdwfl/dwfl_error.c new file mode 100644 index 00000000..4bd1a50f --- /dev/null +++ b/libdwfl/dwfl_error.c @@ -0,0 +1,222 @@ +/* Error handling in libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <assert.h> +#include <libintl.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> + +#include "libdwflP.h" + + +#ifdef USE_TLS +/* The error number. */ +static __thread int global_error; +#else +/* This is the key for the thread specific memory. */ +static tls_key_t key; + +/* The error number. Used in non-threaded programs. */ +static int global_error; +static bool threaded; +/* We need to initialize the thread-specific data. */ +once_define (static, once); + +/* The initialization and destruction functions. */ +static void init (void); +static void free_key_mem (void *mem); +#endif /* TLS */ + + +int +dwfl_errno (void) +{ + int result; + +#ifndef USE_TLS + /* If we have not yet initialized the buffer do it now. */ + once_execute (once, init); + + if (threaded) + { + /* We do not allocate memory for the data. It is only a word. + We can store it in place of the pointer. */ + result = (intptr_t) getspecific (key); + + setspecific (key, (void *) (intptr_t) DWFL_E_NOERROR); + return result; + } +#endif /* TLS */ + + result = global_error; + global_error = DWFL_E_NOERROR; + return result; +} + + +static const struct msgtable +{ +#define DWFL_ERROR(name, text) char msg_##name[sizeof text]; + DWFL_ERRORS +#undef DWFL_ERROR +} msgtable = + { +#define DWFL_ERROR(name, text) text, + DWFL_ERRORS +#undef DWFL_ERROR + }; +#define msgstr (&msgtable.msg_NOERROR[0]) + +static const uint_fast16_t msgidx[] = +{ +#define DWFL_ERROR(name, text) \ + [DWFL_E_##name] = offsetof (struct msgtable, msg_##name), + DWFL_ERRORS +#undef DWFL_ERROR +}; +#define nmsgidx (sizeof msgidx / sizeof msgidx[0]) + + +static inline int +canonicalize (Dwfl_Error error) +{ + unsigned int value; + + switch (error) + { + default: + value = error; + if ((value &~ 0xffff) != 0) + break; + assert (value < nmsgidx); + break; + case DWFL_E_ERRNO: + value = DWFL_E (ERRNO, errno); + break; + case DWFL_E_LIBELF: + value = DWFL_E (LIBELF, elf_errno ()); + break; + case DWFL_E_LIBDW: + value = DWFL_E (LIBDW, dwarf_errno ()); + break; +#if 0 + DWFL_E_LIBEBL: + value = DWFL_E (LIBEBL, ebl_errno ()); + break; +#endif + } + + return value; +} + +int +internal_function_def +__libdwfl_canon_error (Dwfl_Error error) +{ + return canonicalize (error); +} + +void +internal_function_def +__libdwfl_seterrno (Dwfl_Error error) +{ + int value = canonicalize (error); + +#ifndef USE_TLS + /* If we have not yet initialized the buffer do it now. */ + once_execute (once, init); + + if (threaded) + /* We do not allocate memory for the data. It is only a word. + We can store it in place of the pointer. */ + setspecific (key, (void *) (intptr_t) value); +#endif /* TLS */ + + global_error = value; +} + + +const char * +dwfl_errmsg (error) + int error; +{ + if (error == 0 || error == -1) + { + int last_error; + +#ifndef USE_TLS + /* If we have not yet initialized the buffer do it now. */ + once_execute (once, init); + + if (threaded) + /* We do not allocate memory for the data. It is only a word. + We can store it in place of the pointer. */ + last_error = (intptr_t) getspecific (key); + else +#endif /* TLS */ + last_error = global_error; + + if (error == 0 && last_error == 0) + return NULL; + + error = last_error; + global_error = DWFL_E_NOERROR; + } + + switch (error &~ 0xffff) + { + case OTHER_ERROR (ERRNO): + return strerror_r (error & 0xffff, "bad", 0); + case OTHER_ERROR (LIBELF): + return elf_errmsg (error & 0xffff); + case OTHER_ERROR (LIBDW): + return dwarf_errmsg (error & 0xffff); +#if 0 + case OTHER_ERROR (LIBEBL): + return ebl_errmsg (error & 0xffff); +#endif + } + + return _(&msgstr[msgidx[(unsigned int) error < nmsgidx + ? error : DWFL_E_UNKNOWN_ERROR]]); +} +INTDEF (dwfl_errmsg) + + +#ifndef USE_TLS +/* Free the thread specific data, this is done if a thread terminates. */ +static void +free_key_mem (void *mem __attribute__ ((unused))) +{ + setspecific (key, NULL); +} + + +/* Initialize the key for the global variable. */ +static void +init (void) +{ + // XXX Screw you, gcc4, the unused function attribute does not work. + __asm ("" :: "r" (free_key_mem)); + + if (key_create (&key, free_key_mem) == 0) + /* Creating the key succeeded. */ + threaded = true; +} +#endif /* TLS */ diff --git a/libdwfl/dwfl_getdwarf.c b/libdwfl/dwfl_getdwarf.c new file mode 100644 index 00000000..cae49dab --- /dev/null +++ b/libdwfl/dwfl_getdwarf.c @@ -0,0 +1,40 @@ +/* Iterate through modules to fetch Dwarf information. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +ptrdiff_t +dwfl_getdwarf (Dwfl *dwfl, + int (*callback) (Dwfl_Module *, void **, + const char *, Dwarf_Addr, + Dwarf *, Dwarf_Addr, void *), + void *arg, + ptrdiff_t offset) +{ + if (dwfl == NULL) + return -1; + + if ((size_t) offset > dwfl->nmodules) + return -1; + + while ((size_t) offset < dwfl->nmodules) + { + Dwfl_Module *mod = dwfl->modules[offset++]; + Dwarf_Addr bias = 0; + Dwarf *dw = INTUSE(dwfl_module_getdwarf) (mod, &bias); + if ((*callback) (MODCB_ARGS (mod), dw, bias, arg) != DWARF_CB_OK) + return offset; + } + + return 0; +} diff --git a/libdwfl/dwfl_getmodules.c b/libdwfl/dwfl_getmodules.c new file mode 100644 index 00000000..4fc14880 --- /dev/null +++ b/libdwfl/dwfl_getmodules.c @@ -0,0 +1,37 @@ +/* Iterate through modules. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +ptrdiff_t +dwfl_getmodules (Dwfl *dwfl, + int (*callback) (Dwfl_Module *, void **, + const char *, Dwarf_Addr, void *), + void *arg, + ptrdiff_t offset) +{ + if (dwfl == NULL) + return -1; + + if ((size_t) offset > dwfl->nmodules) + return -1; + + while ((size_t) offset < dwfl->nmodules) + { + Dwfl_Module *mod = dwfl->modules[offset++]; + if ((*callback) (MODCB_ARGS (mod), arg) != DWARF_CB_OK) + return offset; + } + + return 0; +} diff --git a/libdwfl/dwfl_getsrc.c b/libdwfl/dwfl_getsrc.c new file mode 100644 index 00000000..3ceb6329 --- /dev/null +++ b/libdwfl/dwfl_getsrc.c @@ -0,0 +1,21 @@ +/* Find source location for PC address. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwfl_Line * +dwfl_getsrc (Dwfl *dwfl, Dwarf_Addr addr) +{ + return INTUSE(dwfl_module_getsrc) (INTUSE(dwfl_addrmodule) (dwfl, addr), + addr); +} diff --git a/libdwfl/dwfl_lineinfo.c b/libdwfl/dwfl_lineinfo.c new file mode 100644 index 00000000..4771e003 --- /dev/null +++ b/libdwfl/dwfl_lineinfo.c @@ -0,0 +1,40 @@ +/* Get information from a source line record returned by libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include "../libdw/libdwP.h" + +extern const char * +dwfl_lineinfo (Dwfl_Line *line, Dwarf_Addr *addr, int *linep, int *colp, + Dwarf_Word *mtime, Dwarf_Word *length) +{ + if (line == NULL) + return NULL; + + struct dwfl_cu *cu = dwfl_linecu (line); + const Dwarf_Line *info = &cu->die.cu->lines->info[line->idx]; + + if (addr != NULL) + *addr = info->addr - cu->mod->debug.bias; + if (linep != NULL) + *linep = info->line; + if (colp != NULL) + *colp = info->column; + + struct Dwarf_Fileinfo_s *file = &info->files->info[info->file]; + if (mtime != NULL) + *mtime = file->mtime; + if (length != NULL) + *length = file->length; + return file->name; +} diff --git a/libdwfl/dwfl_linemodule.c b/libdwfl/dwfl_linemodule.c new file mode 100644 index 00000000..233dbd41 --- /dev/null +++ b/libdwfl/dwfl_linemodule.c @@ -0,0 +1,23 @@ +/* Fetch the module containing a source line record returned by libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwfl_Module * +dwfl_linemodule (Dwfl_Line *line) +{ + if (line == NULL) + return NULL; + + return dwfl_linecu (line)->mod; +} diff --git a/libdwfl/dwfl_module.c b/libdwfl/dwfl_module.c new file mode 100644 index 00000000..a6d9f41f --- /dev/null +++ b/libdwfl/dwfl_module.c @@ -0,0 +1,188 @@ +/* Maintenance of module list in libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include <search.h> + +static void +free_cu (struct dwfl_cu *cu) +{ + if (cu->lines != NULL) + free (cu->lines); + free (cu); +} + +static void +nofree (void *arg __attribute__ ((unused))) +{ +} + +void +internal_function_def +__libdwfl_module_free (Dwfl_Module *mod) +{ + if (mod->lazy_cu_root != NULL) + tdestroy (mod->lazy_cu_root, nofree); + + if (mod->aranges != NULL) + free (mod->aranges); + + if (mod->cu != NULL) + { + for (size_t i = 0; i < mod->ncu; ++i) + free_cu (mod->cu[i]); + free (mod->cu); + } + + if (mod->dw != NULL) + dwarf_end (mod->dw); + + if (mod->ebl != NULL) + ebl_closebackend (mod->ebl); + + if (mod->debug.elf != mod->main.elf && mod->debug.elf != NULL) + elf_end (mod->debug.elf); + if (mod->main.elf != NULL) + elf_end (mod->main.elf); + + free (mod->name); +} + +void +dwfl_report_begin (Dwfl *dwfl) +{ + for (Dwfl_Module *m = dwfl->modulelist; m != NULL; m = m->next) + m->gc = true; + + if (dwfl->modules != NULL) + free (dwfl->modules); + dwfl->modules = NULL; + dwfl->nmodules = 0; +} +INTDEF (dwfl_report_begin) + +/* Report that a module called NAME pans addresses [START, END). + Returns the module handle, either existing or newly allocated, + or returns a null pointer for an allocation error. */ +Dwfl_Module * +dwfl_report_module (Dwfl *dwfl, const char *name, + GElf_Addr start, GElf_Addr end) +{ + Dwfl_Module **tailp = &dwfl->modulelist, **prevp = tailp; + for (Dwfl_Module *m = *prevp; m != NULL; m = *(prevp = &m->next)) + { + if (m->low_addr == start && m->high_addr == end + && !strcmp (m->name, name)) + { + /* This module is still here. Move it to the place in the list + after the last module already reported. */ + + *prevp = m->next; + m->next = *tailp; + m->gc = false; + *tailp = m; + return m; + } + + if (! m->gc) + tailp = &m->next; + } + + Dwfl_Module *mod = calloc (1, sizeof *mod); + if (mod == NULL) + goto nomem; + + mod->name = strdup (name); + if (mod->name == NULL) + { + free (mod); + nomem: + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + + mod->low_addr = start; + mod->high_addr = end; + mod->dwfl = dwfl; + + mod->next = *tailp; + *tailp = mod; + ++dwfl->nmodules; + + return mod; +} +INTDEF (dwfl_report_module) + +static int +compare_modules (const void *a, const void *b) +{ + Dwfl_Module *const *p1 = a, *const *p2 = b; + const Dwfl_Module *m1 = *p1, *m2 = *p2; + if (m1 == NULL) + return -1; + if (m2 == NULL) + return 1; + return (GElf_Sxword) (m1->low_addr - m2->low_addr); +} + + +/* Finish reporting the current set of modules to the library. + If REMOVED is not null, it's called for each module that + existed before but was not included in the current report. + Returns a nonzero return value from the callback. + DWFL cannot be used until this function has returned zero. */ +int dwfl_report_end (Dwfl *dwfl, + int (*removed) (Dwfl_Module *, void *, + const char *, Dwarf_Addr, + void *arg), + void *arg) +{ + assert (dwfl->modules == NULL); + + Dwfl_Module **tailp = &dwfl->modulelist; + while (*tailp != NULL) + { + Dwfl_Module *m = *tailp; + if (m->gc && removed != NULL) + { + int result = (*removed) (MODCB_ARGS (m), arg); + if (result != 0) + return result; + } + if (m->gc) + { + *tailp = m->next; + __libdwfl_module_free (m); + } + else + tailp = &m->next; + } + + dwfl->modules = malloc (dwfl->nmodules * sizeof dwfl->modules[0]); + if (dwfl->modules == NULL && dwfl->nmodules != 0) + return -1; + + size_t i = 0; + for (Dwfl_Module *m = dwfl->modulelist; m != NULL; m = m->next) + { + assert (! m->gc); + dwfl->modules[i++] = m; + } + assert (i == dwfl->nmodules); + + qsort (dwfl->modules, dwfl->nmodules, sizeof dwfl->modules[0], + &compare_modules); + + return 0; +} +INTDEF (dwfl_report_end) diff --git a/libdwfl/dwfl_module_addrdie.c b/libdwfl/dwfl_module_addrdie.c new file mode 100644 index 00000000..29e2dfe8 --- /dev/null +++ b/libdwfl/dwfl_module_addrdie.c @@ -0,0 +1,30 @@ +/* Fetch the CU DIE for a PC address in a given module. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwarf_Die * +dwfl_module_addrdie (Dwfl_Module *mod, Dwarf_Addr addr, Dwarf_Addr *bias) +{ + if (INTUSE(dwfl_module_getdwarf) (mod, bias) == NULL) + return NULL; + + struct dwfl_cu *cu; + Dwfl_Error error = __libdwfl_addrcu (mod, addr, &cu); + if (likely (error == DWFL_E_NOERROR)) + return &cu->die; + + __libdwfl_seterrno (error); + return NULL; +} +INTDEF (dwfl_module_addrdie) diff --git a/libdwfl/dwfl_module_getdwarf.c b/libdwfl/dwfl_module_getdwarf.c new file mode 100644 index 00000000..03cf8f05 --- /dev/null +++ b/libdwfl/dwfl_module_getdwarf.c @@ -0,0 +1,480 @@ +/* Find debugging and symbol information for a module in libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include <fcntl.h> +#include <string.h> +#include "../libdw/libdwP.h" /* DWARF_E_* values are here. */ + + +/* Open libelf FILE->fd and compute the load base of ELF as loaded in MOD. + When we return success, FILE->elf and FILE->bias are set up. */ +static inline Dwfl_Error +open_elf (Dwfl_Module *mod, struct dwfl_file *file) +{ + if (file->elf == NULL) + { + if (file->fd < 0) + return CBFAIL; + + file->elf = elf_begin (file->fd, ELF_C_READ_MMAP_PRIVATE, NULL); + } + + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (file->elf, &ehdr_mem); + if (ehdr == NULL) + return DWFL_E (LIBELF, elf_errno ()); + + mod->isrel = ehdr->e_type == ET_REL; + + file->bias = 0; + for (uint_fast16_t i = 0; i < ehdr->e_phnum; ++i) + { + GElf_Phdr ph_mem; + GElf_Phdr *ph = gelf_getphdr (file->elf, i, &ph_mem); + if (ph == NULL) + return DWFL_E_LIBELF; + if (ph->p_type == PT_LOAD) + { + file->bias = ((mod->low_addr & -ph->p_align) + - (ph->p_vaddr & -ph->p_align)); + break; + } + } + + return DWFL_E_NOERROR; +} + +/* Find the main ELF file for this module and open libelf on it. + When we return success, MOD->main.elf and MOD->main.bias are set up. */ +static void +find_file (Dwfl_Module *mod) +{ + if (mod->main.elf != NULL /* Already done. */ + || mod->elferr != DWFL_E_NOERROR) /* Cached failure. */ + return; + + mod->main.fd = (*mod->dwfl->callbacks->find_elf) (MODCB_ARGS (mod), + &mod->main.name, + &mod->main.elf); + mod->elferr = open_elf (mod, &mod->main); +} + +/* Find the separate debuginfo file for this module and open libelf on it. + When we return success, MOD->debug is set up. */ +static Dwfl_Error +find_debuginfo (Dwfl_Module *mod) +{ + size_t shstrndx; + if (elf_getshstrndx (mod->main.elf, &shstrndx) < 0) + return DWFL_E_LIBELF; + + Elf_Scn *scn = elf_getscn (mod->main.elf, 0); + if (scn == NULL) + return DWFL_E_LIBELF; + do + { + GElf_Shdr shdr_mem, *shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr == NULL) + return DWFL_E_LIBELF; + + const char *name = elf_strptr (mod->main.elf, shstrndx, shdr->sh_name); + if (name == NULL) + return DWFL_E_LIBELF; + + if (!strcmp (name, ".gnu_debuglink")) + break; + + scn = elf_nextscn (mod->main.elf, scn); + } while (scn != NULL); + + const char *debuglink_file = NULL; + GElf_Word debuglink_crc = 0; + if (scn != NULL) + { + /* Found the .gnu_debuglink section. Extract its contents. */ + Elf_Data *rawdata = elf_rawdata (scn, NULL); + if (rawdata == NULL) + return DWFL_E_LIBELF; + + Elf_Data crcdata = + { + .d_type = ELF_T_WORD, + .d_buf = &debuglink_crc, + .d_size = sizeof debuglink_crc, + .d_version = EV_CURRENT, + }; + Elf_Data conv = + { + .d_type = ELF_T_WORD, + .d_buf = rawdata->d_buf + rawdata->d_size - sizeof debuglink_crc, + .d_size = sizeof debuglink_crc, + .d_version = EV_CURRENT, + }; + + GElf_Ehdr ehdr_mem; + GElf_Ehdr *ehdr = gelf_getehdr (mod->main.elf, &ehdr_mem); + if (ehdr == NULL) + return DWFL_E_LIBELF; + + Elf_Data *d = gelf_xlatetom (mod->main.elf, &crcdata, &conv, + ehdr->e_ident[EI_DATA]); + if (d == NULL) + return DWFL_E_LIBELF; + assert (d == &crcdata); + + debuglink_file = rawdata->d_buf; + } + + mod->debug.fd = (*mod->dwfl->callbacks->find_debuginfo) (MODCB_ARGS (mod), + mod->main.name, + debuglink_file, + debuglink_crc, + &mod->debug.name); + return open_elf (mod, &mod->debug); +} + + +/* Try to find a symbol table in FILE. */ +static Dwfl_Error +load_symtab (struct dwfl_file *file, struct dwfl_file **symfile, + Elf_Scn **symscn, Elf_Scn **xndxscn, + size_t *syments, GElf_Word *strshndx) +{ + Elf_Scn *scn = NULL; + while ((scn = elf_nextscn (file->elf, scn)) != NULL) + { + GElf_Shdr shdr_mem, *shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr != NULL) + switch (shdr->sh_type) + { + case SHT_SYMTAB: + *symscn = scn; + *symfile = file; + *strshndx = shdr->sh_link; + *syments = shdr->sh_size / shdr->sh_entsize; + if (*symscn != NULL && *xndxscn != NULL) + return DWFL_E_NOERROR; + break; + + case SHT_DYNSYM: + /* Use this if need be, but keep looking for SHT_SYMTAB. */ + *symscn = scn; + *symfile = file; + *strshndx = shdr->sh_link; + *syments = shdr->sh_size / shdr->sh_entsize; + break; + + case SHT_SYMTAB_SHNDX: + *xndxscn = scn; + break; + + default: + break; + } + } + return DWFL_E_NO_SYMTAB; +} + +/* Try to find a symbol table in either MOD->main.elf or MOD->debug.elf. */ +static void +find_symtab (Dwfl_Module *mod) +{ + if (mod->symdata != NULL /* Already done. */ + || mod->symerr != DWFL_E_NOERROR) /* Cached previous failure. */ + return; + + find_file (mod); + mod->symerr = mod->elferr; + if (mod->symerr != DWFL_E_NOERROR) + return; + + /* First see if the main ELF file has the debugging information. */ + Elf_Scn *symscn = NULL, *xndxscn = NULL; + GElf_Word strshndx; + mod->symerr = load_symtab (&mod->main, &mod->symfile, &symscn, + &xndxscn, &mod->syments, &strshndx); + switch (mod->symerr) + { + default: + return; + + case DWFL_E_NOERROR: + break; + + case DWFL_E_NO_SYMTAB: + /* Now we have to look for a separate debuginfo file. */ + mod->symerr = find_debuginfo (mod); + switch (mod->symerr) + { + default: + return; + + case DWFL_E_NOERROR: + mod->symerr = load_symtab (&mod->debug, &mod->symfile, &symscn, + &xndxscn, &mod->syments, &strshndx); + break; + + case DWFL_E_CB: /* The find_debuginfo hook failed. */ + mod->symerr = DWFL_E_NO_SYMTAB; + break; + } + + switch (mod->symerr) + { + default: + return; + + case DWFL_E_NOERROR: + break; + + case DWFL_E_NO_SYMTAB: + if (symscn == NULL) + return; + /* We still have the dynamic symbol table. */ + mod->symerr = DWFL_E_NOERROR; + break; + } + break; + } + + /* This does some sanity checks on the string table section. */ + if (elf_strptr (mod->symfile->elf, strshndx, 0) == NULL) + { + elferr: + mod->symerr = DWFL_E (LIBELF, elf_errno ()); + return; + } + + /* Cache the data; MOD->syments was set above. */ + + mod->symstrdata = elf_rawdata (elf_getscn (mod->symfile->elf, strshndx), + NULL); + if (mod->symstrdata == NULL) + goto elferr; + + if (xndxscn == NULL) + mod->symxndxdata = NULL; + else + { + mod->symxndxdata = elf_rawdata (xndxscn, NULL); + if (mod->symxndxdata == NULL) + goto elferr; + } + + mod->symdata = elf_rawdata (symscn, NULL); + if (mod->symdata == NULL) + goto elferr; +} + + +/* Try to start up libdw on DEBUGFILE. */ +static Dwfl_Error +load_dw (Dwfl_Module *mod, Elf *debugfile) +{ + if (mod->isrel) + { + const Dwfl_Callbacks *const cb = mod->dwfl->callbacks; + + /* The debugging sections have to be relocated. */ + if (cb->section_address == NULL) + return DWFL_E_NOREL; + + if (mod->ebl == NULL) + { + mod->ebl = ebl_openbackend (mod->main.elf); + if (mod->ebl == NULL) + return DWFL_E_LIBEBL; + } + + find_symtab (mod); + Dwfl_Error result = mod->symerr; + if (result == DWFL_E_NOERROR) + result = __libdwfl_relocate (mod); + if (result != DWFL_E_NOERROR) + return result; + } + + mod->dw = dwarf_begin_elf (debugfile, DWARF_C_READ, NULL); + if (mod->dw == NULL) + { + int err = dwarf_errno (); + return err == DWARF_E_NO_DWARF ? DWFL_E_NO_DWARF : DWFL_E (LIBDW, err); + } + + /* Until we have iterated through all CU's, we might do lazy lookups. */ + mod->lazycu = 1; + + return DWFL_E_NOERROR; +} + +/* Try to start up libdw on either the main file or the debuginfo file. */ +static void +find_dw (Dwfl_Module *mod) +{ + if (mod->dw != NULL /* Already done. */ + || mod->dwerr != DWFL_E_NOERROR) /* Cached previous failure. */ + return; + + find_file (mod); + mod->dwerr = mod->elferr; + if (mod->dwerr != DWFL_E_NOERROR) + return; + + /* First see if the main ELF file has the debugging information. */ + mod->dwerr = load_dw (mod, mod->main.elf); + switch (mod->dwerr) + { + case DWFL_E_NOERROR: + mod->debug.elf = mod->main.elf; + mod->debug.bias = mod->main.bias; + return; + + case DWFL_E_NO_DWARF: + break; + + default: + goto canonicalize; + } + + /* Now we have to look for a separate debuginfo file. */ + mod->dwerr = find_debuginfo (mod); + switch (mod->dwerr) + { + case DWFL_E_NOERROR: + mod->dwerr = load_dw (mod, mod->debug.elf); + break; + + case DWFL_E_CB: /* The find_debuginfo hook failed. */ + mod->dwerr = DWFL_E_NO_DWARF; + return; + + default: + break; + } + + canonicalize: + mod->dwerr = __libdwfl_canon_error (mod->dwerr); +} + + +Elf * +dwfl_module_getelf (Dwfl_Module *mod, GElf_Addr *loadbase) +{ + if (mod == NULL) + return NULL; + + find_file (mod); + if (mod->elferr == DWFL_E_NOERROR) + { + *loadbase = mod->main.bias; + return mod->main.elf; + } + + __libdwfl_seterrno (mod->elferr); + return NULL; +} +INTDEF (dwfl_module_getelf) + + +Dwarf * +dwfl_module_getdwarf (Dwfl_Module *mod, Dwarf_Addr *bias) +{ + if (mod == NULL) + return NULL; + + find_dw (mod); + if (mod->dwerr == DWFL_E_NOERROR) + { + *bias = mod->debug.bias; + return mod->dw; + } + + __libdwfl_seterrno (mod->dwerr); + return NULL; +} +INTDEF (dwfl_module_getdwarf) + + +const char * +dwfl_module_addrname (Dwfl_Module *mod, GElf_Addr addr) +{ + if (mod == NULL) + return NULL; + + find_symtab (mod); + if (mod->symerr != DWFL_E_NOERROR) + { + __libdwfl_seterrno (mod->symerr); + return NULL; + } + + addr -= mod->symfile->bias; + + /* Look through the symbol table for a matching symbol. */ + size_t symshstrndx = SHN_UNDEF; + for (size_t i = 1; i < mod->syments; ++i) + { + GElf_Sym sym_mem; + GElf_Word shndx; + GElf_Sym *sym = gelf_getsymshndx (mod->symdata, mod->symxndxdata, + i, &sym_mem, &shndx); + if (sym != NULL) + { + GElf_Addr symaddr = sym->st_value; + + if (sym->st_shndx != SHN_XINDEX) + shndx = sym->st_shndx; + + if (mod->isrel) + /* In an ET_REL file, the symbol table values are relative + to the section, not to the module's load base. */ + switch (shndx) + { + case SHN_UNDEF: /* Undefined symbol can't match an address. */ + case SHN_COMMON: /* Nor can a common defn. */ + continue; + + case SHN_ABS: /* Symbol value is already absolute. */ + break; + + default: + { + Dwfl_Error result = DWFL_E_LIBELF; + if (likely (symshstrndx != SHN_UNDEF) + || elf_getshstrndx (mod->symfile->elf, + &symshstrndx) == 0) + result = __libdwfl_relocate_value (mod, symshstrndx, + shndx, &symaddr); + if (unlikely (result != DWFL_E_NOERROR)) + { + __libdwfl_seterrno (result); + return NULL; + } + break; + } + } + + if (symaddr <= addr && addr < symaddr + sym->st_size) + { + if (unlikely (sym->st_name >= mod->symstrdata->d_size)) + { + __libdwfl_seterrno (DWFL_E_BADSTROFF); + return NULL; + } + return (const char *) mod->symstrdata->d_buf + sym->st_name; + } + } + } + + return NULL; +} diff --git a/libdwfl/dwfl_module_getsrc.c b/libdwfl/dwfl_module_getsrc.c new file mode 100644 index 00000000..3b341b92 --- /dev/null +++ b/libdwfl/dwfl_module_getsrc.c @@ -0,0 +1,60 @@ +/* Find source location for PC address in module. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include "../libdw/libdwP.h" + +Dwfl_Line * +dwfl_module_getsrc (Dwfl_Module *mod, Dwarf_Addr addr) +{ + Dwarf_Addr bias; + if (INTUSE(dwfl_module_getdwarf) (mod, &bias) == NULL) + return NULL; + + struct dwfl_cu *cu; + Dwfl_Error error = __libdwfl_addrcu (mod, addr, &cu); + if (likely (error == DWFL_E_NOERROR)) + error = __libdwfl_cu_getsrclines (cu); + if (likely (error == DWFL_E_NOERROR)) + { + /* The lines are sorted by address, so we can use binary search. */ + size_t l = 0, u = cu->die.cu->lines->nlines; + while (l < u) + { + size_t idx = (l + u) / 2; + if (addr < cu->die.cu->lines->info[idx].addr) + u = idx; + else if (addr > cu->die.cu->lines->info[idx].addr) + l = idx + 1; + else + return &cu->lines->idx[idx]; + } + + if (cu->die.cu->lines->nlines > 0) + assert (cu->die.cu->lines->info + [cu->die.cu->lines->nlines - 1].end_sequence); + + /* If none were equal, the closest one below is what we want. + We never want the last one, because it's the end-sequence + marker with an address at the high bound of the CU's code. */ + if (u > 0 && u < cu->die.cu->lines->nlines + && addr > cu->die.cu->lines->info[u - 1].addr) + return &cu->lines->idx[u - 1]; + + error = DWFL_E_ADDR_OUTOFRANGE; + } + + __libdwfl_seterrno (error); + return NULL; +} +INTDEF (dwfl_module_getsrc) diff --git a/libdwfl/dwfl_module_getsrc_file.c b/libdwfl/dwfl_module_getsrc_file.c new file mode 100644 index 00000000..81f57ed6 --- /dev/null +++ b/libdwfl/dwfl_module_getsrc_file.c @@ -0,0 +1,144 @@ +/* Find matching source locations in a module. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include "../libdw/libdwP.h" + + +int +dwfl_module_getsrc_file (Dwfl_Module *mod, + const char *fname, int lineno, int column, + Dwfl_Line ***srcsp, size_t *nsrcs) +{ + if (mod == NULL) + return -1; + + bool is_basename = strchr (fname, '/') == NULL; + + size_t max_match = *nsrcs ?: ~0u; + size_t act_match = *nsrcs; + size_t cur_match = 0; + Dwfl_Line **match = *nsrcs == 0 ? NULL : *srcsp; + + struct dwfl_cu *cu = NULL; + Dwfl_Error error; + while ((error = __libdwfl_nextcu (mod, cu, &cu)) == DWFL_E_NOERROR + && cu != NULL + && (error = __libdwfl_cu_getsrclines (cu)) == DWFL_E_NOERROR) + { + inline const char *dwarf_line_file (const Dwarf_Line *line) + { + return line->files->info[line->file].name; + } + inline Dwarf_Line *dwfl_line (const Dwfl_Line *line) + { + return &dwfl_linecu (line)->die.cu->lines->info[line->idx]; + } + inline const char *dwfl_line_file (const Dwfl_Line *line) + { + return dwarf_line_file (dwfl_line (line)); + } + + /* Search through all the line number records for a matching + file and line/column number. If any of the numbers is zero, + no match is performed. */ + const char *lastfile = NULL; + bool lastmatch = false; + for (size_t cnt = 0; cnt < cu->die.cu->lines->nlines; ++cnt) + { + Dwarf_Line *line = &cu->die.cu->lines->info[cnt]; + + if (unlikely (line->file >= line->files->nfiles)) + { + __libdwfl_seterrno (DWFL_E (LIBDW, DWARF_E_INVALID_DWARF)); + return -1; + } + else + { + const char *file = dwarf_line_file (line); + if (file != lastfile) + { + /* Match the name with the name the user provided. */ + lastfile = file; + lastmatch = !strcmp (is_basename ? basename (file) : file, + fname); + } + } + if (!lastmatch) + continue; + + /* See whether line and possibly column match. */ + if (lineno != 0 + && (lineno > line->line + || (column != 0 && column > line->column))) + /* Cannot match. */ + continue; + + /* Determine whether this is the best match so far. */ + size_t inner; + for (inner = 0; inner < cur_match; ++inner) + if (dwfl_line_file (match[inner]) == dwarf_line_file (line)) + break; + if (inner < cur_match + && (dwfl_line (match[inner])->line != line->line + || dwfl_line (match[inner])->line != lineno + || (column != 0 + && (dwfl_line (match[inner])->column != line->column + || dwfl_line (match[inner])->column != column)))) + { + /* We know about this file already. If this is a better + match for the line number, use it. */ + if (dwfl_line (match[inner])->line >= line->line + && (dwfl_line (match[inner])->line != line->line + || dwfl_line (match[inner])->column >= line->column)) + /* Use the new line. Otherwise the old one. */ + match[inner] = &cu->lines->idx[cnt]; + continue; + } + + if (cur_match < max_match) + { + if (cur_match == act_match) + { + /* Enlarge the array for the results. */ + act_match += 10; + Dwfl_Line **newp = realloc (match, + act_match + * sizeof (Dwfl_Line *)); + if (newp == NULL) + { + free (match); + __libdwfl_seterrno (DWFL_E_NOMEM); + return -1; + } + match = newp; + } + + match[cur_match++] = &cu->lines->idx[cnt]; + } + } + } + + if (cur_match > 0) + { + assert (*nsrcs == 0 || *srcsp == match); + + *nsrcs = cur_match; + *srcsp = match; + + return 0; + } + + __libdwfl_seterrno (DWFL_E_NO_MATCH); + return -1; +} diff --git a/libdwfl/dwfl_module_info.c b/libdwfl/dwfl_module_info.c new file mode 100644 index 00000000..e8c27b9d --- /dev/null +++ b/libdwfl/dwfl_module_info.c @@ -0,0 +1,44 @@ +/* Return information about a module. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +const char * +dwfl_module_info (Dwfl_Module *mod, void ***userdata, + Dwarf_Addr *start, Dwarf_Addr *end, + Dwarf_Addr *dwbias, Dwarf_Addr *symbias, + const char **mainfile, const char **debugfile) +{ + if (mod == NULL) + return NULL; + + if (userdata) + *userdata = &mod->userdata; + if (start) + *start = mod->low_addr; + if (end) + *end = mod->high_addr; + + if (dwbias) + *dwbias = mod->debug.elf == NULL ? (Dwarf_Addr) -1 : mod->debug.bias; + if (symbias) + *symbias = mod->symfile == NULL ? (Dwarf_Addr) -1 : mod->symfile->bias; + + if (mainfile) + *mainfile = mod->main.name; + + if (debugfile) + *debugfile = mod->debug.name; + + return mod->name; +} diff --git a/libdwfl/dwfl_module_nextcu.c b/libdwfl/dwfl_module_nextcu.c new file mode 100644 index 00000000..c089047f --- /dev/null +++ b/libdwfl/dwfl_module_nextcu.c @@ -0,0 +1,29 @@ +/* Iterate through DWARF compilation units in a module. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwarf_Die * +dwfl_module_nextcu (Dwfl_Module *mod, Dwarf_Die *lastcu, Dwarf_Addr *bias) +{ + if (INTUSE(dwfl_module_getdwarf) (mod, bias) == NULL) + return NULL; + + struct dwfl_cu *cu; + Dwfl_Error error = __libdwfl_nextcu (mod, (struct dwfl_cu *) lastcu, &cu); + if (likely (error == DWFL_E_NOERROR)) + return &cu->die; /* Same as a cast, so ok for null too. */ + + __libdwfl_seterrno (error); + return NULL; +} diff --git a/libdwfl/dwfl_nextcu.c b/libdwfl/dwfl_nextcu.c new file mode 100644 index 00000000..a5565c69 --- /dev/null +++ b/libdwfl/dwfl_nextcu.c @@ -0,0 +1,52 @@ +/* Iterate through DWARF compilation units across all modules. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +Dwarf_Die * +dwfl_nextcu (Dwfl *dwfl, Dwarf_Die *lastcu, Dwarf_Addr *bias) +{ + if (dwfl == NULL) + return NULL; + + struct dwfl_cu *cu = (struct dwfl_cu *) lastcu; + Dwfl_Module *mod; + + if (cu == NULL) + { + mod = dwfl->modulelist; + goto nextmod; + } + else + mod = cu->mod; + + Dwfl_Error error; + while ((error = __libdwfl_nextcu (mod, cu, &cu)) == DWFL_E_NOERROR) + { + if (cu != NULL) + { + *bias = mod->debug.bias; + return &cu->die; + } + + mod = mod->next; + + nextmod: + if (mod == NULL || INTUSE(dwfl_module_getdwarf) (mod, bias) == NULL) + return NULL; + } + + __libdwfl_seterrno (error); + return NULL; +} +INTDEF (dwfl_nextcu) diff --git a/libdwfl/dwfl_report_elf.c b/libdwfl/dwfl_report_elf.c new file mode 100644 index 00000000..21c9e846 --- /dev/null +++ b/libdwfl/dwfl_report_elf.c @@ -0,0 +1,117 @@ +/* Report a module to libdwfl based on ELF program headers. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include <fcntl.h> +#include <unistd.h> + + +Dwfl_Module * +dwfl_report_elf (Dwfl *dwfl, const char *name, + const char *file_name, int fd, GElf_Addr base) +{ + bool closefd = false; + + if (fd < 0) + { + fd = open64 (file_name, O_RDONLY); + if (fd < 0) + { + __libdwfl_seterrno (DWFL_E_ERRNO); + return NULL; + } + closefd = true; + } + + Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL); + + GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr (elf, &ehdr_mem); + if (ehdr == NULL) + { + elf_error: + __libdwfl_seterrno (DWFL_E_LIBELF); + if (closefd) + close (fd); + return NULL; + } + + GElf_Addr start = 0, end = 0; + for (uint_fast16_t i = 0; i < ehdr->e_phnum; ++i) + { + GElf_Phdr phdr_mem, *ph = gelf_getphdr (elf, i, &phdr_mem); + if (ph == NULL) + goto elf_error; + if (ph->p_type == PT_LOAD) + { + start = base + (ph->p_vaddr & -ph->p_align); + break; + } + } + + for (uint_fast16_t i = ehdr->e_phnum; i-- > 0;) + { + GElf_Phdr phdr_mem, *ph = gelf_getphdr (elf, i, &phdr_mem); + if (ph == NULL) + goto elf_error; + if (ph->p_type == PT_LOAD) + { + end = base + (ph->p_vaddr + ph->p_memsz); + break; + } + } + + if (end == 0) + { + __libdwfl_seterrno (DWFL_E_NO_PHDR); + if (closefd) + close (fd); + return NULL; + } + + Dwfl_Module *m = INTUSE(dwfl_report_module) (dwfl, name, + base + start, base + end); + if (m != NULL) + { + if (m->main.name == NULL) + { + m->main.name = strdup (file_name); + m->main.fd = fd; + } + else if ((fd >= 0 && m->main.fd != fd) + || strcmp (m->main.name, file_name)) + { + elf_end (elf); + overlap: + if (closefd) + close (fd); + m->gc = true; + __libdwfl_seterrno (DWFL_E_OVERLAP); + m = NULL; + } + + /* Preinstall the open ELF handle for the module. */ + if (m->main.elf == NULL) + { + m->main.elf = elf; + m->main.bias = base; + } + else + { + elf_end (elf); + if (m->main.bias != base) + goto overlap; + } + } + return m; +} +INTDEF (dwfl_report_elf) diff --git a/libdwfl/elf-from-memory.c b/libdwfl/elf-from-memory.c new file mode 100644 index 00000000..3175ab16 --- /dev/null +++ b/libdwfl/elf-from-memory.c @@ -0,0 +1,328 @@ +/* Reconstruct an ELF file by reading the segments out of remote memory. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include <config.h> +#include "../libelf/libelfP.h" +#undef _ + +#include "libdwflP.h" + +#include <gelf.h> +#include <sys/types.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +/* Reconstruct an ELF file by reading the segments out of remote memory + based on the ELF file header at EHDR_VMA and the ELF program headers it + points to. If not null, *LOADBASEP is filled in with the difference + between the addresses from which the segments were read, and the + addresses the file headers put them at. + + The function READ_MEMORY is called to copy at least MINREAD and at most + MAXREAD bytes from the remote memory at target address ADDRESS into the + local buffer at DATA; it should return -1 for errors (with code in + `errno'), 0 if it failed to read at least MINREAD bytes due to EOF, or + the number of bytes read if >= MINREAD. ARG is passed through. */ + +Elf * +elf_from_remote_memory (GElf_Addr ehdr_vma, + GElf_Addr *loadbasep, + ssize_t (*read_memory) (void *arg, void *data, + GElf_Addr address, + size_t minread, + size_t maxread), + void *arg) +{ + /* First read in the file header and check its sanity. */ + + const size_t initial_bufsize = 256; + unsigned char *buffer = malloc (initial_bufsize); + if (buffer == NULL) + { + no_memory: + __libdwfl_seterrno (DWFL_E_NOMEM); + return NULL; + } + + ssize_t nread = (*read_memory) (arg, buffer, ehdr_vma, + sizeof (Elf32_Ehdr), initial_bufsize); + if (nread <= 0) + { + read_error: + free (buffer); + __libdwfl_seterrno (nread < 0 ? DWFL_E_ERRNO : DWFL_E_TRUNCATED); + return NULL; + } + + if (memcmp (buffer, ELFMAG, SELFMAG) != 0) + { + bad_elf: + __libdwfl_seterrno (DWFL_E_BADELF); + return NULL; + } + + /* Extract the information we need from the file header. */ + + union + { + Elf32_Ehdr e32; + Elf64_Ehdr e64; + } ehdr; + Elf_Data xlatefrom = + { + .d_type = ELF_T_EHDR, + .d_buf = buffer, + .d_version = EV_CURRENT, + }; + Elf_Data xlateto = + { + .d_type = ELF_T_EHDR, + .d_buf = &ehdr, + .d_size = sizeof ehdr, + .d_version = EV_CURRENT, + }; + + GElf_Off phoff; + uint_fast16_t phnum; + uint_fast16_t phentsize; + GElf_Off shdrs_end; + + switch (buffer[EI_CLASS]) + { + case ELFCLASS32: + xlatefrom.d_size = sizeof (Elf32_Ehdr); + if (elf32_xlatetom (&xlateto, &xlatefrom, buffer[EI_DATA]) == NULL) + { + libelf_error: + __libdwfl_seterrno (DWFL_E_LIBELF); + return NULL; + } + phoff = ehdr.e32.e_phoff; + phnum = ehdr.e32.e_phnum; + phentsize = ehdr.e32.e_phentsize; + if (phentsize != sizeof (Elf32_Phdr) || phnum == 0) + goto bad_elf; + shdrs_end = ehdr.e32.e_shoff + ehdr.e32.e_shnum * ehdr.e32.e_shentsize; + break; + + case ELFCLASS64: + xlatefrom.d_size = sizeof (Elf64_Ehdr); + if (elf32_xlatetom (&xlateto, &xlatefrom, buffer[EI_DATA]) == NULL) + goto libelf_error; + phoff = ehdr.e64.e_phoff; + phnum = ehdr.e64.e_phnum; + phentsize = ehdr.e64.e_phentsize; + if (phentsize != sizeof (Elf64_Phdr) || phnum == 0) + goto bad_elf; + shdrs_end = ehdr.e64.e_shoff + ehdr.e64.e_shnum * ehdr.e64.e_shentsize; + break; + + default: + goto bad_elf; + } + + + /* The file header tells where to find the program headers. + These are what we use to actually choose what to read. */ + + xlatefrom.d_type = xlateto.d_type = ELF_T_PHDR; + xlatefrom.d_size = phnum * phentsize; + + if ((size_t) nread >= phoff + phnum * phentsize) + /* We already have all the phdrs from the initial read. */ + xlatefrom.d_buf = buffer + phoff; + else + { + /* Read in the program headers. */ + + if (initial_bufsize < phnum * phentsize) + { + unsigned char *newbuf = realloc (buffer, phnum * phentsize); + if (newbuf == NULL) + { + free (buffer); + goto no_memory; + } + buffer = newbuf; + } + nread = (*read_memory) (arg, buffer, ehdr_vma + phoff, + phnum * phentsize, phnum * phentsize); + if (nread <= 0) + goto read_error; + + xlatefrom.d_buf = buffer; + } + + union + { + Elf32_Phdr p32[phnum]; + Elf64_Phdr p64[phnum]; + } phdrs; + + xlateto.d_buf = &phdrs; + xlateto.d_size = sizeof phdrs; + + /* Scan for PT_LOAD segments to find the total size of the file image. */ + size_t contents_size = 0; + GElf_Off segments_end = 0; + GElf_Addr loadbase = ehdr_vma; + switch (ehdr.e32.e_ident[EI_CLASS]) + { + inline void handle_segment (GElf_Addr vaddr, GElf_Off offset, + GElf_Xword filesz, GElf_Xword align) + { + GElf_Off segment_end = ((offset + filesz + align - 1) & -align); + + if (segment_end > (GElf_Off) contents_size) + contents_size = segment_end; + + if ((offset & -align) == 0 && loadbase == ehdr_vma) + loadbase = ehdr_vma - (vaddr & -align); + + segments_end = offset + filesz; + } + + case ELFCLASS32: + if (elf32_xlatetom (&xlateto, &xlatefrom, + ehdr.e32.e_ident[EI_DATA]) == NULL) + goto libelf_error; + for (uint_fast16_t i = 0; i < phnum; ++i) + if (phdrs.p32[i].p_type == PT_LOAD) + handle_segment (phdrs.p32[i].p_vaddr, phdrs.p32[i].p_offset, + phdrs.p32[i].p_filesz, phdrs.p32[i].p_align); + break; + + case ELFCLASS64: + if (elf32_xlatetom (&xlateto, &xlatefrom, + ehdr.e32.e_ident[EI_DATA]) == NULL) + goto libelf_error; + for (uint_fast16_t i = 0; i < phnum; ++i) + if (phdrs.p32[i].p_type == PT_LOAD) + handle_segment (phdrs.p64[i].p_vaddr, phdrs.p64[i].p_offset, + phdrs.p64[i].p_filesz, phdrs.p64[i].p_align); + break; + + default: + abort (); + break; + } + + /* Trim the last segment so we don't bother with zeros in the last page + that are off the end of the file. However, if the extra bit in that + page includes the section headers, keep them. */ + if ((GElf_Off) contents_size > segments_end + && (GElf_Off) contents_size >= shdrs_end) + { + contents_size = segments_end; + if ((GElf_Off) contents_size < shdrs_end) + contents_size = shdrs_end; + } + else + contents_size = segments_end; + + free (buffer); + + /* Now we know the size of the whole image we want read in. */ + buffer = calloc (1, contents_size); + if (buffer == NULL) + goto no_memory; + + switch (ehdr.e32.e_ident[EI_CLASS]) + { + inline bool handle_segment (GElf_Addr vaddr, GElf_Off offset, + GElf_Xword filesz, GElf_Xword align) + { + GElf_Off start = offset & -align; + GElf_Off end = (offset + filesz + align - 1) & -align; + if (end > (GElf_Off) contents_size) + end = contents_size; + nread = (*read_memory) (arg, buffer + start, + (loadbase + vaddr) & -align, + end - start, end - start); + return nread <= 0; + } + + case ELFCLASS32: + for (uint_fast16_t i = 0; i < phnum; ++i) + if (phdrs.p32[i].p_type == PT_LOAD) + if (handle_segment (phdrs.p32[i].p_vaddr, phdrs.p32[i].p_offset, + phdrs.p32[i].p_filesz, phdrs.p32[i].p_align)) + goto read_error; + + /* If the segments visible in memory didn't include the section + headers, then clear them from the file header. */ + if (contents_size < shdrs_end) + { + ehdr.e32.e_shoff = 0; + ehdr.e32.e_shnum = 0; + ehdr.e32.e_shstrndx = 0; + } + + /* This will normally have been in the first PT_LOAD segment. But it + conceivably could be missing, and we might have just changed it. */ + xlatefrom.d_type = xlateto.d_type = ELF_T_EHDR; + xlatefrom.d_size = xlateto.d_size = sizeof ehdr.e32; + xlatefrom.d_buf = &ehdr.e32; + xlateto.d_buf = buffer; + if (elf32_xlatetof (&xlateto, &xlatefrom, + ehdr.e32.e_ident[EI_DATA]) == NULL) + goto libelf_error; + break; + + case ELFCLASS64: + for (uint_fast16_t i = 0; i < phnum; ++i) + if (phdrs.p32[i].p_type == PT_LOAD) + if (handle_segment (phdrs.p64[i].p_vaddr, phdrs.p64[i].p_offset, + phdrs.p64[i].p_filesz, phdrs.p64[i].p_align)) + goto read_error; + + /* If the segments visible in memory didn't include the section + headers, then clear them from the file header. */ + if (contents_size < shdrs_end) + { + ehdr.e64.e_shoff = 0; + ehdr.e64.e_shnum = 0; + ehdr.e64.e_shstrndx = 0; + } + + /* This will normally have been in the first PT_LOAD segment. But it + conceivably could be missing, and we might have just changed it. */ + xlatefrom.d_type = xlateto.d_type = ELF_T_EHDR; + xlatefrom.d_size = xlateto.d_size = sizeof ehdr.e64; + xlatefrom.d_buf = &ehdr.e64; + xlateto.d_buf = buffer; + if (elf32_xlatetof (&xlateto, &xlatefrom, + ehdr.e64.e_ident[EI_DATA]) == NULL) + goto libelf_error; + break; + + default: + abort (); + break; + } + + /* Now we have the image. Open libelf on it. */ + + Elf *elf = elf_memory ((char *) buffer, contents_size); + if (elf == NULL) + { + free (buffer); + return NULL; + } + + elf->flags |= ELF_F_MALLOCED; + if (loadbasep != NULL) + *loadbasep = loadbase; + return elf; +} diff --git a/libdwfl/find-debuginfo.c b/libdwfl/find-debuginfo.c new file mode 100644 index 00000000..c9e640dd --- /dev/null +++ b/libdwfl/find-debuginfo.c @@ -0,0 +1,157 @@ +/* Standard find_debuginfo callback for libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include "system.h" + + +#define DEFAULT_DEBUGINFO_PATH ":.debug:/usr/lib/debug" + + +/* Try to open64 [DIR/][SUBDIR/]DEBUGLINK, return file descriptor or -1. + On success, *DEBUGINFO_FILE_NAME has the malloc'd name of the open file. */ +static int +try_open (const char *dir, const char *subdir, const char *debuglink, + char **debuginfo_file_name) +{ + char *fname = NULL; + if (dir == NULL && subdir == NULL) + fname = strdup (debuglink); + else if (subdir == NULL) + asprintf (&fname, "%s/%s", dir, debuglink); + else if (dir == NULL) + asprintf (&fname, "%s/%s", subdir, debuglink); + else + asprintf (&fname, "%s/%s/%s", dir, subdir, debuglink); + + if (fname == NULL) + return -1; + + int fd = open64 (fname, O_RDONLY); + if (fd < 0) + free (fname); + else + *debuginfo_file_name = fname; + + return fd; +} + +/* Return true iff the FD's contents CRC matches DEBUGLINK_CRC. */ +static inline bool +check_crc (int fd, GElf_Word debuglink_crc) +{ + uint32_t file_crc; + return crc32_file (fd, &file_crc) == 0 && file_crc == debuglink_crc; +} + +int +dwfl_standard_find_debuginfo (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *modname __attribute__ ((unused)), + GElf_Addr base __attribute__ ((unused)), + const char *file_name, + const char *debuglink_file, + GElf_Word debuglink_crc, + char **debuginfo_file_name) +{ + bool cancheck = true; + + const char *file_basename = file_name == NULL ? NULL : basename (file_name); + if (debuglink_file == NULL) + { + if (file_basename == NULL) + { + errno = 0; + return -1; + } + + size_t len = strlen (file_basename); + char *localname = alloca (len + sizeof ".debug"); + memcpy (localname, file_basename, len); + memcpy (&localname[len], ".debug", sizeof ".debug"); + debuglink_file = localname; + cancheck = false; + } + + /* Look for a file named DEBUGLINK_FILE in the directories + indicated by the debug directory path setting. */ + + const Dwfl_Callbacks *const cb = mod->dwfl->callbacks; + char *path = strdupa ((cb->debuginfo_path ? *cb->debuginfo_path : NULL) + ?: DEFAULT_DEBUGINFO_PATH); + + /* A leading - or + in the whole path sets whether to check file CRCs. */ + bool defcheck = true; + if (path[0] == '-' || path[0] == '+') + { + defcheck = path[0] == '+'; + ++path; + } + + char *file_dirname = (file_basename == file_name ? NULL + : strndupa (file_name, file_basename - 1 - file_name)); + char *p; + while ((p = strsep (&path, ":")) != NULL) + { + /* A leading - or + says whether to check file CRCs for this element. */ + bool check = defcheck; + if (*p == '+' || *p == '-') + check = *p++ == '+'; + check = check && cancheck; + + const char *dir, *subdir; + switch (p[0]) + { + case '\0': + /* An empty entry says to try the main file's directory. */ + dir = file_dirname; + subdir = NULL; + break; + case '/': + /* An absolute path says to look there for a subdirectory + named by the main file's absolute directory. + This cannot be applied to a relative file name. */ + if (file_dirname == NULL || file_dirname[0] != '/') + continue; + dir = p; + subdir = file_dirname + 1; + break; + default: + /* A relative path says to try a subdirectory of that name + in the main file's directory. */ + dir = file_dirname; + subdir = p; + break; + } + + char *fname; + int fd = try_open (dir, subdir, debuglink_file, &fname); + if (fd < 0) + continue; + if (!check || check_crc (fd, debuglink_crc)) + { + *debuginfo_file_name = fname; + return fd; + } + free (fname); + close (fd); + } + + /* No dice. */ + errno = 0; + return -1; +} +INTDEF (dwfl_standard_find_debuginfo) diff --git a/libdwfl/libdwfl.h b/libdwfl/libdwfl.h new file mode 100644 index 00000000..8fb9fdac --- /dev/null +++ b/libdwfl/libdwfl.h @@ -0,0 +1,262 @@ +/* Interfaces for libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#ifndef _LIBDWFL_H +#define _LIBDWFL_H 1 + +#include <libdw.h> + +/* Handle for a session using the library. */ +typedef struct Dwfl Dwfl; + +/* Handle for a module. */ +typedef struct Dwfl_Module Dwfl_Module; + +/* Handle describing a line record. */ +typedef struct Dwfl_Line Dwfl_Line; + +/* Callbacks. */ +typedef struct +{ + int (*find_elf) (Dwfl_Module *mod, void **userdata, + const char *modname, Dwarf_Addr base, + char **file_name, Elf **elfp); + + int (*find_debuginfo) (Dwfl_Module *mod, void **userdata, + const char *modname, Dwarf_Addr base, + const char *file_name, + const char *debuglink_file, GElf_Word debuglink_crc, + char **debuginfo_file_name); + + /* Fill *ADDR with the loaded address of the + section called SECNAME in the given module. */ + int (*section_address) (Dwfl_Module *mod, void **userdata, + const char *modname, Dwarf_Addr base, + const char *secname, Dwarf_Addr *addr); + + char **debuginfo_path; /* See dwfl_standard_find_debuginfo. */ +} Dwfl_Callbacks; + + +/* Start a new session with the library. */ +Dwfl *dwfl_begin (const Dwfl_Callbacks *callbacks); + +/* End a session. */ +void dwfl_end (Dwfl *); + +/* Return error code of last failing function call. This value is kept + separately for each thread. */ +extern int dwfl_errno (void); + +/* Return error string for ERROR. If ERROR is zero, return error string + for most recent error or NULL if none occurred. If ERROR is -1 the + behaviour is similar to the last case except that not NULL but a legal + string is returned. */ +extern const char *dwfl_errmsg (int err); + + +/* Start reporting the current set of modules to the library. No calls but + dwfl_report_module can be made on DWFL until dwfl_report_end is called. */ +extern void dwfl_report_begin (Dwfl *dwfl); + +/* Report that a module called NAME spans addresses [START, END). + Returns the module handle, either existing or newly allocated, + or returns a null pointer for an allocation error. */ +extern Dwfl_Module *dwfl_report_module (Dwfl *dwfl, const char *name, + Dwarf_Addr start, Dwarf_Addr end); + +/* Report a module with start and end addresses computed from the ELF + program headers in the given file, plus BASE. FD may be -1 to open + FILE_NAME. On success, FD is consumed by the library, and the + `find_elf' callback will not be used for this module. */ +extern Dwfl_Module *dwfl_report_elf (Dwfl *dwfl, const char *name, + const char *file_name, int fd, + GElf_Addr base); + +/* Finish reporting the current set of modules to the library. + If REMOVED is not null, it's called for each module that + existed before but was not included in the current report. + Returns a nonzero return value from the callback. + The callback may call dwfl_report_module; doing so with the + details of the module being removed prevents its removal. + DWFL cannot be used until this function has returned zero. */ +extern int dwfl_report_end (Dwfl *dwfl, + int (*removed) (Dwfl_Module *, void *, + const char *, Dwarf_Addr, + void *arg), + void *arg); + +/* Return the name of the module, and for each non-null argument store + interesting details: *USERDATA is a location for storing your own + pointer, **USERDATA is initially null; *START and *END give the address + range covered by the module; *DWBIAS is the address bias for debugging + information, and *SYMBIAS for symbol table entries (either is -1 if not + yet accessed); *MAINFILE is the name of the ELF file, and *DEBUGFILE the + name of the debuginfo file (might be equal to *MAINFILE; either is null + if not yet accessed). */ +extern const char *dwfl_module_info (Dwfl_Module *mod, void ***userdata, + Dwarf_Addr *start, Dwarf_Addr *end, + Dwarf_Addr *dwbias, Dwarf_Addr *symbias, + const char **mainfile, + const char **debugfile); + +/* Iterate through the modules, starting the walk with OFFSET == 0. + Calls *CALLBACK for each module as long as it returns DWARF_CB_OK. + When *CALLBACK returns another value, the walk stops and the + return value can be passed as OFFSET to resume it. Returns 0 when + there are no more modules, or -1 for errors. */ +extern ptrdiff_t dwfl_getmodules (Dwfl *dwfl, + int (*callback) (Dwfl_Module *, void **, + const char *, Dwarf_Addr, + void *arg), + void *arg, + ptrdiff_t offset); + + +/*** Standard callbacks ***/ + +/* Standard find_debuginfo callback function. + This is controlled by a string specifying directories to look in. + If `debuginfo_path' is set in the Dwfl_Callbacks structure + and the char * it points to is not null, that supplies the string. + Otherwise a default path is used. + + If the first character of the string is + or - that says to check or to + ignore (respectively) the CRC32 checksum from the .gnu_debuglink + section. The default is to check it. The remainder of the string is + composed of elements separated by colons. Each element can start with + + or - to override the global checksum behavior. If the remainder of the + element is empty, the directory containing the main file is tried; if + it's an absolute path name, the absolute directory path containing the + main file is taken as a subdirectory of this path; a relative path name + is taken as a subdirectory of the directory containing the main file. + Hence for /bin/ls, string ":.debug:/usr/lib/debug" says to look in /bin, + then /bin/.debug, then /usr/lib/debug/bin, for the file name in the + .gnu_debuglink section (or "ls.debug" if none was found). */ + +extern int dwfl_standard_find_debuginfo (Dwfl_Module *, void **, + const char *, Dwarf_Addr, + const char *, const char *, + GElf_Word, char **); + + +/* Callbacks for working with kernel modules in the running Linux kernel. */ +extern int dwfl_linux_kernel_find_elf (Dwfl_Module *, void **, + const char *, Dwarf_Addr, + char **, Elf **); +extern int dwfl_linux_kernel_module_section_address (Dwfl_Module *, void **, + const char *, Dwarf_Addr, + const char *, + Dwarf_Addr *addr); + +/* Call dwfl_report_elf for the running Linux kernel. + Returns zero on success, -1 if dwfl_report_module failed, + or an errno code if opening the kernel binary failed. */ +extern int dwfl_linux_kernel_report_kernel (Dwfl *dwfl); + +/* Call dwfl_report_module for each kernel module in the running Linux kernel. + Returns zero on success, -1 if dwfl_report_module failed, + or an errno code if reading the list of modules failed. */ +extern int dwfl_linux_kernel_report_modules (Dwfl *dwfl); + + +/* Call dwfl_report_module for each file mapped into the address space of PID. + Returns zero on success, -1 if dwfl_report_module failed, + or an errno code if opening the kernel binary failed. */ +int dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid); + +/* Trivial find_elf callback for use with dwfl_linux_proc_report. + This uses the module name as a file name directly and tries to open it + if it begin with a slash, or handles the magic string "[vdso]". */ +int dwfl_linux_proc_find_elf (Dwfl_Module *mod, void **userdata, + const char *module_name, Dwarf_Addr base, + char **file_name, Elf **); + +/* Standard argument parsing for using a standard callback set. */ +struct argp; +extern const struct argp *dwfl_standard_argp (void) __attribute__ ((const)); + + +/*** Dwarf access functions ***/ + +/* Find the module containing the given address. */ +extern Dwfl_Module *dwfl_addrmodule (Dwfl *dwfl, Dwarf_Addr address); + +/* Fetch the module main ELF file (where the allocated sections + are found) for use with libelf. If successful, fills in *BIAS + with the difference between addresses within the loaded module + and those in symbol tables or Dwarf information referring to it. */ +extern Elf *dwfl_module_getelf (Dwfl_Module *, GElf_Addr *bias); + +/* Fetch the module's debug information for use with libdw. + If successful, fills in *BIAS with the difference between + addresses within the loaded module and those to use with libdw. */ +extern Dwarf *dwfl_module_getdwarf (Dwfl_Module *, Dwarf_Addr *bias) + __nonnull_attribute__ (2); + +/* Get the libdw handle for each module. */ +extern ptrdiff_t dwfl_getdwarf (Dwfl *, + int (*callback) (Dwfl_Module *, void **, + const char *, Dwarf_Addr, + Dwarf *, Dwarf_Addr, void *), + void *arg, ptrdiff_t offset); + +/* Look up the module containing ADDR and return its debugging information, + loading it if necessary. */ +extern Dwarf *dwfl_addrdwarf (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Addr *bias) + __nonnull_attribute__ (3); + + +/* Find the CU containing ADDR and return its DIE. */ +extern Dwarf_Die *dwfl_addrdie (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Addr *bias) + __nonnull_attribute__ (3); +extern Dwarf_Die *dwfl_module_addrdie (Dwfl_Module *mod, + Dwarf_Addr addr, Dwarf_Addr *bias) + __nonnull_attribute__ (3); + +/* Iterate through the CUs, start with null for LASTCU. */ +extern Dwarf_Die *dwfl_nextcu (Dwfl *dwfl, Dwarf_Die *lastcu, Dwarf_Addr *bias) + __nonnull_attribute__ (3); +extern Dwarf_Die *dwfl_module_nextcu (Dwfl_Module *mod, + Dwarf_Die *lastcu, Dwarf_Addr *bias) + __nonnull_attribute__ (3); + +/* Return the module containing the CU DIE. */ +extern Dwfl_Module *dwfl_cumodule (Dwarf_Die *cudie); + + +/* Get source for address. */ +extern Dwfl_Line *dwfl_module_getsrc (Dwfl_Module *mod, Dwarf_Addr addr); +extern Dwfl_Line *dwfl_getsrc (Dwfl *dwfl, Dwarf_Addr addr); + +/* Get address for source. */ +extern int dwfl_module_getsrc_file (Dwfl_Module *mod, + const char *fname, int lineno, int column, + Dwfl_Line ***srcsp, size_t *nsrcs); + +/* Return the module containing this line record. */ +extern Dwfl_Module *dwfl_linemodule (Dwfl_Line *line); + +/* Return the source file name and fill in other information. + Arguments may be null for unneeded fields. */ +extern const char *dwfl_lineinfo (Dwfl_Line *line, Dwarf_Addr *addr, + int *linep, int *colp, + Dwarf_Word *mtime, Dwarf_Word *length); + + +/* Find the symbol that ADDRESS lies inside, and return its name. */ +const char *dwfl_module_addrname (Dwfl_Module *mod, GElf_Addr address); + + + +#endif /* libdwfl.h */ diff --git a/libdwfl/libdwfl.map b/libdwfl/libdwfl.map new file mode 100644 index 00000000..1cf3d3fe --- /dev/null +++ b/libdwfl/libdwfl.map @@ -0,0 +1,39 @@ +ELFUTILS_1.0 { + global: + dwfl_addrdie; + dwfl_addrdwarf; + dwfl_addrmodule; + dwfl_begin; + dwfl_end; + dwfl_errmsg; + dwfl_errno; + dwfl_getdwarf; + dwfl_getsrc; + dwfl_linecu; + dwfl_lineinfo; + dwfl_linemodule; + dwfl_linux_kernel_find_elf; + dwfl_linux_kernel_module_section_address; + dwfl_linux_kernel_report_kernel; + dwfl_linux_kernel_report_modules; + dwfl_linux_proc_find_elf; + dwfl_linux_proc_report; + dwfl_module_addrdie; + dwfl_module_addrname; + dwfl_module_getdwarf; + dwfl_module_getelf; + dwfl_module_getsrc; + dwfl_module_getsrc_file; + dwfl_module_info; + dwfl_module_nextcu; + dwfl_nextcu; + dwfl_report_begin; + dwfl_report_elf; + dwfl_report_end; + dwfl_report_module; + dwfl_standard_argp; + dwfl_standard_find_debuginfo; + + local: + *; +}; diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h new file mode 100644 index 00000000..ccf6ba5c --- /dev/null +++ b/libdwfl/libdwflP.h @@ -0,0 +1,237 @@ +/* Internal definitions for libdwfl. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#ifndef _LIBDWFLP_H +#define _LIBDWFLP_H 1 + +#ifndef PACKAGE +# include <config.h> +#endif +#include <libdwfl.h> +#include <libebl.h> +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +/* gettext helper macros. */ +#define _(Str) dgettext ("elfutils", Str) + +#define DWFL_ERRORS \ + DWFL_ERROR (NOERROR, N_("no error")) \ + DWFL_ERROR (UNKNOWN_ERROR, N_("unknown error")) \ + DWFL_ERROR (NOMEM, N_("out of memory")) \ + DWFL_ERROR (ERRNO, N_("See errno")) \ + DWFL_ERROR (LIBELF, N_("See elf_errno")) \ + DWFL_ERROR (LIBDW, N_("See dwarf_errno")) \ + DWFL_ERROR (LIBEBL, N_("See ebl_errno (XXX missing)")) \ + DWFL_ERROR (NOREL, N_("Callbacks missing for ET_REL file")) \ + DWFL_ERROR (BADRELTYPE, N_("Unsupported relocation type")) \ + DWFL_ERROR (BADRELOFF, N_("r_offset is bogus")) \ + DWFL_ERROR (BADSTROFF, N_("offset out of range")) \ + DWFL_ERROR (RELUNDEF, N_("relocation refers to undefined symbol")) \ + DWFL_ERROR (CB, N_("Callback returned failure")) \ + DWFL_ERROR (NO_DWARF, N_("No DWARF information found")) \ + DWFL_ERROR (NO_SYMTAB, N_("No symbol table found")) \ + DWFL_ERROR (NO_PHDR, N_("No ELF program headers")) \ + DWFL_ERROR (OVERLAP, N_("address range overlaps an existing module")) \ + DWFL_ERROR (ADDR_OUTOFRANGE, N_("address out of range")) \ + DWFL_ERROR (NO_MATCH, N_("no matching address range")) \ + DWFL_ERROR (TRUNCATED, N_("image truncated")) \ + DWFL_ERROR (BADELF, N_("not a valid ELF file")) + +#define DWFL_ERROR(name, text) DWFL_E_##name, +typedef enum { DWFL_ERRORS DWFL_E_NUM } Dwfl_Error; +#undef DWFL_ERROR + +#define OTHER_ERROR(name) ((unsigned int) DWFL_E_##name << 16) +#define DWFL_E(name, errno) (OTHER_ERROR (name) | (errno)) + +extern int __libdwfl_canon_error (Dwfl_Error error) internal_function; +extern void __libdwfl_seterrno (Dwfl_Error error) internal_function; + +struct Dwfl +{ + const Dwfl_Callbacks *callbacks; + + Dwfl_Module *modulelist; /* List in order used by full traversals. */ + + Dwfl_Module **modules; + size_t nmodules; +}; + +struct dwfl_file +{ + char *name; + int fd; + + Elf *elf; + GElf_Addr bias; /* Actual load address - p_vaddr. */ +}; + +struct Dwfl_Module +{ + Dwfl *dwfl; + struct Dwfl_Module *next; /* Link on Dwfl.moduelist. */ + + void *userdata; + + char *name; /* Iterator name for this module. */ + GElf_Addr low_addr, high_addr; + + struct dwfl_file main, debug; + Ebl *ebl; + bool isrel; /* True iff this is an ET_REL file. */ + Dwfl_Error elferr; /* Previous failure to open main file. */ + + struct dwfl_file *symfile; /* Either main or debug. */ + Elf_Data *symdata; /* Data in the ELF symbol table section. */ + size_t syments; /* sh_size / sh_entsize of that section. */ + const Elf_Data *symstrdata; /* Data for its string table. */ + Elf_Data *symxndxdata; /* Data in the extended section index table. */ + Dwfl_Error symerr; /* Previous failure to load symbols. */ + + Dwarf *dw; /* libdw handle for its debugging info. */ + Dwfl_Error dwerr; /* Previous failure to load info. */ + + /* Known CU's in this module. */ + struct dwfl_cu *first_cu, **cu; + unsigned int ncu; + + void *lazy_cu_root; /* Table indexed by Dwarf_Off of CU. */ + unsigned int lazycu; /* Possible users, deleted when none left. */ + + struct dwfl_arange *aranges; /* Mapping of addresses in module to CUs. */ + unsigned int naranges; + + bool gc; /* Mark/sweep flag. */ +}; + + + +/* Information cached about each CU in Dwfl_Module.dw. */ +struct dwfl_cu +{ + /* This caches libdw information about the CU. It's also the + address passed back to users, so we take advantage of the + fact that it's placed first to cast back. */ + Dwarf_Die die; + + Dwfl_Module *mod; /* Pointer back to containing module. */ + + struct dwfl_cu *next; /* CU immediately following in the file. */ + + struct Dwfl_Lines *lines; +}; + +struct Dwfl_Lines +{ + struct dwfl_cu *cu; + + /* This is what the opaque Dwfl_Line * pointers we pass to users are. + We need to recover pointers to our struct dwfl_cu and a record in + libdw's Dwarf_Line table. To minimize the memory used in addition + to libdw's Dwarf_Lines buffer, we just point to our own index in + this table, and have one pointer back to the CU. The indices here + match those in libdw's Dwarf_CU.lines->info table. */ + struct Dwfl_Line + { + unsigned int idx; /* My index in the dwfl_cu.lines table. */ + } idx[0]; +}; + +static inline struct dwfl_cu * +dwfl_linecu (const Dwfl_Line *line) +{ + const struct Dwfl_Lines *lines = ((const void *) line + - offsetof (struct Dwfl_Lines, + idx[line->idx])); + return lines->cu; +} + +/* This describes a contiguous address range that lies in a single CU. + We condense runs of Dwarf_Arange entries for the same CU into this. */ +struct dwfl_arange +{ + struct dwfl_cu *cu; + size_t arange; /* Index in Dwarf_Aranges. */ +}; + + + + +extern void __libdwfl_module_free (Dwfl_Module *mod) internal_function; + + +/* Process relocations in debugging sections in an ET_REL file. + MOD->debug.elf must be opened with ELF_C_READ_MMAP_PRIVATE or ELF_C_READ, + to make it possible to relocate the data in place (or ELF_C_RDWR or + ELF_C_RDWR_MMAP if you intend to modify the Elf file on disk). After + this, dwarf_begin_elf on MOD->debug.elf will read the relocated data. */ +extern Dwfl_Error __libdwfl_relocate (Dwfl_Module *) internal_function; + +/* Adjust *VALUE from section-relative to absolute. + MOD->dwfl->callbacks->section_address is called to determine the actual + address of a loaded section. */ +extern Dwfl_Error __libdwfl_relocate_value (Dwfl_Module *mod, + size_t m_shstrndx, + Elf32_Word shndx, + GElf_Addr *value) + internal_function; + +/* Iterate through all the CU's in the module. Start by passing a null + LASTCU, and then pass the last *CU returned. Success return with null + *CU no more CUs. */ +extern Dwfl_Error __libdwfl_nextcu (Dwfl_Module *mod, struct dwfl_cu *lastcu, + struct dwfl_cu **cu) internal_function; + +/* Find the CU by address. */ +extern Dwfl_Error __libdwfl_addrcu (Dwfl_Module *mod, Dwarf_Addr addr, + struct dwfl_cu **cu) internal_function; + +/* Ensure that CU->lines (and CU->cu->lines) is set up. */ +extern Dwfl_Error __libdwfl_cu_getsrclines (struct dwfl_cu *cu) + internal_function; + + + + +/* Avoid PLT entries. */ +INTDECL (dwfl_begin) +INTDECL (dwfl_errmsg) +INTDECL (dwfl_addrmodule) +INTDECL (dwfl_addrdwarf) +INTDECL (dwfl_addrdie) +INTDECL (dwfl_module_addrdie) +INTDECL (dwfl_module_getdwarf) +INTDECL (dwfl_module_getelf) +INTDECL (dwfl_module_getsrc) +INTDECL (dwfl_report_elf) +INTDECL (dwfl_report_begin) +INTDECL (dwfl_report_module) +INTDECL (dwfl_report_end) +INTDECL (dwfl_standard_find_debuginfo) +INTDECL (dwfl_linux_kernel_find_elf) +INTDECL (dwfl_linux_kernel_module_section_address) +INTDECL (dwfl_linux_proc_report) +INTDECL (dwfl_linux_proc_find_elf) +INTDECL (dwfl_linux_kernel_report_kernel) +INTDECL (dwfl_linux_kernel_report_modules) + +/* Leading arguments standard to callbacks passed a Dwfl_Module. */ +#define MODCB_ARGS(mod) (mod), &(mod)->userdata, (mod)->name, (mod)->low_addr +#define CBFAIL (errno ? DWFL_E (ERRNO, errno) : DWFL_E_CB); + + +#endif /* libdwflP.h */ diff --git a/libdwfl/lines.c b/libdwfl/lines.c new file mode 100644 index 00000000..3eb4c195 --- /dev/null +++ b/libdwfl/lines.c @@ -0,0 +1,37 @@ +/* Fetch source line info for CU. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include "../libdw/libdwP.h" + +Dwfl_Error +internal_function_def +__libdwfl_cu_getsrclines (struct dwfl_cu *cu) +{ + if (cu->lines == NULL) + { + Dwarf_Lines *lines; + size_t nlines; + if (dwarf_getsrclines (&cu->die, &lines, &nlines) != 0) + return DWFL_E_LIBDW; + + cu->lines = malloc (offsetof (struct Dwfl_Lines, idx[nlines])); + if (cu->lines == NULL) + return DWFL_E_NOMEM; + cu->lines->cu = cu; + for (unsigned int i = 0; i < nlines; ++i) + cu->lines->idx[i].idx = i; + } + + return DWFL_E_NOERROR; +} diff --git a/libdwfl/linux-kernel-modules.c b/libdwfl/linux-kernel-modules.c new file mode 100644 index 00000000..65e5ca2d --- /dev/null +++ b/libdwfl/linux-kernel-modules.c @@ -0,0 +1,243 @@ +/* Standard libdwfl callbacks for debugging the running Linux kernel. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include <config.h> +#undef _FILE_OFFSET_BITS /* Doesn't jibe with fts. */ + +#include "libdwflP.h" +#include <inttypes.h> +#include <errno.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <string.h> +#include <stdlib.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <unistd.h> +#include <fts.h> + + +#define MODULEDIRFMT "/lib/modules/%s" + +#define MODULELIST "/proc/modules" +#define SECADDRFMT "/sys/module/%s/sections/%s" + + +static inline const char * +kernel_release (void) +{ + /* Cache the `uname -r` string we'll use. */ + static struct utsname utsname; + if (utsname.release[0] == '\0' && uname (&utsname) != 0) + return NULL; + return utsname.release; +} + +/* Find the ELF file for the running kernel and dwfl_report_elf it. */ +int +dwfl_linux_kernel_report_kernel (Dwfl *dwfl) +{ + if (dwfl == NULL) + return -1; + + const char *release = kernel_release (); + if (release == NULL) + return errno; + + char *fname = NULL; + asprintf (&fname, "/boot/vmlinux-%s", release); + if (fname == NULL) + return -1; + int fd = open64 (fname, O_RDONLY); + if (fd < 0) + { + free (fname); + fname = NULL; + asprintf (&fname, "/usr/lib/debug" MODULEDIRFMT "/vmlinux", release); + if (fname == NULL) + return -1; + fd = open64 (fname, O_RDONLY); + } + + int result = 0; + if (fd < 0) + result = errno; + else if (INTUSE(dwfl_report_elf) (dwfl, "kernel", fname, fd, 0) == NULL) + { + close (fd); + result = -1; + } + + free (fname); + + return result; +} +INTDEF (dwfl_linux_kernel_report_kernel) + +/* Dwfl_Callbacks.find_elf for the running Linux kernel and its modules. */ + +int +dwfl_linux_kernel_find_elf (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *module_name, + Dwarf_Addr base __attribute__ ((unused)), + char **file_name, + Elf **elfp __attribute__ ((unused))) +{ + const char *release = kernel_release (); + if (release == NULL) + return -1; + + /* Do "find /lib/modules/`uname -r` -name MODULE_NAME.ko". */ + + char *modulesdir[] = { NULL, NULL }; + asprintf (&modulesdir[0], MODULEDIRFMT "/kernel", release); + if (modulesdir[0] == NULL) + return -1; + + FTS *fts = fts_open (modulesdir, FTS_LOGICAL | FTS_NOSTAT, NULL); + if (fts == NULL) + { + free (modulesdir[0]); + return -1; + } + + size_t namelen = strlen (module_name); + FTSENT *f; + int error = ENOENT; + while ((f = fts_read (fts)) != NULL) + { + error = ENOENT; + switch (f->fts_info) + { + case FTS_F: + case FTS_NSOK: + /* See if this file name is "MODULE_NAME.ko". */ + if (f->fts_namelen == namelen + 3 + && !memcmp (f->fts_name, module_name, namelen) + && !memcmp (f->fts_name + namelen, ".ko", 4)) + { + int fd = open64 (f->fts_accpath, O_RDONLY); + *file_name = strdup (f->fts_path); + fts_close (fts); + if (fd < 0) + free (*file_name); + else if (*file_name == NULL) + { + close (fd); + fd = -1; + } + return fd; + } + break; + + case FTS_ERR: + case FTS_DNR: + case FTS_NS: + error = f->fts_errno; + break; + + default: + break; + } + } + + errno = error; + return -1; +} +INTDEF (dwfl_linux_kernel_find_elf) + +/* Dwfl_Callbacks.section_address for kernel modules in the running Linux. + We read the information from /sys/module directly. */ + +int +dwfl_linux_kernel_module_section_address +(Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *modname, Dwarf_Addr base __attribute__ ((unused)), + const char *secname, Dwarf_Addr *addr) +{ + char *sysfile = NULL; + asprintf (&sysfile, SECADDRFMT, modname, secname); + if (sysfile == NULL) + return ENOMEM; + + FILE *f = fopen (sysfile, "r"); + if (f == NULL) + { + if (errno == ENOENT) + { + /* The .modinfo and .data.percpu sections are never kept + loaded in the kernel. If the kernel was compiled without + CONFIG_MODULE_UNLOAD, the .exit.* sections are not + actually loaded at all. + + Just relocate these bogusly to zero. This part of the + debug information will not be of any use. */ + + if (!strcmp (secname, ".modinfo") + || !strcmp (secname, ".data.percpu") + || !strncmp (secname, ".exit", 5)) + { + *addr = 0; + return DWARF_CB_OK; + } + } + + return DWARF_CB_ABORT; + } + + (void) __fsetlocking (f, FSETLOCKING_BYCALLER); + + int result = (fscanf (f, "%" PRIi64 "\n", addr) == 1 ? 0 + : ferror_unlocked (f) ? errno : ENOEXEC); + fclose (f); + + if (result == 0) + return DWARF_CB_OK; + + errno = result; + return DWARF_CB_ABORT; +} +INTDEF (dwfl_linux_kernel_module_section_address) + +int +dwfl_linux_kernel_report_modules (Dwfl *dwfl) +{ + FILE *f = fopen (MODULELIST, "r"); + if (f == NULL) + return errno; + + (void) __fsetlocking (f, FSETLOCKING_BYCALLER); + + int result = 0; + Dwarf_Addr modaddr; + unsigned long int modsz; + char modname[128]; + while (fscanf (f, "%128s %lu %*s %*s %*s %" PRIi64 "\n", + modname, &modsz, &modaddr) == 3) + if (INTUSE(dwfl_report_module) (dwfl, modname, + modaddr, modaddr + modsz) == NULL) + { + result = -1; + break; + } + + if (result == 0) + result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC; + + fclose (f); + + return result; +} +INTDEF (dwfl_linux_kernel_report_modules) diff --git a/libdwfl/linux-proc-maps.c b/libdwfl/linux-proc-maps.c new file mode 100644 index 00000000..2611c73a --- /dev/null +++ b/libdwfl/linux-proc-maps.c @@ -0,0 +1,285 @@ +/* Standard libdwfl callbacks for debugging a live Linux process. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" +#include <inttypes.h> +#include <sys/types.h> +#include <errno.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdbool.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> +#include <endian.h> + + +#define PROCMAPSFMT "/proc/%d/maps" +#define PROCMEMFMT "/proc/%d/mem" +#define PROCAUXVFMT "/proc/%d/auxv" + + +/* Search /proc/PID/auxv for the AT_SYSINFO_EHDR tag. */ + +static int +find_sysinfo_ehdr (pid_t pid, GElf_Addr *sysinfo_ehdr) +{ + char *fname = NULL; + asprintf (&fname, PROCAUXVFMT, pid); + if (fname == NULL) + return ENOMEM; + + int fd = open64 (fname, O_RDONLY); + free (fname); + if (fd < 0) + return errno == ENOENT ? 0 : errno; + + ssize_t nread; + do + { + union + { + char buffer[sizeof (long int) * 2 * 64]; + Elf64_auxv_t a64[sizeof (long int) * 2 * 64 / sizeof (Elf64_auxv_t)]; + Elf32_auxv_t a32[sizeof (long int) * 2 * 32 / sizeof (Elf32_auxv_t)]; + } d; + nread = read (fd, &d, sizeof d); + if (nread > 0) + { + switch (sizeof (long int)) + { + case 4: + for (size_t i = 0; (char *) &d.a32[i] < &d.buffer[nread]; ++i) + if (d.a32[i].a_type == AT_SYSINFO_EHDR) + { + *sysinfo_ehdr = d.a32[i].a_un.a_val; + nread = 0; + break; + } + break; + case 8: + for (size_t i = 0; (char *) &d.a64[i] < &d.buffer[nread]; ++i) + if (d.a64[i].a_type == AT_SYSINFO_EHDR) + { + *sysinfo_ehdr = d.a64[i].a_un.a_val; + nread = 0; + break; + } + break; + default: + abort (); + break; + } + } + } + while (nread > 0); + + close (fd); + + return nread < 0 ? errno : 0; +} + +int +dwfl_linux_proc_report (Dwfl *dwfl, pid_t pid) +{ + if (dwfl == NULL) + return -1; + + /* We'll notice the AT_SYSINFO_EHDR address specially when we hit it. */ + GElf_Addr sysinfo_ehdr = 0; + int result = find_sysinfo_ehdr (pid, &sysinfo_ehdr); + if (result != 0) + return result; + + char *fname = NULL; + asprintf (&fname, PROCMAPSFMT, pid); + if (fname == NULL) + return ENOMEM; + + FILE *f = fopen (fname, "r"); + free (fname); + if (f == NULL) + return errno; + + (void) __fsetlocking (f, FSETLOCKING_BYCALLER); + + unsigned int last_dmajor = -1, last_dminor = -1; + uint64_t last_ino = -1; + char *last_file = NULL; + Dwarf_Addr low = 0, high = 0; + + inline bool report (void) + { + if (last_file != NULL) + { + if (INTUSE(dwfl_report_module) (dwfl, last_file, low, high) == NULL) + { + free (last_file); + return true; + } + last_file = NULL; + } + return false; + } + + char *line = NULL; + size_t linesz; + ssize_t len; + while ((len = getline (&line, &linesz, f)) > 0) + { + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + + Dwarf_Addr start, end, offset; + unsigned int dmajor, dminor; + uint64_t ino; + int nread = -1; + if (sscanf (line, "%" PRIx64 "-%" PRIx64 " %*s %" PRIx64 + " %x:%x %" PRIi64 " %n", + &start, &end, &offset, &dmajor, &dminor, &ino, &nread) < 6 + || nread <= 0) + { + free (line); + return ENOEXEC; + } + + /* If this is the special mapping AT_SYSINFO_EHDR pointed us at, + report the last one and then this special one. */ + if (start == sysinfo_ehdr && start != 0) + { + if (report ()) + { + bad_report: + free (line); + fclose (f); + return -1; + } + + low = start; + high = end; + if (asprintf (&last_file, "[vdso: %d]", (int) pid) < 0 + || report ()) + goto bad_report; + } + + char *file = line + nread + strspn (line + nread, " \t"); + if (file[0] == '\0' || (ino == 0 && dmajor == 0 && dminor == 0)) + /* This line doesn't indicate a file mapping. */ + continue; + + if (last_file != NULL + && ino == last_ino && dmajor == last_dmajor && dminor == last_dminor) + { + /* This is another portion of the same file's mapping. */ + assert (!strcmp (last_file, file)); + high = end; + } + else + { + /* This is a different file mapping. Report the last one. */ + if (report ()) + goto bad_report; + low = start; + high = end; + last_file = strdup (file); + last_ino = ino; + last_dmajor = dmajor; + last_dminor = dminor; + } + } + free (line); + + result = ferror_unlocked (f) ? errno : feof_unlocked (f) ? 0 : ENOEXEC; + fclose (f); + + /* Report the final one. */ + bool lose = report (); + + return result != 0 ? result : lose ? -1 : 0; +} +INTDEF (dwfl_linux_proc_report) + + +static ssize_t +read_proc_memory (void *arg, void *data, GElf_Addr address, + size_t minread, size_t maxread) +{ + const int fd = *(const int *) arg; + ssize_t nread = pread64 (fd, data, maxread, (off64_t) address); + if (nread > 0 && (size_t) nread < minread) + nread = 0; + return nread; +} + +extern Elf *elf_from_remote_memory (GElf_Addr ehdr_vma, + GElf_Addr *loadbasep, + ssize_t (*read_memory) (void *arg, + void *data, + GElf_Addr address, + size_t minread, + size_t maxread), + void *arg); + + +/* Dwfl_Callbacks.find_elf */ + +int +dwfl_linux_proc_find_elf (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *module_name, Dwarf_Addr base, + char **file_name, Elf **elfp) +{ + if (module_name[0] == '/') + { + int fd = open64 (module_name, O_RDONLY); + if (fd >= 0) + { + *file_name = strdup (module_name); + if (*file_name == NULL) + { + close (fd); + return ENOMEM; + } + } + return fd; + } + + int pid; + if (sscanf (module_name, "[vdso: %d]", &pid) == 1) + { + /* Special case for in-memory ELF image. */ + + char *fname = NULL; + asprintf (&fname, PROCMEMFMT, pid); + if (fname == NULL) + return -1; + + int fd = open64 (fname, O_RDONLY); + free (fname); + if (fd < 0) + return -1; + + *elfp = elf_from_remote_memory (base, NULL, &read_proc_memory, &fd); + + close (fd); + + *file_name = NULL; + return -1; + } + + abort (); + return -1; +} +INTDEF (dwfl_linux_proc_find_elf) diff --git a/libdwfl/loc2c-runtime.h b/libdwfl/loc2c-runtime.h new file mode 100644 index 00000000..af27f791 --- /dev/null +++ b/libdwfl/loc2c-runtime.h @@ -0,0 +1,125 @@ +/* target operations */ + +#include <linux/types.h> +#define intptr_t long +#define uintptr_t unsigned long + + +/* These three macro definitions are generic, just shorthands + used by the generated code. */ + +#define op_abs(x) (x < 0 ? -x : x) + +#define fetch_bitfield(target, base, higherbits, nbits) \ + target = (((base) >> (sizeof (base) * 8 - (higherbits) - (nbits))) \ + & (((__typeof (base)) 1 << (nbits)) - 1)) + +#define store_bitfield(target, base, higherbits, nbits) \ + target = (target \ + &~ ((((__typeof (base)) 1 << (nbits)) - 1) \ + << (sizeof (base) * 8 - (higherbits) - (nbits))) \ + | ((__typeof (base)) (value) \ + << (sizeof (base) * 8 - (higherbits) - (nbits)))) + + +/* These operations are target-specific. */ +#include <asm/uaccess.h> + +#define fetch_register(regno) ((intptr_t) regs->dwarf_register_##regno) + +#if defined __i386__ + +#define dwarf_register_0 eax +#define dwarf_register_1 ecx +#define dwarf_register_2 edx +#define dwarf_register_3 ebx +#define dwarf_register_4 esp +#define dwarf_register_5 ebp +#define dwarf_register_6 esi +#define dwarf_register_7 edi + +#elif defined __x86_64__ + +#define dwarf_register_0 eax +#define dwarf_register_1 edx +#define dwarf_register_2 ecx +#define dwarf_register_3 ebx +#define dwarf_register_4 esi +#define dwarf_register_5 edi +#define dwarf_register_6 ebp +#define dwarf_register_7 esp +#define dwarf_register_8 r8 +#define dwarf_register_9 r9 +#define dwarf_register_10 r10 +#define dwarf_register_11 r11 +#define dwarf_register_12 r12 +#define dwarf_register_13 r13 +#define dwarf_register_14 r14 +#define dwarf_register_15 r15 + +#elif defined __powerpc__ + +#undef fetch_register +#define fetch_register(regno) ((intptr_t) regs->gpr[regno]) + +#endif + +#if defined __i386__ || defined __x86_64__ + +#define deref(size, addr) \ + ({ \ + int _bad = 0; \ + u8 _b; u16 _w; u32 _l; u64 _q; \ + intptr_t _v; \ + switch (size) \ + { \ + case 1: __get_user_asm(_b,addr,_bad,"b","b","=q",1); _v = _b; break; \ + case 2: __get_user_asm(_w,addr,_bad,"w","w","=r",1); _v = _w; break; \ + case 4: __get_user_asm(_l,addr,_bad,"l","","=r",1); _v = _l; break; \ + case 8: __get_user_asm(_q,addr,_bad,"q","","=r",1); _v = _q; break; \ + default: _v = __get_user_bad(); \ + } \ + if (_bad) \ + goto deref_fault; \ + _v; \ + }) + +#elif defined __powerpc64__ + +#define deref(size, addr) \ + ({ \ + int _bad = 0; \ + intptr_t _v; \ + switch (size) \ + { \ + case 1: __get_user_asm(_v,addr,_bad,"lbz",1); break; \ + case 2: __get_user_asm(_v,addr,_bad,"lhz",1); break; \ + case 4: __get_user_asm(_v,addr,_bad,"lwz",1); break; \ + case 8: __get_user_asm(_v,addr,_bad,"ld",1); break; \ + default: _v = __get_user_bad(); \ + } \ + if (_bad) \ + goto deref_fault; \ + _v; \ + }) + +#elif defined __powerpc__ + +#define deref(size, addr) \ + ({ \ + int _bad = 0; \ + intptr_t _v; \ + switch (size) \ + { \ + case 1: __get_user_asm(_v,addr,_bad,"lbz"); break; \ + case 2: __get_user_asm(_v,addr,_bad,"lhz"); break; \ + case 4: __get_user_asm(_v,addr,_bad,"lwz"); break; \ + case 8: __get_user_asm(_v,addr,_bad,"ld"); break; \ + default: _v = __get_user_bad(); \ + } \ + if (_bad) \ + goto deref_fault; \ + _v; \ + }) + +#endif diff --git a/libdwfl/loc2c.c b/libdwfl/loc2c.c new file mode 100644 index 00000000..816eb06a --- /dev/null +++ b/libdwfl/loc2c.c @@ -0,0 +1,1398 @@ +#include <config.h> +#include <inttypes.h> +#include <stdbool.h> +#include <obstack.h> +#include <stdio.h> +#include <stdlib.h> +#include <error.h> +#include <dwarf.h> +#include <libdw.h> +#include <assert.h> + +#define _(x) x + +#define STACK_TYPE "intptr_t" /* Must be the signed type. */ +#define UTYPE "uintptr_t" /* Must be the unsigned type. */ +#define SFORMAT "%" PRId64 "L" +#define UFORMAT "%" PRIu64 "UL" +#define AFORMAT "%#" PRIx64 "UL" +#define STACKFMT "s%u" + +struct location +{ + struct location *next; + + const Dwarf_Loc *ops; + size_t nops; + + Dwarf_Word byte_size; + + enum { loc_address, loc_register, loc_noncontiguous, loc_final } type; + union + { + struct /* loc_address or loc_final */ + { + char *program; /* C fragment, leaves address in s0. */ + unsigned int stack_depth; /* Temporaries "s0..<N>" used by it. */ + struct location *frame_base; + bool used_deref; /* Program uses "deref" macro. */ + } address; + unsigned int regno; /* loc_register */ + struct location *pieces; /* loc_noncontiguous */ + }; +}; + + +static const char * +dwarf_diename_integrate (Dwarf_Die *die) +{ + Dwarf_Attribute attr_mem; + return dwarf_formstring (dwarf_attr_integrate (die, DW_AT_name, &attr_mem)); +} + +/* Synthesize a new loc_address using the program on the obstack. */ +static struct location * +new_synthetic_loc (struct obstack *pool, struct location *origin, bool deref) +{ + obstack_1grow (pool, '\0'); + char *program = obstack_finish (pool); + + struct location *loc = obstack_alloc (pool, sizeof *loc); + loc->next = NULL; + loc->byte_size = 0; + loc->type = loc_address; + loc->address.program = program; + loc->address.stack_depth = 0; + loc->address.frame_base = NULL; + loc->address.used_deref = deref; + + if (origin->type == loc_register) + { + loc->ops = origin->ops; + loc->nops = origin->nops; + } + else + { + loc->ops = NULL; + loc->nops = 0; + } + + return loc; +} + + +/* Die in the middle of an expression. */ +static struct location * +lose (const char *failure, const Dwarf_Loc *lexpr, size_t i) +{ + error (2, 0, _("%s in DWARF expression [%Zu] at %" PRIu64 + " (%#x: %" PRId64 ", %" PRId64 ")"), + failure, i, lexpr[i].offset, + lexpr[i].atom, lexpr[i].number, lexpr[i].number2); + return NULL; +} + +/* Translate a (constrained) DWARF expression into C code + emitted to the obstack POOL. INDENT is the number of indentation levels. + ADDRBIAS is the difference between runtime and Dwarf info addresses. + INPUT is null or an expression to be initially pushed on the stack. + If NEED_FB is null, fail on DW_OP_fbreg, else set *NEED_FB to true + and emit "frame_base" for it. On success, set *MAX_STACK to the number + of stack slots required. On failure, set *LOSER to the index in EXPR + of the operation we could not handle. + + Returns a failure message or null for success. */ + +static const char * +translate (struct obstack *pool, int indent, Dwarf_Addr addrbias, + const Dwarf_Loc *expr, const size_t len, + struct location *input, + bool *need_fb, size_t *loser, + struct location *loc) +{ + loc->ops = expr; + loc->nops = len; + +#define DIE(msg) return (*loser = i, _(msg)) + +#define emit(fmt, ...) obstack_printf (pool, fmt, ## __VA_ARGS__) + + unsigned int stack_depth = 0, max_stack = 0; + inline void deepen (void) + { + if (stack_depth == max_stack) + ++max_stack; + } + +#define POP(var) \ + if (stack_depth > 0) \ + --stack_depth; \ + else if (tos_register != -1) \ + fetch_tos_register (); \ + else \ + goto underflow; \ + int var = stack_depth +#define PUSH (deepen (), stack_depth++) +#define STACK(idx) (stack_depth - 1 - (idx)) + + /* Don't put stack operations in the arguments to this. */ +#define push(fmt, ...) \ + emit ("%*s" STACKFMT " = " fmt ";\n", indent * 2, "", PUSH, ## __VA_ARGS__) + + int tos_register = -1; + inline void fetch_tos_register (void) + { + deepen (); + emit ("%*s" STACKFMT " = fetch_register (%d);\n", + indent * 2, "", stack_depth, tos_register); + tos_register = -1; + } + + if (input != NULL) + switch (input->type) + { + case loc_address: + push ("addr"); + break; + + case loc_register: + tos_register = input->regno; + break; + + default: + abort (); + break; + } + + size_t i; + + inline const char *finish (struct location *piece) + { + if (stack_depth > 1) + DIE ("multiple values left on stack"); + if (stack_depth == 1) + { + obstack_1grow (pool, '\0'); + char *program = obstack_finish (pool); + piece->type = loc_address; + piece->address.program = program; + piece->address.stack_depth = max_stack; + piece->address.frame_base = NULL; + } + else if (tos_register == -1) + DIE ("stack underflow"); + else if (obstack_object_size (pool) != 0) + DIE ("register value must stand alone in location expression"); + else + { + piece->type = loc_register; + piece->regno = tos_register; + } + return NULL; + } + + struct location *pieces = NULL, **tailpiece = &pieces; + size_t piece_expr_start = 0; + for (i = 0; i < len; ++i) + { + unsigned int reg; + uint_fast8_t sp; + Dwarf_Word value; + + switch (expr[i].atom) + { + /* Basic stack operations. */ + case DW_OP_nop: + break; + + case DW_OP_dup: + if (stack_depth < 1) + goto underflow; + else + { + unsigned int tos = STACK (0); + push (STACKFMT, tos); + } + break; + + case DW_OP_drop: + POP (ignore); + emit ("%*s/* drop " STACKFMT "*/\n", indent * 2, "", ignore); + break; + + case DW_OP_pick: + sp = expr[i].number; + op_pick: + if (sp >= stack_depth) + goto underflow; + sp = STACK (sp); + push (STACKFMT, sp); + break; + + case DW_OP_over: + sp = 1; + goto op_pick; + + case DW_OP_swap: + if (stack_depth < 2) + goto underflow; + deepen (); /* Use a temporary slot. */ + emit ("%*s" + STACKFMT " = " STACKFMT ", " + STACKFMT " = " STACKFMT ", " + STACKFMT " = " STACKFMT ";\n", + indent * 2, "", + STACK (-1), STACK (0), + STACK (0), STACK (1), + STACK (1), STACK (-1)); + break; + + case DW_OP_rot: + if (stack_depth < 3) + goto underflow; + deepen (); /* Use a temporary slot. */ + emit ("%*s" + STACKFMT " = " STACKFMT ", " + STACKFMT " = " STACKFMT ", " + STACKFMT " = " STACKFMT ", " + STACKFMT " = " STACKFMT ";\n", + indent * 2, "", + STACK (-1), STACK (0), + STACK (0), STACK (1), + STACK (1), STACK (2), + STACK (3), STACK (-1)); + break; + + + /* Control flow operations. */ + case DW_OP_skip: + { + Dwarf_Off target = expr[i].offset + 3 + expr[i].number; + while (i + 1 < len && expr[i + 1].offset < target) + ++i; + if (expr[i + 1].offset != target) + DIE ("invalid skip target"); + break; + } + + case DW_OP_bra: + DIE ("conditional branches not supported"); + break; + + + /* Memory access. */ + case DW_OP_deref: + { + POP (addr); + push ("deref (sizeof (void *), " STACKFMT ")", addr); + loc->address.used_deref = true; + } + break; + + case DW_OP_deref_size: + { + POP (addr); + push ("deref (" UFORMAT ", " STACKFMT ")", + expr[i].number, addr); + loc->address.used_deref = true; + } + break; + + case DW_OP_xderef: + { + POP (addr); + POP (as); + push ("xderef (sizeof (void *), " STACKFMT ", " STACKFMT ")", + addr, as); + loc->address.used_deref = true; + } + break; + + case DW_OP_xderef_size: + { + POP (addr); + POP (as); + push ("xderef (" UFORMAT ", " STACKFMT ", " STACKFMT ")", + expr[i].number, addr, as); + loc->address.used_deref = true; + } + break; + + /* Constant-value operations. */ + + case DW_OP_addr: + push (AFORMAT, addrbias + expr[i].number); + break; + + case DW_OP_lit0 ... DW_OP_lit31: + value = expr[i].atom - DW_OP_lit0; + goto op_const; + + case DW_OP_const1u: + case DW_OP_const1s: + case DW_OP_const2u: + case DW_OP_const2s: + case DW_OP_const4u: + case DW_OP_const4s: + case DW_OP_const8u: + case DW_OP_const8s: + case DW_OP_constu: + case DW_OP_consts: + value = expr[i].number; + op_const: + push (SFORMAT, value); + break; + + /* Arithmetic operations. */ +#define UNOP(dw_op, c_op) \ + case DW_OP_##dw_op: \ + { \ + POP (tos); \ + push ("%s (" STACKFMT ")", #c_op, tos); \ + } \ + break +#define BINOP(dw_op, c_op) \ + case DW_OP_##dw_op: \ + { \ + POP (b); \ + POP (a); \ + push (STACKFMT " %s " STACKFMT, a, #c_op, b); \ + } \ + break + + UNOP (abs, op_abs); + BINOP (and, &); + BINOP (div, /); + BINOP (minus, -); + BINOP (mod, %); + BINOP (mul, *); + UNOP (neg, -); + UNOP (not, ~); + BINOP (or, |); + BINOP (plus, +); + BINOP (shl, <<); + BINOP (shra, >>); + BINOP (xor, ^); + + /* Comparisons are binary operators too. */ + BINOP (le, <=); + BINOP (ge, >=); + BINOP (eq, ==); + BINOP (lt, <); + BINOP (gt, >); + BINOP (ne, !=); + +#undef UNOP +#undef BINOP + + case DW_OP_shr: + { + POP (b); + POP (a); + push ("(%s) " STACKFMT " >> (%s)" STACKFMT, + UTYPE, a, UTYPE, b); + break; + } + + case DW_OP_plus_uconst: + { + POP (x); + push (STACKFMT " + " UFORMAT, x, expr[i].number); + } + break; + + + /* Register-relative addressing. */ + case DW_OP_breg0 ... DW_OP_breg31: + reg = expr[i].atom - DW_OP_breg0; + value = expr[i].number; + goto op_breg; + + case DW_OP_bregx: + reg = expr[i].number; + value = expr[i].number2; + op_breg: + push ("fetch_register (%u) + " SFORMAT, reg, value); + break; + + case DW_OP_fbreg: + if (need_fb == NULL) + DIE ("DW_OP_fbreg from DW_AT_frame_base"); + *need_fb = true; + push ("frame_base + " SFORMAT, expr[i].number); + break; + + /* Direct register contents. */ + case DW_OP_reg0 ... DW_OP_reg31: + reg = expr[i].atom - DW_OP_reg0; + goto op_reg; + + case DW_OP_regx: + reg = expr[i].number; + op_reg: + tos_register = reg; + break; + + /* Special magic. */ + case DW_OP_piece: + if (stack_depth > 1) + /* If this ever happens we could copy the program. */ + DIE ("DW_OP_piece left multiple values on stack"); + else + { + struct location *piece = obstack_alloc (pool, sizeof *piece); + const char *failure = finish (piece); + if (unlikely (failure != NULL)) + return failure; + + piece->ops = &expr[piece_expr_start]; + piece->nops = i - piece_expr_start; + piece_expr_start = i + 1; + + piece->byte_size = expr[i].number; + + *tailpiece = piece; + tailpiece = &piece->next; + piece->next = NULL; + } + break; + + case DW_OP_push_object_address: + DIE ("XXX DW_OP_push_object_address"); + break; + + default: + DIE ("unrecognized operation"); + break; + } + } + + if (likely (pieces == NULL)) + return finish (loc); + + if (piece_expr_start != i) + DIE ("extra operations after last DW_OP_piece"); + + loc->type = loc_noncontiguous; + loc->pieces = pieces; + + return NULL; + + underflow: + DIE ("stack underflow"); + +#undef emit +#undef push +#undef PUSH +#undef POP +#undef STACK +#undef DIE +} + +/* Translate a location starting from an address or nothing. */ +static struct location * +location_from_address (struct obstack *pool, + int indent, Dwarf_Addr dwbias, + const Dwarf_Loc *expr, size_t len, Dwarf_Addr address, + struct location **input, Dwarf_Attribute *fb_attr) +{ + bool need_fb = false; + size_t loser; + struct location *loc = obstack_alloc (pool, sizeof *loc); + const char *failure = translate (pool, indent + 1, dwbias, expr, len, + *input, &need_fb, &loser, loc); + if (unlikely (failure != NULL)) + return lose (failure, expr, loser); + + loc->next = NULL; + if (need_fb) + { + /* The main expression uses DW_OP_fbreg, so we need to compute + the DW_AT_frame_base attribute expression's value first. */ + + Dwarf_Loc *fb_expr; + size_t fb_len; + switch (dwarf_addrloclists (fb_attr, address - dwbias, + &fb_expr, &fb_len, 1)) + { + case 1: /* Should always happen. */ + if (fb_len == 0) + goto fb_inaccessible; + break; + + default: /* Shouldn't happen. */ + case -1: + error (2, 0, "dwarf_addrloclists (form %#x): %s", + dwarf_whatform (fb_attr), dwarf_errmsg (-1)); + return NULL; + + case 0: /* Shouldn't happen. */ + fb_inaccessible: + error (2, 0, "DW_AT_frame_base not accessible at this address"); + return NULL; + } + + loc->address.frame_base = obstack_alloc (pool, sizeof *loc); + failure = translate (pool, indent + 1, dwbias, fb_expr, fb_len, NULL, + NULL, &loser, loc->address.frame_base); + if (unlikely (failure != NULL)) + return lose (failure, fb_expr, loser); + } + + if (*input != NULL) + (*input)->next = loc; + *input = loc; + + return loc; +} + +/* Translate a location starting from a non-address "on the top of the + stack". The *INPUT location is a register name or noncontiguous + object specification, and this expression wants to find the "address" + of an object relative to that "address". */ + +static struct location * +location_relative (struct obstack *pool, + int indent, Dwarf_Addr dwbias, + const Dwarf_Loc *expr, size_t len, Dwarf_Addr address, + struct location **input, Dwarf_Attribute *fb_attr) +{ + Dwarf_Sword *stack; + unsigned int stack_depth = 0, max_stack = 0; + inline void deepen (void) + { + if (stack_depth == max_stack) + { + ++max_stack; + obstack_blank (pool, sizeof stack[0]); + stack = (void *) obstack_base (pool); + } + } + +#define POP(var) \ + if (stack_depth > 0) \ + --stack_depth; \ + else \ + goto underflow; \ + int var = stack_depth +#define PUSH (deepen (), stack_depth++) +#define STACK(idx) (stack_depth - 1 - (idx)) +#define STACKWORD(idx) stack[STACK (idx)] + + /* Don't put stack operations in the arguments to this. */ +#define push(value) (stack[PUSH] = (value)) + + const char *failure = NULL; +#define DIE(msg) do { failure = _(msg); goto fail; } while (0) + + struct location *head = NULL; + size_t i; + for (i = 0; i < len; ++i) + { + uint_fast8_t sp; + Dwarf_Word value; + + switch (expr[i].atom) + { + /* Basic stack operations. */ + case DW_OP_nop: + break; + + case DW_OP_dup: + if (stack_depth < 1) + goto underflow; + else + { + unsigned int tos = STACK (0); + push (stack[tos]); + } + break; + + case DW_OP_drop: + if (stack_depth > 0) + --stack_depth; + else if (*input != NULL) + /* Mark that we have consumed the input. */ + *input = NULL; + else + /* Hits if cleared above, or if we had no input at all. */ + goto underflow; + break; + + case DW_OP_pick: + sp = expr[i].number; + op_pick: + if (sp >= stack_depth) + goto underflow; + sp = STACK (sp); + push (stack[sp]); + break; + + case DW_OP_over: + sp = 1; + goto op_pick; + + case DW_OP_swap: + if (stack_depth < 2) + goto underflow; + deepen (); /* Use a temporary slot. */ + STACKWORD (-1) = STACKWORD (0); + STACKWORD (0) = STACKWORD (1); + STACKWORD (1) = STACKWORD (-1); + break; + + case DW_OP_rot: + if (stack_depth < 3) + goto underflow; + deepen (); /* Use a temporary slot. */ + STACKWORD (-1) = STACKWORD (0); + STACKWORD (0) = STACKWORD (1); + STACKWORD (2) = STACKWORD (2); + STACKWORD (2) = STACKWORD (-1); + break; + + + /* Control flow operations. */ + case DW_OP_bra: + { + POP (taken); + if (stack[taken] == 0) + break; + } + /*FALLTHROUGH*/ + + case DW_OP_skip: + { + Dwarf_Off target = expr[i].offset + 3 + expr[i].number; + while (i + 1 < len && expr[i + 1].offset < target) + ++i; + if (expr[i + 1].offset != target) + DIE ("invalid skip target"); + break; + } + + /* Memory access. */ + case DW_OP_deref: + case DW_OP_deref_size: + case DW_OP_xderef: + case DW_OP_xderef_size: + + /* Register-relative addressing. */ + case DW_OP_breg0 ... DW_OP_breg31: + case DW_OP_bregx: + case DW_OP_fbreg: + + /* This started from a register, but now it's following a pointer. + So we can do the translation starting from address here. */ + return location_from_address (pool, indent, dwbias, + expr, len, address, input, fb_attr); + + + /* Constant-value operations. */ + case DW_OP_addr: + push (dwbias + expr[i].number); + break; + + case DW_OP_lit0 ... DW_OP_lit31: + value = expr[i].atom - DW_OP_lit0; + goto op_const; + + case DW_OP_const1u: + case DW_OP_const1s: + case DW_OP_const2u: + case DW_OP_const2s: + case DW_OP_const4u: + case DW_OP_const4s: + case DW_OP_const8u: + case DW_OP_const8s: + case DW_OP_constu: + case DW_OP_consts: + value = expr[i].number; + op_const: + push (value); + break; + + /* Arithmetic operations. */ +#define UNOP(dw_op, c_op) \ + case DW_OP_##dw_op: \ + { \ + POP (tos); \ + push (c_op (stack[tos])); \ + } \ + break +#define BINOP(dw_op, c_op) \ + case DW_OP_##dw_op: \ + { \ + POP (b); \ + POP (a); \ + push (stack[a] c_op stack[b]); \ + } \ + break + +#define op_abs(x) (x < 0 ? -x : x) + UNOP (abs, op_abs); + BINOP (and, &); + BINOP (div, /); + BINOP (mod, %); + BINOP (mul, *); + UNOP (neg, -); + UNOP (not, ~); + BINOP (or, |); + BINOP (shl, <<); + BINOP (shra, >>); + BINOP (xor, ^); + + /* Comparisons are binary operators too. */ + BINOP (le, <=); + BINOP (ge, >=); + BINOP (eq, ==); + BINOP (lt, <); + BINOP (gt, >); + BINOP (ne, !=); + +#undef UNOP +#undef BINOP + + case DW_OP_shr: + { + POP (b); + POP (a); + push ((Dwarf_Word) stack[a] >> (Dwarf_Word) stack[b]); + break; + } + + /* Simple addition we may be able to handle relative to + the starting register name. */ + case DW_OP_minus: + { + POP (tos); + value = -stack[tos]; + goto plus; + } + case DW_OP_plus: + { + POP (tos); + value = stack[tos]; + goto plus; + } + case DW_OP_plus_uconst: + value = expr[i].number; + plus: + if (stack_depth > 0) + { + /* It's just private diddling after all. */ + POP (a); + push (stack[a] + value); + break; + } + if (*input == NULL) + goto underflow; + + /* This is the primary real-world case: the expression takes + the input address and adds a constant offset. */ + + while ((*input)->type == loc_noncontiguous) + { + /* We are starting from a noncontiguous object (DW_OP_piece). + Find the piece we want. */ + + struct location *piece = (*input)->pieces; + while (piece != NULL && value >= piece->byte_size) + { + value -= piece->byte_size; + piece = piece->next; + } + if (piece == NULL) + DIE ("offset outside available pieces"); + + *input = piece; + } + + switch ((*input)->type) + { + case loc_address: + { + /* The piece we want is actually in memory. Use the same + program to compute the address from the preceding input. */ + + struct location *loc = obstack_alloc (pool, sizeof *loc); + *loc = **input; + if (head == NULL) + head = loc; + (*input)->next = loc; + if (value == 0) + { + /* The piece addresses exactly where we want to go. */ + loc->next = NULL; + *input = loc; + } + else + { + /* Add a second fragment to offset the piece address. */ + obstack_printf (pool, "%*saddr += " SFORMAT "\n", + indent * 2, "", value); + *input = loc->next = new_synthetic_loc (pool, *input, + false); + } + + if (i + 1 < len) + { + /* This expression keeps going, but further + computations now have an address to start with. + So we can punt to the address computation generator. */ + loc = location_from_address (pool, indent, dwbias, + &expr[i + 1], len - i - 1, + address, input, fb_attr); + if (loc == NULL) + return NULL; + } + + /* That's all she wrote. */ + return head; + } + + case loc_register: + // XXX + + default: + abort (); + } + break; + + /* Direct register contents. */ + case DW_OP_reg0 ... DW_OP_reg31: + case DW_OP_regx: + DIE ("register"); + break; + + /* Special magic. */ + case DW_OP_piece: + DIE ("DW_OP_piece"); + break; + + case DW_OP_push_object_address: + DIE ("XXX DW_OP_push_object_address"); + break; + + default: + DIE ("unrecognized operation"); + break; + } + } + + if (stack_depth > 1) + DIE ("multiple values left on stack"); + + if (stack_depth > 0) /* stack_depth == 1 */ + { + if (*input != NULL) + DIE ("multiple values left on stack"); + + /* Could handle this if it ever actually happened. */ + DIE ("relative expression computed constant"); + } + + return head; + + underflow: + if (*input == NULL) + DIE ("stack underflow"); + else + DIE ("cannot handle location expression"); + + fail: + return lose (failure, expr, i); +} + + +/* Translate a C fragment for the location expression, using *INPUT + as the starting location, begin from scratch if *INPUT is null. + If DW_OP_fbreg is used, it may have a subfragment computing from + the FB_ATTR location expression. + + On errors, exit and never return (XXX ?). On success, return the + first fragment created, which is also chained onto (*INPUT)->next. + *INPUT is then updated with the new tail of that chain. */ + +struct location * +c_translate_location (struct obstack *pool, + int indent, Dwarf_Addr dwbias, + Dwarf_Attribute *loc_attr, Dwarf_Addr address, + struct location **input, Dwarf_Attribute *fb_attr) +{ + Dwarf_Loc *expr; + size_t len; + switch (dwarf_addrloclists (loc_attr, address - dwbias, &expr, &len, 1)) + { + case 1: /* Should always happen. */ + if (len == 0) + goto inaccessible; + break; + + default: /* Shouldn't happen. */ + case -1: + error (2, 0, "dwarf_addrloclists (form %#x): %s", + dwarf_whatform (fb_attr), dwarf_errmsg (-1)); + return NULL; + + case 0: /* Shouldn't happen. */ + inaccessible: + error (2, 0, "not accessible at this address"); + return NULL; + } + + ++indent; + switch (*input == NULL ? loc_address : (*input)->type) + { + case loc_address: + /* We have a previous address computation. + This expression will compute starting with that on the stack. */ + return location_from_address (pool, indent, dwbias, expr, len, address, + input, fb_attr); + + case loc_noncontiguous: + case loc_register: + /* The starting point is not an address computation, but a + register. We can only handle limited computations from here. */ + return location_relative (pool, indent, dwbias, expr, len, address, + input, fb_attr); + + case loc_final: /* Bogus caller. */ + default: + abort (); + break; + } + + return NULL; +} + + +/* Emit "uintNN_t TARGET = ...;". */ +static bool +emit_base_fetch (struct obstack *pool, Dwarf_Word byte_size, + const char *target, bool decl, struct location *loc) +{ + if (decl) + obstack_printf (pool, "uint%" PRIu64 "_t ", byte_size * 8); + + switch (loc->type) + { + case loc_address: + if (byte_size != 0 && byte_size != (Dwarf_Word) -1) + obstack_printf (pool, "%s = deref (%" PRIu64 ", addr); ", + target, byte_size); + else + obstack_printf (pool, "%s = deref (sizeof %s, addr); ", + target, target); + return true; + + case loc_register: + obstack_printf (pool, "%s = fetch_register (%u);", target, loc->regno); + break; + + case loc_noncontiguous: + /* Could be handled if it ever happened. */ + error (2, 0, _("noncontiguous locations not supported")); + break; + + default: + abort (); + break; + } + + return false; +} + +/* Translate a fragment to dereference the given pointer type, + where *INPUT is the location of the pointer with that type. + + We chain on a loc_address program that yields this pointer value + (i.e. the location of what it points to). */ + +void +c_translate_pointer (struct obstack *pool, int indent, + Dwarf_Addr dwbias __attribute__ ((unused)), + Dwarf_Die *typedie, struct location **input) +{ + obstack_printf (pool, "%*s{ ", (indent + 2) * 2, ""); + + bool deref = false; + Dwarf_Attribute attr_mem; + Dwarf_Word byte_size; + if (dwarf_attr_integrate (typedie, DW_AT_byte_size, &attr_mem) == NULL) + { + obstack_printf (pool, "uintptr_t "); + emit_base_fetch (pool, 0, "tmp", false, *input); + } + else if (dwarf_formudata (&attr_mem, &byte_size) != 0) + error (2, 0, + _("cannot get byte_size attribute for type %s: %s"), + dwarf_diename_integrate (typedie) ?: "<anonymous>", + dwarf_errmsg (-1)); + else + deref = emit_base_fetch (pool, byte_size, "tmp", true, *input); + + obstack_printf (pool, " addr = tmp; }\n"); + + struct location *loc = new_synthetic_loc (pool, *input, deref); + (*input)->next = loc; + *input = loc; +} + +/* Determine the byte size of a base type. */ +static Dwarf_Word +base_byte_size (Dwarf_Die *typedie) +{ + Dwarf_Attribute attr_mem; + Dwarf_Word size; + if (dwarf_attr_integrate (typedie, DW_AT_byte_size, &attr_mem) != NULL + && dwarf_formudata (&attr_mem, &size) == 0) + return size; + + error (2, 0, + _("cannot get byte_size attribute for type %s: %s"), + dwarf_diename_integrate (typedie) ?: "<anonymous>", + dwarf_errmsg (-1)); + return -1; +} + +/* Emit a code fragment like: + { uintNN_t tmp = ...; S1 S2 S3, tmp, B0, Bn); } +*/ +static void +emit_bitfield (struct obstack *pool, int indent, + Dwarf_Die *die, Dwarf_Word byte_size, struct location *loc, + const char *s1, const char *s2, const char *s3) +{ + Dwarf_Word bit_offset, bit_size; + Dwarf_Attribute attr_mem; + if (dwarf_attr_integrate (die, DW_AT_bit_offset, &attr_mem) == NULL + || dwarf_formudata (&attr_mem, &bit_offset) != 0 + || dwarf_attr_integrate (die, DW_AT_bit_size, &attr_mem) == NULL + || dwarf_formudata (&attr_mem, &bit_size) != 0) + error (2, 0, _("cannot get bit field parameters: %s"), + dwarf_errmsg (-1)); + + /* Emit "{ uintNN_t tmp = ...;" to fetch the base type. */ + + obstack_printf (pool, "%*s{ ", indent * 2, ""); + emit_base_fetch (pool, byte_size, "tmp", true, loc); + + obstack_printf (pool, "%s%s%s, tmp, %" PRIu64 ", %" PRIu64 "); }\n", + s1, s2, s3, bit_offset, bit_size); +} + +/* Translate a fragment to fetch the value of variable or member DIE + at the *INPUT location and store it in variable TARGET. */ + +void +c_translate_fetch (struct obstack *pool, int indent, + Dwarf_Addr dwbias __attribute__ ((unused)), + Dwarf_Die *die, Dwarf_Attribute *typeattr, + struct location **input, const char *target) +{ + ++indent; + + Dwarf_Attribute size_attr; + Dwarf_Word byte_size; + if (dwarf_attr_integrate (die, DW_AT_byte_size, &size_attr) == NULL + || dwarf_formudata (&size_attr, &byte_size) != 0) + { + Dwarf_Die basedie; + if (dwarf_formref_die (typeattr, &basedie) == NULL) + error (2, 0, _("cannot get type of field: %s"), dwarf_errmsg (-1)); + byte_size = base_byte_size (&basedie); + } + + bool deref = false; + if (dwarf_hasattr_integrate (die, DW_AT_bit_offset)) + /* This is a bit field. */ + emit_bitfield (pool, indent, die, byte_size, *input, + "fetch_bitfield (", target, ""); + else + switch (byte_size) + { + case 1: + case 2: + case 4: + case 8: + obstack_printf (pool, "%*s", indent * 2, ""); + deref = emit_base_fetch (pool, byte_size, target, false, *input); + obstack_printf (pool, "\n"); + break; + + default: + /* Could handle this generating call to memcpy equivalent. */ + error (2, 0, _("fetch is larger than base integer types")); + break; + } + + struct location *loc = new_synthetic_loc (pool, *input, deref); + loc->type = loc_final; + (*input)->next = loc; + *input = loc; +} + +void +c_translate_addressof (struct obstack *pool, int indent, + Dwarf_Addr dwbias __attribute__ ((unused)), + Dwarf_Die *die, + Dwarf_Attribute *typeattr __attribute__ ((unused)), + struct location **input, const char *target) +{ + ++indent; + + if (dwarf_hasattr_integrate (die, DW_AT_bit_offset)) + error (2, 0, _("cannot take the address of a bit field")); + + switch ((*input)->type) + { + case loc_address: + obstack_printf (pool, "%*s%s = addr;\n", indent * 2, "", target); + (*input)->next = new_synthetic_loc (pool, *input, false); + (*input)->next->type = loc_final; + break; + + case loc_register: + error (2, 0, _("cannot take address of object in register")); + break; + case loc_noncontiguous: + error (2, 0, _("cannot take address of noncontiguous object")); + break; + + default: + abort(); + break; + } +} + + +/* Determine the element stride of an array type. */ +static Dwarf_Word +array_stride (Dwarf_Die *typedie) +{ + Dwarf_Attribute attr_mem; + if (dwarf_attr_integrate (typedie, DW_AT_stride_size, &attr_mem) != NULL) + { + Dwarf_Word stride; + if (dwarf_formudata (&attr_mem, &stride) == 0) + return stride; + error (2, 0, _("cannot get stride_size attribute array type %s: %s"), + dwarf_diename_integrate (typedie) ?: "<anonymous>", + dwarf_errmsg (-1)); + } + + Dwarf_Die die_mem; + if (dwarf_attr_integrate (typedie, DW_AT_type, &attr_mem) == NULL + || dwarf_formref_die (&attr_mem, &die_mem) == NULL) + error (2, 0, _("cannot get element type of array type %s: %s"), + dwarf_diename_integrate (typedie) ?: "<anonymous>", + dwarf_errmsg (-1)); + + if (dwarf_attr_integrate (&die_mem, DW_AT_byte_size, &attr_mem) != NULL) + { + Dwarf_Word stride; + if (dwarf_formudata (&attr_mem, &stride) == 0) + return stride; + error (2, 0, + _("cannot get byte_size attribute for array element type %s: %s"), + dwarf_diename_integrate (&die_mem) ?: "<anonymous>", + dwarf_errmsg (-1)); + } + + error (2, 0, _("confused about array element size")); + return 0; +} + +void +c_translate_array (struct obstack *pool, int indent, + Dwarf_Addr dwbias __attribute__ ((unused)), + Dwarf_Die *typedie, struct location **input, + const char *idx, Dwarf_Word const_idx) +{ + ++indent; + + Dwarf_Word stride = array_stride (typedie); + + struct location *loc = *input; + while (loc->type == loc_noncontiguous) + { + if (idx != NULL) + error (2, 0, _("cannot dynamically index noncontiguous array")); + else + { + Dwarf_Word offset = const_idx * stride; + struct location *piece = loc->pieces; + while (piece != NULL && offset >= piece->byte_size) + { + offset -= piece->byte_size; + piece = piece->next; + } + if (piece == NULL) + error (2, 0, _("constant index is outside noncontiguous array")); + if (offset % stride != 0) + error (2, 0, _("noncontiguous array splits elements")); + const_idx = offset / stride; + loc = piece; + } + } + + switch (loc->type) + { + case loc_address: + ++indent; + if (idx != NULL) + obstack_printf (pool, "%*saddr += %s * " UFORMAT ";\n", + indent * 2, "", idx, stride); + else + obstack_printf (pool, "%*saddr += " UFORMAT " * " UFORMAT ";\n", + indent * 2, "", const_idx, stride); + loc = new_synthetic_loc (pool, loc, false); + break; + + case loc_register: + error (2, 0, _("cannot index array stored in a register")); + break; + + default: + abort(); + break; + } + + (*input)->next = loc; + *input = (*input)->next; +} + + +/* Emitting C code for finalized fragments. */ + + +#define emit(fmt, ...) fprintf (out, fmt, ## __VA_ARGS__) + +/* Open a block with a comment giving the original DWARF expression. */ +static void +emit_header (FILE *out, struct location *loc, unsigned int hindent) +{ + if (loc->ops == NULL) + emit ("%*s{ // synthesized\n", hindent * 2, ""); + else + { + emit ("%*s{ // DWARF expression:", hindent * 2, ""); + for (size_t i = 0; i < loc->nops; ++i) + { + emit (" %#x", loc->ops[i].atom); + if (loc->ops[i].number2 == 0) + { + if (loc->ops[i].number != 0) + emit ("(%" PRId64 ")", loc->ops[i].number); + } + else + emit ("(%" PRId64 ",%" PRId64 ")", + loc->ops[i].number, loc->ops[i].number2); + } + emit ("\n"); + } +} + +/* Emit a code fragment to assign the target variable to a register value. */ +static void +emit_loc_register (FILE *out, struct location *loc, unsigned int indent, + const char *target) +{ + assert (loc->type == loc_register); + + emit ("%*s%s = fetch_register (%u);\n", + indent * 2, "", target, loc->regno); +} + +/* Emit a code fragment to assign the target variable to an address. */ +static void +emit_loc_address (FILE *out, struct location *loc, unsigned int indent, + const char *target) +{ + assert (loc->type == loc_address); + + if (loc->address.stack_depth == 0) + /* Synthetic program. */ + emit ("%s", loc->address.program); + else + { + emit ("%*s%s " STACKFMT, (indent + 1) * 2, "", STACK_TYPE, 0); + for (unsigned int i = 1; i < loc->address.stack_depth; ++i) + emit (", " STACKFMT, i); + emit (";\n"); + + emit ("%s%*s%s = " STACKFMT ";\n", loc->address.program, + (indent + 1) * 2, "", target, 0); + } +} + +/* Emit a code fragment to declare the target variable and + assign it to an address-sized value. */ +static void +emit_loc_value (FILE *out, struct location *loc, unsigned int indent, + const char *target, bool declare) +{ + if (declare) + emit ("%*s%s %s;\n", indent * 2, "", STACK_TYPE, target); + + emit_header (out, loc, indent); + + switch (loc->type) + { + default: + abort (); + break; + + case loc_register: + emit_loc_register (out, loc, indent, target); + break; + + case loc_address: + if (loc->address.frame_base != NULL) + emit_loc_value (out, loc->address.frame_base, indent, + "frame_base", true); + emit_loc_address (out, loc, indent, target); + break; + } + + emit ("%*s}\n", indent * 2, ""); +} + +bool +c_emit_location (FILE *out, struct location *loc, int indent) +{ + emit ("%*s{\n", indent * 2, ""); + + bool deref = false; + for (bool declare_addr = true; loc->next != NULL; loc = loc->next) + switch (loc->type) + { + case loc_address: + /* Emit the program fragment to calculate the address. */ + emit_loc_value (out, loc, indent + 1, "addr", declare_addr); + declare_addr = false; + deref = deref || loc->address.used_deref; + break; + + case loc_register: + case loc_noncontiguous: + /* These don't produce any code directly. + The next address/final record incorporates the value. */ + break; + + case loc_final: /* Should be last in chain! */ + default: + abort (); + break; + } + + if (loc->type != loc_final) /* Unfinished chain. */ + abort (); + + emit ("%s%*s}\n", loc->address.program, indent * 2, ""); + + return deref; +} + +#undef emit diff --git a/libdwfl/loc2c.h b/libdwfl/loc2c.h new file mode 100644 index 00000000..15d89280 --- /dev/null +++ b/libdwfl/loc2c.h @@ -0,0 +1,63 @@ +#include <libdw.h> + +struct obstack; /* Use <obstack.h> */ +struct location; /* Opaque */ + + +/* Translate a C fragment for the location expression, using *INPUT + as the starting location, begin from scratch if *INPUT is null. + If DW_OP_fbreg is used, it may have a subfragment computing from + the FB_ATTR location expression. + + On errors, exit and never return (XXX ?). On success, return the + first fragment created, which is also chained onto (*INPUT)->next. + *INPUT is then updated with the new tail of that chain. + *USED_DEREF is set to true iff the "deref" runtime operation + was used, otherwise it is not modified. */ +struct location *c_translate_location (struct obstack *, int indent, + Dwarf_Addr bias, + Dwarf_Attribute *loc_attr, + Dwarf_Addr address, + struct location **input, + Dwarf_Attribute *fb_attr); + +/* Translate a fragment to dereference the given pointer type, + where *INPUT is the location of the pointer with that type. */ +void c_translate_pointer (struct obstack *pool, int indent, + Dwarf_Addr dwbias, Dwarf_Die *typedie, + struct location **input); + +/* Translate a fragment to index an array (turning the location + of the array into the location of an element). If IDX is non-null, + it's a string of C code to emit in the fragment as the array index. + If the index is a known constant, IDX should be null and CONST_IDX + is used instead (this case can handle local arrays in registers). */ +void c_translate_array (struct obstack *pool, int indent, + Dwarf_Addr dwbias, Dwarf_Die *typedie, + struct location **input, + const char *idx, Dwarf_Word const_idx); + +/* Translate a fragment to compute the address of the input location + and assign it to the variable TARGET. This doesn't really do anything + (it always emits "TARGET = addr;"), but it will barf if the location + is a register or noncontiguous object. */ +void c_translate_addressof (struct obstack *pool, int indent, + Dwarf_Addr dwbias, Dwarf_Die *die, + Dwarf_Attribute *typeattr, + struct location **input, const char *target); + +/* Translate a fragment to fetch the value of variable or member DIE + at the *INPUT location and store it in variable TARGET. + This handles base integer types and bit fields. */ +void c_translate_fetch (struct obstack *pool, int indent, + Dwarf_Addr dwbias __attribute__ ((unused)), + Dwarf_Die *die, Dwarf_Attribute *typeattr, + struct location **input, const char *target); + +/* Emit the C fragment built up at LOC (i.e., the return value from the + first c_translate_location call made). INDENT should match that + passed to c_translate_* previously. + + Writes complete lines of C99, code forming a complete C block, to STREAM. + Return value is true iff that code uses the `deref' runtime macros. */ +bool c_emit_location (FILE *stream, struct location *loc, int indent); diff --git a/libdwfl/ptest.c b/libdwfl/ptest.c new file mode 100644 index 00000000..948e971a --- /dev/null +++ b/libdwfl/ptest.c @@ -0,0 +1,117 @@ +/* Test program for libdwfl basic module tracking, relocation. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdlib.h> +#include <string.h> +#include <error.h> +#include <locale.h> +#include <argp.h> +#include <libdwfl.h> + + +static int +print_func (Dwarf_Func *func, void *arg) +{ + const Dwarf_Addr dwbias = *(Dwarf_Addr *) arg; + + const char *file = dwarf_func_file (func); + int line = -1; + dwarf_func_line (func, &line); + const char *fct = dwarf_func_name (func); + + printf (" %s:%d: %s:", file, line, fct); + + Dwarf_Addr lo = -1, hi = -1, entry = -1; + if (dwarf_func_lowpc (func, &lo) == 0) + lo += dwbias; + else + printf (" (lowpc => %s)", dwarf_errmsg (-1)); + if (dwarf_func_highpc (func, &hi) == 0) + hi += dwbias; + else + printf (" (highpc => %s)", dwarf_errmsg (-1)); + if (dwarf_func_entrypc (func, &entry) == 0) + entry += dwbias; + else + printf (" (entrypc => %s)", dwarf_errmsg (-1)); + + if (lo != (Dwarf_Addr) -1 || hi != (Dwarf_Addr) -1 + || entry != (Dwarf_Addr) -1) + printf (" %#" PRIx64 "..%#" PRIx64 " => %#" PRIx64 "\n", + lo, hi, entry); + else + puts (""); + + return DWARF_CB_OK; +} + +static int +print_module (Dwfl_Module *mod __attribute__ ((unused)), + void **userdata __attribute__ ((unused)), + const char *name, Dwarf_Addr base, + Dwarf *dw, Dwarf_Addr bias, + void *arg __attribute__ ((unused))) +{ + printf ("module: %30s %08" PRIx64 " %12p %" PRIx64 " (%s)\n", + name, base, dw, bias, dwfl_errmsg (-1)); + + if (dw != NULL) + { + Dwarf_Off off = 0; + size_t cuhl; + Dwarf_Off noff; + + while (dwarf_nextcu (dw, off, &noff, &cuhl, NULL, NULL, NULL) == 0) + { + Dwarf_Die die_mem; + Dwarf_Die *die = dwarf_offdie (dw, off + cuhl, &die_mem); + + (void) dwarf_getfuncs (die, print_func, &bias, 0); + + off = noff; + } + } + + return DWARF_CB_OK; +} + +int +main (int argc, char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + Dwfl *dwfl = NULL; + (void) argp_parse (dwfl_standard_argp (), argc, argv, 0, NULL, &dwfl); + assert (dwfl != NULL); + + ptrdiff_t p = 0; + do + p = dwfl_getdwarf (dwfl, &print_module, NULL, p); + while (p > 0); + if (p < 0) + error (2, 0, "dwfl_getdwarf: %s", dwfl_errmsg (-1)); + + dwfl_end (dwfl); + + return 0; +} diff --git a/libdwfl/relocate.c b/libdwfl/relocate.c new file mode 100644 index 00000000..41de6d18 --- /dev/null +++ b/libdwfl/relocate.c @@ -0,0 +1,293 @@ +/* Relocate debug information. + Copyright (C) 2005 Red Hat, Inc. + + This program is Open Source software; you can redistribute it and/or + modify it under the terms of the Open Software License version 1.0 as + published by the Open Source Initiative. + + You should have received a copy of the Open Software License along + with this program; if not, you may obtain a copy of the Open Software + License version 1.0 from http://www.opensource.org/licenses/osl.php or + by writing the Open Source Initiative c/o Lawrence Rosen, Esq., + 3001 King Ranch Road, Ukiah, CA 95482. */ + +#include "libdwflP.h" + +typedef uint8_t GElf_Byte; + +/* Adjust *VALUE to add the load address of the SHNDX section. + We update the section header in place to cache the result. */ + +Dwfl_Error +internal_function_def +__libdwfl_relocate_value (Dwfl_Module *mod, size_t symshstrndx, + Elf32_Word shndx, GElf_Addr *value) +{ + Elf_Scn *refscn = elf_getscn (mod->symfile->elf, shndx); + GElf_Shdr refshdr_mem, *refshdr = gelf_getshdr (refscn, &refshdr_mem); + if (refshdr == NULL) + return DWFL_E_LIBELF; + + if ((refshdr->sh_flags & SHF_ALLOC) && refshdr->sh_addr == 0) + { + /* This is a loaded section. Find its actual + address and update the section header. */ + const char *name = elf_strptr (mod->symfile->elf, symshstrndx, + refshdr->sh_name); + if (name == NULL) + return DWFL_E_LIBELF; + + if ((*mod->dwfl->callbacks->section_address) (MODCB_ARGS (mod), name, + &refshdr->sh_addr)) + return CBFAIL; + + if (refshdr->sh_addr == 0) + /* The callback resolved this to zero, indicating it wasn't + really loaded but we don't really care. Mark it so we + don't check it again for the next relocation. */ + refshdr->sh_flags &= ~SHF_ALLOC; + + /* Update the in-core file's section header to show the final + load address (or unloadedness). This serves as a cache, + so we won't get here again for the same section. */ + if (! gelf_update_shdr (refscn, refshdr)) + return DWFL_E_LIBELF; + } + + /* Apply the adjustment. */ + *value += refshdr->sh_addr; + return DWFL_E_NOERROR; +} + +Dwfl_Error +internal_function_def +__libdwfl_relocate (Dwfl_Module *mod) +{ + assert (mod->isrel); + + GElf_Ehdr ehdr_mem; + const GElf_Ehdr *ehdr = gelf_getehdr (mod->debug.elf, &ehdr_mem); + if (ehdr == NULL) + return DWFL_E_LIBELF; + + size_t symshstrndx, d_shstrndx; + if (elf_getshstrndx (mod->symfile->elf, &symshstrndx) < 0) + return DWFL_E_LIBELF; + if (mod->symfile == &mod->debug) + d_shstrndx = symshstrndx; + else if (elf_getshstrndx (mod->debug.elf, &d_shstrndx) < 0) + return DWFL_E_LIBELF; + + /* Look at each section in the debuginfo file, and process the + relocation sections for debugging sections. */ + Dwfl_Error result = DWFL_E_NO_DWARF; + Elf_Scn *scn = NULL; + while ((scn = elf_nextscn (mod->debug.elf, scn)) != NULL) + { + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + + if (shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) + { + /* It's a relocation section. First, fetch the name of the + section these relocations apply to. */ + + Elf_Scn *tscn = elf_getscn (mod->debug.elf, shdr->sh_info); + if (tscn == NULL) + return DWFL_E_LIBELF; + + GElf_Shdr tshdr_mem; + GElf_Shdr *tshdr = gelf_getshdr (tscn, &tshdr_mem); + const char *tname = elf_strptr (mod->debug.elf, d_shstrndx, + tshdr->sh_name); + if (tname == NULL) + return DWFL_E_LIBELF; + + if (! ebl_debugscn_p (mod->ebl, tname)) + /* This relocation section is not for a debugging section. + Nothing to do here. */ + continue; + + /* Fetch the section data that needs the relocations applied. */ + Elf_Data *tdata = elf_rawdata (tscn, NULL); + if (tdata == NULL) + return DWFL_E_LIBELF; + + /* Apply one relocation. Returns true for any invalid data. */ + Dwfl_Error relocate (GElf_Addr offset, const GElf_Sxword *addend, + int rtype, int symndx) + { + /* First, resolve the symbol to an absolute value. */ + GElf_Addr value; + inline Dwfl_Error adjust (GElf_Word shndx) + { + return __libdwfl_relocate_value (mod, symshstrndx, + shndx, &value); + } + + if (symndx == STN_UNDEF) + /* When strip removes a section symbol referring to a + section moved into the debuginfo file, it replaces + that symbol index in relocs with STN_UNDEF. We + don't actually need the symbol, because those relocs + are always references relative to the nonallocated + debugging sections, which start at zero. */ + value = 0; + else + { + GElf_Sym sym_mem; + GElf_Word shndx; + GElf_Sym *sym = gelf_getsymshndx (mod->symdata, + mod->symxndxdata, + symndx, &sym_mem, + &shndx); + if (sym == NULL) + return DWFL_E_LIBELF; + + value = sym->st_value; + if (sym->st_shndx != SHN_XINDEX) + shndx = sym->st_shndx; + switch (shndx) + { + case SHN_ABS: + break; + + case SHN_UNDEF: + case SHN_COMMON: + return DWFL_E_RELUNDEF; + + default: + { + Dwfl_Error error = adjust (shndx); + if (error != DWFL_E_NOERROR) + return error; + break; + } + } + } + + /* These are the types we can relocate. */ +#define TYPES DO_TYPE (BYTE, Byte) DO_TYPE (HALF, Half) \ + DO_TYPE (WORD, Word) DO_TYPE (SWORD, Sword) \ + DO_TYPE (XWORD, Xword) DO_TYPE (SXWORD, Sxword) + size_t size; + Elf_Type type = ebl_reloc_simple_type (mod->ebl, rtype); + switch (type) + { +#define DO_TYPE(NAME, Name) \ + case ELF_T_##NAME: \ + size = sizeof (GElf_##Name); \ + break; + TYPES +#undef DO_TYPE + default: + return DWFL_E_BADRELTYPE; + } + + if (offset + size >= tdata->d_size) + return DWFL_E_BADRELOFF; + +#define DO_TYPE(NAME, Name) GElf_##Name Name; + union { TYPES } tmpbuf; +#undef DO_TYPE + Elf_Data tmpdata = + { + .d_type = type, + .d_buf = &tmpbuf, + .d_size = size, + .d_version = EV_CURRENT, + }; + Elf_Data rdata = + { + .d_type = type, + .d_buf = tdata->d_buf + offset, + .d_size = size, + .d_version = EV_CURRENT, + }; + + /* XXX check for overflow? */ + if (addend) + { + /* For the addend form, we have the value already. */ + value += *addend; + switch (type) + { +#define DO_TYPE(NAME, Name) \ + case ELF_T_##NAME: \ + tmpbuf.Name = value; \ + break; + TYPES +#undef DO_TYPE + default: + abort (); + } + } + else + { + /* Extract the original value and apply the reloc. */ + Elf_Data *d = gelf_xlatetom (mod->main.elf, &tmpdata, &rdata, + ehdr->e_ident[EI_DATA]); + if (d == NULL) + return DWFL_E_LIBELF; + assert (d == &tmpdata); + switch (type) + { +#define DO_TYPE(NAME, Name) \ + case ELF_T_##NAME: \ + tmpbuf.Name += (GElf_##Name) value; \ + break; + TYPES +#undef DO_TYPE + default: + abort (); + } + } + + /* Now convert the relocated datum back to the target + format. This will write into rdata.d_buf, which + points into the raw section data being relocated. */ + Elf_Data *s = gelf_xlatetof (mod->main.elf, &rdata, &tmpdata, + ehdr->e_ident[EI_DATA]); + if (s == NULL) + return DWFL_E_LIBELF; + assert (s == &rdata); + + /* We have applied this relocation! */ + return DWFL_E_NOERROR; + } + + /* Fetch the relocation section and apply each reloc in it. */ + Elf_Data *reldata = elf_getdata (scn, NULL); + if (reldata == NULL) + return DWFL_E_LIBELF; + + result = DWFL_E_NOERROR; + size_t nrels = shdr->sh_size / shdr->sh_entsize; + if (shdr->sh_type == SHT_REL) + for (size_t relidx = 0; !result && relidx < nrels; ++relidx) + { + GElf_Rel rel_mem, *r = gelf_getrel (reldata, relidx, &rel_mem); + if (r == NULL) + return DWFL_E_LIBELF; + result = relocate (r->r_offset, NULL, + GELF_R_TYPE (r->r_info), + GELF_R_SYM (r->r_info)); + } + else + for (size_t relidx = 0; !result && relidx < nrels; ++relidx) + { + GElf_Rela rela_mem, *r = gelf_getrela (reldata, relidx, + &rela_mem); + if (r == NULL) + return DWFL_E_LIBELF; + result = relocate (r->r_offset, &r->r_addend, + GELF_R_TYPE (r->r_info), + GELF_R_SYM (r->r_info)); + } + if (result != DWFL_E_NOERROR) + break; + } + } + + return result; +} diff --git a/libdwfl/test2.c b/libdwfl/test2.c new file mode 100644 index 00000000..4c1e7ade --- /dev/null +++ b/libdwfl/test2.c @@ -0,0 +1,268 @@ +#include <config.h> +#include <assert.h> +#include <inttypes.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdlib.h> +#include <string.h> +#include <error.h> +#include <locale.h> +#include <argp.h> +#include <libdwfl.h> +#include <dwarf.h> +#include "../libdw/libdwP.h" +#include <obstack.h> + +#include "loc2c.h" + +static const char * +dwarf_diename_integrate (Dwarf_Die *die) +{ + Dwarf_Attribute attr_mem; + return dwarf_formstring (dwarf_attr_integrate (die, DW_AT_name, &attr_mem)); +} + + + +static void +handle_variable (Dwarf_Die *scopes, int nscopes, int out, + Dwarf_Addr cubias, Dwarf_Die *vardie, Dwarf_Addr pc, + char **fields) +{ +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + struct obstack pool; + obstack_init (&pool); + + /* Figure out the appropriate frame base for accessing this variable. + XXX not handling nested functions + XXX inlines botched + */ + Dwarf_Attribute fb_attr_mem, *fb_attr = NULL; + for (int inner = 0; inner < nscopes; ++inner) + { + switch (dwarf_tag (&scopes[inner])) + { + default: + continue; + case DW_TAG_subprogram: + case DW_TAG_entry_point: + case DW_TAG_inlined_subroutine: /* XXX */ + if (inner >= out) + fb_attr = dwarf_attr_integrate (&scopes[inner], + DW_AT_frame_base, + &fb_attr_mem); + break; + } + break; + } + + Dwarf_Attribute attr_mem; + + if (dwarf_attr_integrate (vardie, DW_AT_location, &attr_mem) == NULL) + error (2, 0, _("cannot get location of variable: %s"), + dwarf_errmsg (-1)); + +#define FIELD "addr" +#define emit(fmt, ...) printf (" addr = " fmt "\n", ## __VA_ARGS__) + + struct location *head, *tail = NULL; + head = c_translate_location (&pool, 1, cubias, &attr_mem, pc, + &tail, fb_attr); + + if (dwarf_attr_integrate (vardie, DW_AT_type, &attr_mem) == NULL) + error (2, 0, _("cannot get type of variable: %s"), + dwarf_errmsg (-1)); + + Dwarf_Die die_mem, *die = vardie; + while (*fields != NULL) + { + die = dwarf_formref_die (&attr_mem, &die_mem); + + const int typetag = dwarf_tag (die); + switch (typetag) + { + case DW_TAG_typedef: + /* Just iterate on the referent type. */ + break; + + case DW_TAG_pointer_type: + if (**fields == '+') + goto subscript; + /* A "" field means explicit pointer dereference and we consume it. + Otherwise the next field implicitly gets the dereference. */ + if (**fields == '\0') + ++fields; + c_translate_pointer (&pool, 1, cubias, die, &tail); + break; + + case DW_TAG_array_type: + if (**fields == '+') + { + subscript:; + char *endp = *fields + 1; + uintmax_t idx = strtoumax (*fields + 1, &endp, 0); + if (endp == NULL || endp == *fields || *endp != '\0') + c_translate_array (&pool, 1, cubias, die, &tail, + *fields + 1, 0); + else + c_translate_array (&pool, 1, cubias, die, &tail, + NULL, idx); + ++fields; + } + else + error (2, 0, _("bad field for array type: %s"), *fields); + break; + + case DW_TAG_structure_type: + case DW_TAG_union_type: + switch (dwarf_child (die, &die_mem)) + { + case 1: /* No children. */ + error (2, 0, _("empty struct %s"), + dwarf_diename_integrate (die) ?: "<anonymous>"); + break; + case -1: /* Error. */ + default: /* Shouldn't happen */ + error (2, 0, _("%s %s: %s"), + typetag == DW_TAG_union_type ? "union" : "struct", + dwarf_diename_integrate (die) ?: "<anonymous>", + dwarf_errmsg (-1)); + break; + + case 0: + break; + } + while (dwarf_tag (die) != DW_TAG_member + || ({ const char *member = dwarf_diename_integrate (die); + member == NULL || strcmp (member, *fields); })) + if (dwarf_siblingof (die, &die_mem) != 0) + error (2, 0, _("field name %s not found"), *fields); + + if (dwarf_attr_integrate (die, DW_AT_data_member_location, + &attr_mem) == NULL) + { + /* Union members don't usually have a location, + but just use the containing union's location. */ + if (typetag != DW_TAG_union_type) + error (2, 0, _("no location for field %s: %s"), + *fields, dwarf_errmsg (-1)); + } + else + c_translate_location (&pool, 1, cubias, &attr_mem, pc, + &tail, NULL); + ++fields; + break; + + case DW_TAG_base_type: + error (2, 0, _("field %s vs base type %s"), + *fields, dwarf_diename_integrate (die) ?: "<anonymous type>"); + break; + + case -1: + error (2, 0, _("cannot find type: %s"), dwarf_errmsg (-1)); + break; + + default: + error (2, 0, _("%s: unexpected type tag %#x"), + dwarf_diename_integrate (die) ?: "<anonymous type>", + dwarf_tag (die)); + break; + } + + /* Now iterate on the type in DIE's attribute. */ + if (dwarf_attr_integrate (die, DW_AT_type, &attr_mem) == NULL) + error (2, 0, _("cannot get type of field: %s"), dwarf_errmsg (-1)); + } + + c_translate_fetch (&pool, 1, cubias, die, &attr_mem, &tail, "value"); + + printf ("#define PROBEADDR %#" PRIx64 "ULL\n", pc); + puts ("static void print_value(struct pt_regs *regs)\n" + "{\n" + " intptr_t value;"); + + bool deref = c_emit_location (stdout, head, 1); + + puts (" printk (\" ---> %ld\\n\", (unsigned long) value);\n" + " return;"); + + if (deref) + puts ("\n" + " deref_fault:\n" + " printk (\" => BAD FETCH\\n\");"); + + puts ("}"); +} + +int +main (int argc, char **argv) +{ + /* We use no threads here which can interfere with handling a stream. */ + (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER); + + /* Set locale. */ + (void) setlocale (LC_ALL, ""); + + Dwfl *dwfl = NULL; + int argi; + (void) argp_parse (dwfl_standard_argp (), argc, argv, 0, &argi, &dwfl); + assert (dwfl != NULL); + + if (argi == argc) + error (2, 0, "need address argument"); + + char *endp; + uintmax_t pc = strtoumax (argv[argi], &endp, 0); + if (endp == argv[argi]) + error (2, 0, "bad address argument"); + + Dwarf_Addr cubias; + Dwarf_Die *cudie = dwfl_addrdie (dwfl, pc, &cubias); + if (cudie == NULL) + error (EXIT_FAILURE, 0, "dwfl_addrdie: %s", dwfl_errmsg (-1)); + + Dwarf_Die *scopes; + int n = dwarf_getscopes (cudie, pc - cubias, &scopes); + if (n < 0) + error (EXIT_FAILURE, 0, "dwarf_getscopes: %s", dwarf_errmsg (-1)); + else if (n == 0) + error (EXIT_FAILURE, 0, "%#" PRIx64 ": not in any scope\n", pc); + + if (++argi == argc) + error (2, 0, "need variable arguments"); + + char *spec = argv[argi++]; + + int lineno = 0, colno = 0, shadow = 0; + char *at = strchr (spec, '@'); + if (at != NULL) + { + *at++ = '\0'; + if (sscanf (at, "%*[^:]:%i:%i", &lineno, &colno) < 1) + lineno = 0; + } + else + { + int len; + if (sscanf (spec, "%*[^+]%n+%i", &len, &shadow) == 2) + spec[len] = '\0'; + } + + Dwarf_Die vardie; + int out = dwarf_getscopevar (scopes, n, spec, shadow, at, lineno, colno, + &vardie); + if (out == -2) + error (0, 0, "no match for %s (+%d, %s:%d:%d)", + spec, shadow, at, lineno, colno); + else if (out < 0) + error (0, 0, "dwarf_getscopevar: %s (+%d, %s:%d:%d): %s", + spec, shadow, at, lineno, colno, dwarf_errmsg (-1)); + else + handle_variable (scopes, n, out, cubias, &vardie, pc, &argv[argi]); + + dwfl_end (dwfl); + + return 0; +} |