aboutsummaryrefslogtreecommitdiffstats
path: root/src/cxx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cxx.c')
-rw-r--r--src/cxx.c643
1 files changed, 643 insertions, 0 deletions
diff --git a/src/cxx.c b/src/cxx.c
new file mode 100644
index 0000000..4391ebe
--- /dev/null
+++ b/src/cxx.c
@@ -0,0 +1,643 @@
+/* Copyright (C) 2001, 2002, 2003, 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 <alloca.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"
+#include "reloc-info.h"
+
+static struct
+ {
+ const char *prefix;
+ unsigned char prefix_len, st_info, check_pltref;
+ }
+specials[] =
+ {
+ /* G++ 3.0 ABI. */
+ /* Virtual table. */
+ { "_ZTV", 4, GELF_ST_INFO (STB_WEAK, STT_OBJECT), 1 },
+ /* Typeinfo. */
+ { "_ZTI", 4, GELF_ST_INFO (STB_WEAK, STT_OBJECT), 0 },
+ /* G++ 2.96-RH ABI. */
+ /* Virtual table. */
+ { "__vt_", 5, GELF_ST_INFO (STB_WEAK, STT_OBJECT), 0 },
+ { NULL, 0, 0, 0 }
+ };
+
+struct find_cxx_sym_valsize
+{
+ GElf_Addr start;
+ GElf_Addr end;
+ unsigned int idx;
+ unsigned char mark;
+};
+
+struct find_cxx_sym_cache
+{
+ Elf_Data *symtab, *strtab;
+ int symsec, strsec, count;
+ struct find_cxx_sym_valsize vals[];
+};
+
+struct find_cxx_sym
+{
+ DSO *dso;
+ int n;
+ struct find_cxx_sym_cache *cache;
+ struct prelink_entry *ent;
+ Elf_Data *symtab, *strtab;
+ int symsec, strsec;
+ int lastndx;
+ GElf_Sym sym;
+};
+
+static int
+cachecmp (const void *a, const void *b)
+{
+ GElf_Addr va = ((const struct find_cxx_sym_valsize *) a)->start;
+ GElf_Addr vb = ((const struct find_cxx_sym_valsize *) b)->start;
+
+ if (va < vb)
+ return -1;
+ if (va > vb)
+ return 1;
+
+ va = ((const struct find_cxx_sym_valsize *) a)->end;
+ vb = ((const struct find_cxx_sym_valsize *) b)->end;
+
+ if (va < vb)
+ return -1;
+
+ return va > vb;
+}
+
+static struct find_cxx_sym_cache *
+create_cache (DSO *dso, int plt)
+{
+ Elf_Data *symtab, *strtab;
+ Elf_Scn *scn;
+ int symsec, strsec, ndx, dndx, maxndx;
+ struct find_cxx_sym_cache *cache;
+ GElf_Addr top;
+
+ symsec = addr_to_sec (dso, dso->info[DT_SYMTAB]);
+ if (symsec == -1)
+ return (struct find_cxx_sym_cache *) -1UL;
+ scn = dso->scn[symsec];
+ symtab = elf_getdata (scn, NULL);
+ assert (elf_getdata (scn, symtab) == NULL);
+ strsec = addr_to_sec (dso, dso->info[DT_STRTAB]);
+ if (strsec == -1)
+ return (struct find_cxx_sym_cache *) -1UL;
+ scn = dso->scn[strsec];
+ strtab = elf_getdata (scn, NULL);
+ assert (elf_getdata (scn, strtab) == NULL);
+ maxndx = symtab->d_size / dso->shdr[symsec].sh_entsize;
+
+ cache = malloc (sizeof (*cache) + sizeof (cache->vals[0]) * maxndx);
+ if (cache == NULL)
+ {
+ error (0, ENOMEM, "%s: Could load symbol table", dso->filename);
+ return NULL;
+ }
+
+ cache->symsec = symsec;
+ cache->strsec = strsec;
+ cache->symtab = symtab;
+ cache->strtab = strtab;
+ for (ndx = 0, dndx = 0; ndx < maxndx; ++ndx)
+ {
+ GElf_Sym sym;
+ const char *name;
+ int k;
+
+ gelfx_getsym (dso->elf, symtab, ndx, &sym);
+ if (plt)
+ {
+ if (sym.st_shndx != SHN_UNDEF || sym.st_value == 0)
+ continue;
+ }
+ else if (sym.st_shndx == SHN_UNDEF)
+ continue;
+ cache->vals[dndx].start = sym.st_value;
+ cache->vals[dndx].end = sym.st_value + sym.st_size;
+ cache->vals[dndx].idx = ndx;
+ cache->vals[dndx].mark = 0;
+ name = (const char *) strtab->d_buf + sym.st_name;
+ if (!plt && ELF32_ST_VISIBILITY (sym.st_other) == STV_DEFAULT)
+ for (k = 0; specials[k].prefix; ++k)
+ if (sym.st_info == specials[k].st_info
+ && strncmp (name, specials[k].prefix,
+ specials[k].prefix_len) == 0)
+ {
+ cache->vals[dndx].mark = 1;
+ break;
+ }
+ ++dndx;
+ }
+
+ maxndx = dndx;
+ qsort (cache->vals, maxndx, sizeof (cache->vals[0]), cachecmp);
+
+ if (!plt)
+ {
+ for (top = 0, ndx = 0; ndx < maxndx; ++ndx)
+ {
+ if (cache->vals[ndx].start < top
+ || (ndx < maxndx - 1
+ && cache->vals[ndx].end > cache->vals[ndx + 1].start))
+ cache->vals[ndx].mark = 0;
+ if (cache->vals[ndx].end > top)
+ top = cache->vals[ndx].end;
+ }
+
+ for (ndx = dndx = 0; ndx < maxndx; ++ndx)
+ if (cache->vals[ndx].mark)
+ cache->vals[dndx++] = cache->vals[ndx];
+ }
+ cache->count = dndx;
+ return cache;
+}
+
+static int
+find_cxx_sym (struct prelink_info *info, GElf_Addr addr,
+ struct find_cxx_sym *fcs, int reloc_size,
+ struct find_cxx_sym_cache **cache)
+{
+ int n, ndeps = info->ent->ndepends + 1;
+ unsigned int hi, lo, mid;
+ DSO *dso = NULL;
+ struct find_cxx_sym_cache *c;
+
+ if (fcs->dso == NULL
+ || addr < fcs->dso->base
+ || addr >= fcs->dso->end)
+ {
+ for (n = 1; n < ndeps; ++n)
+ {
+ dso = info->dsos[n];
+ if (addr >= dso->base
+ && addr < dso->end)
+ break;
+ }
+
+ if (n == ndeps
+ && addr >= info->dso->base
+ && addr < info->dso->end)
+ {
+ n = 0;
+ dso = info->dso;
+ }
+
+ assert (n < ndeps);
+
+ if (cache[n] == NULL)
+ {
+ cache[n] = create_cache (dso, 0);
+ if (cache[n] == NULL)
+ return -2;
+ }
+ if (cache[n] == (struct find_cxx_sym_cache *) -1UL)
+ return -1;
+
+ fcs->n = n;
+ fcs->ent = n ? info->ent->depends[n - 1] : info->ent;
+ fcs->dso = dso;
+ fcs->cache = cache[n];
+ fcs->symsec = fcs->cache->symsec;
+ fcs->symtab = fcs->cache->symtab;
+ fcs->strsec = fcs->cache->strsec;
+ fcs->strtab = fcs->cache->strtab;
+ fcs->lastndx = -1;
+ }
+ else
+ dso = fcs->dso;
+
+ c = fcs->cache;
+ lo = 0;
+ hi = c->count;
+ if (fcs->lastndx != -1)
+ {
+ if (c->vals[fcs->lastndx].start <= addr)
+ {
+ lo = fcs->lastndx;
+ if (hi - lo >= 16)
+ {
+ if (c->vals[lo + 2].start > addr)
+ hi = lo + 2;
+ else if (c->vals[lo + 15].start > addr)
+ hi = lo + 15;
+ }
+ }
+ else
+ {
+ hi = fcs->lastndx;
+ if (hi >= 15)
+ {
+ if (c->vals[hi - 2].start <= addr)
+ lo = hi - 2;
+ else if (c->vals[hi - 15].start <= addr)
+ lo = hi - 15;
+ }
+ }
+ }
+ while (lo < hi)
+ {
+ mid = (lo + hi) / 2;
+ if (c->vals[mid].start <= addr)
+ {
+ if (c->vals[mid].end >= addr + reloc_size)
+ {
+ gelfx_getsym (dso->elf, fcs->symtab, c->vals[mid].idx,
+ &fcs->sym);
+ fcs->lastndx = mid;
+ return c->vals[mid].idx;
+ }
+ lo = mid + 1;
+ }
+ else
+ hi = mid;
+ }
+
+ return -1;
+}
+
+/* The idea here is that C++ virtual tables are always emitted
+ in .gnu.linkonce.d.* sections as WEAK symbols and they
+ need to be the same.
+ We check if they are and if yes, remove conflicts against
+ virtual tables which will not be used. */
+
+int
+remove_redundant_cxx_conflicts (struct prelink_info *info)
+{
+ int i, j, k, n, o, state, removed = 0;
+ int ndx, sec;
+ unsigned int hi, lo, mid;
+ int reloc_type, reloc_size;
+ struct find_cxx_sym fcs1, fcs2;
+ char *mem1, *mem2;
+ const char *name = NULL, *secname = NULL;
+ GElf_Addr symtab_start;
+ GElf_Word symoff;
+ Elf_Data *binsymtab = NULL;
+ int binsymtabsec;
+ struct prelink_conflict *conflict;
+ struct find_cxx_sym_cache **cache;
+ struct find_cxx_sym_cache *binsymcache = NULL;
+ int ret = 0;
+ int rtype_class_valid;
+
+ /* Don't bother doing this for non-C++ programs. */
+ for (i = 0; i < info->ent->ndepends; ++i)
+ if (strstr (info->ent->depends[i]->canon_filename, "libstdc++"))
+ break;
+ if (i == info->ent->ndepends)
+ return 0;
+
+ binsymtabsec = addr_to_sec (info->dso, info->dso->info[DT_SYMTAB]);
+ if (binsymtabsec != -1)
+ {
+ Elf_Scn *scn = info->dso->scn[binsymtabsec];
+
+ binsymtab = elf_getdata (scn, NULL);
+ assert (elf_getdata (scn, binsymtab) == NULL);
+ }
+
+ rtype_class_valid = info->dso->arch->rtype_class_valid;
+
+ state = 0;
+ memset (&fcs1, 0, sizeof (fcs1));
+ memset (&fcs2, 0, sizeof (fcs2));
+ cache = alloca (sizeof (struct find_cxx_sym_cache *)
+ * (info->ent->ndepends + 1));
+ memset (cache, '\0', sizeof (struct find_cxx_sym_cache *)
+ * (info->ent->ndepends + 1));
+ for (i = 0; i < info->conflict_rela_size; ++i)
+ {
+ size_t cidx;
+
+ reloc_type = reloc_r_type (info->dso, info->conflict_rela[i].r_info);
+ reloc_size = info->dso->arch->reloc_size (reloc_type);
+
+ if (reloc_r_sym (info->dso, info->conflict_rela[i].r_info) != 0)
+ continue;
+
+ if (state
+ && fcs1.sym.st_value <= info->conflict_rela[i].r_offset
+ && fcs1.sym.st_value + fcs1.sym.st_size
+ >= info->conflict_rela[i].r_offset + reloc_size)
+ {
+ if (state == 3)
+ goto remove_noref;
+ if (state == 2)
+ goto check_pltref;
+ continue;
+ }
+
+ n = find_cxx_sym (info, info->conflict_rela[i].r_offset,
+ &fcs1, reloc_size, cache);
+
+ state = 0;
+ if (n == -1)
+ continue;
+ if (n == -2)
+ {
+ ret = 1;
+ goto out_free_cache;
+ }
+ state = 1;
+ sec = addr_to_sec (fcs1.dso, fcs1.sym.st_value);
+ if (sec == -1)
+ continue;
+ secname = strptr (fcs1.dso, fcs1.dso->ehdr.e_shstrndx,
+ fcs1.dso->shdr[sec].sh_name);
+ if (secname == NULL)
+ continue;
+
+ name = (const char *) fcs1.strtab->d_buf + fcs1.sym.st_name;
+
+ for (k = 0; specials[k].prefix; ++k)
+ if (ELF32_ST_VISIBILITY (fcs1.sym.st_other) == STV_DEFAULT
+ && fcs1.sym.st_info == specials[k].st_info
+ && strncmp (name, specials[k].prefix, specials[k].prefix_len) == 0)
+ break;
+
+ if (specials[k].prefix == NULL)
+ continue;
+
+ if (strcmp (secname, ".data") != 0
+ && strcmp (secname, ".data.rel.ro") != 0
+ && strcmp (secname, ".sdata") != 0)
+ continue;
+
+ if (specials[k].check_pltref)
+ state = 2;
+
+ symtab_start = fcs1.dso->shdr[fcs1.symsec].sh_addr - fcs1.dso->base;
+ symoff = symtab_start + n * fcs1.dso->shdr[fcs1.symsec].sh_entsize;
+
+ cidx = 0;
+ if (info->conflicts[fcs1.n].hash != &info->conflicts[fcs1.n].first)
+ cidx = symoff % 251;
+ for (conflict = info->conflicts[fcs1.n].hash[cidx]; conflict;
+ conflict = conflict->next)
+ if (conflict->symoff == symoff
+ && conflict->reloc_class == rtype_class_valid)
+ break;
+
+ if (conflict == NULL)
+ goto check_pltref;
+
+ if (conflict->conflict.ent != fcs1.ent
+ || fcs1.dso->base + conflict->conflictval != fcs1.sym.st_value)
+ goto check_pltref;
+
+ if (verbose > 4)
+ error (0, 0, "Possible C++ conflict removal from unreferenced table at %s:%s+%d",
+ fcs1.dso->filename, name,
+ (int) (info->conflict_rela[i].r_offset - fcs1.sym.st_value));
+
+ /* Limit size slightly. */
+ if (fcs1.sym.st_size > 16384)
+ goto check_pltref;
+
+ o = find_cxx_sym (info, conflict->lookup.ent->base + conflict->lookupval,
+ &fcs2, fcs1.sym.st_size, cache);
+
+ if (o == -2)
+ {
+ ret = 1;
+ goto out_free_cache;
+ }
+
+ if (o == -1
+ || fcs1.sym.st_size != fcs2.sym.st_size
+ || fcs1.sym.st_info != fcs2.sym.st_info
+ || ELF32_ST_VISIBILITY (fcs2.sym.st_other) != STV_DEFAULT
+ || strcmp (name, (char *) fcs2.strtab->d_buf + fcs2.sym.st_name) != 0)
+ goto check_pltref;
+
+ mem1 = malloc (fcs1.sym.st_size * 2);
+ if (mem1 == NULL)
+ {
+ error (0, ENOMEM, "%s: Could not compare %s arrays",
+ info->dso->filename, name);
+ ret = 1;
+ goto out_free_cache;
+ }
+
+ mem2 = mem1 + fcs1.sym.st_size;
+
+ if (get_relocated_mem (info, fcs1.dso, fcs1.sym.st_value, mem1,
+ fcs1.sym.st_size, 0)
+ || get_relocated_mem (info, fcs2.dso, fcs2.sym.st_value, mem2,
+ fcs1.sym.st_size, 0)
+ || memcmp (mem1, mem2, fcs1.sym.st_size) != 0)
+ {
+ free (mem1);
+ goto check_pltref;
+ }
+
+ free (mem1);
+
+ state = 3;
+
+remove_noref:
+ if (verbose > 3)
+ error (0, 0, "Removing C++ conflict from unreferenced table at %s:%s+%d",
+ fcs1.dso->filename, name,
+ (int) (info->conflict_rela[i].r_offset - fcs1.sym.st_value));
+
+ info->conflict_rela[i].r_info =
+ reloc_r_info (info->dso, 1,
+ reloc_r_type (info->dso, info->conflict_rela[i].r_info));
+ ++removed;
+ continue;
+
+check_pltref:
+ /* If the binary calls directly (or takes its address) one of the
+ methods in a virtual table, but doesn't define it, there is no
+ need to leave conflicts in the virtual table which will only
+ slow down the code (as it has to hop through binary's .plt
+ back to the method). */
+ if (state != 2
+ || info->conflict_rela[i].r_addend < info->dso->base
+ || info->conflict_rela[i].r_addend >= info->dso->end
+ || binsymtab == NULL)
+ continue;
+
+ if (binsymcache == NULL)
+ {
+ binsymcache = create_cache (info->dso, 1);
+ if (binsymcache == NULL)
+ {
+ ret = 1;
+ goto out_free_cache;
+ }
+ }
+ if (binsymcache == (struct find_cxx_sym_cache *) -1UL)
+ continue;
+
+ lo = 0;
+ mid = 0;
+ hi = binsymcache->count;
+ while (lo < hi)
+ {
+ mid = (lo + hi) / 2;
+ if (binsymcache->vals[mid].start < info->conflict_rela[i].r_addend)
+ lo = mid + 1;
+ else if (binsymcache->vals[mid].start
+ > info->conflict_rela[i].r_addend)
+ hi = mid;
+ else
+ break;
+ }
+ if (lo >= hi)
+ continue;
+
+ while (mid > 0 && binsymcache->vals[mid - 1].start
+ == info->conflict_rela[i].r_addend)
+ --mid;
+
+ while (mid < binsymcache->count
+ && binsymcache->vals[mid].start
+ == info->conflict_rela[i].r_addend)
+ {
+ GElf_Sym sym;
+
+ ndx = binsymcache->vals[mid].idx;
+ mid++;
+ gelfx_getsym (info->dso->elf, binsymtab, ndx, &sym);
+ assert (sym.st_value == info->conflict_rela[i].r_addend);
+ if (sym.st_shndx == SHN_UNDEF && sym.st_value)
+ {
+ struct prelink_symbol *s;
+ size_t maxidx, l;
+
+ if (verbose > 4)
+ error (0, 0, "Possible C++ conflict removal due to reference to binary's .plt at %s:%s+%d",
+ fcs1.dso->filename, name,
+ (int) (info->conflict_rela[i].r_offset
+ - fcs1.sym.st_value));
+
+ for (s = &info->symbols[ndx]; s; s = s->next)
+ if (s->reloc_class == RTYPE_CLASS_PLT)
+ break;
+
+ if (s == NULL)
+ break;
+
+ maxidx = 1;
+ if (info->conflicts[fcs1.n].hash
+ != &info->conflicts[fcs1.n].first)
+ {
+ if (info->conflicts[fcs1.n].hash2 == NULL)
+ {
+ info->conflicts[fcs1.n].hash2
+ = calloc (sizeof (struct prelink_conflict *), 251);
+ if (info->conflicts[fcs1.n].hash2 != NULL)
+ {
+ for (l = 0; l < 251; l++)
+ for (conflict = info->conflicts[fcs1.n].hash[l];
+ conflict; conflict = conflict->next)
+ if (conflict->reloc_class == rtype_class_valid
+ && conflict->conflict.ent)
+ {
+ size_t ccidx
+ = (conflict->lookup.ent->base
+ + conflict->lookupval) % 251;
+ conflict->next2
+ = info->conflicts[fcs1.n].hash2[ccidx];
+ info->conflicts[fcs1.n].hash2[ccidx]
+ = conflict;
+ }
+ }
+ }
+ if (info->conflicts[fcs1.n].hash2 != NULL)
+ {
+ size_t ccidx = info->conflict_rela[i].r_addend % 251;
+ for (conflict = info->conflicts[fcs1.n].hash2[ccidx];
+ conflict; conflict = conflict->next2)
+ if (conflict->lookup.ent->base + conflict->lookupval
+ == info->conflict_rela[i].r_addend
+ && (conflict->conflict.ent->base
+ + conflict->conflictval
+ == s->u.ent->base + s->value))
+ goto pltref_remove;
+ break;
+ }
+ maxidx = 251;
+ }
+
+ for (l = 0; l < maxidx; l++)
+ for (conflict = info->conflicts[fcs1.n].hash[l];
+ conflict; conflict = conflict->next)
+ if (conflict->lookup.ent->base + conflict->lookupval
+ == info->conflict_rela[i].r_addend
+ && conflict->conflict.ent
+ && (conflict->conflict.ent->base
+ + conflict->conflictval == s->u.ent->base + s->value)
+ && conflict->reloc_class == rtype_class_valid)
+ {
+pltref_remove:
+ if (verbose > 3)
+ error (0, 0, "Removing C++ conflict due to reference to binary's .plt at %s:%s+%d",
+ fcs1.dso->filename, name,
+ (int) (info->conflict_rela[i].r_offset
+ - fcs1.sym.st_value));
+
+ info->conflict_rela[i].r_info =
+ reloc_r_info (info->dso, 1, reloc_r_type (info->dso, info->conflict_rela[i].r_info));
+ ++removed;
+ goto pltref_check_done;
+ }
+
+pltref_check_done:
+ break;
+ }
+ }
+ }
+
+ if (removed)
+ {
+ for (i = 0, j = 0; i < info->conflict_rela_size; ++i)
+ if (reloc_r_sym (info->dso, info->conflict_rela[i].r_info) == 0)
+ {
+ if (i != j)
+ info->conflict_rela[j] = info->conflict_rela[i];
+ ++j;
+ }
+ info->conflict_rela_size = j;
+ }
+
+out_free_cache:
+ for (i = 0; i < info->ent->ndepends + 1; i++)
+ if (cache[i] && cache[i] != (struct find_cxx_sym_cache *) -1UL)
+ free (cache[i]);
+ if (binsymcache && binsymcache != (struct find_cxx_sym_cache *) -1UL)
+ free (binsymcache);
+ return ret;
+}