summaryrefslogtreecommitdiffstats
path: root/trunk/src/cxx.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/cxx.c')
-rw-r--r--trunk/src/cxx.c377
1 files changed, 377 insertions, 0 deletions
diff --git a/trunk/src/cxx.c b/trunk/src/cxx.c
new file mode 100644
index 0000000..b3530f0
--- /dev/null
+++ b/trunk/src/cxx.c
@@ -0,0 +1,377 @@
+/* Copyright (C) 2001, 2002, 2003 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"
+
+struct find_cxx_sym
+{
+ DSO *dso;
+ int n;
+ struct prelink_entry *ent;
+ Elf_Data *symtab, *strtab;
+ int symsec, strsec;
+ GElf_Sym sym;
+};
+
+static int
+find_cxx_sym (struct prelink_info *info, GElf_Addr addr,
+ struct find_cxx_sym *fcs, int reloc_size)
+{
+ int n, ndeps = info->ent->ndepends + 1;
+ int ndx, maxndx;
+ DSO *dso = NULL;
+ Elf_Scn *scn;
+
+ 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);
+ fcs->n = n;
+ fcs->ent = n ? info->ent->depends[n - 1] : info->ent;
+ fcs->dso = dso;
+ fcs->symsec = addr_to_sec (dso, dso->info[DT_SYMTAB]);
+ if (fcs->symsec == -1)
+ {
+ fcs->ent = NULL;
+ return -1;
+ }
+ scn = dso->scn[fcs->symsec];
+ fcs->symtab = elf_getdata (scn, NULL);
+ assert (elf_getdata (scn, fcs->symtab) == NULL);
+ fcs->strsec = addr_to_sec (dso, dso->info[DT_STRTAB]);
+ if (fcs->strsec == -1)
+ {
+ fcs->ent = NULL;
+ return -1;
+ }
+ scn = dso->scn[fcs->strsec];
+ fcs->strtab = elf_getdata (scn, NULL);
+ assert (elf_getdata (scn, fcs->strtab) == NULL);
+ }
+ else
+ dso = fcs->dso;
+
+ maxndx = fcs->symtab->d_size / dso->shdr[fcs->symsec].sh_entsize;
+ for (ndx = 0; ndx < maxndx; ++ndx)
+ {
+ gelfx_getsym (dso->elf, fcs->symtab, ndx, &fcs->sym);
+ if (fcs->sym.st_value <= addr
+ && fcs->sym.st_value + fcs->sym.st_size >= addr + reloc_size)
+ break;
+ }
+
+ if (ndx == maxndx)
+ return -1;
+
+ return ndx;
+}
+
+/* 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, maxndx, sec;
+ 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;
+ static struct
+ {
+ unsigned char *prefix;
+ unsigned char prefix_len, st_info, check_pltref;
+ unsigned char *section;
+ }
+ specials[] =
+ {
+ /* G++ 3.0 ABI. */
+ /* Virtual table. */
+ { "_ZTV", 4, GELF_ST_INFO (STB_WEAK, STT_OBJECT), 1, ".data" },
+ /* Typeinfo. */
+ { "_ZTI", 4, GELF_ST_INFO (STB_WEAK, STT_OBJECT), 0, ".data" },
+ /* G++ 2.96-RH ABI. */
+ /* Virtual table. */
+ { "__vt_", 5, GELF_ST_INFO (STB_WEAK, STT_OBJECT), 0, ".data" },
+ { NULL, 0, 0, 0, NULL }
+ };
+
+ /* 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);
+ }
+
+ state = 0;
+ memset (&fcs1, 0, sizeof (fcs1));
+ memset (&fcs2, 0, sizeof (fcs2));
+ for (i = 0; i < info->conflict_rela_size; ++i)
+ {
+ reloc_type = GELF_R_TYPE (info->conflict_rela[i].r_info);
+ reloc_size = info->dso->arch->reloc_size (reloc_type);
+
+ if (GELF_R_SYM (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);
+
+ name = (const char *) fcs1.strtab->d_buf + fcs1.sym.st_name;
+ state = 0;
+ if (n == -1)
+ continue;
+ 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;
+
+ 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
+ && strcmp (secname, specials[k].section) == 0)
+ break;
+
+ if (specials[k].prefix == NULL)
+ continue;
+
+ /* Now check there are no other symbols pointing to it. */
+ maxndx = fcs1.symtab->d_size / fcs1.dso->shdr[fcs1.symsec].sh_entsize;
+ for (ndx = 0; ndx < maxndx; ++ndx)
+ if (ndx != n)
+ {
+ GElf_Sym sym;
+
+ gelfx_getsym (fcs1.dso->elf, fcs1.symtab, ndx, &sym);
+ if ((sym.st_value + sym.st_size > fcs1.sym.st_value
+ && sym.st_value < fcs1.sym.st_value + fcs1.sym.st_size)
+ || sym.st_value == fcs1.sym.st_value)
+ break;
+ }
+
+ if (ndx < maxndx)
+ 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;
+
+ for (conflict = info->conflicts[fcs1.n]; 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);
+
+ 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);
+ return 1;
+ }
+
+ mem2 = mem1 + fcs1.sym.st_size;
+
+ if (get_relocated_mem (info, fcs1.dso, fcs1.sym.st_value, mem1,
+ fcs1.sym.st_size)
+ || get_relocated_mem (info, fcs2.dso, fcs2.sym.st_value, mem2,
+ fcs1.sym.st_size)
+ || 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 =
+ GELF_R_INFO (1, GELF_R_TYPE (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;
+
+ maxndx = binsymtab->d_size / info->dso->shdr[binsymtabsec].sh_entsize;
+ for (ndx = 0; ndx < maxndx; ++ndx)
+ {
+ GElf_Sym sym;
+
+ gelfx_getsym (info->dso->elf, binsymtab, ndx, &sym);
+ if (sym.st_value == info->conflict_rela[i].r_addend)
+ {
+ if (sym.st_shndx == SHN_UNDEF && sym.st_value)
+ {
+ struct prelink_symbol *s;
+
+ 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)
+ {
+ for (conflict = info->conflicts[fcs1.n]; 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)
+ {
+ 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 =
+ GELF_R_INFO (1, GELF_R_TYPE (info->conflict_rela[i].r_info));
+ ++removed;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (removed)
+ {
+ for (i = 0, j = 0; i < info->conflict_rela_size; ++i)
+ if (GELF_R_SYM (info->conflict_rela[i].r_info) == 0)
+ {
+ if (i != j)
+ info->conflict_rela[j] = info->conflict_rela[i];
+ ++j;
+ }
+ info->conflict_rela_size = j;
+ }
+
+ return 0;
+}