summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--trunk/ChangeLog22
-rw-r--r--trunk/src/cxx.c401
-rw-r--r--trunk/testsuite/Makefile.am2
-rw-r--r--trunk/testsuite/Makefile.in2
-rwxr-xr-xtrunk/testsuite/cxx1.sh7
-rw-r--r--trunk/testsuite/cxx2.C51
-rwxr-xr-xtrunk/testsuite/cxx2.sh19
7 files changed, 389 insertions, 115 deletions
diff --git a/trunk/ChangeLog b/trunk/ChangeLog
index 2f854bd..4134722 100644
--- a/trunk/ChangeLog
+++ b/trunk/ChangeLog
@@ -1,3 +1,25 @@
+2007-10-08 Jakub Jelinek <jakub@redhat.com>
+
+ * src/cxx.c: Include alloca.h.
+ (specials): Moved out of remove_redundant_cxx_conflicts to toplevel.
+ Remove section fields.
+ (find_cxx_sym_valsize, find_cxx_sym_cache): New structs.
+ (struct find_cxx_sym): Add cache and lastndx fields.
+ (cachecmp, create_cache): New functions.
+ (find_cxx_sym): Add cache argument. If cache[n] is NULL, call
+ create_cache. Do a binary search in cache[n]->vals array instead
+ of always reading the whole symbol table.
+ (remove_redundant_cxx_conflicts): Adjust find_cxx_sym caller.
+ Check if secname is ".data" or ".data.rel.ro" instead of comparing
+ it against specials[k].secname. Use binary search in binsymcache
+ when doing check_pltref checks.
+ * testsuite/Makefile.am (TESTS): Add cxx2.sh.
+ * testsuite/Makefile.in: Regenerated.
+ * testsuite/cxx1.sh: Check that some conflicts are optimized out
+ by C++ optimizations.
+ * testsuite/cxx2.sh: New test.
+ * testsuite/cxx2.C: New.
+
2007-10-04 Jakub Jelinek <jakub@redhat.com>
* src/prelink.h (read_config): New prototype.
diff --git a/trunk/src/cxx.c b/trunk/src/cxx.c
index 738431b..b619b26 100644
--- a/trunk/src/cxx.c
+++ b/trunk/src/cxx.c
@@ -16,6 +16,7 @@
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>
@@ -26,24 +27,168 @@
#include <sys/wait.h>
#include "prelink.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 *fcs, int reloc_size,
+ struct find_cxx_sym_cache **cache)
{
int n, ndeps = info->ent->ndepends + 1;
- int ndx, maxndx;
+ unsigned int hi, lo, mid;
DSO *dso = NULL;
- Elf_Scn *scn;
+ struct find_cxx_sym_cache *c;
if (fcs->dso == NULL
|| addr < fcs->dso->base
@@ -66,44 +211,51 @@ find_cxx_sym (struct prelink_info *info, GElf_Addr addr,
}
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->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);
+ 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;
- maxndx = fcs->symtab->d_size / dso->shdr[fcs->symsec].sh_entsize;
- for (ndx = 0; ndx < maxndx; ++ndx)
+ c = fcs->cache;
+ lo = 0;
+ hi = c->count;
+ while (lo < hi)
{
- 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;
+ 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;
}
- if (ndx == maxndx)
- return -1;
-
- return ndx;
+ return -1;
}
/* The idea here is that C++ virtual tables are always emitted
@@ -116,7 +268,8 @@ int
remove_redundant_cxx_conflicts (struct prelink_info *info)
{
int i, j, k, n, o, state, removed = 0;
- int ndx, maxndx, sec;
+ int ndx, sec;
+ unsigned int hi, lo, mid;
int reloc_type, reloc_size;
struct find_cxx_sym fcs1, fcs2;
char *mem1, *mem2;
@@ -126,24 +279,9 @@ remove_redundant_cxx_conflicts (struct prelink_info *info)
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 }
- };
+ struct find_cxx_sym_cache **cache;
+ struct find_cxx_sym_cache *binsymcache = NULL;
+ int ret = 0;
/* Don't bother doing this for non-C++ programs. */
for (i = 0; i < info->ent->ndepends; ++i)
@@ -164,6 +302,10 @@ remove_redundant_cxx_conflicts (struct prelink_info *info)
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)
{
reloc_type = GELF_R_TYPE (info->conflict_rela[i].r_info);
@@ -185,11 +327,16 @@ remove_redundant_cxx_conflicts (struct prelink_info *info)
}
n = find_cxx_sym (info, info->conflict_rela[i].r_offset,
- &fcs1, reloc_size);
+ &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)
@@ -204,28 +351,14 @@ remove_redundant_cxx_conflicts (struct prelink_info *info)
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)
+ && strncmp (name, specials[k].prefix, specials[k].prefix_len) == 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)
+ if (strcmp (secname, ".data") != 0
+ && strcmp (secname, ".data.rel.ro") != 0)
continue;
if (specials[k].check_pltref)
@@ -257,7 +390,13 @@ remove_redundant_cxx_conflicts (struct prelink_info *info)
goto check_pltref;
o = find_cxx_sym (info, conflict->lookup.ent->base + conflict->lookupval,
- &fcs2, fcs1.sym.st_size);
+ &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
@@ -271,7 +410,8 @@ remove_redundant_cxx_conflicts (struct prelink_info *info)
{
error (0, ENOMEM, "%s: Could not compare %s arrays",
info->dso->filename, name);
- return 1;
+ ret = 1;
+ goto out_free_cache;
}
mem2 = mem1 + fcs1.sym.st_size;
@@ -313,50 +453,85 @@ check_pltref:
|| binsymtab == NULL)
continue;
- maxndx = binsymtab->d_size / info->dso->shdr[binsymtabsec].sh_entsize;
- for (ndx = 0; ndx < maxndx; ++ndx)
+ 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);
- if (sym.st_value == info->conflict_rela[i].r_addend)
+ assert (sym.st_value == info->conflict_rela[i].r_addend);
+ if (sym.st_shndx == SHN_UNDEF && sym.st_value)
{
- 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;
- }
- }
+ 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;
+ }
break;
}
}
@@ -374,5 +549,11 @@ check_pltref:
info->conflict_rela_size = j;
}
- return 0;
+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;
}
diff --git a/trunk/testsuite/Makefile.am b/trunk/testsuite/Makefile.am
index 3af929b..e378e3b 100644
--- a/trunk/testsuite/Makefile.am
+++ b/trunk/testsuite/Makefile.am
@@ -12,7 +12,7 @@ TESTS = movelibs.sh \
shuffle6.sh shuffle7.sh shuffle8.sh undo1.sh \
layout1.sh layout2.sh \
tls1.sh tls2.sh tls3.sh tls4.sh tls5.sh tls6.sh tls7.sh \
- cxx1.sh quick1.sh quick2.sh quick3.sh cycle1.sh cycle2.sh \
+ cxx1.sh cxx2.sh quick1.sh quick2.sh quick3.sh cycle1.sh cycle2.sh \
deps1.sh deps2.sh \
undosyslibs.sh
TESTS_ENVIRONMENT = \
diff --git a/trunk/testsuite/Makefile.in b/trunk/testsuite/Makefile.in
index 53feb82..8b73262 100644
--- a/trunk/testsuite/Makefile.in
+++ b/trunk/testsuite/Makefile.in
@@ -107,7 +107,7 @@ TESTS = movelibs.sh \
shuffle6.sh shuffle7.sh shuffle8.sh undo1.sh \
layout1.sh layout2.sh \
tls1.sh tls2.sh tls3.sh tls4.sh tls5.sh tls6.sh tls7.sh \
- cxx1.sh quick1.sh quick2.sh quick3.sh cycle1.sh cycle2.sh \
+ cxx1.sh cxx2.sh quick1.sh quick2.sh quick3.sh cycle1.sh cycle2.sh \
deps1.sh deps2.sh \
undosyslibs.sh
diff --git a/trunk/testsuite/cxx1.sh b/trunk/testsuite/cxx1.sh
index a48cc65..c99f198 100755
--- a/trunk/testsuite/cxx1.sh
+++ b/trunk/testsuite/cxx1.sh
@@ -11,8 +11,9 @@ savelibs
echo $PRELINK -vvvv ${PRELINK_OPTS--vm} ./cxx1 > cxx1.log
$PRELINK -vvvv ${PRELINK_OPTS--vm} ./cxx1 >> cxx1.log 2>&1 || exit 1
grep ^`echo $PRELINK | sed 's/ .*$/: /'` cxx1.log | grep -q -v 'C++ conflict' && exit 2
-LD_LIBRARY_PATH=. ./cxx1 || exit 3
-readelf -a ./cxx1 >> cxx1.log 2>&1 || exit 4
+[ $( grep ^`echo $PRELINK | sed 's/ .*$/: /'` cxx1.log | grep 'Removing C++ conflict' | wc -l ) -ge 5 ] || exit 3
+LD_LIBRARY_PATH=. ./cxx1 || exit 4
+readelf -a ./cxx1 >> cxx1.log 2>&1 || exit 5
# So that it is not prelinked again
chmod -x ./cxx1
-comparelibs >> cxx1.log 2>&1 || exit 5
+comparelibs >> cxx1.log 2>&1 || exit 6
diff --git a/trunk/testsuite/cxx2.C b/trunk/testsuite/cxx2.C
new file mode 100644
index 0000000..8b6e0bf
--- /dev/null
+++ b/trunk/testsuite/cxx2.C
@@ -0,0 +1,51 @@
+#include "cxx1.h"
+extern "C" void abort (void);
+
+int A::a ()
+{
+ return 30;
+}
+
+int A::b ()
+{
+ return 31;
+}
+
+int B::a ()
+{
+ return 32;
+}
+
+int C::a ()
+{
+ return 33;
+}
+
+int C::b ()
+{
+ return 34;
+}
+
+void
+check (A *x, B *y)
+{
+ C d;
+ if (x->b () != 31)
+ abort ();
+ if (y->B::a () != 32)
+ abort ();
+ if (d.a () != 33)
+ abort ();
+ if (d.C::b () != 34)
+ abort ();
+}
+
+int
+main ()
+{
+ A x;
+ if (x.a () != 30)
+ abort ();
+ do_check (check, &x);
+ return 0;
+}
diff --git a/trunk/testsuite/cxx2.sh b/trunk/testsuite/cxx2.sh
new file mode 100755
index 0000000..edad081
--- /dev/null
+++ b/trunk/testsuite/cxx2.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+. `dirname $0`/functions.sh
+rm -f cxx2 cxx2lib*.so cxx2.log
+rm -f prelink.cache
+$CXX -shared -O2 -fpic -o cxx2lib1.so $srcdir/cxx1lib1.C
+$CXX -shared -O2 -fpic -o cxx2lib2.so $srcdir/cxx1lib2.C cxx2lib1.so
+BINS="cxx2"
+LIBS="cxx2lib1.so cxx2lib2.so"
+$CXXLINK -o cxx2 $srcdir/cxx2.C -Wl,--rpath-link,. cxx2lib2.so
+savelibs
+echo $PRELINK -vvvv ${PRELINK_OPTS--vm} ./cxx2 > cxx2.log
+$PRELINK -vvvv ${PRELINK_OPTS--vm} ./cxx2 >> cxx2.log 2>&1 || exit 1
+grep ^`echo $PRELINK | sed 's/ .*$/: /'` cxx2.log | grep -q -v 'C++ conflict' && exit 2
+[ $( grep ^`echo $PRELINK | sed 's/ .*$/: /'` cxx2.log | grep 'Removing C++ conflict' | wc -l ) -ge 9 ] || exit 3
+LD_LIBRARY_PATH=. ./cxx2 || exit 4
+readelf -a ./cxx2 >> cxx2.log 2>&1 || exit 5
+# So that it is not prelinked again
+chmod -x ./cxx2
+comparelibs >> cxx2.log 2>&1 || exit 6