summaryrefslogtreecommitdiff
path: root/src/findtextrel.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/findtextrel.c')
-rw-r--r--src/findtextrel.c596
1 files changed, 596 insertions, 0 deletions
diff --git a/src/findtextrel.c b/src/findtextrel.c
new file mode 100644
index 00000000..662fe95c
--- /dev/null
+++ b/src/findtextrel.c
@@ -0,0 +1,596 @@
+/* Locate source files or functions which caused text relocations.
+ Copyright (C) 2005 Red Hat, Inc.
+ Written by Ulrich Drepper <drepper@redhat.com>, 2005.
+
+ 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 <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, LOCALEDIR);
+
+ /* Initialize the message catalog. */
+ (void) textdomain (PACKAGE);
+
+ /* 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, 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\
+"), "2005");
+ 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;
+ }
+}