diff options
Diffstat (limited to 'src/get.c')
-rw-r--r-- | src/get.c | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/src/get.c b/src/get.c new file mode 100644 index 0000000..d10efcc --- /dev/null +++ b/src/get.c @@ -0,0 +1,760 @@ +/* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 Red Hat, Inc. + Written by Jakub Jelinek <jakub@redhat.com>, 2001. + + This program 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; either version 2, or (at your option) + any later version. + + This program 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 this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include <config.h> +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> +#include "prelink.h" + +int +is_ldso_soname (const char *soname) +{ + if (! strcmp (soname, "ld-linux.so.2") + || ! strcmp (soname, "ld.so.1") + || ! strcmp (soname, "ld-linux-ia64.so.2") + || ! strcmp (soname, "ld-linux-x86-64.so.2") + || ! strcmp (soname, "ld64.so.1") + || ! strcmp (soname, "ld-linux.so.3") + || ! strcmp (soname, "ld-linux-armhf.so.3")) + return 1; + return 0; +} + +static void +conflict_hash_init (struct prelink_conflicts *conflicts) +{ + struct prelink_conflict **hash + = calloc (sizeof (struct prelink_conflict *), 251); + struct prelink_conflict *conflict, *next; + size_t idx; + + if (hash == NULL) + return; + + for (conflict = conflicts->first; conflict; conflict = next) + { + next = conflict->next; + idx = conflict->symoff % 251; + conflict->next = hash[idx]; + hash[idx] = conflict; + } + conflicts->hash = hash; +} + +static int +prelink_record_relocations (struct prelink_info *info, FILE *f, + const char *ent_filename) +{ + char buffer[8192]; + DSO *dso = info->dso; + struct prelink_entry *ent, *ent2; + struct prelink_tls *tls; + struct deps + { + struct prelink_entry *ent; + char *soname; + GElf_Addr start; + GElf_Addr l_addr; + GElf_Addr tls_modid; + GElf_Addr tls_offset; + } deps[info->ent->ndepends + 1]; + char *r; + int i, ndeps = 0, undef = 0, seen = 0, tdeps = 0; + int mask_32bit = (info->dso->ehdr.e_ident[EI_CLASS] == ELFCLASS32); + + /* Record the dependencies. */ + while ((r = fgets (buffer, 8192, f)) != NULL) + { + char *soname, *filename, *p, *q; + GElf_Addr start = 0, l_addr = 0, tls_modid = 0, tls_offset = 0; + unsigned long long l; + + if (buffer[0] != '\t' || (filename = strstr (buffer, " => ")) == NULL) + break; + soname = buffer + 1; + p = strstr (filename + sizeof (" => "), " (0x"); + if (p != NULL) + { + l = strtoull (p + sizeof (" (0x") - 1, &q, 16); + start = (GElf_Addr) l; + if (start != l || strncmp (q, ", 0x", sizeof (", 0x") - 1)) + p = NULL; + else + { + l = strtoull (q + sizeof (", 0x") - 1, &q, 16); + l_addr = (GElf_Addr) l; + if (l_addr != l || q[-1] == 'x') + p = NULL; + else if (strncmp (q, ") TLS(0x", sizeof (") TLS(0x") - 1) == 0) + { + l = strtoull (q + sizeof (") TLS(0x") - 1, &q, 16); + tls_modid = (GElf_Addr) l; + if (tls_modid != l || q[-1] == 'x' + || strncmp (q, ", 0x", sizeof (", 0x") - 1)) + p = NULL; + else + { + l = strtoull (q + sizeof (", 0x") - 1, &q, 16); + tls_offset = (GElf_Addr) l; + if (tls_offset != l || q[-1] == 'x') + p = NULL; + } + } + if (p && strcmp (q, ")\n")) + p = NULL; + } + } + if (p == NULL) + { + p = strchr (buffer, '\n'); + if (p != NULL) + *p = '\0'; + error (0, 0, "Could not parse line `%s'", buffer); + goto error_out; + } + *filename = '\0'; + filename += sizeof (" => ") - 1; + *p = '\0'; + + if (ndeps > info->ent->ndepends) + { + error (0, 0, "%s: Recorded %d dependencies, now seeing %d\n", + info->ent->filename, info->ent->ndepends, ndeps - 1); + goto error_out; + } + + tdeps = ndeps - seen + 1; + if (! seen + && (strcmp (info->ent->filename, filename) == 0 + || (info->ent->filename != ent_filename + && strcmp (ent_filename, filename) == 0) + || strcmp (info->ent->canon_filename, filename) == 0)) + { + seen = 1; + tdeps = 0; + } + else if (ent2 = info->ent->depends [tdeps - 1], + strcmp (ent2->filename, filename) != 0 + && strcmp (ent2->canon_filename, filename) != 0) + { + struct prelink_link *hardlink; + + for (hardlink = ent2->hardlink; hardlink; hardlink = hardlink->next) + if (strcmp (hardlink->canon_filename, filename) == 0) + break; + + if (hardlink == NULL) + { + struct stat64 st; + + if (wrap_stat64 (filename, &st) < 0) + { + error (0, errno, "%s: Could not stat %s", + info->ent->filename, filename); + goto error_out; + } + + if (st.st_dev != ent2->dev || st.st_ino != ent2->ino) + { + error (0, 0, "%s: %s => %s does not match recorded dependency", + info->ent->filename, soname, filename); + goto error_out; + } + } + } + + if (! tdeps) + deps[0].ent = info->ent; + else + deps[tdeps].ent = info->ent->depends[tdeps - 1]; + deps[tdeps].soname = strdup (soname); + if (deps[tdeps].soname == NULL) + { + error (0, ENOMEM, "Could not record `%s' SONAME", soname); + goto error_out; + } + deps[tdeps].start = start; + deps[tdeps].l_addr = l_addr; + deps[tdeps].tls_modid = tls_modid; + deps[tdeps].tls_offset = tls_offset; + ++ndeps; + } + + if (ndeps != info->ent->ndepends + 1) + { + error (0, 0, "%s: Recorded %d dependencies, now seeing %d\n", + info->ent->filename, info->ent->ndepends, ndeps - 1); + goto error_out; + } + + if (r == NULL && !ndeps) + { + error (0, 0, "%s: %s did not print any lookup lines", info->ent->filename, + dynamic_linker ?: dso->arch->dynamic_linker); + goto error_out; + } + + info->tls = malloc (ndeps * sizeof (struct prelink_tls)); + if (info->tls == NULL) + { + error (0, ENOMEM, "%s: Could not record dependency TLS information", + dso->filename); + goto error_out; + } + + for (i = 0; i < ndeps; i++) + { + info->tls[i].modid = deps[i].tls_modid; + info->tls[i].offset = deps[i].tls_offset; + } + + if (dso->ehdr.e_type == ET_EXEC || dso->arch->create_opd) + { + info->conflicts = (struct prelink_conflicts *) + calloc (sizeof (struct prelink_conflicts), ndeps); + if (info->conflicts == NULL) + { + error (0, ENOMEM, "%s: Can't build list of conflicts", info->ent->filename); + goto error_out; + } + for (i = 0; i < ndeps; i++) + info->conflicts[i].hash = &info->conflicts[i].first; + } + do + { + unsigned long long symstart, symoff, valstart[3], value[3]; + int reloc_class, len, type = 1, ifunc = 0; + char *symname; + + r = strchr (buffer, '\n'); + if (r) + *r = '\0'; + if (strncmp (buffer, "lookup ", sizeof ("lookup ") - 1) == 0) + { + struct prelink_symbol *s; + + if (sscanf (buffer, "lookup 0x%llx 0x%llx -> 0x%llx 0x%llx %n", + &symstart, &symoff, &valstart[0], &value[0], &len) != 4) + { + error (0, 0, "%s: Could not parse `%s'", info->ent->filename, buffer); + goto error_out; + } + + if (buffer[len] == '/') + { + ++len; + type = 0; + } + + reloc_class = strtoul (buffer + len, &symname, 16); + if (buffer + len == symname || (reloc_class == 0 && type) + || (*symname != ' ' && *symname != '\t')) + { + error (0, 0, "%s: Could not parse `%s'", info->ent->filename, buffer); + goto error_out; + } + + if (type) + reloc_class = dso->arch->reloc_class (reloc_class); + else + { + if (reloc_class & 8) + { + reloc_class = ((reloc_class & ~8) + | dso->arch->rtype_class_valid); + ifunc = 1; + } + else if ((reloc_class | RTYPE_CLASS_VALID) == RTYPE_CLASS_TLS) + reloc_class |= RTYPE_CLASS_VALID; + else + reloc_class |= dso->arch->rtype_class_valid; + } + + while (*symname == ' ' || *symname == '\t') ++symname; + + ent = NULL; + tls = NULL; + if (symstart == deps[0].start + || ((reloc_class == RTYPE_CLASS_TLS || ifunc) + && info->conflicts)) + { + for (i = 0; i < ndeps; i++) + if (deps[i].start == valstart[0]) + { + if (reloc_class == RTYPE_CLASS_TLS) + tls = info->tls + i; + else + { + ent = deps[i].ent; + /* If the library the symbol is bound to is already + prelinked, adjust the value so that it is relative + to library base. */ + if (mask_32bit) + value[0] -= (Elf32_Addr) (deps[i].start - deps[i].l_addr); + else + value[0] -= deps[i].start - deps[i].l_addr; + } + break; + } + + if (ent == NULL && tls == NULL && valstart[0]) + { + error (0, 0, "Could not find base 0x%08llx in the list of bases `%s'", + valstart[0], buffer); + goto error_out; + } + } + + if (symstart == deps[0].start && (!ifunc || info->conflicts == NULL)) + { + /* Only interested in relocations from the current object. */ + if (symoff < info->symtab_start || symoff >= info->symtab_end) + { + error (0, 0, "%s: Symbol `%s' offset 0x%08llx does not point into .dynsym section", + info->ent->filename, symname, symoff); + goto error_out; + } + + if (ent == info->ent + && reloc_class != RTYPE_CLASS_TLS) + value[0] = adjust_old_to_new (info->dso, value[0]); + + s = &info->symbols[(symoff - info->symtab_start) + / info->symtab_entsize]; + if (s->reloc_class) + { + while (s->reloc_class != reloc_class && s->next != NULL) + s = s->next; + if (s->reloc_class == reloc_class) + { + if ((reloc_class != RTYPE_CLASS_TLS && s->u.ent != ent) + || (reloc_class == RTYPE_CLASS_TLS + && s->u.tls != tls) + || s->value != value[0]) + { + error (0, 0, "%s: Symbol `%s' with the same reloc type resolves to different values each time", + info->ent->filename, symname); + goto error_out; + } + s = NULL; + } + else + { + s->next = (struct prelink_symbol *) + malloc (sizeof (struct prelink_symbol)); + if (s->next == NULL) + { + error (0, ENOMEM, "Cannot build symbol lookup map"); + goto error_out; + } + s = s->next; + } + } + if (s) + { + if (reloc_class == RTYPE_CLASS_TLS) + s->u.tls = tls; + else + s->u.ent = ent; + s->value = value[0]; + s->reloc_class = reloc_class; + s->next = NULL; + } + } + else if ((reloc_class == RTYPE_CLASS_TLS || ifunc) + && info->conflicts) + { + struct prelink_conflict *conflict; + int symowner; + size_t idx; + + for (symowner = 0; symowner < ndeps; symowner++) + if (deps[symowner].start == symstart) + break; + if (symowner == ndeps) + { + error (0, 0, "Could not find base 0x%08llx in the list of bases `%s'", + symstart, buffer); + goto error_out; + } + + idx = 0; + if (info->conflicts[symowner].hash != &info->conflicts[symowner].first) + idx = symoff % 251; + for (conflict = info->conflicts[symowner].hash[idx]; conflict; + conflict = conflict->next) + if (conflict->symoff == symoff + && conflict->reloc_class == reloc_class) + { + if ((reloc_class != RTYPE_CLASS_TLS + && (conflict->lookup.ent != ent + || conflict->conflict.ent != ent)) + || (reloc_class == RTYPE_CLASS_TLS + && (conflict->lookup.tls != tls + || conflict->conflict.tls != tls)) + || conflict->lookupval != value[0] + || conflict->conflictval != value[0]) + { + error (0, 0, "%s: Symbol `%s' with the same reloc type resolves to different values each time", + info->ent->filename, symname); + goto error_out; + } + break; + } + if (conflict == NULL) + { + conflict = malloc (sizeof (struct prelink_conflict)); + if (conflict == NULL) + { + error (0, ENOMEM, "Cannot build list of conflicts"); + goto error_out; + } + + conflict->next = info->conflicts[symowner].hash[idx]; + conflict->next2 = NULL; + info->conflicts[symowner].hash[idx] = conflict; + if (reloc_class != RTYPE_CLASS_TLS) + { + conflict->lookup.ent = ent; + conflict->conflict.ent = ent; + } + else + { + conflict->lookup.tls = tls; + conflict->conflict.tls = tls; + } + conflict->lookupval = value[0]; + conflict->conflictval = value[0]; + conflict->symoff = symoff; + conflict->reloc_class = reloc_class; + conflict->used = 0; + conflict->ifunc = ifunc; + conflict->symname = strdup(symname); + if (++info->conflicts[symowner].count == 16) + conflict_hash_init (&info->conflicts[symowner]); + } + } + } + else if (strncmp (buffer, "conflict ", sizeof ("conflict ") - 1) == 0) + { + if (sscanf (buffer, "conflict 0x%llx 0x%llx -> 0x%llx 0x%llx x 0x%llx 0x%llx %n", + &symstart, &symoff, &valstart[0], &value[0], + &valstart[1], &value[1], &len) != 6) + { + error (0, 0, "%s: Could not parse `%s'", info->ent->filename, buffer); + goto error_out; + } + + if (buffer[len] == '/') + { + ++len; + type = 0; + } + + reloc_class = strtoul (buffer + len, &symname, 16); + if (buffer + len == symname || (reloc_class == 0 && type) + || (*symname != ' ' && *symname != '\t')) + { + error (0, 0, "%s: Could not parse `%s'", info->ent->filename, buffer); + goto error_out; + } + + if (type) + reloc_class = dso->arch->reloc_class (reloc_class); + else + { + if (reloc_class & 8) + { + reloc_class = ((reloc_class & ~8) + | dso->arch->rtype_class_valid); + ifunc = 1; + } + else if ((reloc_class | RTYPE_CLASS_VALID) == RTYPE_CLASS_TLS) + reloc_class |= RTYPE_CLASS_VALID; + else + reloc_class |= dso->arch->rtype_class_valid; + } + + while (*symname == ' ' || *symname == '\t') ++symname; + + if (symstart == deps[0].start) + { + error (0, 0, "Conflict in _dl_loaded `%s'", buffer); + goto error_out; + } + + if (info->conflicts) + { + struct prelink_entry *ents[2]; + struct prelink_tls *tlss[2]; + struct prelink_conflict *conflict; + int symowner, j; + size_t idx; + + for (symowner = 1; symowner < ndeps; symowner++) + if (deps[symowner].start == symstart) + break; + if (symowner == ndeps) + { + error (0, 0, "Could not find base 0x%08llx in the list of bases `%s'", + symstart, buffer); + goto error_out; + } + + for (j = 0; j < 2; j++) + { + ents[j] = NULL; + tlss[j] = NULL; + for (i = 0; i < ndeps; i++) + if (deps[i].start == valstart[j]) + { + if (reloc_class == RTYPE_CLASS_TLS) + tlss[j] = info->tls + i; + else + { + ents[j] = deps[i].ent; + /* If the library the symbol is bound to is already + prelinked, adjust the value so that it is relative + to library base. */ + if (mask_32bit) + value[j] -= (Elf32_Addr) (deps[i].start - deps[i].l_addr); + else + value[j] -= deps[i].start - deps[i].l_addr; + } + break; + } + if (ents[j] == NULL && tlss[j] == NULL && valstart[j]) + { + error (0, 0, "Could not find base 0x%08llx in the list of bases `%s'", + valstart[j], buffer); + goto error_out; + } + } + + idx = 0; + if (info->conflicts[symowner].hash + != &info->conflicts[symowner].first) + idx = symoff % 251; + for (conflict = info->conflicts[symowner].hash[idx]; conflict; + conflict = conflict->next) + if (conflict->symoff == symoff + && conflict->reloc_class == reloc_class) + { + if ((reloc_class != RTYPE_CLASS_TLS + && (conflict->lookup.ent != ents[0] + || conflict->conflict.ent != ents[1])) + || (reloc_class == RTYPE_CLASS_TLS + && (conflict->lookup.tls != tlss[0] + || conflict->conflict.tls != tlss[1])) + || conflict->lookupval != value[0] + || conflict->conflictval != value[1]) + { + error (0, 0, "%s: Symbol `%s' with the same reloc type resolves to different values each time", + info->ent->filename, symname); + goto error_out; + } + break; + } + if (conflict == NULL) + { + conflict = malloc (sizeof (struct prelink_conflict)); + if (conflict == NULL) + { + error (0, ENOMEM, "Cannot build list of conflicts"); + goto error_out; + } + + conflict->next = info->conflicts[symowner].hash[idx]; + conflict->next2 = NULL; + info->conflicts[symowner].hash[idx] = conflict; + if (reloc_class != RTYPE_CLASS_TLS) + { + conflict->lookup.ent = ents[0]; + conflict->conflict.ent = ents[1]; + } + else + { + conflict->lookup.tls = tlss[0]; + conflict->conflict.tls = tlss[1]; + } + conflict->lookupval = value[0]; + conflict->conflictval = value[1]; + conflict->symoff = symoff; + conflict->reloc_class = reloc_class; + conflict->used = 0; + conflict->ifunc = ifunc; + conflict->symname = strdup(symname); + if (++info->conflicts[symowner].count == 16) + conflict_hash_init (&info->conflicts[symowner]); + } + } + } + else if (strncmp (buffer, "undefined symbol: ", + sizeof ("undefined symbol: ") - 1) == 0 && ! undef) + { + undef = 1; + if (verbose) + error (0, 0, "Warning: %s has undefined non-weak symbols", + info->ent->filename); + } + } while (fgets (buffer, 8192, f) != NULL); + + info->sonames = malloc (ndeps * sizeof (const char *)); + if (info->sonames == NULL) + { + error (0, ENOMEM, "%s: Could not record dependency SONAMEs", dso->filename); + goto error_out; + } + + for (i = 0; i < ndeps; i++) + info->sonames[i] = deps[i].soname; + + return 0; + +error_out: + for (i = 0; i < ndeps; i++) + free (deps[i].soname); + return 1; +} + +int +prelink_get_relocations (struct prelink_info *info) +{ + FILE *f; + DSO *dso = info->dso; + const char *argv[8]; + const char *envp[4]; + int i, ret, status; + char *p; + const char *dl = dynamic_linker ?: dso->arch->dynamic_linker; + const char *ent_filename; + int etype = info->dso->ehdr.e_type; + + if (info->ent->type == ET_DYN) + { + assert (info->ent->base == dso->base); + if (info->ent->end < dso->end) + { + error (0, 0, "%s: grew since it has been recorded", info->ent->filename); + return 0; + } + } + else + { + info->ent->base = dso->base; + info->ent->end = dso->end; + } + + if (is_ldso_soname (info->dso->soname)) + return 1; + + info->symbol_count = (info->symtab_end - info->symtab_start) + / info->symtab_entsize; + info->symbols = calloc (sizeof (struct prelink_symbol), info->symbol_count); + + if (strchr (info->ent->filename, '/') != NULL) + ent_filename = info->ent->filename; + else + { + size_t flen = strlen (info->ent->filename); + char *p = alloca (2 + flen + 1); + memcpy (p, "./", 2); + memcpy (p + 2, info->ent->filename, flen + 1); + ent_filename = p; + } + if (prelink_rtld == NULL) + { + i = 0; + argv[i++] = dl; + if (ld_library_path) + { + argv[i++] = "--library-path"; + argv[i++] = ld_library_path; + } + argv[i++] = ent_filename; + argv[i] = NULL; + envp[0] = "LD_TRACE_LOADED_OBJECTS=1"; + envp[1] = "LD_BIND_NOW=1"; + p = alloca (sizeof "LD_TRACE_PRELINKING=" + strlen (info->ent->filename)); + strcpy (stpcpy (p, "LD_TRACE_PRELINKING="), info->ent->filename); + envp[2] = p; + envp[3] = NULL; + ret = 2; + f = execve_open (dl, (char * const *)argv, (char * const *)envp); + } + else + { + i = 0; + argv[i++] = prelink_rtld; + if (ld_library_path) + { + argv[i++] = "--library-path"; + argv[i++] = ld_library_path; + } + + if(etype == ET_EXEC && ld_preload) { + argv[i++] = "--ld-preload"; + argv[i++] = ld_preload; + } + + argv[i++] = "--target-paths"; + argv[i++] = ent_filename; + argv[i] = NULL; + p = alloca (sizeof "RTLD_TRACE_PRELINKING=" + strlen (info->ent->filename)); + strcpy (stpcpy (p, "RTLD_TRACE_PRELINKING="), info->ent->filename); + envp[0] = p; + p = alloca (sizeof "PATH=" + strlen (getenv ("PATH"))); + sprintf (p, "PATH=%s", getenv ("PATH")); + envp[1] = p; + envp[2] = NULL; + if (sysroot) + { + p = alloca (sizeof "PRELINK_SYSROOT=" + strlen (sysroot)); + sprintf (p, "PRELINK_SYSROOT=%s", sysroot); + envp[2] = p; + envp[3] = NULL; + } + ret = 2; + f = execve_open (prelink_rtld, (char * const *)argv, (char * const *)envp); + } + + if (f == NULL) + { + error (0, errno, "%s: Could not trace symbol resolving", + info->ent->filename); + return 0; + } + + if (prelink_record_relocations (info, f, ent_filename)) + ret = 0; + + if ((status = execve_close (f))) + { + if (ret) + error (0, status == -1 ? errno : 0, + "%s Could not trace symbol resolving", info->ent->filename); + return 0; + } + + return ret; +} |