mirror of
git://sourceware.org/git/glibc.git
synced 2026-09-09 05:59:27 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c59ed1f74 | ||
|
|
93e07b2f9e | ||
|
|
f0ead7b63b | ||
|
|
7f1de8894b |
+24
-2
@@ -193,7 +193,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \
|
||||
tst-debug1 tst-main1 tst-absolute-sym tst-absolute-zero tst-big-note \
|
||||
tst-unwind-ctor tst-unwind-main tst-audit13 \
|
||||
tst-sonamemove-link tst-sonamemove-dlopen tst-dlopen-tlsmodid \
|
||||
tst-dlopen-self
|
||||
tst-dlopen-self tst-initfinilazyfail tst-dlopenfail
|
||||
# reldep9
|
||||
tests-internal += loadtest unload unload2 circleload1 \
|
||||
neededtest neededtest2 neededtest3 neededtest4 \
|
||||
@@ -281,7 +281,9 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
|
||||
tst-main1mod tst-libc_dlvsym-dso tst-absolute-sym-lib \
|
||||
tst-absolute-zero-lib tst-big-note-lib tst-unwind-ctor-lib \
|
||||
tst-audit13mod1 tst-sonamemove-linkmod1 \
|
||||
tst-sonamemove-runmod1 tst-sonamemove-runmod2
|
||||
tst-sonamemove-runmod1 tst-sonamemove-runmod2 \
|
||||
tst-initlazyfailmod tst-finilazyfailmod \
|
||||
tst-dlopenfailmod1 tst-dlopenfaillinkmod tst-dlopenfailmod2
|
||||
# Most modules build with _ISOMAC defined, but those filtered out
|
||||
# depend on internal headers.
|
||||
modules-names-tests = $(filter-out ifuncmod% tst-libc_dlvsym-dso tst-tlsmod%,\
|
||||
@@ -1569,3 +1571,23 @@ $(objpfx)tst-big-note-lib.so: $(objpfx)tst-big-note-lib.o
|
||||
$(objpfx)tst-unwind-ctor: $(objpfx)tst-unwind-ctor-lib.so
|
||||
|
||||
CFLAGS-tst-unwind-main.c += -funwind-tables -DUSE_PTHREADS=0
|
||||
|
||||
$(objpfx)tst-initfinilazyfail: $(libdl)
|
||||
$(objpfx)tst-initfinilazyfail.out: \
|
||||
$(objpfx)tst-initlazyfailmod.so $(objpfx)tst-finilazyfailmod.so
|
||||
# Override -z defs, so that we can reference an undefined symbol.
|
||||
# Force lazy binding for the same reason.
|
||||
LDFLAGS-tst-initlazyfailmod.so = \
|
||||
-Wl,-z,lazy -Wl,--unresolved-symbols=ignore-all
|
||||
LDFLAGS-tst-finilazyfailmod.so = \
|
||||
-Wl,-z,lazy -Wl,--unresolved-symbols=ignore-all
|
||||
|
||||
$(objpfx)tst-dlopenfail: $(libdl)
|
||||
$(objpfx)tst-dlopenfail.out: \
|
||||
$(objpfx)tst-dlopenfailmod1.so $(objpfx)tst-dlopenfailmod2.so
|
||||
# Order matters here. tst-dlopenfaillinkmod.so's soname ensures
|
||||
# a run-time loader failure.
|
||||
$(objpfx)tst-dlopenfailmod1.so: \
|
||||
$(shared-thread-library) $(objpfx)tst-dlopenfaillinkmod.so
|
||||
LDFLAGS-tst-dlopenfaillinkmod.so = -Wl,-soname,tst-dlopenfail-missingmod.so
|
||||
$(objpfx)tst-dlopenfailmod2.so: $(shared-thread-library)
|
||||
|
||||
+32
-28
@@ -106,6 +106,30 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Invoke dstructors for CLOSURE (a struct link_map *). Called with
|
||||
exception handling temporarily disabled, to make errors fatal. */
|
||||
static void
|
||||
call_destructors (void *closure)
|
||||
{
|
||||
struct link_map *map = closure;
|
||||
|
||||
if (map->l_info[DT_FINI_ARRAY] != NULL)
|
||||
{
|
||||
ElfW(Addr) *array =
|
||||
(ElfW(Addr) *) (map->l_addr
|
||||
+ map->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
|
||||
unsigned int sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
|
||||
/ sizeof (ElfW(Addr)));
|
||||
|
||||
while (sz-- > 0)
|
||||
((fini_t) array[sz]) ();
|
||||
}
|
||||
|
||||
/* Next try the old-style destructor. */
|
||||
if (map->l_info[DT_FINI] != NULL)
|
||||
DL_CALL_DT_FINI (map, ((void *) map->l_addr
|
||||
+ map->l_info[DT_FINI]->d_un.d_ptr));
|
||||
}
|
||||
|
||||
void
|
||||
_dl_close_worker (struct link_map *map, bool force)
|
||||
@@ -144,14 +168,6 @@ _dl_close_worker (struct link_map *map, bool force)
|
||||
char done[nloaded];
|
||||
struct link_map *maps[nloaded];
|
||||
|
||||
/* Clear DF_1_NODELETE to force object deletion. We don't need to touch
|
||||
l_tls_dtor_count because forced object deletion only happens when an
|
||||
error occurs during object load. Destructor registration for TLS
|
||||
non-POD objects should not have happened till then for this
|
||||
object. */
|
||||
if (force)
|
||||
map->l_flags_1 &= ~DF_1_NODELETE;
|
||||
|
||||
/* Run over the list and assign indexes to the link maps and enter
|
||||
them into the MAPS array. */
|
||||
int idx = 0;
|
||||
@@ -181,7 +197,7 @@ _dl_close_worker (struct link_map *map, bool force)
|
||||
/* Check whether this object is still used. */
|
||||
if (l->l_type == lt_loaded
|
||||
&& l->l_direct_opencount == 0
|
||||
&& (l->l_flags_1 & DF_1_NODELETE) == 0
|
||||
&& l->l_nodelete != link_map_nodelete_active
|
||||
/* See CONCURRENCY NOTES in cxa_thread_atexit_impl.c to know why
|
||||
acquire is sufficient and correct. */
|
||||
&& atomic_load_acquire (&l->l_tls_dtor_count) == 0
|
||||
@@ -264,10 +280,11 @@ _dl_close_worker (struct link_map *map, bool force)
|
||||
if (!used[i])
|
||||
{
|
||||
assert (imap->l_type == lt_loaded
|
||||
&& (imap->l_flags_1 & DF_1_NODELETE) == 0);
|
||||
&& imap->l_nodelete != link_map_nodelete_active);
|
||||
|
||||
/* Call its termination function. Do not do it for
|
||||
half-cooked objects. */
|
||||
half-cooked objects. Temporarily disable exception
|
||||
handling, so that errors are fatal. */
|
||||
if (imap->l_init_called)
|
||||
{
|
||||
/* When debugging print a message first. */
|
||||
@@ -276,22 +293,9 @@ _dl_close_worker (struct link_map *map, bool force)
|
||||
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
|
||||
imap->l_name, nsid);
|
||||
|
||||
if (imap->l_info[DT_FINI_ARRAY] != NULL)
|
||||
{
|
||||
ElfW(Addr) *array =
|
||||
(ElfW(Addr) *) (imap->l_addr
|
||||
+ imap->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
|
||||
unsigned int sz = (imap->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
|
||||
/ sizeof (ElfW(Addr)));
|
||||
|
||||
while (sz-- > 0)
|
||||
((fini_t) array[sz]) ();
|
||||
}
|
||||
|
||||
/* Next try the old-style destructor. */
|
||||
if (imap->l_info[DT_FINI] != NULL)
|
||||
DL_CALL_DT_FINI (imap, ((void *) imap->l_addr
|
||||
+ imap->l_info[DT_FINI]->d_un.d_ptr));
|
||||
if (imap->l_info[DT_FINI_ARRAY] != NULL
|
||||
|| imap->l_info[DT_FINI] != NULL)
|
||||
_dl_catch_exception (NULL, call_destructors, imap);
|
||||
}
|
||||
|
||||
#ifdef SHARED
|
||||
@@ -816,7 +820,7 @@ _dl_close (void *_map)
|
||||
before we took the lock. There is no way to detect this (see below)
|
||||
so we proceed assuming this isn't the case. First see whether we
|
||||
can remove the object at all. */
|
||||
if (__glibc_unlikely (map->l_flags_1 & DF_1_NODELETE))
|
||||
if (__glibc_unlikely (map->l_nodelete == link_map_nodelete_active))
|
||||
{
|
||||
/* Nope. Do nothing. */
|
||||
__rtld_lock_unlock_recursive (GL(dl_load_lock));
|
||||
|
||||
@@ -173,6 +173,18 @@ int
|
||||
_dl_catch_exception (struct dl_exception *exception,
|
||||
void (*operate) (void *), void *args)
|
||||
{
|
||||
/* If exception is NULL, temporarily disable exception handling.
|
||||
Exceptions during operate (args) are fatal. */
|
||||
if (exception == NULL)
|
||||
{
|
||||
struct catch *const old = catch_hook;
|
||||
catch_hook = NULL;
|
||||
operate (args);
|
||||
/* If we get here, the operation was successful. */
|
||||
catch_hook = old;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need not handle `receiver' since setting a `catch' is handled
|
||||
before it. */
|
||||
|
||||
|
||||
+61
-19
@@ -192,9 +192,10 @@ enter_unique_sym (struct unique_sym *table, size_t size,
|
||||
Return the matching symbol in RESULT. */
|
||||
static void
|
||||
do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
|
||||
const struct link_map *map, struct sym_val *result,
|
||||
struct link_map *map, struct sym_val *result,
|
||||
int type_class, const ElfW(Sym) *sym, const char *strtab,
|
||||
const ElfW(Sym) *ref, const struct link_map *undef_map)
|
||||
const ElfW(Sym) *ref, const struct link_map *undef_map,
|
||||
int flags)
|
||||
{
|
||||
/* We have to determine whether we already found a symbol with this
|
||||
name before. If not then we have to add it to the search table.
|
||||
@@ -222,7 +223,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
|
||||
copy from the copy addressed through the
|
||||
relocation. */
|
||||
result->s = sym;
|
||||
result->m = (struct link_map *) map;
|
||||
result->m = map;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -311,9 +312,19 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
|
||||
new_hash, strtab + sym->st_name, sym, map);
|
||||
|
||||
if (map->l_type == lt_loaded)
|
||||
/* Make sure we don't unload this object by
|
||||
setting the appropriate flag. */
|
||||
((struct link_map *) map)->l_flags_1 |= DF_1_NODELETE;
|
||||
{
|
||||
/* Make sure we don't unload this object by
|
||||
setting the appropriate flag. */
|
||||
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
|
||||
&& map->l_nodelete == link_map_nodelete_inactive)
|
||||
_dl_debug_printf ("\
|
||||
marking %s [%lu] as NODELETE due to unique symbol\n",
|
||||
map->l_name, map->l_ns);
|
||||
if (flags & DL_LOOKUP_INSIDE_DLOPEN)
|
||||
map->l_nodelete = link_map_nodelete_pending;
|
||||
else
|
||||
map->l_nodelete = link_map_nodelete_active;
|
||||
}
|
||||
}
|
||||
++tab->n_elements;
|
||||
|
||||
@@ -525,8 +536,9 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
|
||||
return 1;
|
||||
|
||||
case STB_GNU_UNIQUE:;
|
||||
do_lookup_unique (undef_name, new_hash, map, result, type_class,
|
||||
sym, strtab, ref, undef_map);
|
||||
do_lookup_unique (undef_name, new_hash, (struct link_map *) map,
|
||||
result, type_class, sym, strtab, ref,
|
||||
undef_map, flags);
|
||||
return 1;
|
||||
|
||||
default:
|
||||
@@ -568,9 +580,13 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
|
||||
if (undef_map == map)
|
||||
return 0;
|
||||
|
||||
/* Avoid references to objects which cannot be unloaded anyway. */
|
||||
/* Avoid references to objects which cannot be unloaded anyway. We
|
||||
do not need to record dependencies if this object goes away
|
||||
during dlopen failure, either. IFUNC resolvers with relocation
|
||||
dependencies may pick an dependency which can be dlclose'd, but
|
||||
such IFUNC resolvers are undefined anyway. */
|
||||
assert (map->l_type == lt_loaded);
|
||||
if ((map->l_flags_1 & DF_1_NODELETE) != 0)
|
||||
if (map->l_nodelete != link_map_nodelete_inactive)
|
||||
return 0;
|
||||
|
||||
struct link_map_reldeps *l_reldeps
|
||||
@@ -678,16 +694,33 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
|
||||
|
||||
/* Redo the NODELETE check, as when dl_load_lock wasn't held
|
||||
yet this could have changed. */
|
||||
if ((map->l_flags_1 & DF_1_NODELETE) != 0)
|
||||
if (map->l_nodelete != link_map_nodelete_inactive)
|
||||
goto out;
|
||||
|
||||
/* If the object with the undefined reference cannot be removed ever
|
||||
just make sure the same is true for the object which contains the
|
||||
definition. */
|
||||
if (undef_map->l_type != lt_loaded
|
||||
|| (undef_map->l_flags_1 & DF_1_NODELETE) != 0)
|
||||
|| (undef_map->l_nodelete != link_map_nodelete_inactive))
|
||||
{
|
||||
map->l_flags_1 |= DF_1_NODELETE;
|
||||
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
|
||||
&& map->l_nodelete == link_map_nodelete_inactive)
|
||||
{
|
||||
if (undef_map->l_name[0] == '\0')
|
||||
_dl_debug_printf ("\
|
||||
marking %s [%lu] as NODELETE due to reference to main program\n",
|
||||
map->l_name, map->l_ns);
|
||||
else
|
||||
_dl_debug_printf ("\
|
||||
marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
|
||||
map->l_name, map->l_ns,
|
||||
undef_map->l_name, undef_map->l_ns);
|
||||
}
|
||||
|
||||
if (flags & DL_LOOKUP_INSIDE_DLOPEN)
|
||||
map->l_nodelete = link_map_nodelete_pending;
|
||||
else
|
||||
map->l_nodelete = link_map_nodelete_active;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -712,7 +745,18 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
|
||||
no fatal problem. We simply make sure the referenced object
|
||||
cannot be unloaded. This is semantically the correct
|
||||
behavior. */
|
||||
map->l_flags_1 |= DF_1_NODELETE;
|
||||
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
|
||||
&& map->l_nodelete == link_map_nodelete_inactive)
|
||||
_dl_debug_printf ("\
|
||||
marking %s [%lu] as NODELETE due to memory allocation failure\n",
|
||||
map->l_name, map->l_ns);
|
||||
if (flags & DL_LOOKUP_INSIDE_DLOPEN)
|
||||
/* In case of non-lazy binding, we could actually
|
||||
report the memory allocation error, but for now, we
|
||||
use the conservative approximation as well. */
|
||||
map->l_nodelete = link_map_nodelete_pending;
|
||||
else
|
||||
map->l_nodelete = link_map_nodelete_active;
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
@@ -792,11 +836,9 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
|
||||
|
||||
bump_num_relocations ();
|
||||
|
||||
/* No other flag than DL_LOOKUP_ADD_DEPENDENCY or DL_LOOKUP_GSCOPE_LOCK
|
||||
is allowed if we look up a versioned symbol. */
|
||||
assert (version == NULL
|
||||
|| (flags & ~(DL_LOOKUP_ADD_DEPENDENCY | DL_LOOKUP_GSCOPE_LOCK))
|
||||
== 0);
|
||||
/* DL_LOOKUP_RETURN_NEWEST does not make sense for versioned
|
||||
lookups. */
|
||||
assert (version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST));
|
||||
|
||||
size_t i = 0;
|
||||
if (__glibc_unlikely (skip_map != NULL))
|
||||
|
||||
+420
-180
@@ -33,6 +33,7 @@
|
||||
#include <stap-probe.h>
|
||||
#include <atomic.h>
|
||||
#include <libc-internal.h>
|
||||
#include <array_length.h>
|
||||
|
||||
#include <dl-dst.h>
|
||||
#include <dl-prop.h>
|
||||
@@ -50,22 +51,38 @@ struct dl_open_args
|
||||
struct link_map *map;
|
||||
/* Namespace ID. */
|
||||
Lmid_t nsid;
|
||||
|
||||
/* Original value of _ns_global_scope_pending_adds. Set by
|
||||
dl_open_worker. Only valid if nsid is a real namespace
|
||||
(non-negative). */
|
||||
unsigned int original_global_scope_pending_adds;
|
||||
|
||||
/* Original parameters to the program and the current environment. */
|
||||
int argc;
|
||||
char **argv;
|
||||
char **env;
|
||||
};
|
||||
|
||||
|
||||
static int
|
||||
add_to_global (struct link_map *new)
|
||||
/* Called in case the global scope cannot be extended. */
|
||||
static void __attribute__ ((noreturn))
|
||||
add_to_global_prepare_failure (struct link_map *new)
|
||||
{
|
||||
struct link_map **new_global;
|
||||
unsigned int to_add = 0;
|
||||
unsigned int cnt;
|
||||
_dl_signal_error (ENOMEM, new->l_libname->name, NULL,
|
||||
N_ ("cannot extend global scope"));
|
||||
}
|
||||
|
||||
/* Grow the global scope array for the namespace, so that all the new
|
||||
global objects can be added later in add_to_global_finish, without
|
||||
risk of memory allocation failure. add_to_global_prepare raises
|
||||
exceptions for memory allocation errors. */
|
||||
static void
|
||||
add_to_global_prepare (struct link_map *new)
|
||||
{
|
||||
struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
|
||||
|
||||
/* Count the objects we have to put in the global scope. */
|
||||
for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
|
||||
unsigned int to_add = 0;
|
||||
for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
|
||||
if (new->l_searchlist.r_list[cnt]->l_global == 0)
|
||||
++to_add;
|
||||
|
||||
@@ -83,47 +100,51 @@ add_to_global (struct link_map *new)
|
||||
in an realloc() call. Therefore we allocate a completely new
|
||||
array the first time we have to add something to the locale scope. */
|
||||
|
||||
struct link_namespaces *ns = &GL(dl_ns)[new->l_ns];
|
||||
if (__builtin_add_overflow (ns->_ns_global_scope_pending_adds, to_add,
|
||||
&ns->_ns_global_scope_pending_adds))
|
||||
add_to_global_prepare_failure (new);
|
||||
|
||||
unsigned int new_size = 0; /* 0 means no new allocation. */
|
||||
void *old_global = NULL; /* Old allocation if free-able. */
|
||||
|
||||
/* Minimum required element count for resizing. Adjusted below for
|
||||
an exponential resizing policy. */
|
||||
size_t required_new_size;
|
||||
if (__builtin_add_overflow (ns->_ns_main_searchlist->r_nlist,
|
||||
ns->_ns_global_scope_pending_adds,
|
||||
&required_new_size))
|
||||
add_to_global_prepare_failure (new);
|
||||
|
||||
if (ns->_ns_global_scope_alloc == 0)
|
||||
{
|
||||
/* This is the first dynamic object given global scope. */
|
||||
ns->_ns_global_scope_alloc
|
||||
= ns->_ns_main_searchlist->r_nlist + to_add + 8;
|
||||
new_global = (struct link_map **)
|
||||
malloc (ns->_ns_global_scope_alloc * sizeof (struct link_map *));
|
||||
if (__builtin_add_overflow (required_new_size, 8, &new_size))
|
||||
add_to_global_prepare_failure (new);
|
||||
}
|
||||
else if (required_new_size > ns->_ns_global_scope_alloc)
|
||||
{
|
||||
if (__builtin_mul_overflow (required_new_size, 2, &new_size))
|
||||
add_to_global_prepare_failure (new);
|
||||
|
||||
/* The old array was allocated with our malloc, not the minimal
|
||||
malloc. */
|
||||
old_global = ns->_ns_main_searchlist->r_list;
|
||||
}
|
||||
|
||||
if (new_size > 0)
|
||||
{
|
||||
size_t allocation_size;
|
||||
if (__builtin_mul_overflow (new_size, sizeof (struct link_map *),
|
||||
&allocation_size))
|
||||
add_to_global_prepare_failure (new);
|
||||
struct link_map **new_global = malloc (allocation_size);
|
||||
if (new_global == NULL)
|
||||
{
|
||||
ns->_ns_global_scope_alloc = 0;
|
||||
nomem:
|
||||
_dl_signal_error (ENOMEM, new->l_libname->name, NULL,
|
||||
N_("cannot extend global scope"));
|
||||
return 1;
|
||||
}
|
||||
add_to_global_prepare_failure (new);
|
||||
|
||||
/* Copy over the old entries. */
|
||||
ns->_ns_main_searchlist->r_list
|
||||
= memcpy (new_global, ns->_ns_main_searchlist->r_list,
|
||||
(ns->_ns_main_searchlist->r_nlist
|
||||
* sizeof (struct link_map *)));
|
||||
}
|
||||
else if (ns->_ns_main_searchlist->r_nlist + to_add
|
||||
> ns->_ns_global_scope_alloc)
|
||||
{
|
||||
/* We have to extend the existing array of link maps in the
|
||||
main map. */
|
||||
struct link_map **old_global
|
||||
= GL(dl_ns)[new->l_ns]._ns_main_searchlist->r_list;
|
||||
size_t new_nalloc = ((ns->_ns_global_scope_alloc + to_add) * 2);
|
||||
memcpy (new_global, ns->_ns_main_searchlist->r_list,
|
||||
ns->_ns_main_searchlist->r_nlist * sizeof (struct link_map *));
|
||||
|
||||
new_global = (struct link_map **)
|
||||
malloc (new_nalloc * sizeof (struct link_map *));
|
||||
if (new_global == NULL)
|
||||
goto nomem;
|
||||
|
||||
memcpy (new_global, old_global,
|
||||
ns->_ns_global_scope_alloc * sizeof (struct link_map *));
|
||||
|
||||
ns->_ns_global_scope_alloc = new_nalloc;
|
||||
ns->_ns_global_scope_alloc = new_size;
|
||||
ns->_ns_main_searchlist->r_list = new_global;
|
||||
|
||||
if (!RTLD_SINGLE_THREAD_P)
|
||||
@@ -131,16 +152,28 @@ add_to_global (struct link_map *new)
|
||||
|
||||
free (old_global);
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually add the new global objects to the global scope. Must be
|
||||
called after add_to_global_prepare. This function cannot fail. */
|
||||
static void
|
||||
add_to_global_finish (struct link_map *new)
|
||||
{
|
||||
struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
|
||||
|
||||
/* Now add the new entries. */
|
||||
unsigned int new_nlist = ns->_ns_main_searchlist->r_nlist;
|
||||
for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
|
||||
for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
|
||||
{
|
||||
struct link_map *map = new->l_searchlist.r_list[cnt];
|
||||
|
||||
if (map->l_global == 0)
|
||||
{
|
||||
map->l_global = 1;
|
||||
|
||||
/* The array has been resized by add_to_global_prepare. */
|
||||
assert (new_nlist < ns->_ns_global_scope_alloc);
|
||||
|
||||
ns->_ns_main_searchlist->r_list[new_nlist++] = map;
|
||||
|
||||
/* We modify the global scope. Report this. */
|
||||
@@ -149,10 +182,24 @@ add_to_global (struct link_map *new)
|
||||
map->l_name, map->l_ns);
|
||||
}
|
||||
}
|
||||
|
||||
/* Some of the pending adds have been performed by the loop above.
|
||||
Adjust the counter accordingly. */
|
||||
unsigned int added = new_nlist - ns->_ns_main_searchlist->r_nlist;
|
||||
assert (added <= ns->_ns_global_scope_pending_adds);
|
||||
ns->_ns_global_scope_pending_adds -= added;
|
||||
|
||||
atomic_write_barrier ();
|
||||
ns->_ns_main_searchlist->r_nlist = new_nlist;
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* Combination of add_to_global_prepare and add_to_global_finish.
|
||||
(This can signal exceptions via add_to_global_prepare.) */
|
||||
static void
|
||||
add_to_global (struct link_map *new)
|
||||
{
|
||||
add_to_global_prepare (new);
|
||||
add_to_global_finish (new);
|
||||
}
|
||||
|
||||
/* Search link maps in all namespaces for the DSO that contains the object at
|
||||
@@ -177,6 +224,247 @@ _dl_find_dso_for_object (const ElfW(Addr) addr)
|
||||
}
|
||||
rtld_hidden_def (_dl_find_dso_for_object);
|
||||
|
||||
/* Returned from scope_size if the new map was found in the existing
|
||||
scope. */
|
||||
enum { scope_size_found_new = (size_t) -1 };
|
||||
|
||||
/* Return the length of the scope for MAP. If NEW->l_searchlist is
|
||||
found in the scope, return scope_size_found_new. */
|
||||
static size_t
|
||||
scope_size (struct link_map *map, struct link_map *new)
|
||||
{
|
||||
size_t cnt;
|
||||
for (cnt = 0; map->l_scope[cnt] != NULL; ++cnt)
|
||||
if (map->l_scope[cnt] == &new->l_searchlist)
|
||||
return scope_size_found_new;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* Resize the scopes of depended-upon objects, so that the new object
|
||||
can be added later without further allocation of memory. This
|
||||
function can raise an exceptions due to malloc failure. */
|
||||
static void
|
||||
resize_scopes (struct link_map *new)
|
||||
{
|
||||
/* If the file is not loaded now as a dependency, add the search
|
||||
list of the newly loaded object to the scope. */
|
||||
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
|
||||
/* If the initializer has been called already, the object has
|
||||
not been loaded here and now. */
|
||||
if (imap->l_init_called && imap->l_type == lt_loaded)
|
||||
{
|
||||
size_t cnt = scope_size (imap, new);
|
||||
if (cnt == scope_size_found_new)
|
||||
/* Avoid duplicates. */
|
||||
continue;
|
||||
|
||||
if (__glibc_unlikely (cnt + 1 >= imap->l_scope_max))
|
||||
{
|
||||
/* The 'r_scope' array is too small. Allocate a new one
|
||||
dynamically. */
|
||||
size_t new_size;
|
||||
struct r_scope_elem **newp;
|
||||
|
||||
if (imap->l_scope != imap->l_scope_mem
|
||||
&& imap->l_scope_max < array_length (imap->l_scope_mem))
|
||||
{
|
||||
new_size = array_length (imap->l_scope_mem);
|
||||
newp = imap->l_scope_mem;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_size = imap->l_scope_max * 2;
|
||||
newp = (struct r_scope_elem **)
|
||||
malloc (new_size * sizeof (struct r_scope_elem *));
|
||||
if (newp == NULL)
|
||||
_dl_signal_error (ENOMEM, "dlopen", NULL,
|
||||
N_("cannot create scope list"));
|
||||
}
|
||||
|
||||
/* Copy the array and the terminating NULL. */
|
||||
memcpy (newp, imap->l_scope,
|
||||
(cnt + 1) * sizeof (imap->l_scope[0]));
|
||||
struct r_scope_elem **old = imap->l_scope;
|
||||
|
||||
imap->l_scope = newp;
|
||||
|
||||
if (old != imap->l_scope_mem)
|
||||
_dl_scope_free (old);
|
||||
|
||||
imap->l_scope_max = new_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Second stage of resize_scopes: Add NEW to the scopes. Also print
|
||||
debugging information about scopes if requested.
|
||||
|
||||
This function cannot raise an exception because all required memory
|
||||
has been allocated by a previous call to resize_scopes. */
|
||||
static void
|
||||
update_scopes (struct link_map *new)
|
||||
{
|
||||
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
int from_scope = 0;
|
||||
|
||||
if (imap->l_init_called && imap->l_type == lt_loaded)
|
||||
{
|
||||
size_t cnt = scope_size (imap, new);
|
||||
if (cnt == scope_size_found_new)
|
||||
/* Avoid duplicates. */
|
||||
continue;
|
||||
|
||||
assert (cnt + 1 < imap->l_scope_max);
|
||||
|
||||
/* First terminate the extended list. Otherwise a thread
|
||||
might use the new last element and then use the garbage
|
||||
at offset IDX+1. */
|
||||
imap->l_scope[cnt + 1] = NULL;
|
||||
atomic_write_barrier ();
|
||||
imap->l_scope[cnt] = &new->l_searchlist;
|
||||
|
||||
from_scope = cnt;
|
||||
}
|
||||
|
||||
/* Print scope information. */
|
||||
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
|
||||
_dl_show_scope (imap, from_scope);
|
||||
}
|
||||
}
|
||||
|
||||
/* Call _dl_add_to_slotinfo with DO_ADD set to false, to allocate
|
||||
space in GL (dl_tls_dtv_slotinfo_list). This can raise an
|
||||
exception. */
|
||||
static bool
|
||||
resize_tls_slotinfo (struct link_map *new)
|
||||
{
|
||||
bool any_tls = false;
|
||||
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
|
||||
/* Only add TLS memory if this object is loaded now and
|
||||
therefore is not yet initialized. */
|
||||
if (! imap->l_init_called && imap->l_tls_blocksize > 0)
|
||||
{
|
||||
_dl_add_to_slotinfo (imap, false);
|
||||
any_tls = true;
|
||||
}
|
||||
}
|
||||
return any_tls;
|
||||
}
|
||||
|
||||
/* Second stage of TLS update, after resize_tls_slotinfo. This
|
||||
function does not raise any exception. It should only be called if
|
||||
resize_tls_slotinfo returned true. */
|
||||
static void
|
||||
update_tls_slotinfo (struct link_map *new)
|
||||
{
|
||||
unsigned int first_static_tls = new->l_searchlist.r_nlist;
|
||||
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
|
||||
/* Only add TLS memory if this object is loaded now and
|
||||
therefore is not yet initialized. */
|
||||
if (! imap->l_init_called && imap->l_tls_blocksize > 0)
|
||||
{
|
||||
_dl_add_to_slotinfo (imap, true);
|
||||
|
||||
if (imap->l_need_tls_init
|
||||
&& first_static_tls == new->l_searchlist.r_nlist)
|
||||
first_static_tls = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (__builtin_expect (++GL(dl_tls_generation) == 0, 0))
|
||||
_dl_fatal_printf (N_("\
|
||||
TLS generation counter wrapped! Please report this."));
|
||||
|
||||
/* We need a second pass for static tls data, because
|
||||
_dl_update_slotinfo must not be run while calls to
|
||||
_dl_add_to_slotinfo are still pending. */
|
||||
for (unsigned int i = first_static_tls; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
|
||||
if (imap->l_need_tls_init
|
||||
&& ! imap->l_init_called
|
||||
&& imap->l_tls_blocksize > 0)
|
||||
{
|
||||
/* For static TLS we have to allocate the memory here and
|
||||
now, but we can delay updating the DTV. */
|
||||
imap->l_need_tls_init = 0;
|
||||
#ifdef SHARED
|
||||
/* Update the slot information data for at least the
|
||||
generation of the DSO we are allocating data for. */
|
||||
|
||||
/* FIXME: This can terminate the process on memory
|
||||
allocation failure. It is not possible to raise
|
||||
exceptions from this context; to fix this bug,
|
||||
_dl_update_slotinfo would have to be split into two
|
||||
operations. */
|
||||
_dl_update_slotinfo (imap->l_tls_modid);
|
||||
#endif
|
||||
|
||||
GL(dl_init_static_tls) (imap);
|
||||
assert (imap->l_need_tls_init == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark the objects as NODELETE if required. This is delayed until
|
||||
after dlopen failure is not possible, so that _dl_close can clean
|
||||
up objects if necessary. */
|
||||
static void
|
||||
activate_nodelete (struct link_map *new, int mode)
|
||||
{
|
||||
if (mode & RTLD_NODELETE || new->l_nodelete == link_map_nodelete_pending)
|
||||
{
|
||||
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
|
||||
_dl_debug_printf ("activating NODELETE for %s [%lu]\n",
|
||||
new->l_name, new->l_ns);
|
||||
new->l_nodelete = link_map_nodelete_active;
|
||||
}
|
||||
|
||||
/* Update all objects in our scope. Any of them could have been
|
||||
promoted to NODELETE status due to new relocations. */
|
||||
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
if (imap->l_nodelete == link_map_nodelete_pending)
|
||||
{
|
||||
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
|
||||
_dl_debug_printf ("activating NODELETE for %s [%lu]\n",
|
||||
imap->l_name, imap->l_ns);
|
||||
imap->l_nodelete = link_map_nodelete_active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* struct dl_init_args and call_dl_init are used to call _dl_init with
|
||||
exception handling disabled. */
|
||||
struct dl_init_args
|
||||
{
|
||||
struct link_map *new;
|
||||
int argc;
|
||||
char **argv;
|
||||
char **env;
|
||||
};
|
||||
|
||||
static void
|
||||
call_dl_init (void *closure)
|
||||
{
|
||||
struct dl_init_args *args = closure;
|
||||
_dl_init (args->new, args->argc, args->argv, args->env);
|
||||
}
|
||||
|
||||
static void
|
||||
dl_open_worker (void *a)
|
||||
{
|
||||
@@ -208,6 +496,10 @@ dl_open_worker (void *a)
|
||||
args->nsid = call_map->l_ns;
|
||||
}
|
||||
|
||||
/* Retain the old value, so that it can be restored. */
|
||||
args->original_global_scope_pending_adds
|
||||
= GL (dl_ns)[args->nsid]._ns_global_scope_pending_adds;
|
||||
|
||||
/* One might be tempted to assert that we are RT_CONSISTENT at this point, but that
|
||||
may not be true if this is a recursive call to dlopen. */
|
||||
_dl_debug_initialize (0, args->nsid);
|
||||
@@ -225,12 +517,6 @@ dl_open_worker (void *a)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mark the object as not deletable if the RTLD_NODELETE flags was passed.
|
||||
Do this early so that we don't skip marking the object if it was
|
||||
already loaded. */
|
||||
if (__glibc_unlikely (mode & RTLD_NODELETE))
|
||||
new->l_flags_1 |= DF_1_NODELETE;
|
||||
|
||||
if (__glibc_unlikely (mode & __RTLD_SPROF))
|
||||
/* This happens only if we load a DSO for 'sprof'. */
|
||||
return;
|
||||
@@ -246,16 +532,33 @@ dl_open_worker (void *a)
|
||||
_dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
|
||||
new->l_name, new->l_ns, new->l_direct_opencount);
|
||||
|
||||
/* If the user requested the object to be in the global namespace
|
||||
but it is not so far, add it now. */
|
||||
/* If the user requested the object to be in the global
|
||||
namespace but it is not so far, add it now. This can raise
|
||||
an exception to do a malloc failure. */
|
||||
if ((mode & RTLD_GLOBAL) && new->l_global == 0)
|
||||
(void) add_to_global (new);
|
||||
add_to_global (new);
|
||||
|
||||
/* Mark the object as not deletable if the RTLD_NODELETE flags
|
||||
was passed. */
|
||||
if (__glibc_unlikely (mode & RTLD_NODELETE))
|
||||
{
|
||||
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES)
|
||||
&& new->l_nodelete == link_map_nodelete_inactive)
|
||||
_dl_debug_printf ("marking %s [%lu] as NODELETE\n",
|
||||
new->l_name, new->l_ns);
|
||||
new->l_nodelete = link_map_nodelete_active;
|
||||
}
|
||||
|
||||
assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Schedule NODELETE marking for the directly loaded object if
|
||||
requested. */
|
||||
if (__glibc_unlikely (mode & RTLD_NODELETE))
|
||||
new->l_nodelete = link_map_nodelete_pending;
|
||||
|
||||
/* Load that object's dependencies. */
|
||||
_dl_map_object_deps (new, NULL, 0, 0,
|
||||
mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
|
||||
@@ -327,6 +630,14 @@ dl_open_worker (void *a)
|
||||
|
||||
int relocation_in_progress = 0;
|
||||
|
||||
/* Perform relocation. This can trigger lazy binding in IFUNC
|
||||
resolvers. For NODELETE mappings, these dependencies are not
|
||||
recorded because the flag has not been applied to the newly
|
||||
loaded objects. This means that upon dlopen failure, these
|
||||
NODELETE objects can be unloaded despite existing references to
|
||||
them. However, such relocation dependencies in IFUNC resolvers
|
||||
are undefined anyway, so this is not a problem. */
|
||||
|
||||
for (unsigned int i = nmaps; i-- > 0; )
|
||||
{
|
||||
l = maps[i];
|
||||
@@ -356,7 +667,7 @@ dl_open_worker (void *a)
|
||||
_dl_start_profile ();
|
||||
|
||||
/* Prevent unloading the object. */
|
||||
GL(dl_profile_map)->l_flags_1 |= DF_1_NODELETE;
|
||||
GL(dl_profile_map)->l_nodelete = link_map_nodelete_active;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -370,133 +681,41 @@ dl_open_worker (void *a)
|
||||
relocation. */
|
||||
_dl_open_check (new);
|
||||
|
||||
/* If the file is not loaded now as a dependency, add the search
|
||||
list of the newly loaded object to the scope. */
|
||||
bool any_tls = false;
|
||||
unsigned int first_static_tls = new->l_searchlist.r_nlist;
|
||||
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
int from_scope = 0;
|
||||
/* This only performs the memory allocations. The actual update of
|
||||
the scopes happens below, after failure is impossible. */
|
||||
resize_scopes (new);
|
||||
|
||||
/* If the initializer has been called already, the object has
|
||||
not been loaded here and now. */
|
||||
if (imap->l_init_called && imap->l_type == lt_loaded)
|
||||
{
|
||||
struct r_scope_elem **runp = imap->l_scope;
|
||||
size_t cnt = 0;
|
||||
/* Increase the size of the GL (dl_tls_dtv_slotinfo_list) data
|
||||
structure. */
|
||||
bool any_tls = resize_tls_slotinfo (new);
|
||||
|
||||
while (*runp != NULL)
|
||||
{
|
||||
if (*runp == &new->l_searchlist)
|
||||
break;
|
||||
++cnt;
|
||||
++runp;
|
||||
}
|
||||
/* Perform the necessary allocations for adding new global objects
|
||||
to the global scope below. */
|
||||
if (mode & RTLD_GLOBAL)
|
||||
add_to_global_prepare (new);
|
||||
|
||||
if (*runp != NULL)
|
||||
/* Avoid duplicates. */
|
||||
continue;
|
||||
/* Demarcation point: After this, no recoverable errors are allowed.
|
||||
All memory allocations for new objects must have happened
|
||||
before. */
|
||||
|
||||
if (__glibc_unlikely (cnt + 1 >= imap->l_scope_max))
|
||||
{
|
||||
/* The 'r_scope' array is too small. Allocate a new one
|
||||
dynamically. */
|
||||
size_t new_size;
|
||||
struct r_scope_elem **newp;
|
||||
activate_nodelete (new, mode);
|
||||
|
||||
#define SCOPE_ELEMS(imap) \
|
||||
(sizeof (imap->l_scope_mem) / sizeof (imap->l_scope_mem[0]))
|
||||
/* Second stage after resize_scopes: Actually perform the scope
|
||||
update. After this, dlsym and lazy binding can bind to new
|
||||
objects. */
|
||||
update_scopes (new);
|
||||
|
||||
if (imap->l_scope != imap->l_scope_mem
|
||||
&& imap->l_scope_max < SCOPE_ELEMS (imap))
|
||||
{
|
||||
new_size = SCOPE_ELEMS (imap);
|
||||
newp = imap->l_scope_mem;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_size = imap->l_scope_max * 2;
|
||||
newp = (struct r_scope_elem **)
|
||||
malloc (new_size * sizeof (struct r_scope_elem *));
|
||||
if (newp == NULL)
|
||||
_dl_signal_error (ENOMEM, "dlopen", NULL,
|
||||
N_("cannot create scope list"));
|
||||
}
|
||||
/* FIXME: It is unclear whether the order here is correct.
|
||||
Shouldn't new objects be made available for binding (and thus
|
||||
execution) only after there TLS data has been set up
|
||||
correctly? */
|
||||
|
||||
memcpy (newp, imap->l_scope, cnt * sizeof (imap->l_scope[0]));
|
||||
struct r_scope_elem **old = imap->l_scope;
|
||||
|
||||
imap->l_scope = newp;
|
||||
|
||||
if (old != imap->l_scope_mem)
|
||||
_dl_scope_free (old);
|
||||
|
||||
imap->l_scope_max = new_size;
|
||||
}
|
||||
|
||||
/* First terminate the extended list. Otherwise a thread
|
||||
might use the new last element and then use the garbage
|
||||
at offset IDX+1. */
|
||||
imap->l_scope[cnt + 1] = NULL;
|
||||
atomic_write_barrier ();
|
||||
imap->l_scope[cnt] = &new->l_searchlist;
|
||||
|
||||
/* Print only new scope information. */
|
||||
from_scope = cnt;
|
||||
}
|
||||
/* Only add TLS memory if this object is loaded now and
|
||||
therefore is not yet initialized. */
|
||||
else if (! imap->l_init_called
|
||||
/* Only if the module defines thread local data. */
|
||||
&& __builtin_expect (imap->l_tls_blocksize > 0, 0))
|
||||
{
|
||||
/* Now that we know the object is loaded successfully add
|
||||
modules containing TLS data to the slot info table. We
|
||||
might have to increase its size. */
|
||||
_dl_add_to_slotinfo (imap);
|
||||
|
||||
if (imap->l_need_tls_init
|
||||
&& first_static_tls == new->l_searchlist.r_nlist)
|
||||
first_static_tls = i;
|
||||
|
||||
/* We have to bump the generation counter. */
|
||||
any_tls = true;
|
||||
}
|
||||
|
||||
/* Print scope information. */
|
||||
if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
|
||||
_dl_show_scope (imap, from_scope);
|
||||
}
|
||||
|
||||
/* Bump the generation number if necessary. */
|
||||
if (any_tls && __builtin_expect (++GL(dl_tls_generation) == 0, 0))
|
||||
_dl_fatal_printf (N_("\
|
||||
TLS generation counter wrapped! Please report this."));
|
||||
|
||||
/* We need a second pass for static tls data, because _dl_update_slotinfo
|
||||
must not be run while calls to _dl_add_to_slotinfo are still pending. */
|
||||
for (unsigned int i = first_static_tls; i < new->l_searchlist.r_nlist; ++i)
|
||||
{
|
||||
struct link_map *imap = new->l_searchlist.r_list[i];
|
||||
|
||||
if (imap->l_need_tls_init
|
||||
&& ! imap->l_init_called
|
||||
&& imap->l_tls_blocksize > 0)
|
||||
{
|
||||
/* For static TLS we have to allocate the memory here and
|
||||
now, but we can delay updating the DTV. */
|
||||
imap->l_need_tls_init = 0;
|
||||
#ifdef SHARED
|
||||
/* Update the slot information data for at least the
|
||||
generation of the DSO we are allocating data for. */
|
||||
_dl_update_slotinfo (imap->l_tls_modid);
|
||||
#endif
|
||||
|
||||
GL(dl_init_static_tls) (imap);
|
||||
assert (imap->l_need_tls_init == 0);
|
||||
}
|
||||
}
|
||||
/* Second stage after resize_tls_slotinfo: Update the slotinfo data
|
||||
structures. */
|
||||
if (any_tls)
|
||||
/* FIXME: This calls _dl_update_slotinfo, which aborts the process
|
||||
on memory allocation failure. */
|
||||
update_tls_slotinfo (new);
|
||||
|
||||
/* Notify the debugger all new objects have been relocated. */
|
||||
if (relocation_in_progress)
|
||||
@@ -506,15 +725,23 @@ TLS generation counter wrapped! Please report this."));
|
||||
DL_STATIC_INIT (new);
|
||||
#endif
|
||||
|
||||
/* Run the initializer functions of new objects. */
|
||||
_dl_init (new, args->argc, args->argv, args->env);
|
||||
/* Run the initializer functions of new objects. Temporarily
|
||||
disable the exception handler, so that lazy binding failures are
|
||||
fatal. */
|
||||
{
|
||||
struct dl_init_args init_args =
|
||||
{
|
||||
.new = new,
|
||||
.argc = args->argc,
|
||||
.argv = args->argv,
|
||||
.env = args->env
|
||||
};
|
||||
_dl_catch_exception (NULL, call_dl_init, &init_args);
|
||||
}
|
||||
|
||||
/* Now we can make the new map available in the global scope. */
|
||||
if (mode & RTLD_GLOBAL)
|
||||
/* Move the object in the global namespace. */
|
||||
if (add_to_global (new) != 0)
|
||||
/* It failed. */
|
||||
return;
|
||||
add_to_global_finish (new);
|
||||
|
||||
#ifndef SHARED
|
||||
/* We must be the static _dl_open in libc.a. A static program that
|
||||
@@ -596,6 +823,19 @@ no more namespaces available for dlmopen()"));
|
||||
_dl_unload_cache ();
|
||||
#endif
|
||||
|
||||
/* Do this for both the success and failure cases. The old value
|
||||
has only been determined if the namespace ID was assigned (i.e.,
|
||||
it is not __LM_ID_CALLER). In the success case, we actually may
|
||||
have consumed more pending adds than planned (because the local
|
||||
scopes overlap in case of a recursive dlopen, the inner dlopen
|
||||
doing some of the globalization work of the outer dlopen), so the
|
||||
old pending adds value is larger than absolutely necessary.
|
||||
Since it is just a conservative upper bound, this is harmless.
|
||||
The top-level dlopen call will restore the field to zero. */
|
||||
if (args.nsid >= 0)
|
||||
GL (dl_ns)[args.nsid]._ns_global_scope_pending_adds
|
||||
= args.original_global_scope_pending_adds;
|
||||
|
||||
/* See if an error occurred during loading. */
|
||||
if (__glibc_unlikely (exception.errstring != NULL))
|
||||
{
|
||||
|
||||
+2
-1
@@ -244,7 +244,8 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
|
||||
v = (version); \
|
||||
_lr = _dl_lookup_symbol_x (strtab + (*ref)->st_name, l, (ref), \
|
||||
scope, v, _tc, \
|
||||
DL_LOOKUP_ADD_DEPENDENCY, NULL); \
|
||||
DL_LOOKUP_ADD_DEPENDENCY \
|
||||
| DL_LOOKUP_INSIDE_DLOPEN, NULL); \
|
||||
l->l_lookup_cache.ret = (*ref); \
|
||||
l->l_lookup_cache.value = _lr; })) \
|
||||
: l)
|
||||
|
||||
+6
-3
@@ -883,7 +883,7 @@ _dl_tls_get_addr_soft (struct link_map *l)
|
||||
|
||||
|
||||
void
|
||||
_dl_add_to_slotinfo (struct link_map *l)
|
||||
_dl_add_to_slotinfo (struct link_map *l, bool do_add)
|
||||
{
|
||||
/* Now that we know the object is loaded successfully add
|
||||
modules containing TLS data to the dtv info table. We
|
||||
@@ -939,6 +939,9 @@ cannot create TLS data structures"));
|
||||
}
|
||||
|
||||
/* Add the information into the slotinfo data structure. */
|
||||
listp->slotinfo[idx].map = l;
|
||||
listp->slotinfo[idx].gen = GL(dl_tls_generation) + 1;
|
||||
if (do_add)
|
||||
{
|
||||
listp->slotinfo[idx].map = l;
|
||||
listp->slotinfo[idx].gen = GL(dl_tls_generation) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,8 @@ elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
|
||||
if (info[VERSYMIDX (DT_FLAGS_1)] != NULL)
|
||||
{
|
||||
l->l_flags_1 = info[VERSYMIDX (DT_FLAGS_1)]->d_un.d_val;
|
||||
if (l->l_flags_1 & DF_1_NODELETE)
|
||||
l->l_nodelete = link_map_nodelete_pending;
|
||||
|
||||
/* Only DT_1_SUPPORTED_MASK bits are supported, and we would like
|
||||
to assert this, but we can't. Users have been setting
|
||||
|
||||
+2
-2
@@ -2214,7 +2214,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
|
||||
|
||||
/* Add object to slot information data if necessasy. */
|
||||
if (l->l_tls_blocksize != 0 && tls_init_tp_called)
|
||||
_dl_add_to_slotinfo (l);
|
||||
_dl_add_to_slotinfo (l, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2259,7 +2259,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
|
||||
|
||||
/* Add object to slot information data if necessasy. */
|
||||
if (l->l_tls_blocksize != 0 && tls_init_tp_called)
|
||||
_dl_add_to_slotinfo (l);
|
||||
_dl_add_to_slotinfo (l, true);
|
||||
}
|
||||
rtld_timer_stop (&relocate_time, start);
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/* Test dlopen rollback after failures involving NODELETE objects (bug 20839).
|
||||
Copyright (C) 2019 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 <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <gnu/lib-names.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <support/check.h>
|
||||
#include <support/xdlfcn.h>
|
||||
|
||||
static int
|
||||
do_test (void)
|
||||
{
|
||||
/* This test uses libpthread as the canonical NODELETE module. If
|
||||
libpthread is no longer NODELETE because it has been merged into
|
||||
libc, the test needs to be updated. */
|
||||
TEST_VERIFY (dlsym (NULL, "pthread_create") == NULL);
|
||||
|
||||
/* This is expected to fail because of the missing dependency. */
|
||||
puts ("info: attempting to load tst-dlopenfailmod1.so");
|
||||
TEST_VERIFY (dlopen ("tst-dlopenfailmod1.so", RTLD_LAZY) == NULL);
|
||||
const char *message = dlerror ();
|
||||
TEST_COMPARE_STRING (message,
|
||||
"tst-dlopenfail-missingmod.so:"
|
||||
" cannot open shared object file:"
|
||||
" No such file or directory");
|
||||
|
||||
/* Do not probe for the presence of libpthread at this point because
|
||||
that might trigger relocation if bug 20839 is present, obscuring
|
||||
a subsequent crash. */
|
||||
|
||||
/* This is expected to succeed. */
|
||||
puts ("info: loading tst-dlopenfailmod2.so");
|
||||
void *handle = xdlopen ("tst-dlopenfailmod2.so", RTLD_NOW);
|
||||
xdlclose (handle);
|
||||
|
||||
/* libpthread should remain loaded. */
|
||||
TEST_VERIFY (dlopen (LIBPTHREAD_SO, RTLD_LAZY | RTLD_NOLOAD) != NULL);
|
||||
TEST_VERIFY (dlsym (NULL, "pthread_create") == NULL);
|
||||
|
||||
/* We can make libpthread global, and then the symbol should become
|
||||
available. */
|
||||
TEST_VERIFY (dlopen (LIBPTHREAD_SO, RTLD_LAZY | RTLD_GLOBAL) != NULL);
|
||||
TEST_VERIFY (dlsym (NULL, "pthread_create") != NULL);
|
||||
|
||||
/* sem_open is sufficiently complex to depend on relocations. */
|
||||
void *(*sem_open_ptr) (const char *, int flag, ...)
|
||||
= dlsym (NULL, "sem_open");
|
||||
if (sem_open_ptr == NULL)
|
||||
/* Hurd does not implement sem_open. */
|
||||
puts ("warning: sem_open not found, further testing not possible");
|
||||
else
|
||||
{
|
||||
errno = 0;
|
||||
TEST_VERIFY (sem_open_ptr ("/", 0) == NULL);
|
||||
TEST_COMPARE (errno, EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <support/test-driver.c>
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Empty module with a soname which is not available at run time.
|
||||
Copyright (C) 2019 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/>. */
|
||||
@@ -0,0 +1,36 @@
|
||||
/* Module which depends on two modules: one NODELETE, one missing.
|
||||
Copyright (C) 2019 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/>. */
|
||||
|
||||
/* Note: Due to the missing second module, this object cannot be
|
||||
loaded at run time. */
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Force linking against libpthread. */
|
||||
void *pthread_create_reference = pthread_create;
|
||||
|
||||
/* The constructor will never be executed because the module cannot be
|
||||
loaded. */
|
||||
static void __attribute__ ((constructor))
|
||||
init (void)
|
||||
{
|
||||
puts ("tst-dlopenfailmod1 constructor executed");
|
||||
_exit (1);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Module which depends on on a NODELETE module, and can be loaded.
|
||||
Copyright (C) 2019 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 <pthread.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Force linking against libpthread. */
|
||||
void *pthread_create_reference = pthread_create;
|
||||
|
||||
static void __attribute__ ((constructor))
|
||||
init (void)
|
||||
{
|
||||
puts ("info: tst-dlopenfailmod2.so constructor invoked");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Helper module for tst-initfinilazyfail: lazy binding failure in destructor.
|
||||
Copyright (C) 2019 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/>. */
|
||||
|
||||
/* An undefined function. Calling it will cause a lazy binding
|
||||
failure. */
|
||||
void undefined_function (void);
|
||||
|
||||
static void __attribute__ ((destructor))
|
||||
fini (void)
|
||||
{
|
||||
undefined_function ();
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* Test that lazy binding failures in constructors and destructors are fatal.
|
||||
Copyright (C) 2019 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 <dlfcn.h>
|
||||
#include <string.h>
|
||||
#include <support/capture_subprocess.h>
|
||||
#include <support/check.h>
|
||||
#include <support/xdlfcn.h>
|
||||
|
||||
static void
|
||||
test_constructor (void *closure)
|
||||
{
|
||||
void *handle = dlopen ("tst-initlazyfailmod.so", RTLD_LAZY);
|
||||
if (handle == NULL)
|
||||
FAIL_EXIT (2, "dlopen did not terminate the process: %s", dlerror ());
|
||||
else
|
||||
FAIL_EXIT (2, "dlopen did not terminate the process (%p)", handle);
|
||||
}
|
||||
|
||||
static void
|
||||
test_destructor (void *closure)
|
||||
{
|
||||
void *handle = xdlopen ("tst-finilazyfailmod.so", RTLD_LAZY);
|
||||
int ret = dlclose (handle);
|
||||
const char *message = dlerror ();
|
||||
if (message != NULL)
|
||||
FAIL_EXIT (2, "dlclose did not terminate the process: %d, %s",
|
||||
ret, message);
|
||||
else
|
||||
FAIL_EXIT (2, "dlopen did not terminate the process: %d", ret);
|
||||
}
|
||||
|
||||
static int
|
||||
do_test (void)
|
||||
{
|
||||
{
|
||||
struct support_capture_subprocess proc
|
||||
= support_capture_subprocess (test_constructor, NULL);
|
||||
support_capture_subprocess_check (&proc, "constructor", 127,
|
||||
sc_allow_stderr);
|
||||
printf ("info: constructor failure output: [[%s]]\n", proc.err.buffer);
|
||||
TEST_VERIFY (strstr (proc.err.buffer,
|
||||
"tst-initfinilazyfail: symbol lookup error: ")
|
||||
!= NULL);
|
||||
TEST_VERIFY (strstr (proc.err.buffer,
|
||||
"tst-initlazyfailmod.so: undefined symbol:"
|
||||
" undefined_function\n") != NULL);
|
||||
support_capture_subprocess_free (&proc);
|
||||
}
|
||||
|
||||
{
|
||||
struct support_capture_subprocess proc
|
||||
= support_capture_subprocess (test_destructor, NULL);
|
||||
support_capture_subprocess_check (&proc, "destructor", 127,
|
||||
sc_allow_stderr);
|
||||
printf ("info: destructor failure output: [[%s]]\n", proc.err.buffer);
|
||||
TEST_VERIFY (strstr (proc.err.buffer,
|
||||
"tst-initfinilazyfail: symbol lookup error: ")
|
||||
!= NULL);
|
||||
TEST_VERIFY (strstr (proc.err.buffer,
|
||||
"tst-finilazyfailmod.so: undefined symbol:"
|
||||
" undefined_function\n") != NULL);
|
||||
support_capture_subprocess_free (&proc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <support/test-driver.c>
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Helper module for tst-initfinilazyfail: lazy binding failure in constructor.
|
||||
Copyright (C) 2019 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/>. */
|
||||
|
||||
/* An undefined function. Calling it will cause a lazy binding
|
||||
failure. */
|
||||
void undefined_function (void);
|
||||
|
||||
static void __attribute__ ((constructor))
|
||||
init (void)
|
||||
{
|
||||
undefined_function ();
|
||||
}
|
||||
@@ -79,6 +79,21 @@ struct r_search_path_struct
|
||||
int malloced;
|
||||
};
|
||||
|
||||
/* Type used by the l_nodelete member. */
|
||||
enum link_map_nodelete
|
||||
{
|
||||
/* This link map can be deallocated. */
|
||||
link_map_nodelete_inactive,
|
||||
|
||||
/* This link map cannot be deallocated. */
|
||||
link_map_nodelete_active,
|
||||
|
||||
/* This link map cannot be deallocated after dlopen has succeded.
|
||||
dlopen turns this into link_map_nodelete_active. dlclose treats
|
||||
this intermediate state as link_map_nodelete_active. */
|
||||
link_map_nodelete_pending,
|
||||
};
|
||||
|
||||
|
||||
/* Structure describing a loaded shared object. The `l_next' and `l_prev'
|
||||
members form a chain of all the shared objects loaded at startup.
|
||||
@@ -203,6 +218,10 @@ struct link_map
|
||||
freed, ie. not allocated with
|
||||
the dummy malloc in ld.so. */
|
||||
|
||||
/* Actually of type enum link_map_nodelete. Separate byte due to
|
||||
concurrent access. Only valid for l_type == lt_loaded. */
|
||||
unsigned char l_nodelete;
|
||||
|
||||
#include <link_map.h>
|
||||
|
||||
/* Collected information about own RPATH directories. */
|
||||
|
||||
@@ -328,7 +328,14 @@ struct rtld_global
|
||||
/* This is zero at program start to signal that the global scope map is
|
||||
allocated by rtld. Later it keeps the size of the map. It might be
|
||||
reset if in _dl_close if the last global object is removed. */
|
||||
size_t _ns_global_scope_alloc;
|
||||
unsigned int _ns_global_scope_alloc;
|
||||
|
||||
/* During dlopen, this is the number of objects that still need to
|
||||
be added to the global scope map. It has to be taken into
|
||||
account when resizing the map, for future map additions after
|
||||
recursive dlopen calls from ELF constructors. */
|
||||
unsigned int _ns_global_scope_pending_adds;
|
||||
|
||||
/* Search table for unique objects. */
|
||||
struct unique_sym_table
|
||||
{
|
||||
@@ -860,7 +867,9 @@ libc_hidden_proto (_dl_catch_error)
|
||||
|
||||
/* Call OPERATE (ARGS). If no error occurs, set *EXCEPTION to zero.
|
||||
Otherwise, store a copy of the raised exception in *EXCEPTION,
|
||||
which has to be freed by _dl_exception_free. */
|
||||
which has to be freed by _dl_exception_free. As a special case, if
|
||||
EXCEPTION is null, call OPERATE (ARGS) with exception handling
|
||||
disabled (so that exceptions are fatal). */
|
||||
int _dl_catch_exception (struct dl_exception *exception,
|
||||
void (*operate) (void *), void *args);
|
||||
libc_hidden_proto (_dl_catch_exception)
|
||||
@@ -914,6 +923,10 @@ enum
|
||||
DL_LOOKUP_RETURN_NEWEST = 2,
|
||||
/* Set if dl_lookup* called with GSCOPE lock held. */
|
||||
DL_LOOKUP_GSCOPE_LOCK = 4,
|
||||
|
||||
/* Set if dl_lookup* is called from within dlopen, for non-lazy
|
||||
binding. */
|
||||
DL_LOOKUP_INSIDE_DLOPEN = 8,
|
||||
};
|
||||
|
||||
/* Lookup versioned symbol. */
|
||||
@@ -1131,8 +1144,15 @@ extern void *_dl_open (const char *name, int mode, const void *caller,
|
||||
old scope, OLD can't be freed until no thread is using it. */
|
||||
extern int _dl_scope_free (void *) attribute_hidden;
|
||||
|
||||
/* Add module to slot information data. */
|
||||
extern void _dl_add_to_slotinfo (struct link_map *l) attribute_hidden;
|
||||
|
||||
/* Add module to slot information data. If DO_ADD is false, only the
|
||||
required memory is allocated. Must be called with GL
|
||||
(dl_load_lock) acquired. If the function has already been called
|
||||
for the link map L with !do_add, then this function will not raise
|
||||
an exception, otherwise it is possible that it encounters a memory
|
||||
allocation failure. */
|
||||
extern void _dl_add_to_slotinfo (struct link_map *l, bool do_add)
|
||||
attribute_hidden;
|
||||
|
||||
/* Update slot information data for at least the generation of the
|
||||
module with the given index. */
|
||||
|
||||
Reference in New Issue
Block a user