/* 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"