| /* Locate source files or functions which caused text relocations. |
| Copyright (C) 2005, 2006, 2007 Red Hat, Inc. |
| This file is part of Red Hat elfutils. |
| Written by Ulrich Drepper <drepper@redhat.com>, 2005. |
| |
| Red Hat elfutils is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by the |
| Free Software Foundation; version 2 of the License. |
| |
| Red Hat elfutils is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License along |
| with Red Hat elfutils; if not, write to the Free Software Foundation, |
| Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA. |
| |
| Red Hat elfutils is an included package of the Open Invention Network. |
| An included package of the Open Invention Network is a package for which |
| Open Invention Network licensees cross-license their patents. No patent |
| license is granted, either expressly or impliedly, by designation as an |
| included package. Should you wish to participate in the Open Invention |
| Network licensing program, please visit www.openinventionnetwork.com |
| <http://www.openinventionnetwork.com>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <argp.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <error.h> |
| #include <fcntl.h> |
| #include <gelf.h> |
| #include <libdw.h> |
| #include <libintl.h> |
| #include <locale.h> |
| #include <search.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| |
| struct segments |
| { |
| GElf_Addr from; |
| GElf_Addr to; |
| }; |
| |
| |
| /* Name and version of program. */ |
| static void print_version (FILE *stream, struct argp_state *state); |
| void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; |
| |
| /* Bug report address. */ |
| const char *argp_program_bug_address = PACKAGE_BUGREPORT; |
| |
| /* Values for the parameters which have no short form. */ |
| #define OPT_DEBUGINFO 0x100 |
| |
| /* Definitions of arguments for argp functions. */ |
| static const struct argp_option options[] = |
| { |
| { NULL, 0, NULL, 0, N_("Input Selection:"), 0 }, |
| { "root", 'r', "PATH", 0, N_("Prepend PATH to all file names"), 0 }, |
| { "debuginfo", OPT_DEBUGINFO, "PATH", 0, |
| N_("Use PATH as root of debuginfo hierarchy"), 0 }, |
| |
| { NULL, 0, NULL, 0, N_("Miscellaneous:"), 0 }, |
| { NULL, 0, NULL, 0, NULL, 0 } |
| }; |
| |
| /* Short description of program. */ |
| static const char doc[] = N_("\ |
| Locate source of text relocations in FILEs (a.out by default)."); |
| |
| /* Strings for arguments in help texts. */ |
| static const char args_doc[] = N_("[FILE...]"); |
| |
| /* Prototype for option handler. */ |
| static error_t parse_opt (int key, char *arg, struct argp_state *state); |
| |
| /* Data structure to communicate with argp functions. */ |
| static struct argp argp = |
| { |
| options, parse_opt, args_doc, doc, NULL, NULL, NULL |
| }; |
| |
| |
| /* Print symbols in file named FNAME. */ |
| static int process_file (const char *fname, bool more_than_one); |
| |
| /* Check for text relocations in the given file. The segment |
| information is known. */ |
| static void check_rel (size_t nsegments, struct segments segments[nsegments], |
| GElf_Addr addr, Elf *elf, Elf_Scn *symscn, Dwarf *dw, |
| const char *fname, bool more_than_one, |
| void **knownsrcs); |
| |
| |
| |
| /* User-provided root directory. */ |
| static const char *rootdir = "/"; |
| |
| /* Root of debuginfo directory hierarchy. */ |
| static const char *debuginfo_root; |
| |
| |
| int |
| main (int argc, char *argv[]) |
| { |
| int remaining; |
| int result = 0; |
| |
| /* Set locale. */ |
| (void) setlocale (LC_ALL, ""); |
| |
| /* Make sure the message catalog can be found. */ |
| (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR); |
| |
| /* Initialize the message catalog. */ |
| (void) textdomain (PACKAGE_TARNAME); |
| |
| /* Parse and process arguments. */ |
| (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL); |
| |
| /* Tell the library which version we are expecting. */ |
| elf_version (EV_CURRENT); |
| |
| /* If the user has not specified the root directory for the |
| debuginfo hierarchy, we have to determine it ourselves. */ |
| if (debuginfo_root == NULL) |
| { |
| // XXX The runtime should provide this information. |
| #if defined __ia64__ || defined __alpha__ |
| debuginfo_root = "/usr/lib/debug"; |
| #else |
| debuginfo_root = (sizeof (long int) == 4 |
| ? "/usr/lib/debug" : "/usr/lib64/debug"); |
| #endif |
| } |
| |
| if (remaining == argc) |
| result = process_file ("a.out", false); |
| else |
| { |
| /* Process all the remaining files. */ |
| const bool more_than_one = remaining + 1 < argc; |
| |
| do |
| result |= process_file (argv[remaining], more_than_one); |
| while (++remaining < argc); |
| } |
| |
| return result; |
| } |
| |
| |
| /* Print the version information. */ |
| static void |
| print_version (FILE *stream, struct argp_state *state __attribute__ ((unused))) |
| { |
| fprintf (stream, "findtextrel (%s) %s\n", PACKAGE_NAME, PACKAGE_VERSION); |
| fprintf (stream, gettext ("\ |
| Copyright (C) %s Red Hat, Inc.\n\ |
| This is free software; see the source for copying conditions. There is NO\n\ |
| warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ |
| "), "2008"); |
| fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); |
| } |
| |
| |
| /* Handle program arguments. */ |
| static error_t |
| parse_opt (int key, char *arg, |
| struct argp_state *state __attribute__ ((unused))) |
| { |
| switch (key) |
| { |
| case 'r': |
| rootdir = arg; |
| break; |
| |
| case OPT_DEBUGINFO: |
| debuginfo_root = arg; |
| break; |
| |
| default: |
| return ARGP_ERR_UNKNOWN; |
| } |
| return 0; |
| } |
| |
| |
| static void |
| noop (void *arg __attribute__ ((unused))) |
| { |
| } |
| |
| |
| static int |
| process_file (const char *fname, bool more_than_one) |
| { |
| int result = 0; |
| void *knownsrcs = NULL; |
| |
| size_t fname_len = strlen (fname); |
| size_t rootdir_len = strlen (rootdir); |
| const char *real_fname = fname; |
| if (fname[0] == '/' && (rootdir[0] != '/' || rootdir[1] != '\0')) |
| { |
| /* Prepend the user-provided root directory. */ |
| char *new_fname = alloca (rootdir_len + fname_len + 2); |
| *((char *) mempcpy (stpcpy (mempcpy (new_fname, rootdir, rootdir_len), |
| "/"), |
| fname, fname_len)) = '\0'; |
| real_fname = new_fname; |
| } |
| |
| int fd = open64 (real_fname, O_RDONLY); |
| if (fd == -1) |
| { |
| error (0, errno, gettext ("cannot open '%s'"), fname); |
| return 1; |
| } |
| |
| Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL); |
| if (elf == NULL) |
| { |
| error (0, 0, gettext ("cannot create ELF descriptor for '%s': %s"), |
| fname, elf_errmsg (-1)); |
| goto err_close; |
| } |
| |
| /* Make sure the file is a DSO. */ |
| GElf_Ehdr ehdr_mem; |
| GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_mem); |
| if (ehdr == NULL) |
| { |
| error (0, 0, gettext ("cannot get ELF header '%s': %s"), |
| fname, elf_errmsg (-1)); |
| err_elf_close: |
| elf_end (elf); |
| err_close: |
| close (fd); |
| return 1; |
| } |
| |
| if (ehdr->e_type != ET_DYN) |
| { |
| error (0, 0, gettext ("'%s' is not a DSO or PIE"), fname); |
| goto err_elf_close; |
| } |
| |
| /* Determine whether the DSO has text relocations at all and locate |
| the symbol table. */ |
| Elf_Scn *symscn = NULL; |
| Elf_Scn *scn = NULL; |
| while ((scn = elf_nextscn (elf, scn)) != NULL) |
| { |
| /* Handle the section if it is a symbol table. */ |
| GElf_Shdr shdr_mem; |
| GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); |
| |
| if (shdr == NULL) |
| { |
| error (0, 0, |
| gettext ("getting get section header of section %zu: %s"), |
| elf_ndxscn (scn), elf_errmsg (-1)); |
| goto err_elf_close; |
| } |
| |
| if (shdr->sh_type == SHT_DYNAMIC) |
| { |
| Elf_Data *data = elf_getdata (scn, NULL); |
| |
| for (size_t cnt = 0; cnt < shdr->sh_size / shdr->sh_entsize; |
| ++cnt) |
| { |
| GElf_Dyn dynmem; |
| GElf_Dyn *dyn; |
| |
| dyn = gelf_getdyn (data, cnt, &dynmem); |
| if (dyn == NULL) |
| { |
| error (0, 0, gettext ("cannot read dynamic section: %s"), |
| elf_errmsg (-1)); |
| goto err_elf_close; |
| } |
| |
| if (dyn->d_tag == DT_TEXTREL |
| || (dyn->d_tag == DT_FLAGS |
| && (dyn->d_un.d_val & DF_TEXTREL) != 0)) |
| goto have_textrel; |
| } |
| } |
| else if (shdr->sh_type == SHT_SYMTAB) |
| symscn = scn; |
| } |
| |
| error (0, 0, gettext ("no text relocations reported in '%s'"), fname); |
| return 1; |
| |
| have_textrel:; |
| int fd2 = -1; |
| Elf *elf2 = NULL; |
| /* Get the address ranges for the loaded segments. */ |
| size_t nsegments_max = 10; |
| size_t nsegments = 0; |
| struct segments *segments |
| = (struct segments *) malloc (nsegments_max * sizeof (segments[0])); |
| if (segments == NULL) |
| error (1, errno, gettext ("while reading ELF file")); |
| |
| for (int i = 0; i < ehdr->e_phnum; ++i) |
| { |
| GElf_Phdr phdr_mem; |
| GElf_Phdr *phdr = gelf_getphdr (elf, i, &phdr_mem); |
| if (phdr == NULL) |
| { |
| error (0, 0, |
| gettext ("cannot get program header index at offset %d: %s"), |
| i, elf_errmsg (-1)); |
| result = 1; |
| goto next; |
| } |
| |
| if (phdr->p_type == PT_LOAD && (phdr->p_flags & PF_W) == 0) |
| { |
| if (nsegments == nsegments_max) |
| { |
| nsegments_max *= 2; |
| segments |
| = (struct segments *) realloc (segments, |
| nsegments_max |
| * sizeof (segments[0])); |
| if (segments == NULL) |
| { |
| error (0, 0, gettext ("\ |
| cannot get program header index at offset %d: %s"), |
| i, elf_errmsg (-1)); |
| result = 1; |
| goto next; |
| } |
| } |
| |
| segments[nsegments].from = phdr->p_vaddr; |
| segments[nsegments].to = phdr->p_vaddr + phdr->p_memsz; |
| ++nsegments; |
| } |
| } |
| |
| if (nsegments > 0) |
| { |
| |
| Dwarf *dw = dwarf_begin_elf (elf, DWARF_C_READ, NULL); |
| /* Look for debuginfo files if the information is not the in |
| opened file itself. This makes only sense if the input file |
| is specified with an absolute path. */ |
| if (dw == NULL && fname[0] == '/') |
| { |
| size_t debuginfo_rootlen = strlen (debuginfo_root); |
| char *difname = (char *) alloca (rootdir_len + debuginfo_rootlen |
| + fname_len + 8); |
| strcpy (mempcpy (stpcpy (mempcpy (mempcpy (difname, rootdir, |
| rootdir_len), |
| debuginfo_root, |
| debuginfo_rootlen), |
| "/"), |
| fname, fname_len), |
| ".debug"); |
| |
| fd2 = open64 (difname, O_RDONLY); |
| if (fd2 != -1 |
| && (elf2 = elf_begin (fd2, ELF_C_READ_MMAP, NULL)) != NULL) |
| dw = dwarf_begin_elf (elf2, DWARF_C_READ, NULL); |
| } |
| |
| /* Look at all relocations and determine which modify |
| write-protected segments. */ |
| scn = NULL; |
| while ((scn = elf_nextscn (elf, scn)) != NULL) |
| { |
| /* Handle the section if it is a symbol table. */ |
| GElf_Shdr shdr_mem; |
| GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); |
| |
| if (shdr == NULL) |
| { |
| error (0, 0, |
| gettext ("cannot get section header of section %Zu: %s"), |
| elf_ndxscn (scn), elf_errmsg (-1)); |
| result = 1; |
| goto next; |
| } |
| |
| if ((shdr->sh_type == SHT_REL || shdr->sh_type == SHT_RELA) |
| && symscn == NULL) |
| { |
| symscn = elf_getscn (elf, shdr->sh_link); |
| if (symscn == NULL) |
| { |
| error (0, 0, gettext ("\ |
| cannot get symbol table section %zu in '%s': %s"), |
| (size_t) shdr->sh_link, fname, elf_errmsg (-1)); |
| result = 1; |
| goto next; |
| } |
| } |
| |
| if (shdr->sh_type == SHT_REL) |
| { |
| Elf_Data *data = elf_getdata (scn, NULL); |
| |
| for (int cnt = 0; |
| (size_t) cnt < shdr->sh_size / shdr->sh_entsize; |
| ++cnt) |
| { |
| GElf_Rel rel_mem; |
| GElf_Rel *rel = gelf_getrel (data, cnt, &rel_mem); |
| if (rel == NULL) |
| { |
| error (0, 0, gettext ("\ |
| cannot get relocation at index %d in section %zu in '%s': %s"), |
| cnt, elf_ndxscn (scn), fname, elf_errmsg (-1)); |
| result = 1; |
| goto next; |
| } |
| |
| check_rel (nsegments, segments, rel->r_offset, elf, |
| symscn, dw, fname, more_than_one, &knownsrcs); |
| } |
| } |
| else if (shdr->sh_type == SHT_RELA) |
| { |
| Elf_Data *data = elf_getdata (scn, NULL); |
| |
| for (int cnt = 0; |
| (size_t) cnt < shdr->sh_size / shdr->sh_entsize; |
| ++cnt) |
| { |
| GElf_Rela rela_mem; |
| GElf_Rela *rela = gelf_getrela (data, cnt, &rela_mem); |
| if (rela == NULL) |
| { |
| error (0, 0, gettext ("\ |
| cannot get relocation at index %d in section %zu in '%s': %s"), |
| cnt, elf_ndxscn (scn), fname, elf_errmsg (-1)); |
| result = 1; |
| goto next; |
| } |
| |
| check_rel (nsegments, segments, rela->r_offset, elf, |
| symscn, dw, fname, more_than_one, &knownsrcs); |
| } |
| } |
| } |
| |
| dwarf_end (dw); |
| } |
| |
| next: |
| elf_end (elf); |
| elf_end (elf2); |
| close (fd); |
| if (fd2 != -1) |
| close (fd2); |
| |
| tdestroy (knownsrcs, noop); |
| |
| return result; |
| } |
| |
| |
| static int |
| ptrcompare (const void *p1, const void *p2) |
| { |
| if ((uintptr_t) p1 < (uintptr_t) p2) |
| return -1; |
| if ((uintptr_t) p1 > (uintptr_t) p2) |
| return 1; |
| return 0; |
| } |
| |
| |
| static void |
| check_rel (size_t nsegments, struct segments segments[nsegments], |
| GElf_Addr addr, Elf *elf, Elf_Scn *symscn, Dwarf *dw, |
| const char *fname, bool more_than_one, void **knownsrcs) |
| { |
| for (size_t cnt = 0; cnt < nsegments; ++cnt) |
| if (segments[cnt].from <= addr && segments[cnt].to > addr) |
| { |
| Dwarf_Die die_mem; |
| Dwarf_Die *die; |
| Dwarf_Line *line; |
| const char *src; |
| |
| if (more_than_one) |
| printf ("%s: ", fname); |
| |
| if ((die = dwarf_addrdie (dw, addr, &die_mem)) != NULL |
| && (line = dwarf_getsrc_die (die, addr)) != NULL |
| && (src = dwarf_linesrc (line, NULL, NULL)) != NULL) |
| { |
| /* There can be more than one relocation against one file. |
| Try to avoid multiple messages. And yes, the code uses |
| pointer comparison. */ |
| if (tfind (src, knownsrcs, ptrcompare) == NULL) |
| { |
| printf (gettext ("%s not compiled with -fpic/-fPIC\n"), src); |
| tsearch (src, knownsrcs, ptrcompare); |
| } |
| return; |
| } |
| else |
| { |
| /* At least look at the symbol table to see which function |
| the modified address is in. */ |
| Elf_Data *symdata = elf_getdata (symscn, NULL); |
| GElf_Shdr shdr_mem; |
| GElf_Shdr *shdr = gelf_getshdr (symscn, &shdr_mem); |
| if (shdr != NULL) |
| { |
| GElf_Addr lowaddr = 0; |
| int lowidx = -1; |
| GElf_Addr highaddr = ~0ul; |
| int highidx = -1; |
| GElf_Sym sym_mem; |
| GElf_Sym *sym; |
| |
| for (int i = 0; (size_t) i < shdr->sh_size / shdr->sh_entsize; |
| ++i) |
| { |
| sym = gelf_getsym (symdata, i, &sym_mem); |
| if (sym == NULL) |
| continue; |
| |
| if (sym->st_value < addr && sym->st_value > lowaddr) |
| { |
| lowaddr = sym->st_value; |
| lowidx = i; |
| } |
| if (sym->st_value > addr && sym->st_value < highaddr) |
| { |
| highaddr = sym->st_value; |
| highidx = i; |
| } |
| } |
| |
| if (lowidx != -1) |
| { |
| sym = gelf_getsym (symdata, lowidx, &sym_mem); |
| assert (sym != NULL); |
| |
| const char *lowstr = elf_strptr (elf, shdr->sh_link, |
| sym->st_name); |
| |
| if (sym->st_value + sym->st_size > addr) |
| { |
| /* It is this function. */ |
| if (tfind (lowstr, knownsrcs, ptrcompare) == NULL) |
| { |
| printf (gettext ("\ |
| the file containing the function '%s' is not compiled with -fpic/-fPIC\n"), |
| lowstr); |
| tsearch (lowstr, knownsrcs, ptrcompare); |
| } |
| } |
| else if (highidx == -1) |
| printf (gettext ("\ |
| the file containing the function '%s' might not be compiled with -fpic/-fPIC\n"), |
| lowstr); |
| else |
| { |
| sym = gelf_getsym (symdata, highidx, &sym_mem); |
| assert (sym != NULL); |
| |
| printf (gettext ("\ |
| either the file containing the function '%s' or the file containing the function '%s' is not compiled with -fpic/-fPIC\n"), |
| lowstr, elf_strptr (elf, shdr->sh_link, |
| sym->st_name)); |
| } |
| return; |
| } |
| else if (highidx != -1) |
| { |
| sym = gelf_getsym (symdata, highidx, &sym_mem); |
| assert (sym != NULL); |
| |
| printf (gettext ("\ |
| the file containing the function '%s' might not be compiled with -fpic/-fPIC\n"), |
| elf_strptr (elf, shdr->sh_link, sym->st_name)); |
| return; |
| } |
| } |
| } |
| |
| printf (gettext ("\ |
| a relocation modifies memory at offset %llu in a write-protected segment\n"), |
| (unsigned long long int) addr); |
| break; |
| } |
| } |
| |
| |
| #include "debugpred.h" |