diff options
Diffstat (limited to 'src/findtextrel.c')
-rw-r--r-- | src/findtextrel.c | 596 |
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; + } +} |