elf: Fix DTV gap reuse logic (BZ #27135)

This is updated version of the 572bd547d5 (reverted by 40ebfd016a)
that fixes the _dl_next_tls_modid issues.

This issue with 572bd547d5 patch is the DTV entry will be only
update on dl_open_worker() with the update_tls_slotinfo() call after
all dependencies are being processed by _dl_map_object_deps().  However
_dl_map_object_deps() itself might call _dl_next_tls_modid(), and since
the _dl_tls_dtv_slotinfo_list::map is not yet set the entry will be
wrongly reused.

This patch fixes by renaming the _dl_next_tls_modid() function to
_dl_assign_tls_modid() and by passing the link_map so it can set
the slotinfo value so a subsequente _dl_next_tls_modid() call will
see the entry as allocated.

The intermediary value is cleared up on remove_slotinfo() for the case
a library fails to load with RTLD_NOW.

This patch fixes BZ #27135.

Checked on x86_64-linux-gnu.

Reviewed-by: Szabolcs Nagy <szabolcs.nagy@arm.com>
This commit is contained in:
Adhemerval Zanella 2021-06-25 10:54:12 -03:00
parent 0e1f068108
commit ba33937be2
8 changed files with 349 additions and 33 deletions

View File

@ -253,6 +253,13 @@ one-hundred = $(foreach x,0 1 2 3 4 5 6 7 8 9, \
0$x 1$x 2$x 3$x 4$x 5$x 6$x 7$x 8$x 9$x) 0$x 1$x 2$x 3$x 4$x 5$x 6$x 7$x 8$x 9$x)
tst-tls-many-dynamic-modules := \ tst-tls-many-dynamic-modules := \
$(foreach n,$(one-hundred),tst-tls-manydynamic$(n)mod) $(foreach n,$(one-hundred),tst-tls-manydynamic$(n)mod)
tst-tls-many-dynamic-modules-dep-suffixes = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 \
14 15 16 17 18 19
tst-tls-many-dynamic-modules-dep = \
$(foreach n,$(tst-tls-many-dynamic-modules-dep-suffixes),tst-tls-manydynamic$(n)mod-dep)
tst-tls-many-dynamic-modules-dep-bad-suffixes = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
tst-tls-many-dynamic-modules-dep-bad = \
$(foreach n,$(tst-tls-many-dynamic-modules-dep-bad-suffixes),tst-tls-manydynamic$(n)mod-dep-bad)
extra-test-objs += $(tlsmod17a-modules:=.os) $(tlsmod18a-modules:=.os) \ extra-test-objs += $(tlsmod17a-modules:=.os) $(tlsmod18a-modules:=.os) \
tst-tlsalign-vars.o tst-tlsalign-vars.o
test-extras += tst-tlsmod17a tst-tlsmod18a tst-tlsalign-vars test-extras += tst-tlsmod17a tst-tlsmod18a tst-tlsalign-vars
@ -325,6 +332,8 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
tst-audit11mod1 tst-audit11mod2 tst-auditmod11 \ tst-audit11mod1 tst-audit11mod2 tst-auditmod11 \
tst-audit12mod1 tst-audit12mod2 tst-audit12mod3 tst-auditmod12 \ tst-audit12mod1 tst-audit12mod2 tst-audit12mod3 tst-auditmod12 \
tst-latepthreadmod $(tst-tls-many-dynamic-modules) \ tst-latepthreadmod $(tst-tls-many-dynamic-modules) \
$(tst-tls-many-dynamic-modules-dep) \
$(tst-tls-many-dynamic-modules-dep-bad) \
tst-nodelete-dlclose-dso tst-nodelete-dlclose-plugin \ tst-nodelete-dlclose-dso tst-nodelete-dlclose-plugin \
tst-main1mod tst-absolute-sym-lib \ tst-main1mod tst-absolute-sym-lib \
tst-absolute-zero-lib tst-big-note-lib tst-unwind-ctor-lib \ tst-absolute-zero-lib tst-big-note-lib tst-unwind-ctor-lib \
@ -1812,10 +1821,63 @@ $(objpfx)tst-rtld-help.out: $(objpfx)ld.so
$(evaluate-test) $(evaluate-test)
# Reuses tst-tls-many-dynamic-modules # Reuses tst-tls-many-dynamic-modules
$(patsubst %,$(objpfx)%.os,$(tst-tls-many-dynamic-modules-dep)): \
$(objpfx)tst-tls-manydynamic%mod-dep.os : tst-tls-manydynamicmod.c
$(compile-command.c) \
-DNAME=tls_global_$* -DSETTER=set_value_$* -DGETTER=get_value_$*
$(patsubst %,$(objpfx)%.os,$(tst-tls-many-dynamic-modules-dep-bad)): \
$(objpfx)tst-tls-manydynamic%mod-dep-bad.os : tst-tls-manydynamicmod.c
$(compile-command.c) \
-DNAME=tls_global_$* -DSETTER=set_value_$* -DGETTER=get_value_$*
tst-tls20mod-bad.so-no-z-defs = yes tst-tls20mod-bad.so-no-z-defs = yes
# Single dependency.
$(objpfx)tst-tls-manydynamic0mod-dep.so: $(objpfx)tst-tls-manydynamic1mod-dep.so
# Double dependencies.
$(objpfx)tst-tls-manydynamic2mod-dep.so: $(objpfx)tst-tls-manydynamic3mod-dep.so \
$(objpfx)tst-tls-manydynamic4mod-dep.so
# Double dependencies with each dependency depent of another module.
$(objpfx)tst-tls-manydynamic5mod-dep.so: $(objpfx)tst-tls-manydynamic6mod-dep.so \
$(objpfx)tst-tls-manydynamic7mod-dep.so
$(objpfx)tst-tls-manydynamic6mod-dep.so: $(objpfx)tst-tls-manydynamic8mod-dep.so
$(objpfx)tst-tls-manydynamic7mod-dep.so: $(objpfx)tst-tls-manydynamic8mod-dep.so
# Long chain with one double dependency in the middle
$(objpfx)tst-tls-manydynamic9mod-dep.so: $(objpfx)tst-tls-manydynamic10mod-dep.so \
$(objpfx)tst-tls-manydynamic11mod-dep.so
$(objpfx)tst-tls-manydynamic10mod-dep.so: $(objpfx)tst-tls-manydynamic12mod-dep.so
$(objpfx)tst-tls-manydynamic12mod-dep.so: $(objpfx)tst-tls-manydynamic13mod-dep.so
# Long chain with two double depedencies in the middle
$(objpfx)tst-tls-manydynamic14mod-dep.so: $(objpfx)tst-tls-manydynamic15mod-dep.so
$(objpfx)tst-tls-manydynamic15mod-dep.so: $(objpfx)tst-tls-manydynamic16mod-dep.so \
$(objpfx)tst-tls-manydynamic17mod-dep.so
$(objpfx)tst-tls-manydynamic16mod-dep.so: $(objpfx)tst-tls-manydynamic18mod-dep.so \
$(objpfx)tst-tls-manydynamic19mod-dep.so
# Same but with an invalid module.
# Single dependency.
$(objpfx)tst-tls-manydynamic0mod-dep-bad.so: $(objpfx)tst-tls20mod-bad.so
# Double dependencies.
$(objpfx)tst-tls-manydynamic1mod-dep-bad.so: $(objpfx)tst-tls-manydynamic2mod-dep-bad.so \
$(objpfx)tst-tls20mod-bad.so
# Double dependencies with each dependency depent of another module.
$(objpfx)tst-tls-manydynamic3mod-dep-bad.so: $(objpfx)tst-tls-manydynamic4mod-dep-bad.so \
$(objpfx)tst-tls-manydynamic5mod-dep-bad.so
$(objpfx)tst-tls-manydynamic4mod-dep-bad.so: $(objpfx)tst-tls20mod-bad.so
$(objpfx)tst-tls-manydynamic5mod-dep-bad.so: $(objpfx)tst-tls20mod-bad.so
# Long chain with one double dependency in the middle
$(objpfx)tst-tls-manydynamic6mod-dep-bad.so: $(objpfx)tst-tls-manydynamic7mod-dep-bad.so \
$(objpfx)tst-tls-manydynamic8mod-dep-bad.so
$(objpfx)tst-tls-manydynamic7mod-dep-bad.so: $(objpfx)tst-tls-manydynamic9mod-dep-bad.so
$(objpfx)tst-tls-manydynamic9mod-dep-bad.so: $(objpfx)tst-tls20mod-bad.so
# Long chain with two double depedencies in the middle
$(objpfx)tst-tls-manydynamic10mod-dep-bad.so: $(objpfx)tst-tls-manydynamic11mod-dep-bad.so
$(objpfx)tst-tls-manydynamic11mod-dep-bad.so: $(objpfx)tst-tls-manydynamic12mod-dep-bad.so \
$(objpfx)tst-tls-manydynamic13mod-dep-bad.so
$(objpfx)tst-tls-manydynamic12mod-dep-bad.so: $(objpfx)tst-tls-manydynamic14mod-dep-bad.so \
$(objpfx)tst-tls20mod-bad.so
$(objpfx)tst-tls20: $(shared-thread-library) $(objpfx)tst-tls20: $(shared-thread-library)
$(objpfx)tst-tls20.out: $(objpfx)tst-tls20mod-bad.so \ $(objpfx)tst-tls20.out: $(objpfx)tst-tls20mod-bad.so \
$(tst-tls-many-dynamic-modules:%=$(objpfx)%.so) $(tst-tls-many-dynamic-modules:%=$(objpfx)%.so) \
$(tst-tls-many-dynamic-modules-dep:%=$(objpfx)%.so) \
$(tst-tls-many-dynamic-modules-dep-bad:%=$(objpfx)%.so) \
# Reuses tst-tls-many-dynamic-modules # Reuses tst-tls-many-dynamic-modules
$(objpfx)tst-tls21: $(shared-thread-library) $(objpfx)tst-tls21: $(shared-thread-library)

View File

@ -77,8 +77,6 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
object that wasn't fully set up. */ object that wasn't fully set up. */
if (__glibc_likely (old_map != NULL)) if (__glibc_likely (old_map != NULL))
{ {
assert (old_map->l_tls_modid == idx);
/* Mark the entry as unused. These can be read concurrently. */ /* Mark the entry as unused. These can be read concurrently. */
atomic_store_relaxed (&listp->slotinfo[idx - disp].gen, atomic_store_relaxed (&listp->slotinfo[idx - disp].gen,
GL(dl_tls_generation) + 1); GL(dl_tls_generation) + 1);
@ -88,7 +86,11 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
/* If this is not the last currently used entry no need to look /* If this is not the last currently used entry no need to look
further. */ further. */
if (idx != GL(dl_tls_max_dtv_idx)) if (idx != GL(dl_tls_max_dtv_idx))
return true; {
/* There is an unused dtv entry in the middle. */
GL(dl_tls_dtv_gaps) = true;
return true;
}
} }
while (idx - disp > (disp == 0 ? 1 + GL(dl_tls_static_nelem) : 0)) while (idx - disp > (disp == 0 ? 1 + GL(dl_tls_static_nelem) : 0))

View File

@ -1498,7 +1498,7 @@ cannot enable executable stack as shared object requires");
not set up TLS data structures, so don't use them now. */ not set up TLS data structures, so don't use them now. */
|| __glibc_likely (GL(dl_tls_dtv_slotinfo_list) != NULL))) || __glibc_likely (GL(dl_tls_dtv_slotinfo_list) != NULL)))
/* Assign the next available module ID. */ /* Assign the next available module ID. */
l->l_tls_modid = _dl_next_tls_modid (); _dl_assign_tls_modid (l);
#ifdef DL_AFTER_LOAD #ifdef DL_AFTER_LOAD
DL_AFTER_LOAD (l); DL_AFTER_LOAD (l);

View File

@ -899,16 +899,6 @@ no more namespaces available for dlmopen()"));
state if relocation failed, for example. */ state if relocation failed, for example. */
if (args.map) if (args.map)
{ {
/* Maybe some of the modules which were loaded use TLS.
Since it will be removed in the following _dl_close call
we have to mark the dtv array as having gaps to fill the
holes. This is a pessimistic assumption which won't hurt
if not true. There is no need to do this when we are
loading the auditing DSOs since TLS has not yet been set
up. */
if ((mode & __RTLD_AUDIT) == 0)
GL(dl_tls_dtv_gaps) = true;
_dl_close_worker (args.map, true); _dl_close_worker (args.map, true);
/* All l_nodelete_pending objects should have been deleted /* All l_nodelete_pending objects should have been deleted

View File

@ -126,8 +126,8 @@ oom (void)
} }
size_t void
_dl_next_tls_modid (void) _dl_assign_tls_modid (struct link_map *l)
{ {
size_t result; size_t result;
@ -157,7 +157,11 @@ _dl_next_tls_modid (void)
} }
if (result - disp < runp->len) if (result - disp < runp->len)
break; {
/* Mark the entry as used, so any dependency see it. */
atomic_store_relaxed (&runp->slotinfo[result - disp].map, l);
break;
}
disp += runp->len; disp += runp->len;
} }
@ -184,17 +188,14 @@ _dl_next_tls_modid (void)
atomic_store_relaxed (&GL(dl_tls_max_dtv_idx), result); atomic_store_relaxed (&GL(dl_tls_max_dtv_idx), result);
} }
return result; l->l_tls_modid = result;
} }
size_t size_t
_dl_count_modids (void) _dl_count_modids (void)
{ {
/* It is rare that we have gaps; see elf/dl-open.c (_dl_open) where /* The count is the max unless dlclose or failed dlopen created gaps. */
we fail to load a module and unload it leaving a gap. If we don't
have gaps then the number of modids is the current maximum so
return that. */
if (__glibc_likely (!GL(dl_tls_dtv_gaps))) if (__glibc_likely (!GL(dl_tls_dtv_gaps)))
return GL(dl_tls_max_dtv_idx); return GL(dl_tls_max_dtv_idx);

View File

@ -1721,7 +1721,7 @@ dl_main (const ElfW(Phdr) *phdr,
/* Add the dynamic linker to the TLS list if it also uses TLS. */ /* Add the dynamic linker to the TLS list if it also uses TLS. */
if (GL(dl_rtld_map).l_tls_blocksize != 0) if (GL(dl_rtld_map).l_tls_blocksize != 0)
/* Assign a module ID. Do this before loading any audit modules. */ /* Assign a module ID. Do this before loading any audit modules. */
GL(dl_rtld_map).l_tls_modid = _dl_next_tls_modid (); _dl_assign_tls_modid (&GL(dl_rtld_map));
audit_list_add_dynamic_tag (&state.audit_list, main_map, DT_AUDIT); audit_list_add_dynamic_tag (&state.audit_list, main_map, DT_AUDIT);
audit_list_add_dynamic_tag (&state.audit_list, main_map, DT_DEPAUDIT); audit_list_add_dynamic_tag (&state.audit_list, main_map, DT_DEPAUDIT);

View File

@ -16,12 +16,14 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */ <http://www.gnu.org/licenses/>. */
#include <array_length.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <pthread.h> #include <pthread.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <support/check.h> #include <support/check.h>
#include <support/support.h> #include <support/support.h>
#include <support/test-driver.h>
#include <support/xdlfcn.h> #include <support/xdlfcn.h>
#include <support/xthread.h> #include <support/xthread.h>
@ -59,28 +61,75 @@ access (int i)
char *buf = xasprintf ("tls_global_%02d", i); char *buf = xasprintf ("tls_global_%02d", i);
dlerror (); dlerror ();
int *p = dlsym (mod[i], buf); int *p = dlsym (mod[i], buf);
printf ("mod[%d]: &tls = %p\n", i, p); if (test_verbose)
printf ("mod[%d]: &tls = %p\n", i, p);
if (p == NULL) if (p == NULL)
FAIL_EXIT1 ("dlsym failed: %s\n", dlerror ()); FAIL_EXIT1 ("dlsym failed: %s\n", dlerror ());
TEST_COMPARE (*p, 0);
++*p; ++*p;
free (buf); free (buf);
} }
static void
access_mod (const char *modname, void *mod, int i)
{
char *modsym = xasprintf ("tls_global_%d", i);
dlerror ();
int *p = dlsym (mod, modsym);
if (test_verbose)
printf ("%s: &tls = %p\n", modname, p);
if (p == NULL)
FAIL_EXIT1 ("dlsym failed: %s\n", dlerror ());
TEST_COMPARE (*p, 0);
++*p;
free (modsym);
}
static void
access_dep (int i)
{
char *modname = xasprintf ("tst-tls-manydynamic%dmod-dep.so", i);
void *moddep = xdlopen (modname, RTLD_LAZY);
access_mod (modname, moddep, i);
free (modname);
xdlclose (moddep);
}
struct start_args
{
const char *modname;
void *mod;
int modi;
int ndeps;
const int *deps;
};
static void * static void *
start (void *a) start (void *a)
{ {
struct start_args *args = a;
for (int i = 0; i < NMOD; i++) for (int i = 0; i < NMOD; i++)
if (mod[i] != NULL) if (mod[i] != NULL)
access (i); access (i);
if (args != NULL)
{
access_mod (args->modname, args->mod, args->modi);
for (int n = 0; n < args->ndeps; n++)
access_dep (args->deps[n]);
}
return 0; return 0;
} }
static int /* This test gaps with shared libraries with dynamic TLS that has no
do_test (void) dependencies. The DTV gap is set with by trying to load an invalid
module, the entry should be used on the dlopen. */
static void
do_test_no_depedency (void)
{ {
int i; for (int i = 0; i < NMOD; i++)
for (i = 0; i < NMOD; i++)
{ {
load_mod (i); load_mod (i);
/* Bump the generation of mod[0] without using new dtv slot. */ /* Bump the generation of mod[0] without using new dtv slot. */
@ -91,8 +140,220 @@ do_test (void)
pthread_t t = xpthread_create (0, start, 0); pthread_t t = xpthread_create (0, start, 0);
xpthread_join (t); xpthread_join (t);
} }
for (i = 0; i < NMOD; i++) for (int i = 0; i < NMOD; i++)
unload_mod (i); unload_mod (i);
}
/* The following test check DTV gaps handling with shared libraries that has
dependencies. It defines 5 different sets:
1. Single dependency:
mod0 -> mod1
2. Double dependency:
mod2 -> [mod3,mod4]
3. Double dependency with each dependency depent of another module:
mod5 -> [mod6,mod7] -> mod8
4. Long chain with one double dependency in the middle:
mod9 -> [mod10, mod11] -> mod12 -> mod13
5. Long chain with two double depedencies in the middle:
mod14 -> mod15 -> [mod16, mod17]
mod15 -> [mod18, mod19]
This does not cover all the possible gaps and configuration, but it
should check if different dynamic shared sets are placed correctly in
different gaps configurations. */
static int
nmodules (uint32_t v)
{
unsigned int r = 0;
while (v >>= 1)
r++;
return r + 1;
}
static inline bool
is_mod_set (uint32_t g, uint32_t n)
{
return (1U << (n - 1)) & g;
}
static void
print_gap (uint32_t g)
{
if (!test_verbose)
return;
printf ("gap: ");
int nmods = nmodules (g);
for (int n = 1; n <= nmods; n++)
printf ("%c", ((1 << (n - 1)) & g) == 0 ? 'G' : 'M');
printf ("\n");
}
static void
do_test_dependency (void)
{
/* Maps the module and its dependencies, use thread to access the TLS on
each loaded module. */
static const int tlsmanydeps0[] = { 1 };
static const int tlsmanydeps1[] = { 3, 4 };
static const int tlsmanydeps2[] = { 6, 7, 8 };
static const int tlsmanydeps3[] = { 10, 11, 12 };
static const int tlsmanydeps4[] = { 15, 16, 17, 18, 19 };
static const struct tlsmanydeps_t
{
int modi;
int ndeps;
const int *deps;
} tlsmanydeps[] =
{
{ 0, array_length (tlsmanydeps0), tlsmanydeps0 },
{ 2, array_length (tlsmanydeps1), tlsmanydeps1 },
{ 5, array_length (tlsmanydeps2), tlsmanydeps2 },
{ 9, array_length (tlsmanydeps3), tlsmanydeps3 },
{ 14, array_length (tlsmanydeps4), tlsmanydeps4 },
};
/* The gap configuration is defined as a bitmap: the bit set represents a
loaded module prior the tests execution, while a bit unsed is a module
unloaded. Not all permtation will show gaps, but it is simpler than
define each one independently. */
for (uint32_t g = 0; g < 64; g++)
{
print_gap (g);
int nmods = nmodules (g);
int mods[nmods];
/* We use '0' as indication for a gap, to avoid the dlclose on iteration
cleanup. */
for (int n = 1; n <= nmods; n++)
{
load_mod (n);
mods[n] = n;
}
for (int n = 1; n <= nmods; n++)
{
if (!is_mod_set (g, n))
{
unload_mod (n);
mods[n] = 0;
}
}
for (int t = 0; t < array_length (tlsmanydeps); t++)
{
char *moddepname = xasprintf ("tst-tls-manydynamic%dmod-dep.so",
tlsmanydeps[t].modi);
void *moddep = xdlopen (moddepname, RTLD_LAZY);
/* Access TLS in all loaded modules. */
struct start_args args =
{
moddepname,
moddep,
tlsmanydeps[t].modi,
tlsmanydeps[t].ndeps,
tlsmanydeps[t].deps
};
pthread_t t = xpthread_create (0, start, &args);
xpthread_join (t);
free (moddepname);
xdlclose (moddep);
}
for (int n = 1; n <= nmods; n++)
if (mods[n] != 0)
unload_mod (n);
}
}
/* The following test check DTV gaps handling with shared libraries that has
invalid dependencies. It defines 5 different sets:
1. Single dependency:
mod0 -> invalid
2. Double dependency:
mod1 -> [mod2,invalid]
3. Double dependency with each dependency depent of another module:
mod3 -> [mod4,mod5] -> invalid
4. Long chain with one double dependency in the middle:
mod6 -> [mod7, mod8] -> mod12 -> invalid
5. Long chain with two double depedencies in the middle:
mod10 -> mod11 -> [mod12, mod13]
mod12 -> [mod14, invalid]
This does not cover all the possible gaps and configuration, but it
should check if different dynamic shared sets are placed correctly in
different gaps configurations. */
static void
do_test_invalid_dependency (bool bind_now)
{
static const int tlsmanydeps[] = { 0, 1, 3, 6, 10 };
/* The gap configuration is defined as a bitmap: the bit set represents a
loaded module prior the tests execution, while a bit unsed is a module
unloaded. Not all permtation will show gaps, but it is simpler than
define each one independently. */
for (uint32_t g = 0; g < 64; g++)
{
print_gap (g);
int nmods = nmodules (g);
int mods[nmods];
/* We use '0' as indication for a gap, to avoid the dlclose on iteration
cleanup. */
for (int n = 1; n <= nmods; n++)
{
load_mod (n);
mods[n] = n;
}
for (int n = 1; n <= nmods; n++)
{
if (!is_mod_set (g, n))
{
unload_mod (n);
mods[n] = 0;
}
}
for (int t = 0; t < array_length (tlsmanydeps); t++)
{
char *moddepname = xasprintf ("tst-tls-manydynamic%dmod-dep-bad.so",
tlsmanydeps[t]);
void *moddep;
if (bind_now)
{
moddep = dlopen (moddepname, RTLD_NOW);
TEST_VERIFY (moddep == 0);
}
else
moddep = dlopen (moddepname, RTLD_LAZY);
/* Access TLS in all loaded modules. */
pthread_t t = xpthread_create (0, start, NULL);
xpthread_join (t);
free (moddepname);
if (!bind_now)
xdlclose (moddep);
}
for (int n = 1; n <= nmods; n++)
if (mods[n] != 0)
unload_mod (n);
}
}
static int
do_test (void)
{
do_test_no_depedency ();
do_test_dependency ();
do_test_invalid_dependency (true);
do_test_invalid_dependency (false);
return 0; return 0;
} }

View File

@ -1171,8 +1171,8 @@ extern ElfW(Addr) _dl_sysdep_start (void **start_argptr,
extern void _dl_sysdep_start_cleanup (void) attribute_hidden; extern void _dl_sysdep_start_cleanup (void) attribute_hidden;
/* Determine next available module ID. */ /* Determine next available module ID and set the L l_tls_modid. */
extern size_t _dl_next_tls_modid (void) attribute_hidden; extern void _dl_assign_tls_modid (struct link_map *l) attribute_hidden;
/* Count the modules with TLS segments. */ /* Count the modules with TLS segments. */
extern size_t _dl_count_modids (void) attribute_hidden; extern size_t _dl_count_modids (void) attribute_hidden;