diff mbox series

[3/3] elf: Process initially loaded objects first in _dl_fini (bug 30869)

Message ID 9cedffbd1938008ab1de4bfe36c6863341c0e224.1695113064.git.fweimer@redhat.com
State New
Headers show
Series Fine-tune ELF destructor ordering (bug 30869) | expand

Commit Message

Florian Weimer Sept. 19, 2023, 8:48 a.m. UTC
This matches historical practice when _dl_fini was using
_dl_sort_maps for ordering destructors.

The old approach gave more control to applications: The initially
loaded objects cannot themselves be unloaded with dlclose, but they
can dlclose other objects.  At that point, their destructors run,
in a more predictable ordering.  Only after all the initially loaded
objects have been destructed, destruct the dlopen'ed objects that
are still around (because they have not been subject to explicit
dlclose calls).

As an additional fix, change the order in which the ELF destructors
are scheduled.  Registration in elf/dl-init.c now happens after
completion of the ELF constructor, so that the match destructor
runs first if the object is executed from _dl_fini.  This helps
with destructor ordering when the destructor for an object is called
from _dl_fini, and that destructor in turn calls dlclose to unload
objects that have been opened.

The ordering tests are written manually because at this time,
scripts/dso-ordering-test.py does not support customizing ELF
constructors.

Fixes commit 6985865bc3ad5b23147ee73466583dd7fdf65892 ("elf: Always
call destructors in reverse constructor order (bug 30785)").
---
 elf/Makefile               | 21 +++++++++++++
 elf/dl-fini.c              | 49 ++++++++++++++++++++----------
 elf/dl-init.c              | 38 +++++++++++++++++-------
 elf/dso-sort-tests-1.def   |  2 +-
 elf/tst-finiorder1.c       | 52 ++++++++++++++++++++++++++++++++
 elf/tst-finiorder1mod1.c   | 43 +++++++++++++++++++++++++++
 elf/tst-finiorder1mod2.c   | 36 ++++++++++++++++++++++
 elf/tst-finiorder2.c       | 61 ++++++++++++++++++++++++++++++++++++++
 elf/tst-finiorder2mod1.c   | 49 ++++++++++++++++++++++++++++++
 elf/tst-finiorder2mod2.c   | 35 ++++++++++++++++++++++
 elf/tst-finiorder2mod3.c   | 36 ++++++++++++++++++++++
 sysdeps/generic/ldsodefs.h |  3 ++
 12 files changed, 399 insertions(+), 26 deletions(-)
 create mode 100644 elf/tst-finiorder1.c
 create mode 100644 elf/tst-finiorder1mod1.c
 create mode 100644 elf/tst-finiorder1mod2.c
 create mode 100644 elf/tst-finiorder2.c
 create mode 100644 elf/tst-finiorder2mod1.c
 create mode 100644 elf/tst-finiorder2mod2.c
 create mode 100644 elf/tst-finiorder2mod3.c
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 9176cbf1e3..9c4cfddbb3 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -412,6 +412,8 @@  tests += \
   tst-dlsym-error \
   tst-filterobj \
   tst-filterobj-dlopen \
+  tst-finiorder1 \
+  tst-finiorder2 \
   tst-glibc-hwcaps \
   tst-glibc-hwcaps-mask \
   tst-glibc-hwcaps-prepend \
@@ -834,6 +836,11 @@  modules-names += \
   tst-filterobj-filtee \
   tst-filterobj-flt \
   tst-finilazyfailmod \
+  tst-finiorder1mod1 \
+  tst-finiorder1mod2 \
+  tst-finiorder2mod1 \
+  tst-finiorder2mod2 \
+  tst-finiorder2mod3 \
   tst-globalmod2 \
   tst-initlazyfailmod \
   tst-initorder2a \
@@ -3018,3 +3025,17 @@  LDFLAGS-tst-dlclose-lazy-mod1.so = -Wl,-z,lazy,--no-as-needed
 $(objpfx)tst-dlclose-lazy-mod1.so: $(objpfx)tst-dlclose-lazy-mod2.so
 $(objpfx)tst-dlclose-lazy.out: \
   $(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.so
+
+LDFLAGS-tst-finiorder1 = -Wl,-export-dynamic
+tst-finiorder1mod1.so-no-z-defs = yes
+tst-finiorder1mod2.so-no-z-defs = yes
+$(objpfx)tst-finiorder1.out: \
+  $(objpfx)tst-finiorder1mod1.so $(objpfx)tst-finiorder1mod2.so
+
+LDFLAGS-tst-finiorder2 = -Wl,-export-dynamic
+tst-finiorder2mod1.so-no-z-defs = yes
+tst-finiorder2mod2.so-no-z-defs = yes
+tst-finiorder2mod3.so-no-z-defs = yes
+$(objpfx)tst-finiorder2: $(objpfx)tst-finiorder2mod1.so
+$(objpfx)tst-finiorder2.out: \
+  $(objpfx)tst-finiorder2mod2.so $(objpfx)tst-finiorder2mod3.so
diff --git a/elf/dl-fini.c b/elf/dl-fini.c
index e201d36651..97cd5cea5c 100644
--- a/elf/dl-fini.c
+++ b/elf/dl-fini.c
@@ -24,15 +24,42 @@ 
 void
 _dl_fini (void)
 {
-  /* Call destructors strictly in the reverse order of constructors.
-     This causes fewer surprises than some arbitrary reordering based
-     on new (relocation) dependencies.  None of the objects are
-     unmapped, so applications can deal with this if their DSOs remain
-     in a consistent state after destructors have run.  */
-
   /* Protect against concurrent loads and unloads.  */
   __rtld_lock_lock_recursive (GL(dl_load_lock));
 
+  /* Always call the destructor of the main program first.  This may
+     trigger dlclose calls, which runs other destructors early.  (The
+     main program is never added to _dl_init_called_list.)  Together
+     with the first loop below, the effect is similar to calling
+     dlclose on the main program (except that nothing is unloaded).  */
+#ifdef SHARED
+  Lmid_t last_ns = LM_ID_BASE;
+  _dl_audit_activity_nsid (LM_ID_BASE, LA_ACT_DELETE);
+#endif
+  /* There is no need to re-enable exceptions because _dl_fini
+     is not called from a context where exceptions are caught.  */
+  _dl_call_fini (GL(dl_ns)[LM_ID_BASE]._ns_loaded);
+  /* Auditing checkpoint: another object closed.  */
+  _dl_audit_objclose (GL(dl_ns)[LM_ID_BASE]._ns_loaded);
+
+  /* Give the initially loaded objects a chance to unload dlopen'ed
+     objects using dlclose.  */
+  for (struct link_map *l = _dl_init_called_library_list; l != NULL;
+       l = l->l_init_called_next)
+    {
+	_dl_call_fini (l);
+	_dl_audit_objclose (l);
+    }
+
+  /* At this point _dl_init_called_list contains objects that have
+     been dlopen'ed but not dlclose'd, and destructors for other
+     objects have been invoked.  Call destructors in the reverse order
+     of constructors (with an exception for audit modules).  This
+     causes fewer surprises than some arbitrary reordering based on
+     new (relocation) dependencies.  None of the objects are unmapped,
+     so applications can rely on DSOs remaining in a consistent state
+     after destructors have run.  */
+
   /* Ignore objects which are opened during shutdown.  */
   struct link_map *local_init_called_list = _dl_init_called_list;
 
@@ -52,7 +79,6 @@  _dl_fini (void)
      still run.  */
 #ifdef SHARED
   int last_pass = GLRO(dl_naudit) > 0;
-  Lmid_t last_ns = -1;
   for (int do_audit = 0; do_audit <= last_pass; ++do_audit)
 #endif
     for (struct link_map *l = local_init_called_list; l != NULL;
@@ -73,19 +99,12 @@  _dl_fini (void)
 	  }
 #endif
 
-	/* There is no need to re-enable exceptions because _dl_fini
-	   is not called from a context where exceptions are caught.  */
 	_dl_call_fini (l);
-
-#ifdef SHARED
-	/* Auditing checkpoint: another object closed.  */
 	_dl_audit_objclose (l);
-#endif
       }
 
 #ifdef SHARED
-  if (last_ns >= 0)
-    _dl_audit_activity_nsid (last_ns, LA_ACT_CONSISTENT);
+  _dl_audit_activity_nsid (last_ns, LA_ACT_CONSISTENT);
 
   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_STATISTICS))
     _dl_debug_printf ("\nruntime linker statistics:\n"
diff --git a/elf/dl-init.c b/elf/dl-init.c
index ffd05b7806..700c49fbc3 100644
--- a/elf/dl-init.c
+++ b/elf/dl-init.c
@@ -22,6 +22,7 @@ 
 #include <elf-initfini.h>
 
 struct link_map *_dl_init_called_list;
+struct link_map *_dl_init_called_library_list;
 
 static void
 call_init (struct link_map *l, int argc, char **argv, char **env)
@@ -48,16 +49,6 @@  call_init (struct link_map *l, int argc, char **argv, char **env)
      progress.)  */
   l->l_map_used = 1;
 
-  /* Record execution before starting any initializers.  This way, if
-     the initializers themselves call dlopen, their ELF destructors
-     will eventually be run before this object is destructed, matching
-     that their ELF constructors have run before this object was
-     constructed.  _dl_fini uses this list for audit callbacks, so
-     register objects on the list even if they do not have a
-     constructor.  */
-  l->l_init_called_next = _dl_init_called_list;
-  _dl_init_called_list = l;
-
   /* Check for object which constructors we do not run here.  */
   if (__builtin_expect (l->l_name[0], 'a') == '\0'
       && l->l_type == lt_executable)
@@ -89,6 +80,33 @@  call_init (struct link_map *l, int argc, char **argv, char **env)
       for (j = 0; j < jm; ++j)
 	((dl_init_t) addrs[j]) (argc, argv, env);
     }
+
+  /* Schedule invocation of the ELF destructors for this object.  Use
+     separate lists for initially loaded objects (lt_library) and
+     dlopened objects (lt_loaded), so that _dl_fini can give a chance
+     to lt_library objects to unload objects using dlclose.
+
+     Do this after completion of the constructors, so that the
+     initializers themselves call dlopen, this object has a chance to
+     influence destructor order with explicit dlclose calls if this
+     object is destructed via _dl_fini.  (If an object is destructed
+     earlier, using dlclose, objects it has dlopened are not eligible
+     for destruction, and the destructor of the explicitly dlclose'd
+     object runs first, but _dl_fini is different.)
+
+     Objects without constructors may still have destructors, and
+     _dl_fini uses this list for audit callbacks, so register objects
+     on the list even if they do not have a constructor.  */
+  if (l->l_type == lt_loaded)
+    {
+      l->l_init_called_next = _dl_init_called_list;
+      _dl_init_called_list = l;
+    }
+  else
+    {
+      l->l_init_called_next = _dl_init_called_library_list;
+      _dl_init_called_library_list = l;
+    }
 }
 
 
diff --git a/elf/dso-sort-tests-1.def b/elf/dso-sort-tests-1.def
index cefc474e4f..9d8610051c 100644
--- a/elf/dso-sort-tests-1.def
+++ b/elf/dso-sort-tests-1.def
@@ -63,4 +63,4 @@  output: .>{+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a(
 # object, that object is initialized last (and not unloaded prematurely).
 # Final destructor order is the opposite of constructor order.
 tst-bz28937: {+a;+b;-b;+c;%c};a->a1;a->a2;a2->a;b->b1;c->a1;c=>a1
-output: .>{+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}<c<a<a1<a2<.
+output: .>{+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}<.<c<a<a1<a2
diff --git a/elf/tst-finiorder1.c b/elf/tst-finiorder1.c
new file mode 100644
index 0000000000..852f010fab
--- /dev/null
+++ b/elf/tst-finiorder1.c
@@ -0,0 +1,52 @@ 
+/* _dl_fini ordering test.
+   Copyright (C) 2023 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/>.  */
+
+/* This test opens mod2 from the ELF constructor of mod1, and expects
+   the main program to be destructed first, followed by mod1, followed
+   by mod2: .>{mod1>mod2>}<.<mod1<mod2 in scripts/dso-ordering-test.py
+   terms.  */
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <unistd.h>
+
+/* Used to check destructor ordering.  */
+int fini_counter;
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf ("info: main program destructor invoked (PID %d)\n", (int) getpid ());
+
+  /* Destructor for the main program runs first.  */
+  TEST_COMPARE (fini_counter, 0);
+  fini_counter = 1;
+}
+
+static int
+do_test (void)
+{
+  /* Leak the handle, so that the destructor is invoked by _dl_fini.  */
+  (void) xdlopen ("tst-finiorder1mod1.so", RTLD_NOW);
+  return 0;
+}
+
+/* Marker value from the tst-finiorder1mod2.so destructor.  */
+#define EXPECTED_STATUS 17
+#include <support/test-driver.c>
diff --git a/elf/tst-finiorder1mod1.c b/elf/tst-finiorder1mod1.c
new file mode 100644
index 0000000000..5735196271
--- /dev/null
+++ b/elf/tst-finiorder1mod1.c
@@ -0,0 +1,43 @@ 
+/* _dl_fini ordering test.  Helper module.
+   Copyright (C) 2023 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/>.  */
+
+#include <stdio.h>
+#include <support/xdlfcn.h>
+#include <unistd.h>
+
+extern int fini_counter;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  /* Leak the handle, rely on _dl_fini constructor invocation.  */
+  (void) xdlopen ("tst-finiorder1mod2.so", RTLD_NOW);
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf ("info: tst-finiorder1mod1.so destructor invoked (%d)\n",
+          fini_counter);
+  if (fini_counter != 1)
+    {
+      puts ("error: unexpected fini_counter");
+      _exit (1);
+    }
+  fini_counter = 2;
+}
diff --git a/elf/tst-finiorder1mod2.c b/elf/tst-finiorder1mod2.c
new file mode 100644
index 0000000000..121faf559a
--- /dev/null
+++ b/elf/tst-finiorder1mod2.c
@@ -0,0 +1,36 @@ 
+/* _dl_fini ordering test.  Helper module producing exit status 17.
+   Copyright (C) 2021-2023 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/>.  */
+
+#include <stdio.h>
+#include <unistd.h>
+
+extern int fini_counter;
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf ("info: tst-finiorder1mod2.so destructor invoked (%d)\n",
+          fini_counter);
+  if (fini_counter != 2)
+    {
+      puts ("error: unexpected fini_counter");
+      _exit (1);
+    }
+  /* Marker value expected by the test driver.  */
+  _exit (17);
+}
diff --git a/elf/tst-finiorder2.c b/elf/tst-finiorder2.c
new file mode 100644
index 0000000000..3a5cfba9b1
--- /dev/null
+++ b/elf/tst-finiorder2.c
@@ -0,0 +1,61 @@ 
+/* _dl_fini ordering test.
+   Copyright (C) 2023 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/>.  */
+
+/* This test dlopens mod3, followed by mod2 (indirectly via the
+   directly linked mod1).  The main program to be destructed first,
+   followed by mod1, mod2, mod3: mod1>.>{mod2>mod3>}<.<mod1<mod2<mod3
+   in scripts/dso-ordering-test.py terms.  */
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <unistd.h>
+
+/* Used to check destructor ordering.  */
+int fini_counter;
+
+extern void init_mod1 (void);
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf ("info: main program destructor invoked (PID %d)\n", (int) getpid ());
+
+  /* Destructor for the main program runs first.  */
+  TEST_COMPARE (fini_counter, 0);
+  fini_counter = 1;
+}
+
+static int
+do_test (void)
+{
+  /* Leak the handle, so that the destructor is invoked by _dl_fini.
+     The other dlopen'ed object (tst-finiorder1mod2.so) is dlclose'd
+     explicitly.  */
+  (void) xdlopen ("tst-finiorder2mod3.so", RTLD_NOW);
+
+  /* Trigger a dlopen call from tst-finiorder2mod1.so, to load
+     tst-finiorder2mod2.so.  */
+  init_mod1 ();
+
+  return 0;
+}
+
+/* Marker value from the tst-finiorder2mod3.so destructor.  */
+#define EXPECTED_STATUS 17
+#include <support/test-driver.c>
diff --git a/elf/tst-finiorder2mod1.c b/elf/tst-finiorder2mod1.c
new file mode 100644
index 0000000000..fc6609554f
--- /dev/null
+++ b/elf/tst-finiorder2mod1.c
@@ -0,0 +1,49 @@ 
+/* _dl_fini ordering test.  Helper module.
+   Copyright (C) 2021-2023 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/>.  */
+
+#include <stdio.h>
+#include <support/xdlfcn.h>
+#include <unistd.h>
+
+extern int fini_counter;
+
+static void *mod2_handle;
+
+void
+init_mod1 (void)
+{
+  mod2_handle = xdlopen ("tst-finiorder2mod2.so", RTLD_NOW);
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  if (mod2_handle == NULL)
+    /* Do nothing in the test wrapper.  */
+    return;
+
+  printf ("info: tst-finiorder1mod1.so destructor invoked (%d)\n",
+          fini_counter);
+  if (fini_counter != 1)
+    {
+      puts ("error: unexpected fini_counter");
+      _exit (1);
+    }
+  fini_counter = 2;
+  xdlclose (mod2_handle);
+}
diff --git a/elf/tst-finiorder2mod2.c b/elf/tst-finiorder2mod2.c
new file mode 100644
index 0000000000..58a1c2b1c6
--- /dev/null
+++ b/elf/tst-finiorder2mod2.c
@@ -0,0 +1,35 @@ 
+/* _dl_fini ordering test.  Helper module.
+   Copyright (C) 2021-2023 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/>.  */
+
+#include <stdio.h>
+#include <unistd.h>
+
+extern int fini_counter;
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf ("info: tst-finiorder2mod2.so destructor invoked (%d)\n",
+          fini_counter);
+  if (fini_counter != 2)
+    {
+      puts ("error: unexpected fini_counter");
+      _exit (1);
+    }
+  fini_counter = 3;
+}
diff --git a/elf/tst-finiorder2mod3.c b/elf/tst-finiorder2mod3.c
new file mode 100644
index 0000000000..bcd7aadd1a
--- /dev/null
+++ b/elf/tst-finiorder2mod3.c
@@ -0,0 +1,36 @@ 
+/* _dl_fini ordering test.  Helper module producing exit status 17.
+   Copyright (C) 2021-2023 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/>.  */
+
+#include <stdio.h>
+#include <unistd.h>
+
+extern int fini_counter;
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf ("info: tst-finiorder2mod3.so destructor invoked (%d)\n",
+          fini_counter);
+  if (fini_counter != 3)
+    {
+      puts ("error: unexpected fini_counter");
+      _exit (1);
+    }
+  /* Marker value expected by the test driver.  */
+  _exit (17);
+}
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 82c5383c7e..050d9d8bc7 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -1041,6 +1041,9 @@  extern void _dl_init (struct link_map *main_map, int argc, char **argv,
    invocation.  */
 extern struct link_map *_dl_init_called_list attribute_hidden;
 
+/*  The same, but for initially loaded libraries.  */
+extern struct link_map *_dl_init_called_library_list attribute_hidden;
+
 /* Call the finalizer functions of all shared objects whose
    initializer functions have completed.  */
 extern void _dl_fini (void) attribute_hidden;