diff mbox series

[RFC,v13,6/9] Add dlmopen / RTLD_SHARED tests

Message ID 20210929162642.21985-7-vivek@collabora.com
State Superseded
Headers show
Series Implementation of RTLD_SHARED for dlmopen | expand

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Vivek Das Mohapatra Sept. 29, 2021, 4:26 p.m. UTC
We ensure a DF_GNU_1_UNIQUE library is loaded (as a DT_NEEDED
dependency) into the target namespace by the test module.

This checks that the code path for dependencies honours the flag
and prepares a base namespace library with a proxy in the target
namespace (since the mechanism for this is not the same as for
DSOs that have already been loaded into the base namespace).
---
 elf/Makefile                         | 106 +++-
 elf/tst-dlmopen-auditmod.c           |  23 +
 elf/tst-dlmopen-common.h             |  33 +
 elf/tst-dlmopen-main.h               | 879 +++++++++++++++++++++++++++
 elf/tst-dlmopen-modules.h            |  21 +
 elf/tst-dlmopen-rtld-audit-shared1.c |   8 +
 elf/tst-dlmopen-rtld-audit-shared2.c |   8 +
 elf/tst-dlmopen-rtld-audit-shared3.c |   7 +
 elf/tst-dlmopen-rtld-audit-shared4.c |   8 +
 elf/tst-dlmopen-rtld-audit-shared5.c |   8 +
 elf/tst-dlmopen-rtld-audit-shared6.c |   8 +
 elf/tst-dlmopen-rtld-audit-unique1.c |   7 +
 elf/tst-dlmopen-rtld-audit-unique2.c |   7 +
 elf/tst-dlmopen-rtld-audit-unique3.c |   7 +
 elf/tst-dlmopen-rtld-audit-unique4.c |   7 +
 elf/tst-dlmopen-rtld-audit-unique5.c |   7 +
 elf/tst-dlmopen-rtld-audit-unique6.c |   7 +
 elf/tst-dlmopen-rtld-shared1.c       |   7 +
 elf/tst-dlmopen-rtld-shared1.h       |  64 ++
 elf/tst-dlmopen-rtld-shared2.c       |   7 +
 elf/tst-dlmopen-rtld-shared2.h       |  66 ++
 elf/tst-dlmopen-rtld-shared3.c       |   7 +
 elf/tst-dlmopen-rtld-shared3.h       |  43 ++
 elf/tst-dlmopen-rtld-shared4.c       |   7 +
 elf/tst-dlmopen-rtld-shared4.h       |  14 +
 elf/tst-dlmopen-rtld-shared5.c       |   7 +
 elf/tst-dlmopen-rtld-shared5.h       |  25 +
 elf/tst-dlmopen-rtld-shared6.c       |   7 +
 elf/tst-dlmopen-rtld-shared6.h       |  36 ++
 elf/tst-dlmopen-rtld-unique1.c       |   7 +
 elf/tst-dlmopen-rtld-unique1.h       |  86 +++
 elf/tst-dlmopen-rtld-unique2.c       |   7 +
 elf/tst-dlmopen-rtld-unique2.h       |  25 +
 elf/tst-dlmopen-rtld-unique3.c       |   7 +
 elf/tst-dlmopen-rtld-unique3.h       |  13 +
 elf/tst-dlmopen-rtld-unique4.c       |   7 +
 elf/tst-dlmopen-rtld-unique4.h       |  14 +
 elf/tst-dlmopen-rtld-unique5.c       |   7 +
 elf/tst-dlmopen-rtld-unique5.h       |  58 ++
 elf/tst-dlmopen-rtld-unique6.c       |   7 +
 elf/tst-dlmopen-rtld-unique6.h       |  51 ++
 elf/tst-dlmopen-sharedmod-norm.c     |  34 ++
 elf/tst-dlmopen-sharedmod-uniq.c     |  33 +
 elf/tst-dlmopen-std-do-test.c        |  12 +
 44 files changed, 1808 insertions(+), 1 deletion(-)
 create mode 100644 elf/tst-dlmopen-auditmod.c
 create mode 100644 elf/tst-dlmopen-common.h
 create mode 100644 elf/tst-dlmopen-main.h
 create mode 100644 elf/tst-dlmopen-modules.h
 create mode 100644 elf/tst-dlmopen-rtld-audit-shared1.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-shared2.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-shared3.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-shared4.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-shared5.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-shared6.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-unique1.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-unique2.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-unique3.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-unique4.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-unique5.c
 create mode 100644 elf/tst-dlmopen-rtld-audit-unique6.c
 create mode 100644 elf/tst-dlmopen-rtld-shared1.c
 create mode 100644 elf/tst-dlmopen-rtld-shared1.h
 create mode 100644 elf/tst-dlmopen-rtld-shared2.c
 create mode 100644 elf/tst-dlmopen-rtld-shared2.h
 create mode 100644 elf/tst-dlmopen-rtld-shared3.c
 create mode 100644 elf/tst-dlmopen-rtld-shared3.h
 create mode 100644 elf/tst-dlmopen-rtld-shared4.c
 create mode 100644 elf/tst-dlmopen-rtld-shared4.h
 create mode 100644 elf/tst-dlmopen-rtld-shared5.c
 create mode 100644 elf/tst-dlmopen-rtld-shared5.h
 create mode 100644 elf/tst-dlmopen-rtld-shared6.c
 create mode 100644 elf/tst-dlmopen-rtld-shared6.h
 create mode 100644 elf/tst-dlmopen-rtld-unique1.c
 create mode 100644 elf/tst-dlmopen-rtld-unique1.h
 create mode 100644 elf/tst-dlmopen-rtld-unique2.c
 create mode 100644 elf/tst-dlmopen-rtld-unique2.h
 create mode 100644 elf/tst-dlmopen-rtld-unique3.c
 create mode 100644 elf/tst-dlmopen-rtld-unique3.h
 create mode 100644 elf/tst-dlmopen-rtld-unique4.c
 create mode 100644 elf/tst-dlmopen-rtld-unique4.h
 create mode 100644 elf/tst-dlmopen-rtld-unique5.c
 create mode 100644 elf/tst-dlmopen-rtld-unique5.h
 create mode 100644 elf/tst-dlmopen-rtld-unique6.c
 create mode 100644 elf/tst-dlmopen-rtld-unique6.h
 create mode 100644 elf/tst-dlmopen-sharedmod-norm.c
 create mode 100644 elf/tst-dlmopen-sharedmod-uniq.c
 create mode 100644 elf/tst-dlmopen-std-do-test.c
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index d7eb7cf476..42991a062b 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -197,6 +197,38 @@  static-dlopen-environment = \
 tst-tls9-static-ENV = $(static-dlopen-environment)
 tst-single_threaded-static-dlopen-ENV = $(static-dlopen-environment)
 
+dlmopen-rtld-tests-norm := \
+	 tst-dlmopen-rtld-shared1 \
+	 tst-dlmopen-rtld-shared2 \
+	 tst-dlmopen-rtld-shared3 \
+	 tst-dlmopen-rtld-shared4 \
+	 tst-dlmopen-rtld-shared5 \
+	 tst-dlmopen-rtld-shared6
+
+dlmopen-rtld-tests-uniq := \
+	 tst-dlmopen-rtld-unique1 \
+	 tst-dlmopen-rtld-unique2 \
+	 tst-dlmopen-rtld-unique3 \
+	 tst-dlmopen-rtld-unique4 \
+	 tst-dlmopen-rtld-unique5 \
+	 tst-dlmopen-rtld-unique6
+
+dlmopen-rtld-audit-tests-norm := \
+	 tst-dlmopen-rtld-audit-shared1 \
+	 tst-dlmopen-rtld-audit-shared2 \
+	 tst-dlmopen-rtld-audit-shared3 \
+	 tst-dlmopen-rtld-audit-shared4 \
+	 tst-dlmopen-rtld-audit-shared5 \
+	 tst-dlmopen-rtld-audit-shared6
+
+dlmopen-rtld-audit-tests-uniq := \
+	 tst-dlmopen-rtld-audit-unique1 \
+	 tst-dlmopen-rtld-audit-unique2 \
+	 tst-dlmopen-rtld-audit-unique3 \
+	 tst-dlmopen-rtld-audit-unique4 \
+	 tst-dlmopen-rtld-audit-unique5 \
+	 tst-dlmopen-rtld-audit-unique6
+
 tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 	 constload1 order noload filter \
 	 reldep reldep2 reldep3 reldep4 nodelete nodelete2 \
@@ -233,7 +265,11 @@  tests-internal += loadtest unload unload2 circleload1 \
 	 neededtest neededtest2 neededtest3 neededtest4 \
 	 tst-tls3 tst-tls6 tst-tls7 tst-tls8 tst-dlmopen2 \
 	 tst-ptrguard1 tst-stackguard1 \
-	 tst-create_format1 tst-tls-surplus tst-dl-hwcaps_split
+	 tst-create_format1 tst-tls-surplus tst-dl-hwcaps_split \
+	 $(dlmopen-rtld-tests-norm) \
+	 $(dlmopen-rtld-tests-uniq) \
+	 $(dlmopen-rtld-audit-tests-norm) \
+	 $(dlmopen-rtld-audit-tests-uniq)
 tests-container += tst-pldd tst-dlopen-tlsmodid-container \
   tst-dlopen-self-container tst-preload-pthread-libc
 test-srcs = tst-pathopt
@@ -298,6 +334,9 @@  modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
 		$(modules-execstack-$(have-z-execstack)) \
 		tst-dlopenrpathmod tst-deep1mod1 tst-deep1mod2 tst-deep1mod3 \
 		tst-dlmopen1mod tst-auditmod1 \
+		tst-dlmopen-sharedmod-norm \
+		tst-dlmopen-sharedmod-uniq \
+		tst-dlmopen-auditmod \
 		unload3mod1 unload3mod2 unload3mod3 unload3mod4 \
 		unload4mod1 unload4mod2 unload4mod3 unload4mod4 \
 		unload6mod1 unload6mod2 unload6mod3 \
@@ -337,6 +376,7 @@  modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
 		tst-tlsalign-lib tst-nodelete-opened-lib tst-nodelete2mod \
 		tst-audit11mod1 tst-audit11mod2 tst-auditmod11 \
 		tst-audit12mod1 tst-audit12mod2 tst-audit12mod3 tst-auditmod12 \
+		tst-dlmopen-auditmod \
 		tst-latepthreadmod $(tst-tls-many-dynamic-modules) \
 		$(tst-tls-many-dynamic-modules-dep) \
 		$(tst-tls-many-dynamic-modules-dep-bad) \
@@ -832,6 +872,9 @@  tst-nodelete-uniquemod.so-no-z-defs = yes
 tst-nodelete-rtldmod.so-no-z-defs = yes
 tst-nodelete-zmod.so-no-z-defs = yes
 tst-nodelete2mod.so-no-z-defs = yes
+tst-dlmopen-sharedmod-norm.so-no-z-defs = yes
+tst-dlmopen-sharedmod-uniq.so-no-z-defs = yes
+tst-dlmopen-auditmod.so-no-z-defs = yes
 
 ifeq ($(build-shared),yes)
 # Build all the modules even when not actually running test programs.
@@ -1251,6 +1294,67 @@  $(objpfx)tst-dlmopen3.out: $(objpfx)tst-dlmopen1mod.so
 
 $(objpfx)tst-dlmopen4.out: $(objpfx)tst-dlmopen1mod.so
 
+ifneq ($(ld-zunique),yes)
+$(objpfx)tst-dlmopen-sharedmod-uniq.so: $(common-objpfx)elf/dynamic-notes.os
+else
+LDFLAGS-tst-dlmopen-sharedmod-uniq.so = -Wl,-z,unique
+endif
+
+# Add a DT_NEEDED entry for a DF_GNU_1_UNIQUE library to the test modules.
+LDFLAGS-tst-dlmopen-sharedmod-norm.so += -Wl,-lrt
+LDFLAGS-tst-dlmopen-sharedmod-uniq.so += -Wl,-lrt
+
+$(objpfx)tst-dlmopen-sharedmod-norm.so: $(libdl)
+$(objpfx)tst-dlmopen-sharedmod-uniq.so: $(libdl)
+$(objpfx)tst-dlmopen-auditmod.so: $(libdl)
+
+dlmopen-rtld-tests-norm-executables := \
+	 $(foreach x,$(dlmopen-rtld-tests-norm),$(objpfx)$(x))
+dlmopen-rtld-tests-norm-out := \
+	 $(foreach x,$(dlmopen-rtld-tests-norm),$(objpfx)$(x).out)
+
+dlmopen-rtld-tests-uniq-executables := \
+	 $(foreach x,$(dlmopen-rtld-tests-uniq),$(objpfx)$(x))
+dlmopen-rtld-tests-uniq-out := \
+	 $(foreach x,$(dlmopen-rtld-tests-uniq),$(objpfx)$(x).out)
+
+dlmopen-rtld-audit-tests-norm-executables := \
+	 $(foreach x,$(dlmopen-rtld-audit-tests-norm),$(objpfx)$(x))
+dlmopen-rtld-audit-tests-norm-out := \
+	 $(foreach x,$(dlmopen-rtld-audit-tests-norm),$(objpfx)$(x).out)
+
+dlmopen-rtld-audit-tests-uniq-executables := \
+	 $(foreach x,$(dlmopen-rtld-audit-tests-uniq),$(objpfx)$(x))
+dlmopen-rtld-audit-tests-uniq-out := \
+	 $(foreach x,$(dlmopen-rtld-audit-tests-uniq),$(objpfx)$(x).out)
+
+$(dlmopen-rtld-tests-norm-executables): $(libdl)
+$(dlmopen-rtld-tests-norm-out): $(objpfx)tst-dlmopen-sharedmod-norm.so
+
+$(dlmopen-rtld-tests-uniq-executables): $(libdl)
+$(dlmopen-rtld-tests-uniq-out): $(objpfx)tst-dlmopen-sharedmod-uniq.so
+
+
+$(dlmopen-rtld-audit-tests-norm-executables): $(libdl)
+$(dlmopen-rtld-audit-tests-norm-out): $(objpfx)tst-dlmopen-sharedmod-norm.so
+$(dlmopen-rtld-audit-tests-norm-out): $(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-shared1-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-shared2-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-shared3-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-shared4-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-shared5-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-shared6-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+
+$(dlmopen-rtld-audit-tests-uniq-executables): $(libdl)
+$(dlmopen-rtld-audit-tests-uniq-out): $(objpfx)tst-dlmopen-sharedmod-uniq.so
+$(dlmopen-rtld-audit-tests-uniq-out): $(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-unique1-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-unique2-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-unique3-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-unique4-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-unique5-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+tst-dlmopen-rtld-audit-unique6-ENV = LD_AUDIT=$(objpfx)tst-dlmopen-auditmod.so
+
 $(objpfx)tst-audit1.out: $(objpfx)tst-auditmod1.so
 tst-audit1-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
 
diff --git a/elf/tst-dlmopen-auditmod.c b/elf/tst-dlmopen-auditmod.c
new file mode 100644
index 0000000000..d630a26cd6
--- /dev/null
+++ b/elf/tst-dlmopen-auditmod.c
@@ -0,0 +1,23 @@ 
+/* Audit module for tst-dlmopen-rtld-audit-*
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+unsigned int
+la_version (unsigned int version)
+{
+  return version;
+}
diff --git a/elf/tst-dlmopen-common.h b/elf/tst-dlmopen-common.h
new file mode 100644
index 0000000000..477e2e30b5
--- /dev/null
+++ b/elf/tst-dlmopen-common.h
@@ -0,0 +1,33 @@ 
+/* Common infrastructure for tst-dlmopen-rtld-*
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#pragma once
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gnu/lib-names.h>
+
+typedef struct
+{
+  const char *name;
+  void *free;
+  void *timer_create;
+} dlmopen_testresult;
+
+typedef dlmopen_testresult * (*dlmopen_testfunc) (void);
diff --git a/elf/tst-dlmopen-main.h b/elf/tst-dlmopen-main.h
new file mode 100644
index 0000000000..8baf160d6c
--- /dev/null
+++ b/elf/tst-dlmopen-main.h
@@ -0,0 +1,879 @@ 
+/* Main infrastructure for tst-dlmopen-rtld-*
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#pragma once
+
+/* dlmopen +/- RTLD_SHARED/RTLD_ISOLATE semantics:
+
+   RTLD_ISOLATE's purpose is to suppress all shared behaviour,
+   mainly used for LD_AUDIT code paths but available to the user
+   and also useful for constructing test case preconditions.
+
+   dlmopen should have the following behaviour:
+
+   Notation:
+        Number of namespace ('+' means make a new one in an empty NS)
+        |
+     [+]X[p] - p indicates a proxy
+      |
+      + -> new enry after the dlmopen call
+
+      Need to be able to inspect:
+
+      list of dl handles before we start (base state)
+      list of dl handles after an action in each namespace
+      ns of a given dl handle
+      _real_ ns of a given handle (ie where does a proxy point)
+
+      In the tables below each line represents a separate test.
+
+      In practice many tests can be implemented in the same executable
+      as some tests set up the required preconditions for other tests
+      if they complete successfully.
+
+      Target is a normal DSO:
+      Before | Target NS | RTLD Flags | After  | handle NS | libc NS
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-shared1:
+      -------+-----------+------------+--------+-----------+---------
+       -     | 0         | -          | +0     | 0         | 0
+       0     | 0         | -          | 0      | 0         | 0
+       0     | 0         | SHARED     | 0      | 0         | 0
+       0     | +         | -          | 0,+1   | 1         | 0
+       0,1   | 0         | -          | 0,1    | 0         | 0
+       0,1   | 0         | SHARED     | 0,1    | 0         | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-shared2:
+      -------+-----------+------------+--------+-----------+---------
+       -     | 0         | SHARED     | +0     | 0         | 0
+       0     | +         | SHARED     | 0,+1p  | 1p        | 0
+       0,1p  | 0         | -          | 0,1p   | 0         | 0
+       0,1p  | 0         | SHARED     | 0,1p   | 0         | 0
+       0,1p  | 1         | -          | 0,1p   | 1p        | 0
+       0,1p  | 1         | SHARED     | 0,1p   | 1p        | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-shared3
+      -------+-----------+------------+--------+-----------+---------
+       -     | +         | -          | +1     | 1         | 0
+       1     | 0         | -          | +0,1   | 0         | 0
+       0,1   | 1         | -          | 0,1    | 1         | 0
+       0,1   | 1         | SHARED     | 0,1    | ERR       | -
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-shared4
+      -------+-----------+------------+--------+-----------+---------
+       -     | +         | SHARED     | +0,+1p | 1p        | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-shared5
+      -------+-----------+------------+--------+-----------+---------
+       1     | 0         | SHARED     | +0,1   | 0         | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-shared6
+      -------+-----------+------------+--------+-----------+---------
+       1     | 1         | -          | 1      | 0         | 0
+       1     | 1         | SHARED     | 1      | ERR       | -
+
+      Target is a DF_GNU_1_UNIQUE DSO:
+      Before | Target NS | RTLD Flags | After  | handle NS | libc NS
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-unique1:
+      -------+-----------+------------+--------+-----------+---------
+       -     | 0         | -          | +0     | 0         | 0
+       0     | 0         | -          | 0      | 0         | 0
+       0     | 0         | SHARED     | 0      | 0         | 0
+       0     | +         | -          | 0,+1p  | 1p        | 0
+       0,1p  | 0         | -          | 0,1p   | 0         | 0
+       0,1p  | 0         | SHARED     | 0,1p   | 0         | 0
+       0,1p  | 1         | -          | 0,1p   | 1p        | 0
+       0,1p  | 1         | SHARED     | 0,1p   | 1p        | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-unique2:
+      -------+-----------+------------+--------+-----------+---------
+       -     | 0         | SHARED     | +0     | 0         | 0
+       0     | +         | SHARED     | 0,+1p  | 1p        | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-unique3:
+      -------+-----------+------------+--------+-----------+---------
+       -     | +         | -          | +0,+1p | 1p        | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-unique4:
+      -------+-----------+------------+--------+-----------+---------
+       -     | +         | SHARED     | +0,+1p | 1p        | 0
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-unique5:
+      -------+-----------+------------+--------+-----------+---------
+       -     | +         | ISOLATE    | +1     | 1         | 1
+       1     | 0         | -          | +0,1   | 0         | 0
+       0,1   | 0         | -          | 0,1    | 0         | 0
+       0,1   | 0         | SHARED     | 0,1    | 0         | 0
+       0,1   | 1         | -          | 0,1    | ERR       | -
+      =======+===========+============+========+===========+=========
+      dlmopen-rtld-unique6:
+      -------+-----------+------------+--------+-----------+---------
+       -     | +1        | ISOLATE    | +1     | 1         | 1
+       1     | 1         | -          | 1      | ERR       | -
+       1     | 1         | SHARED     | 1      | ERR       | -
+       1     | 0         | SHARED     | +0,1   | 0         | 0
+       0,1   | 1         | SHARED     | 0,1    | ERR       | -
+*/
+
+#include "tst-dlmopen-common.h"
+#include <dl-dtprocnum.h>
+#include <link.h>
+#include <array_length.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+#define DSO_NORMAL "$ORIGIN/tst-dlmopen-sharedmod-norm.so"
+#define DSO_UNIQUE "$ORIGIN/tst-dlmopen-sharedmod-uniq.so"
+#define DSO_TESTFN "rtld_shared_testfunc"
+#define DSO_NAMESTUB "tst-dlmopen-sharedmod-"
+#define MAX_NS 16
+
+#define ERROR(test, fmt, ...) \
+  support_print_failure_impl (__FILE__, __LINE__, "%s (%s): " fmt, \
+                              test->name, test->desc, ##__VA_ARGS__)
+
+typedef enum
+  {
+   NONE  = 0,
+   DSO   = 1,
+   PROXY = 2,
+   NEW   = 4,
+  } dso_type;
+
+typedef struct
+{
+  const char *name;
+  const char *desc;
+  int is_prep_stage;
+  const char *dso_name;
+  int failure;
+
+  struct
+  {
+    const char *dso_path;
+    Lmid_t ns;
+    int flags;
+  } args;
+
+  dso_type preloaded[MAX_NS];
+  dso_type loaded[MAX_NS];
+  dso_type handle_type;
+  Lmid_t handle_ns;
+  Lmid_t free_ns;
+} dlmopen_test_spec;
+
+typedef struct { void *handle; Lmid_t ns; } test_handle;
+static test_handle cached_handles[32];
+
+static void
+cache_test_handle (void *handle, Lmid_t ns)
+{
+  int i;
+
+  for (i = 0; i < array_length(cached_handles); i++)
+    if (cached_handles[i].handle == NULL)
+      {
+        cached_handles[i].handle = handle;
+        cached_handles[i].ns = ns;
+        break;
+      }
+}
+
+__attribute__((unused))
+static struct link_map *
+link_map_of_dl_handle (void *handle)
+{
+  struct link_map *lm = NULL;
+
+  if (dlinfo (handle, RTLD_DI_LINKMAP, &lm) == 0)
+    return lm;
+
+  printf ("dlinfo (LINKMAP) for %s in %s failed: %s\n",
+          LIBC_SO, __func__, dlerror ());
+
+  return NULL;
+}
+
+__attribute__((unused))
+static Lmid_t
+ns_of_dl_handle (void *handle)
+{
+  Lmid_t ns = 0;
+
+  if (dlinfo (handle, RTLD_DI_LMID, &ns) == 0)
+    return ns;
+
+  printf ("dlinfo (LMID) for %s in %s failed: %s\n",
+          LIBC_SO, __func__, dlerror ());
+
+  return -1;
+}
+
+__attribute__((unused))
+static Lmid_t real_ns_of_dl_handle (void *handle)
+{
+  Lmid_t ns = 0;
+  struct link_map *lm = link_map_of_dl_handle (handle);
+
+  if (lm == NULL)
+    return -1;
+
+  if (lm->l_proxy)
+    ns = ns_of_dl_handle ((void *) lm->l_real);
+  else
+    ns = ns_of_dl_handle (handle);
+
+  return ns;
+}
+
+__attribute__((unused))
+static const char *str_soname (const char *name)
+{
+  char *slash = NULL;
+
+  if (name == NULL)
+    return NULL;
+
+  if ((slash = strrchr (name, '/')))
+    return ++slash;
+  else
+    return name;
+}
+
+__attribute__((unused))
+static const char *lm_name (struct link_map *lm)
+{
+  if (lm)
+    return lm->l_name;
+
+  return NULL;
+}
+
+
+static int dlm_dso_is_loaded (void *handle)
+{
+  if (handle != RTLD_DEFAULT)
+    {
+      Dl_info sinfo = {};
+
+      /* If it wasn't dynamically loaded it can be of no interest to our test(s).  */
+      if (((struct link_map *) handle)->l_type != lt_loaded)
+        return 0;
+
+      /* Skip link map entries that have not yet been promoted to dl*foo* handles.  */
+      if (((struct link_map *) handle)->l_searchlist.r_list  == NULL ||
+          ((struct link_map *) handle)->l_searchlist.r_nlist == 0)
+        return 0;
+
+      verbose_printf ("checking %p %s for %s\n",
+                      handle, ((struct link_map *)handle)->l_name, DSO_TESTFN);
+
+      void *symbol = dlsym (handle, DSO_TESTFN);
+
+      dladdr (symbol, &sinfo);
+      verbose_printf ("  -> %s (in %s (%p))\n",
+                      sinfo.dli_fname,
+                      sinfo.dli_sname,
+                      sinfo.dli_saddr);
+
+      return symbol ? 1 : 0;
+    }
+
+  for (int i = 0; i < array_length(cached_handles); i++)
+    {
+      if (cached_handles[i].handle == NULL)
+        break;
+
+      if (((struct link_map *) cached_handles[i].handle)->l_type != lt_loaded)
+        continue;
+
+      verbose_printf ("checking %p %s for %s\n",
+                      cached_handles[i].handle,
+                      ((struct link_map *)cached_handles[i].handle)->l_name,
+                      DSO_TESTFN);
+
+      if (dlsym (cached_handles[i].handle, DSO_TESTFN) != NULL)
+        return 1;
+    }
+
+  return 0;
+}
+
+static int
+call_testfunc (dlmopen_test_spec *test, void *handle)
+{
+  Dl_info dli = {};
+  struct link_map *lm = NULL;
+  dlmopen_testfunc func = NULL;
+  dlmopen_testresult *result = NULL;
+
+  if (handle != RTLD_DEFAULT)
+    func = dlsym (handle, DSO_TESTFN);
+
+  if (func == NULL)
+    {
+      ERROR (test, "test function %s not found\n", DSO_TESTFN);
+      return 0;
+    }
+
+  result = (func)();
+
+  if (result == NULL)
+    {
+      ERROR (test, "test function %s returned NULL\n", DSO_TESTFN);
+      return 0;
+    }
+
+  dladdr1 (result->free, &dli, (void **)&lm, RTLD_DL_LINKMAP);
+
+  if (lm == NULL)
+    {
+      ERROR (test, "free() implementation from test module is invalid\n");
+      return 0;
+    }
+
+  if (lm->l_ns != test->free_ns)
+    {
+      ERROR (test,
+             "free() function from test module was from ns %d, expected %d\n",
+             (int)lm->l_ns, (int)test->free_ns);
+      return 0;
+    }
+
+  printf ("%s: %s: %s in ns %d using free() from ns %d: OK\n",
+          test->name, test->args.dso_path, DSO_TESTFN,
+          (int)test->handle_ns, (int)lm->l_ns);
+
+  return 1;
+}
+
+static void *link_map_snapshot_array (void *handle, void *func, Lmid_t ns, size_t *len)
+{
+  struct link_map *lm = NULL;
+  struct link_map *start = NULL;
+
+  if (len != NULL)
+    *len = 0;
+
+  if (handle != NULL)
+    {
+      dlinfo (handle, RTLD_DI_LINKMAP, &lm);
+    }
+  else if (func != NULL)
+    {
+      Dl_info dli = {};
+
+      dladdr1 (func, &dli, (void **)&lm, RTLD_DL_LINKMAP);
+    }
+  else if (ns >= LM_ID_BASE)
+    {
+      for (int i = 0; i < array_length(cached_handles); i++)
+        {
+          if (cached_handles[i].handle == NULL)
+            break;
+
+          if (cached_handles[i].ns != ns)
+            continue;
+
+          dlinfo (cached_handles[i].handle, RTLD_DI_LINKMAP, &lm);
+          break;
+        }
+    }
+
+  if (lm == NULL)
+    return NULL;
+
+  start = lm;
+
+  while (start->l_prev)
+    start = start->l_prev;
+
+  size_t lm_size = 0;
+
+  for (lm = start; lm; lm = lm->l_next)
+    lm_size++;
+
+  struct link_map **lm_list = xcalloc (lm_size + 1, sizeof (struct link_map *));
+
+  if (len != NULL)
+    *len = lm_size;
+
+  int i = 0;
+
+  for (lm = start; lm; lm = lm->l_next)
+    lm_list[i++] = lm;
+  lm_list[i] = NULL;
+
+  return lm_list;
+}
+
+__attribute__((unused))
+static int search_link_map_array (struct link_map **lma, size_t len, void *handle)
+{
+  if (lma == NULL)
+    return 0;
+
+  if (len == 0)
+    return 0;
+
+  struct link_map *target = link_map_of_dl_handle (handle);
+
+  for (int i = 0; i < len; i++)
+    {
+      if (handle == (struct link_map *)(lma[i]))
+        return 1;
+
+      if (target->l_proxy)
+        if (target->l_real == (struct link_map *)(lma[i]))
+          return 1;
+    }
+
+  return 0;
+}
+
+#ifdef DEBUG_DSO_SEARCH
+#define TRACE2(fmt, ...) \
+  printf ("  find-test-dso (%s @ %d): " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
+#else
+#define TRACE2(fmt, ...)
+#endif
+
+static struct link_map *
+find_test_dso_in_link_map_array (struct link_map **lma, size_t len)
+{
+  if (lma == NULL)
+    return NULL;
+
+  if (len == 0)
+    return NULL;
+
+  for (int i = 0; i < len; i++)
+    {
+      if ((lma[i] == NULL) || (lma[i]->l_name == NULL))
+        continue;
+
+      TRACE2 ("%p [%d/%d] %s", lma, i, (int)len - 1,
+              lma[i] ? (lma[i]->l_name ?: "???.so") : "NULL" );
+
+      if (lma[i] && lma[i]->l_name)
+        if (strstr (lma[i]->l_name, DSO_NAMESTUB))
+          if (dlsym (lma[i], DSO_TESTFN) != NULL)
+            return (struct link_map *)lma[i];
+    }
+
+  return NULL;
+}
+
+__attribute__((unused))
+static void *link_map_list (void *handle, void *func, int *len, Lmid_t *ns)
+{
+  struct link_map *lm = NULL;
+  struct link_map *start = NULL;
+
+  if (len)
+    *len = 0;
+
+  if (handle != NULL)
+    {
+      dlinfo (handle, RTLD_DI_LINKMAP, &lm);
+    }
+  else if (func != NULL)
+    {
+      Dl_info dli = {};
+
+      dladdr1 (func, &dli, (void **)&lm, RTLD_DL_LINKMAP);
+    }
+
+  if (lm == NULL)
+    return NULL;
+
+  if (ns)
+    *ns = lm->l_ns;
+
+  /* Rewind to start of link map list:  */
+  start = lm;
+
+  while (start->l_prev != NULL)
+    start = start->l_prev;
+
+  size_t lm_size = 0;
+
+  for (lm = start; lm != NULL; lm = lm->l_next)
+    lm_size++;
+
+  if (len != NULL)
+    *len = lm_size;
+
+  return start;
+}
+
+#ifdef DEBUG_DLMOPEN_TEST_WRAPPER
+#define TRACE(fmt, ...) \
+  printf ("%s (%s @ %d): " fmt "\n", test->name, __FILE__, __LINE__, ##__VA_ARGS__)
+#else
+#define TRACE(fmt, ...)
+#endif
+
+static bool process_test_spec (dlmopen_test_spec *test)
+{
+  void *handle = NULL;
+  size_t lm_before_len[MAX_NS] = { 0 };
+  size_t lm_after_len[MAX_NS] = { 0 };
+  struct link_map **lm_before[MAX_NS] = { NULL };
+  struct link_map **lm_after[MAX_NS] = { NULL };
+  struct link_map *preloads[MAX_NS] = { NULL };
+  int want_preload = 0;
+  int test_status = false;
+
+  TRACE("LD_AUDIT = %s", getenv("LD_AUDIT") ?: "-");
+  TRACE("BEFORE SNAPSHOTS: %p", &lm_before[0]);
+  TRACE("AFTER  SNAPSHOTS: %p", &lm_after[0]);
+  TRACE("preloads        : %p", preloads);
+  TRACE("setup done");
+
+  if (test->args.dso_path && *test->args.dso_path && !test->dso_name)
+    test->dso_name = str_soname (test->args.dso_path);
+
+  TRACE("DSO short name: %s", test->dso_name);
+
+  /* Get the existing link map contents before the test runs:  */
+  lm_before[0] = link_map_snapshot_array (NULL, process_test_spec,
+                                          LM_ID_BASE, &lm_before_len[0]);
+  for (int i = 1; i < MAX_NS; i++)
+    lm_before[i] = link_map_snapshot_array (NULL, NULL, i, &lm_before_len[i]);
+
+  TRACE("link map snapshots cached");
+
+  for (int i = 0; i < MAX_NS; i++)
+    {
+      if (test->preloaded[i] & PROXY)
+        {
+          struct link_map **lm = lm_before[i];
+          want_preload++;
+
+          if (lm != NULL)
+            for (int j = 0; (preloads[i] == NULL) && (j < lm_before_len[i]); j++)
+              if (dlm_dso_is_loaded (lm[j]) && lm[j]->l_proxy)
+                preloads[i] = lm[j];
+
+          if (preloads[i] == NULL)
+            {
+              ERROR (test,
+                     "needed proxy for %s preloaded in NS %d, not found\n",
+                     test->dso_name, i);
+              goto cleanup;
+            }
+        }
+      else if (test->preloaded[i] & DSO)
+        {
+          struct link_map **lm = lm_before[i];
+          int lm_max = lm_before_len[i];
+          want_preload++;
+
+          if (lm != NULL)
+            for (int j = 0; !preloads[i] && (j < lm_max); j++)
+              {
+                if (dlm_dso_is_loaded (lm[j]) && !lm[j]->l_proxy)
+                  preloads[i] = lm[j];
+              }
+          if (!preloads[i])
+            {
+              ERROR (test,
+                     "needed %s preloaded in NS %d, not found\n",
+                     test->dso_name, i);
+              goto cleanup;
+            }
+        }
+    }
+  TRACE("preload checks (A)");
+
+  if (dlm_dso_is_loaded (RTLD_DEFAULT))
+    {
+      /* Test DSO module must _not_ be preloaded, and is:  */
+      if (!want_preload)
+        {
+          ERROR (test, "DSO %s unexpectedly loaded before test\n", test->dso_name);
+          goto cleanup;
+        }
+    }
+  else
+    {
+      /* DSO is not loaded, and must be.  In theory we can never see
+         this error as it should be caught by the preceding preload loop.  */
+      if (want_preload)
+        {
+          ERROR (test, "DSO %s must be preloaded (and is not)\n", test->args.dso_path);
+          goto cleanup;
+        }
+    }
+  TRACE("preload checks (B) %s", test->name);
+
+  if (!(test->args.flags & (RTLD_NOW|RTLD_LAZY)))
+    test->args.flags |= RTLD_NOW;
+
+  handle = dlmopen (test->args.ns, test->args.dso_path, test->args.flags);
+  TRACE("dlmopen returned %p", handle);
+
+  if (handle == NULL)
+    {
+      const char *status = "failed";
+      const char *plabel = "";
+
+      if (test->failure)
+        {
+          test_status = true;
+
+          printf ("%s: dlmopen(%s, %d, 0x%0x) failed: OK (EXPECTED)\n",
+                  test->name, test->args.dso_path,
+                  (int)test->args.ns, (int)test->args.flags);
+          printf ("Returned: %p\n\n", handle);
+
+          goto cleanup;
+        }
+
+      if (test->is_prep_stage)
+        plabel = "(during setup of preconditions): ";
+
+      if (test->args.ns == LM_ID_BASE)
+        ERROR (test, "%sdlmopen (LM_ID_BASE, \"%s\", 0x%x) %s: %s\n",
+               plabel, test->args.dso_path, test->args.flags, status, dlerror ());
+      else
+        ERROR (test, "%sdlmopen (%d, \"%s\", 0x%x) %s: %s\n",
+               plabel, (int)test->args.ns, test->args.dso_path, test->args.flags, status,
+               dlerror ());
+
+      goto cleanup;
+    }
+  else if (test->failure)
+    {
+      ERROR (test, "dlmopen() call should have failed, but did not\n");
+      goto cleanup;
+    }
+
+  TRACE("return status checked");
+
+  if (!dlm_dso_is_loaded (handle))
+    {
+      ERROR (test, "DSO %s (%p) missing function (%s)\n",
+             test->args.dso_path, handle, DSO_TESTFN);
+      goto cleanup;
+    }
+
+  TRACE ("loaded DSO sanity checked");
+
+  Lmid_t hns = ns_of_dl_handle (handle);
+  Lmid_t real_hns = real_ns_of_dl_handle (handle);
+  Lmid_t proxy_ns = 0;
+
+  call_testfunc (test, handle);
+  TRACE (DSO_TESTFN "called");
+
+  cache_test_handle (handle, hns);
+  TRACE ("handle %p cached (ns %d)", handle, (int)hns);
+
+  /* If the real ns was different to the apparent one
+     then we have a proxy and we need to shuffle the values,
+     else leave the proxy ns as 0 as an expect.proxy_ns of 0
+     means we weren't expecting a proxy:  */
+  if (real_hns != hns)
+    {
+      proxy_ns = hns;
+      hns = real_hns;
+    }
+
+  if (proxy_ns)
+    printf ("Returned: proxy ns:%d (real ns: %d)\n\n", (int)proxy_ns, (int)hns);
+  else
+    printf ("Returned: dso ns:%d\n\n", (int)hns);
+
+  Lmid_t expected;
+  if (test->handle_type & PROXY)
+    expected = proxy_ns;
+  else
+    expected = hns;
+
+  TRACE("check expected ns %d", (int)expected);
+
+  if (test->args.ns == LM_ID_NEWLM)
+    {
+      if (expected <= LM_ID_BASE)
+        {
+          ERROR (test, "DSO should have been in NS > %d, was in %d\n",
+                 LM_ID_BASE, (int)expected);
+          goto cleanup;
+        }
+
+      /* For any cases where we can't predict the namespace in advance:  */
+      if (test->handle_ns == LM_ID_NEWLM)
+        test->handle_ns = expected;
+    }
+  else
+    {
+      if (test->args.ns != expected)
+        {
+          ERROR (test, "DSO should have been in NS %d, was in %d\n",
+                 (int)test->args.ns, (int)expected);
+          goto cleanup;
+        }
+    }
+
+  TRACE("ns %d Ok", (int)expected);
+
+if (test->handle_type & PROXY) /* Expecting a proxy.  */
+    {
+      if (proxy_ns != 0) /* Got a proxy.  */
+        {
+          if (test->handle_ns != proxy_ns) /* But not in the right place.  */
+            {
+              ERROR (test, "DSO proxy should have been in ns %d, was in %d\n",
+                     (int)test->handle_ns, (int)proxy_ns);
+              goto cleanup;
+            }
+        }
+      else /* Didn't get a proxy.  */
+        {
+          ERROR (test,
+                 "DSO should have been a proxy in ns %d,"
+                 " was a non-proxy in ns %d\n",
+                 (int)test->handle_ns, (int)hns);
+          goto cleanup;
+        }
+    }
+ else /* Not expecting a proxy.  */
+    {
+      if (proxy_ns > 0)
+        {
+          ERROR (test,
+                 "DSO should NOT have been a proxy,"
+                 " was a proxy in ns %d (real ns %d)\n",
+                 (int)proxy_ns, (int)hns);
+          goto cleanup;
+        }
+
+      if (test->handle_ns != hns)
+        {
+          ERROR (test,
+                 "DSO should have been in ns %d,"
+                 " was in ns %d\n",
+                 (int)test->handle_ns, (int)hns);
+          goto cleanup;
+        }
+    }
+  TRACE ("proxy status Ok");
+
+  /* Get the new link map contents after the test has run:  */
+  lm_after[0] = link_map_snapshot_array (NULL, process_test_spec,
+                                         LM_ID_BASE, &lm_after_len[0]);
+  for (int i = 1; i < MAX_NS; i++)
+    lm_after[i] = link_map_snapshot_array (NULL, NULL, i, &lm_after_len[i]);
+
+  for (int i = 0; i < MAX_NS; i++)
+    {
+      TRACE("checking status of NS %d", i);
+      void *old_handle =
+        find_test_dso_in_link_map_array (lm_before[i], lm_before_len[i]);
+      TRACE ("old handle is %p", old_handle);
+
+      void *new_handle =
+        find_test_dso_in_link_map_array (lm_after[i], lm_after_len[i]);
+      TRACE ("new handle is %p", new_handle);
+
+      if (test->loaded[i] == NONE)
+        {
+          TRACE ("ns %d requirement is NONE", i);
+          if (old_handle != NULL)
+            {
+              ERROR (test,
+                     "Unexpected preload DSO %s in ns %d\n",
+                     lm_name (old_handle), i);
+              goto cleanup;
+            }
+          if (new_handle != NULL)
+            {
+              ERROR (test, "Unexpected new DSO %s in ns %d\n",
+                     lm_name (new_handle), i);
+              goto cleanup;
+            }
+          continue;
+        }
+
+      if (test->loaded[i] & NEW)
+        {
+          TRACE("ns %d requirement is NEW", i);
+          if (old_handle != NULL)
+            {
+              ERROR (test,
+                     "DSO in ns %d should have been a new load,"
+                     " found to have been preloaded\n", i);
+              goto cleanup;
+            }
+          if (new_handle == NULL)
+            {
+              ERROR (test, "Expected DSO in ns %d, not found\n", i);
+              goto cleanup;
+            }
+        }
+      else
+        {
+          TRACE("ns %d requirement is OLD", i);
+          if (new_handle == NULL)
+            {
+              ERROR (test, "Expected new DSO in ns %d, not found\n", i);
+              goto cleanup;
+            }
+
+          if (old_handle != new_handle)
+            {
+              ERROR (test, "DSO in ns %d changed. This should be impossible, "
+                     "sanity check the test code\n", i);
+              goto cleanup;
+            }
+        }
+
+      if (test->loaded[i] & PROXY)
+        {
+          TRACE ("rechecking DSO status in ns %d", i);
+          if (!((struct link_map *)new_handle)->l_proxy)
+            {
+              ERROR (test, "DSO in ns %d should be a proxy but is not\n", i);
+              goto cleanup;
+            }
+        }
+      else
+        {
+          TRACE ("rechecking proxy status in ns %d", i);
+          if (((struct link_map *)new_handle)->l_proxy)
+            {
+              ERROR (test, "DSO in ns %d should NOT be a proxy but is\n", i);
+              goto cleanup;
+            }
+        }
+    }
+
+  test_status = true;
+
+ cleanup:
+  for (int i = 0; i < MAX_NS; i++)
+     free (lm_after[i]);
+  for (int i = 0; i < MAX_NS; i++)
+    free (lm_before[i]);
+
+  return test_status;
+}
diff --git a/elf/tst-dlmopen-modules.h b/elf/tst-dlmopen-modules.h
new file mode 100644
index 0000000000..63aa7730e3
--- /dev/null
+++ b/elf/tst-dlmopen-modules.h
@@ -0,0 +1,21 @@ 
+/* Module-specific infrastructure for tst-dlmopen-rtld-*
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#pragma once
+
+#include "tst-dlmopen-common.h"
diff --git a/elf/tst-dlmopen-rtld-audit-shared1.c b/elf/tst-dlmopen-rtld-audit-shared1.c
new file mode 100644
index 0000000000..f61e00d372
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-shared1.c
@@ -0,0 +1,8 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-shared1.h"
+
+#include "tst-dlmopen-std-do-test.c"
+
diff --git a/elf/tst-dlmopen-rtld-audit-shared2.c b/elf/tst-dlmopen-rtld-audit-shared2.c
new file mode 100644
index 0000000000..93d7c9d502
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-shared2.c
@@ -0,0 +1,8 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-shared2.h"
+
+#include "tst-dlmopen-std-do-test.c"
+
diff --git a/elf/tst-dlmopen-rtld-audit-shared3.c b/elf/tst-dlmopen-rtld-audit-shared3.c
new file mode 100644
index 0000000000..bc1e8f0345
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-shared3.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-shared3.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-audit-shared4.c b/elf/tst-dlmopen-rtld-audit-shared4.c
new file mode 100644
index 0000000000..0a0ecd4317
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-shared4.c
@@ -0,0 +1,8 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-shared4.h"
+
+#include "tst-dlmopen-std-do-test.c"
+
diff --git a/elf/tst-dlmopen-rtld-audit-shared5.c b/elf/tst-dlmopen-rtld-audit-shared5.c
new file mode 100644
index 0000000000..1585e48a95
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-shared5.c
@@ -0,0 +1,8 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-shared5.h"
+
+#include "tst-dlmopen-std-do-test.c"
+
diff --git a/elf/tst-dlmopen-rtld-audit-shared6.c b/elf/tst-dlmopen-rtld-audit-shared6.c
new file mode 100644
index 0000000000..f1b21c295b
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-shared6.c
@@ -0,0 +1,8 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-shared6.h"
+
+#include "tst-dlmopen-std-do-test.c"
+
diff --git a/elf/tst-dlmopen-rtld-audit-unique1.c b/elf/tst-dlmopen-rtld-audit-unique1.c
new file mode 100644
index 0000000000..5605967a5c
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-unique1.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-unique1.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-audit-unique2.c b/elf/tst-dlmopen-rtld-audit-unique2.c
new file mode 100644
index 0000000000..8a1d5317c8
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-unique2.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-unique2.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-audit-unique3.c b/elf/tst-dlmopen-rtld-audit-unique3.c
new file mode 100644
index 0000000000..31848ea9d8
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-unique3.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-unique3.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-audit-unique4.c b/elf/tst-dlmopen-rtld-audit-unique4.c
new file mode 100644
index 0000000000..c79cb378d5
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-unique4.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-unique4.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-audit-unique5.c b/elf/tst-dlmopen-rtld-audit-unique5.c
new file mode 100644
index 0000000000..790fed9b04
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-unique5.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-unique5.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-audit-unique6.c b/elf/tst-dlmopen-rtld-audit-unique6.c
new file mode 100644
index 0000000000..0b9bbabdd2
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-audit-unique6.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 2
+#include "tst-dlmopen-rtld-unique6.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared1.c b/elf/tst-dlmopen-rtld-shared1.c
new file mode 100644
index 0000000000..329a9a91d9
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared1.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-shared1.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared1.h b/elf/tst-dlmopen-rtld-shared1.h
new file mode 100644
index 0000000000..de718fd8ca
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared1.h
@@ -0,0 +1,64 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen:0:none--ns0",
+    .desc = "dlmopen as dlopen",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .loaded = { [0] = DSO|NEW },
+    .handle_type = DSO,
+    .handle_ns = LM_ID_BASE,
+   },
+   {
+    .name = "dlmopen:0:ns0--ns0",
+    .desc = "dlmopen a preloaded DSO in the base NS",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO },
+    .handle_type = DSO,
+    .handle_ns = LM_ID_BASE,
+   },
+   {
+    .name = "dlmopen-shared:0:ns0--ns0",
+    .desc = "dlmopen a preloaded DSO in the base NS with RTLD_SHARED",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO },
+    .handle_type = DSO,
+    .handle_ns = LM_ID_BASE,
+   },
+   {
+    .name = "dlmopen:0:ns0--nsX",
+    .desc = "dlmopen a preloaded DSO in the base NS into a new NS",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_NEWLM,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO|NEW },
+    .handle_type = DSO,
+    .handle_ns = EXPECTED_NS,
+   },
+   {
+    .name = "dlmopen:0:ns0-nsX--ns0-nsX",
+    .desc = "dlmopen a preloaded DSO in the base & secondary NS into the base NS",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .handle_type = DSO,
+    .handle_ns = LM_ID_BASE,
+   },
+   {
+    .name = "dlmopen-shared:0:ns0-nsX--ns0-nsX",
+    .desc = "dlmopen a preloaded DSO in the base & secondary NS into the base NS",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .handle_type = DSO,
+    .handle_ns = LM_ID_BASE,
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-shared2.c b/elf/tst-dlmopen-rtld-shared2.c
new file mode 100644
index 0000000000..b5bfe047e5
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared2.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-shared2.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared2.h b/elf/tst-dlmopen-rtld-shared2.h
new file mode 100644
index 0000000000..779ce62dfb
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared2.h
@@ -0,0 +1,66 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-shared:0:none--ns0",
+    .desc = "dlmopen as dlopen with RTLD_SHARED",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .args.flags  = RTLD_SHARED,
+    .loaded = { [0] = DSO|NEW },
+    .handle_ns = LM_ID_BASE,
+    .handle_type = DSO,
+   },
+   {
+    .name = "dlmopen-shared:X:ns0--ns0-nsXp",
+    .desc = "dlmopen into a new namespace with the target already in the base NS",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_NEWLM,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY|NEW }
+   },
+   {
+    .name = "dlmopen:0:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen into base NS while proxy already in nsX",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+   {
+    .name = "dlmopen-shared:0:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen with RTLD_SHARED into base NS while proxy already in nsX",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+   {
+    .name = "dlmopen:X:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen into NS X while proxy already in nsX",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = EXPECTED_NS,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+   {
+    .name = "dlmopen-shared:X:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen with RTLD_SHARED into NS X while proxy already in nsX",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = EXPECTED_NS,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-shared3.c b/elf/tst-dlmopen-rtld-shared3.c
new file mode 100644
index 0000000000..f1a4c0fb8a
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared3.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-shared3.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared3.h b/elf/tst-dlmopen-rtld-shared3.h
new file mode 100644
index 0000000000..894626b1f4
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared3.h
@@ -0,0 +1,43 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen:X:none--nsX",
+    .desc = "dlmopen into nsX, no copies preloaded",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_NEWLM,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [EXPECTED_NS] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen:0:nsX--ns0-nsX",
+    .desc = "dlmopen into ns 0, copy already loaded in ns X",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen:X:ns0-nsX--nsX",
+    .desc = "dlmopen into ns X, copies already in ns 0 and ns X",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = EXPECTED_NS,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared:X:ns0-nsX--nsX",
+    .desc = "dlmopen RTLD_SHARED into nsX with a DSO already in NS0 and NSX",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = EXPECTED_NS,
+    .args.flags = RTLD_SHARED,
+    .failure = 1,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-shared4.c b/elf/tst-dlmopen-rtld-shared4.c
new file mode 100644
index 0000000000..395ced83c2
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared4.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-shared4.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared4.h b/elf/tst-dlmopen-rtld-shared4.h
new file mode 100644
index 0000000000..9b4d0f06e0
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared4.h
@@ -0,0 +1,14 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-shared:X:none--ns0-nsX",
+    .desc = "dlmopen a new proxy in nsX with no preexisting dso in ns0",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_NEWLM,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = PROXY|NEW },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-shared5.c b/elf/tst-dlmopen-rtld-shared5.c
new file mode 100644
index 0000000000..fe830b4162
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared5.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-shared5.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared5.h b/elf/tst-dlmopen-rtld-shared5.h
new file mode 100644
index 0000000000..13dca63cbd
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared5.h
@@ -0,0 +1,25 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-preload:X:none--nsX",
+    .desc = "preload a DSO into ns1 to prepare for other tests",
+    .is_prep_stage = 1,
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_NEWLM,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [EXPECTED_NS] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen-shared:0:nsX--nsX-ns0",
+    .desc = "dlmopen RTLD_SHARED into ns0 when preloaded into nsX",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = DSO },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-shared6.c b/elf/tst-dlmopen-rtld-shared6.c
new file mode 100644
index 0000000000..989a37d78a
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared6.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-shared6.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-shared6.h b/elf/tst-dlmopen-rtld-shared6.h
new file mode 100644
index 0000000000..de41333619
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-shared6.h
@@ -0,0 +1,36 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-preload:X:none--nsX",
+    .desc = "preload a DSO into nsX to prepare for other tests",
+    .is_prep_stage = 1,
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = LM_ID_NEWLM,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [EXPECTED_NS] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen:X:nsX--nsX",
+    .desc = "dlmopen a dso in nsX while already loaded there",
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = EXPECTED_NS,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { [EXPECTED_NS] = DSO },
+    .loaded = { [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared:X:nsX--nsX",
+    .desc = "dlmopen RTLD_SHARED a dso in nsX while already loaded there",
+    .failure = 1,
+    .args.dso_path = DSO_NORMAL,
+    .args.ns = EXPECTED_NS,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { [EXPECTED_NS] = DSO },
+    .loaded = { [EXPECTED_NS] = DSO },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-unique1.c b/elf/tst-dlmopen-rtld-unique1.c
new file mode 100644
index 0000000000..f2240b5cd9
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique1.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-unique1.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-unique1.h b/elf/tst-dlmopen-rtld-unique1.h
new file mode 100644
index 0000000000..4b7390f772
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique1.h
@@ -0,0 +1,86 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-unique:0:none--ns0",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into ns0",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [0] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen-unique:0:ns0--ns0",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into ns0 while already present",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO },
+   },
+   {
+    .name = "dlmopen-unique-shared:0:ns0--ns0",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso into ns0 while already present",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO },
+   },
+   {
+    .name = "dlmopen-unique:X:ns0--nsX",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into nsX while present in ns0",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_NEWLM,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY|NEW },
+   },
+   {
+    .name = "dlmopen-unique:0:ns0-nsXp--ns0--nsXp",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso already in ns0 proxied in nsX",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+   {
+    .name = "dlmopen-shared-unique:0:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso into ns0 already in ns0 and proxied in nsX",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+   {
+    .name = "dlmopen-unique:0:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into ns0 already in ns0 and proxied in nsX",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = EXPECTED_NS,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+   {
+    .name = "dlmopen-unique:0:ns0-nsXp--ns0-nsXp",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso into ns0 already in ns0 and proxied in nsX",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = EXPECTED_NS,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-unique2.c b/elf/tst-dlmopen-rtld-unique2.c
new file mode 100644
index 0000000000..5dd8c2678d
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique2.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-unique2.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-unique2.h b/elf/tst-dlmopen-rtld-unique2.h
new file mode 100644
index 0000000000..bf6871ab58
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique2.h
@@ -0,0 +1,25 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-shared-unique:0:none--ns0",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso in the base ns",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [0] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen-shared-unique:1:ns0--ns0-ns1p",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso into ns1 while present in ns0",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_NEWLM,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { [0] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = PROXY|NEW },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-unique3.c b/elf/tst-dlmopen-rtld-unique3.c
new file mode 100644
index 0000000000..237b3adcc1
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique3.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-unique3.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-unique3.h b/elf/tst-dlmopen-rtld-unique3.h
new file mode 100644
index 0000000000..139ade0d28
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique3.h
@@ -0,0 +1,13 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-unique:X:none--ns0-ns1p",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into nsX",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_NEWLM,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = PROXY|NEW },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-unique4.c b/elf/tst-dlmopen-rtld-unique4.c
new file mode 100644
index 0000000000..e13ca19fc7
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique4.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-unique4.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-unique4.h b/elf/tst-dlmopen-rtld-unique4.h
new file mode 100644
index 0000000000..1a9b93c2ac
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique4.h
@@ -0,0 +1,14 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-shared-unique:X:none--ns0-nsXp",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_NEWLM,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = PROXY,
+    .preloaded = { },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = PROXY|NEW },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-unique5.c b/elf/tst-dlmopen-rtld-unique5.c
new file mode 100644
index 0000000000..014b10e45e
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique5.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-unique5.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-unique5.h b/elf/tst-dlmopen-rtld-unique5.h
new file mode 100644
index 0000000000..75dca65821
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique5.h
@@ -0,0 +1,58 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-isolate-unique:X:none--nsX",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into NSX with RTLD_ISOLATE",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_NEWLM,
+    .args.flags = RTLD_ISOLATE,
+    .handle_ns = EXPECTED_NS,
+    .free_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [EXPECTED_NS] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen-unique:0:nsX--ns0-nsX",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso already present in NS X",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-unique:0:ns0-nsX--ns0-nsX",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso already in the base NS and NS X",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared-unique:0:ns0-nsX--ns0-nsX",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso already in the base NS and NS X",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared-unique:X:ns0-nsX--ns0-nsX",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso already in the base NS and NS X into NS X",
+    .failure = 1,
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = EXPECTED_NS,
+    .args.flags = RTLD_SHARED,
+    .handle_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+   },
+  };
diff --git a/elf/tst-dlmopen-rtld-unique6.c b/elf/tst-dlmopen-rtld-unique6.c
new file mode 100644
index 0000000000..c0df189d3f
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique6.c
@@ -0,0 +1,7 @@ 
+#include <dlfcn.h>
+#include "tst-dlmopen-main.h"
+
+#define EXPECTED_NS 1
+#include "tst-dlmopen-rtld-unique6.h"
+
+#include "tst-dlmopen-std-do-test.c"
diff --git a/elf/tst-dlmopen-rtld-unique6.h b/elf/tst-dlmopen-rtld-unique6.h
new file mode 100644
index 0000000000..062f1f2277
--- /dev/null
+++ b/elf/tst-dlmopen-rtld-unique6.h
@@ -0,0 +1,51 @@ 
+static dlmopen_test_spec dltest[] =
+  {
+   {
+    .name = "dlmopen-isolate-unique:1:none--ns1--prep",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into NS1 with RTLD_ISOLATE",
+    .is_prep_stage = 1,
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_NEWLM,
+    .args.flags = RTLD_ISOLATE,
+    .handle_ns = EXPECTED_NS,
+    .free_ns = EXPECTED_NS,
+    .handle_type = DSO,
+    .preloaded = { },
+    .loaded = { [EXPECTED_NS] = DSO|NEW },
+   },
+   {
+    .name = "dlmopen-unique:1:nsX--nsX--FAIL",
+    .desc = "dlmopen a DF_GNU_1_UNIQUE dso into NSX when already there",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = EXPECTED_NS,
+    .failure = 1,
+    .preloaded = { [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared-unique:X:nsX--nsX--FAIL",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso into NSX when already there",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = EXPECTED_NS,
+    .args.flags = RTLD_SHARED,
+    .failure = 1,
+    .preloaded = { [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared-unique:0:nsX--ns0-nsX",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso already present in NS X",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = LM_ID_BASE,
+    .handle_ns = 0,
+    .handle_type = DSO,
+    .preloaded = { [EXPECTED_NS] = DSO },
+    .loaded = { [0] = DSO|NEW, [EXPECTED_NS] = DSO },
+   },
+   {
+    .name = "dlmopen-shared-unique:X:ns0-nsX--ns0-nsX--FAIL",
+    .desc = "dlmopen RTLD_SHARED a DF_GNU_1_UNIQUE dso already in the base NS and NS 1",
+    .args.dso_path = DSO_UNIQUE,
+    .args.ns = EXPECTED_NS,
+    .failure = 1,
+    .preloaded = { [0] = DSO, [EXPECTED_NS] = DSO },
+   },
+  };
diff --git a/elf/tst-dlmopen-sharedmod-norm.c b/elf/tst-dlmopen-sharedmod-norm.c
new file mode 100644
index 0000000000..915a256cd9
--- /dev/null
+++ b/elf/tst-dlmopen-sharedmod-norm.c
@@ -0,0 +1,34 @@ 
+#include "tst-dlmopen-modules.h"
+#include <signal.h>
+#include <time.h>
+#include <stdlib.h>
+
+static char user_name[255];
+
+/* This initialiser checks that relocation from DF_GNU_1_UNIQUE
+   dependencies (via DT_NEEDED) work properly - it will segfault
+   if _dl_lookup_symbol_x isn't handling that case. The main test
+   framework for dlmopen checks relocations/lookups via dlmopen.  */
+void __attribute__ ((constructor)) check_relocation (void)
+{
+  char *user = getenv("USER");
+
+  if (user == NULL)
+    return;
+
+  for (int i = 0; i < sizeof (user_name) && *(user + i); i++)
+    user_name[i] = *(user + i);
+
+  user[sizeof (user_name) - 1] = 0;
+}
+
+dlmopen_testresult *rtld_shared_testfunc (void)
+{
+  static dlmopen_testresult result;
+
+  result.name = "norm";
+  result.free = free;
+  result.timer_create = timer_create;
+
+  return &result;
+}
diff --git a/elf/tst-dlmopen-sharedmod-uniq.c b/elf/tst-dlmopen-sharedmod-uniq.c
new file mode 100644
index 0000000000..ec646c0d15
--- /dev/null
+++ b/elf/tst-dlmopen-sharedmod-uniq.c
@@ -0,0 +1,33 @@ 
+#include "tst-dlmopen-modules.h"
+#include <signal.h>
+#include <time.h>
+
+static char user_name[255];
+
+/* This initialiser checks that relocation from DF_GNU_1_UNIQUE
+   dependencies (via DT_NEEDED) work properly - it will segfault
+   if _dl_lookup_symbol_x isn't handling that case. The main test
+   framework for dlmopen checks relocations/lookups via dlmopen.  */
+void __attribute__ ((constructor)) check_relocation (void)
+{
+  char *user = getenv("USER");
+
+  if (user == NULL)
+    return;
+
+  for (int i = 0; i < sizeof (user_name) && *(user + i); i++)
+    user_name[i] = *(user + i);
+
+  user[sizeof (user_name) - 1] = 0;
+}
+
+dlmopen_testresult *rtld_shared_testfunc (void)
+{
+  static dlmopen_testresult result;
+
+  result.name = "noop";
+  result.free = free;
+  result.timer_create = timer_create;
+
+  return &result;
+}
diff --git a/elf/tst-dlmopen-std-do-test.c b/elf/tst-dlmopen-std-do-test.c
new file mode 100644
index 0000000000..9f1d3a3767
--- /dev/null
+++ b/elf/tst-dlmopen-std-do-test.c
@@ -0,0 +1,12 @@ 
+#include <array_length.h>
+
+static int
+do_test (void)
+{
+  for (int i = 0; i < array_length (dltest); i++)
+    if (!process_test_spec (&dltest[i]))
+      return 1;
+  return 0;
+}
+
+#include <support/test-driver.c>