/* Close a shared object opened by `_dl_open'.
- Copyright (C) 1996-2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+ Copyright (C) 1996-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
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, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- 02111-1307 USA. */
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
#include <assert.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <bits/libc-lock.h>
+#include <libc-lock.h>
#include <ldsodefs.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sysdep-cancel.h>
#include <tls.h>
+#include <stap-probe.h>
+
+#include <dl-unmap-segments.h>
/* Type of the constructor functions. */
/* The entry might still be in its unused state if we are closing an
object that wasn't fully set up. */
- if (__builtin_expect (old_map != NULL, 1))
+ if (__glibc_likely (old_map != NULL))
{
assert (old_map->l_tls_modid == idx);
void
-_dl_close_worker (struct link_map *map)
+_dl_close_worker (struct link_map *map, bool force)
{
/* One less direct use. */
--map->l_direct_opencount;
if (map->l_direct_opencount > 0 || map->l_type != lt_loaded
|| dl_close_state != not_pending)
{
- if (map->l_direct_opencount == 0)
- {
- if (map->l_type == lt_loaded)
- dl_close_state = rerun;
- else if (map->l_type == lt_library)
- {
- struct link_map **oldp = map->l_initfini;
- map->l_initfini = map->l_orig_initfini;
- _dl_scope_free (oldp);
- }
- }
+ if (map->l_direct_opencount == 0 && map->l_type == lt_loaded)
+ dl_close_state = rerun;
/* There are still references to this object. Do nothing more. */
- if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0))
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
_dl_debug_printf ("\nclosing file=%s; direct_opencount=%u\n",
map->l_name, map->l_direct_opencount);
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;
l->l_idx = idx;
maps[idx] = l;
++idx;
+
}
assert (idx == nloaded);
if (l->l_type == lt_loaded
&& l->l_direct_opencount == 0
&& (l->l_flags_1 & DF_1_NODELETE) == 0
+ /* 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
&& !used[done_index])
continue;
/* Mark all dependencies as used. */
if (l->l_initfini != NULL)
{
+ /* We are always the zeroth entry, and since we don't include
+ ourselves in the dependency analysis start at 1. */
struct link_map **lp = &l->l_initfini[1];
while (*lp != NULL)
{
if (!used[(*lp)->l_idx])
{
used[(*lp)->l_idx] = 1;
+ /* If we marked a new object as used, and we've
+ already processed it, then we need to go back
+ and process again from that point forward to
+ ensure we keep all of its dependencies also. */
if ((*lp)->l_idx - 1 < done_index)
done_index = (*lp)->l_idx - 1;
}
}
}
- /* Sort the entries. */
- _dl_sort_fini (ns->_ns_loaded, maps, nloaded, used, nsid);
+ /* Sort the entries. We can skip looking for the binary itself which is
+ at the front of the search list for the main namespace. */
+ _dl_sort_maps (maps + (nsid == LM_ID_BASE), nloaded - (nsid == LM_ID_BASE),
+ used + (nsid == LM_ID_BASE), true);
/* Call all termination functions at once. */
#ifdef SHARED
/* Next try the old-style destructor. */
if (imap->l_info[DT_FINI] != NULL)
- (*(void (*) (void)) DL_DT_FINI_ADDRESS
- (imap, ((void *) imap->l_addr
- + imap->l_info[DT_FINI]->d_un.d_ptr))) ();
+ DL_CALL_DT_FINI (imap, ((void *) imap->l_addr
+ + imap->l_info[DT_FINI]->d_un.d_ptr));
}
#ifdef SHARED
/* Auditing checkpoint: we remove an object. */
- if (__builtin_expect (do_audit, 0))
+ if (__glibc_unlikely (do_audit))
{
struct audit_ifaces *afct = GLRO(dl_audit);
for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
imap->l_scope_max = new_size;
}
+ else if (new_list != NULL)
+ {
+ /* We didn't change the scope array, so reset the search
+ list. */
+ imap->l_searchlist.r_list = NULL;
+ imap->l_searchlist.r_nlist = 0;
+ }
/* The loader is gone, so mark the object as not having one.
Note: l_idx != IDX_STILL_USED -> object will be removed. */
#ifdef SHARED
/* Auditing checkpoint: we will start deleting objects. */
- if (__builtin_expect (do_audit, 0))
+ if (__glibc_unlikely (do_audit))
{
struct link_map *head = ns->_ns_loaded;
struct audit_ifaces *afct = GLRO(dl_audit);
struct r_debug *r = _dl_debug_initialize (0, nsid);
r->r_state = RT_DELETE;
_dl_debug_state ();
+ LIBC_PROBE (unmap_start, 2, nsid, r);
if (unload_global)
{
/* Speed up removing most recently added objects. */
j = cnt;
else
- for (i = 0; i < cnt; i++)
+ for (i = 0; i < cnt; i++)
if (ns_msl->r_list[i]->l_removed == 0)
{
if (i != j)
object. We can unmap it. */
/* Remove the object from the dtv slotinfo array if it uses TLS. */
- if (__builtin_expect (imap->l_tls_blocksize > 0, 0))
+ if (__glibc_unlikely (imap->l_tls_blocksize > 0))
{
any_tls = true;
}
}
+ /* Reset unique symbols if forced. */
+ if (force)
+ {
+ struct unique_sym_table *tab = &ns->_ns_unique_sym_table;
+ __rtld_lock_lock_recursive (tab->lock);
+ struct unique_sym *entries = tab->entries;
+ if (entries != NULL)
+ {
+ size_t idx, size = tab->size;
+ for (idx = 0; idx < size; ++idx)
+ {
+ /* Clear unique symbol entries that belong to this
+ object. */
+ if (entries[idx].name != NULL
+ && entries[idx].map == imap)
+ {
+ entries[idx].name = NULL;
+ entries[idx].hashval = 0;
+ tab->n_elements--;
+ }
+ }
+ }
+ __rtld_lock_unlock_recursive (tab->lock);
+ }
+
/* We can unmap all the maps at once. We determined the
start address and length when we loaded the object and
the `munmap' call does the rest. */
DL_UNMAP (imap);
/* Finally, unlink the data structure and free it. */
- if (imap->l_prev != NULL)
- imap->l_prev->l_next = imap->l_next;
- else
+#if DL_NNS == 1
+ /* The assert in the (imap->l_prev == NULL) case gives
+ the compiler license to warn that NS points outside
+ the dl_ns array bounds in that case (as nsid != LM_ID_BASE
+ is tantamount to nsid >= DL_NNS). That should be impossible
+ in this configuration, so just assert about it instead. */
+ assert (nsid == LM_ID_BASE);
+ assert (imap->l_prev != NULL);
+#else
+ if (imap->l_prev == NULL)
{
-#ifdef SHARED
assert (nsid != LM_ID_BASE);
-#endif
ns->_ns_loaded = imap->l_next;
+
+ /* Update the pointer to the head of the list
+ we leave for debuggers to examine. */
+ r->r_map = (void *) ns->_ns_loaded;
}
+ else
+#endif
+ imap->l_prev->l_next = imap->l_next;
--ns->_ns_nloaded;
if (imap->l_next != NULL)
free (imap->l_reldeps);
/* Print debugging message. */
- if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0))
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
_dl_debug_printf ("\nfile=%s [%lu]; destroying link map\n",
imap->l_name, imap->l_ns);
/* If we removed any object which uses TLS bump the generation counter. */
if (any_tls)
{
- if (__builtin_expect (++GL(dl_tls_generation) == 0, 0))
- _dl_fatal_printf ("TLS generation counter wrapped! Please report as described in <http://www.gnu.org/software/libc/bugs.html>.\n");
+ if (__glibc_unlikely (++GL(dl_tls_generation) == 0))
+ _dl_fatal_printf ("TLS generation counter wrapped! Please report as described in "REPORT_BUGS_TO".\n");
if (tls_free_end == GL(dl_tls_static_used))
GL(dl_tls_static_used) = tls_free_start;
#ifdef SHARED
/* Auditing checkpoint: we have deleted all objects. */
- if (__builtin_expect (do_audit, 0))
+ if (__glibc_unlikely (do_audit))
{
struct link_map *head = ns->_ns_loaded;
/* Do not call the functions for any auditing object. */
if (__builtin_expect (ns->_ns_loaded == NULL, 0)
&& nsid == GL(dl_nns) - 1)
do
- {
- --GL(dl_nns);
-#ifndef SHARED
- if (GL(dl_nns) == 0)
- break;
-#endif
- }
+ --GL(dl_nns);
while (GL(dl_ns)[GL(dl_nns) - 1]._ns_loaded == NULL);
/* Notify the debugger those objects are finalized and gone. */
r->r_state = RT_CONSISTENT;
_dl_debug_state ();
+ LIBC_PROBE (unmap_complete, 2, nsid, r);
/* Recheck if we need to retry, release the lock. */
out:
{
struct link_map *map = _map;
- /* First see whether we can remove the object at all. */
- if (__builtin_expect (map->l_flags_1 & DF_1_NODELETE, 0))
+ /* We must take the lock to examine the contents of map and avoid
+ concurrent dlopens. */
+ __rtld_lock_lock_recursive (GL(dl_load_lock));
+
+ /* At this point we are guaranteed nobody else is touching the list of
+ loaded maps, but a concurrent dlclose might have freed our 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))
{
- assert (map->l_init_called);
/* Nope. Do nothing. */
+ __rtld_lock_unlock_recursive (GL(dl_load_lock));
return;
}
+ /* At present this is an unreliable check except in the case where the
+ caller has recursively called dlclose and we are sure the link map
+ has not been freed. In a non-recursive dlclose the map itself
+ might have been freed and this access is potentially a data race
+ with whatever other use this memory might have now, or worse we
+ might silently corrupt memory if it looks enough like a link map.
+ POSIX has language in dlclose that appears to guarantee that this
+ should be a detectable case and given that dlclose should be threadsafe
+ we need this to be a reliable detection.
+ This is bug 20990. */
if (__builtin_expect (map->l_direct_opencount, 1) == 0)
- GLRO(dl_signal_error) (0, map->l_name, NULL, N_("shared object not open"));
-
- /* Acquire the lock. */
- __rtld_lock_lock_recursive (GL(dl_load_lock));
+ {
+ __rtld_lock_unlock_recursive (GL(dl_load_lock));
+ _dl_signal_error (0, map->l_name, NULL, N_("shared object not open"));
+ }
- _dl_close_worker (map);
+ _dl_close_worker (map, false);
__rtld_lock_unlock_recursive (GL(dl_load_lock));
}