diff options
Diffstat (limited to 'newlib/libc/sys/linux/dl/dl-open.c')
-rw-r--r-- | newlib/libc/sys/linux/dl/dl-open.c | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/newlib/libc/sys/linux/dl/dl-open.c b/newlib/libc/sys/linux/dl/dl-open.c new file mode 100644 index 000000000..195361427 --- /dev/null +++ b/newlib/libc/sys/linux/dl/dl-open.c @@ -0,0 +1,487 @@ +/* Load a shared object at runtime, relocate it, and run its initializer. + Copyright (C) 1996,1997,1998,1999,2000,2001 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, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include <assert.h> +#include <dlfcn.h> +#include <errno.h> +#include <libintl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> /* Check whether MAP_COPY is defined. */ +#include <sys/param.h> +#include <ldsodefs.h> +#include <bp-sym.h> + +#include <dl-dst.h> +#include <machine/weakalias.h> + + +extern ElfW(Addr) _dl_sysdep_start (void **start_argptr, + void (*dl_main) (const ElfW(Phdr) *phdr, + ElfW(Word) phnum, + ElfW(Addr) *user_entry)); +weak_extern (BP_SYM (_dl_sysdep_start)) + +/* This function is used to unload the cache file if necessary. */ +extern void _dl_unload_cache (void); + +int __libc_argc = 0; +char **__libc_argv = NULL; + +extern char **environ; + +extern int _dl_lazy; /* Do we do lazy relocations? */ + +/* Undefine the following for debugging. */ +/* #define SCOPE_DEBUG 1 */ +#ifdef SCOPE_DEBUG +static void show_scope (struct link_map *new); +#endif + +extern size_t _dl_platformlen; + +/* We must be carefull not to leave us in an inconsistent state. Thus we + catch any error and re-raise it after cleaning up. */ + +struct dl_open_args +{ + const char *file; + int mode; + const void *caller; + struct link_map *map; +}; + + +static int +add_to_global (struct link_map *new) +{ + struct link_map **new_global; + unsigned int to_add = 0; + unsigned int cnt; + + /* Count the objects we have to put in the global scope. */ + for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt) + if (new->l_searchlist.r_list[cnt]->l_global == 0) + ++to_add; + + /* The symbols of the new objects and its dependencies are to be + introduced into the global scope that will be used to resolve + references from other dynamically-loaded objects. + + The global scope is the searchlist in the main link map. We + extend this list if necessary. There is one problem though: + since this structure was allocated very early (before the libc + is loaded) the memory it uses is allocated by the malloc()-stub + in the ld.so. When we come here these functions are not used + anymore. Instead the malloc() implementation of the libc is + used. But this means the block from the main map cannot be used + in an realloc() call. Therefore we allocate a completely new + array the first time we have to add something to the locale scope. */ + + if (_dl_global_scope_alloc == 0) + { + /* This is the first dynamic object given global scope. */ + _dl_global_scope_alloc = _dl_main_searchlist->r_nlist + to_add + 8; + new_global = (struct link_map **) + malloc (_dl_global_scope_alloc * sizeof (struct link_map *)); + if (new_global == NULL) + { + _dl_global_scope_alloc = 0; + nomem: + _dl_signal_error (ENOMEM, new->l_libname->name, NULL, + N_("cannot extend global scope")); + return 1; + } + + /* Copy over the old entries. */ + memcpy (new_global, _dl_main_searchlist->r_list, + (_dl_main_searchlist->r_nlist * sizeof (struct link_map *))); + + _dl_main_searchlist->r_list = new_global; + } + else if (_dl_main_searchlist->r_nlist + to_add > _dl_global_scope_alloc) + { + /* We have to extend the existing array of link maps in the + main map. */ + new_global = (struct link_map **) + realloc (_dl_main_searchlist->r_list, + ((_dl_global_scope_alloc + to_add + 8) + * sizeof (struct link_map *))); + if (new_global == NULL) + goto nomem; + + _dl_global_scope_alloc += to_add + 8; + _dl_main_searchlist->r_list = new_global; + } + + /* Now add the new entries. */ + for (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; + _dl_main_searchlist->r_list[_dl_main_searchlist->r_nlist] = map; + ++_dl_main_searchlist->r_nlist; + } + } + + return 0; +} + + +static void +dl_open_worker (void *a) +{ + struct dl_open_args *args = a; + const char *file = args->file; + int mode = args->mode; + struct link_map *new, *l; + const char *dst; + int lazy; + unsigned int i; + + /* Maybe we have to expand a DST. */ + dst = strchr (file, '$'); + if (dst != NULL) + { + const void *caller = args->caller; + size_t len = strlen (file); + size_t required; + struct link_map *call_map; + char *new_file; + + /* We have to find out from which object the caller is calling. */ + call_map = NULL; + for (l = _dl_loaded; l; l = l->l_next) + if (caller >= (const void *) l->l_map_start + && caller < (const void *) l->l_map_end) + { + /* There must be exactly one DSO for the range of the virtual + memory. Otherwise something is really broken. */ + call_map = l; + break; + } + + if (call_map == NULL) + /* In this case we assume this is the main application. */ + call_map = _dl_loaded; + + /* Determine how much space we need. We have to allocate the + memory locally. */ + required = DL_DST_REQUIRED (call_map, file, len, _dl_dst_count (dst, 0)); + + /* Get space for the new file name. */ + new_file = (char *) alloca (required + 1); + + /* Generate the new file name. */ + DL_DST_SUBSTITUTE (call_map, file, new_file, 0); + + /* If the substitution failed don't try to load. */ + if (*new_file == '\0') + _dl_signal_error (0, "dlopen", NULL, + N_("empty dynamic string token substitution")); + + /* Now we have a new file name. */ + file = new_file; + } + + /* Load the named object. */ + args->map = new = _dl_map_object (NULL, file, 0, lt_loaded, 0, + mode); + + /* If the pointer returned is NULL this means the RTLD_NOLOAD flag is + set and the object is not already loaded. */ + if (new == NULL) + { + assert (mode & RTLD_NOLOAD); + return; + } + + /* It was already open. */ + if (new->l_searchlist.r_list != NULL) + { + /* Let the user know about the opencount. */ + if (__builtin_expect (_dl_debug_mask & DL_DEBUG_FILES, 0)) + _dl_debug_printf ("opening file=%s; opencount == %u\n\n", + new->l_name, new->l_opencount); + + /* If the user requested the object to be in the global namespace + but it is not so far, add it now. */ + if ((mode & RTLD_GLOBAL) && new->l_global == 0) + (void) add_to_global (new); + + /* Increment just the reference counter of the object. */ + ++new->l_opencount; + + return; + } + + /* Load that object's dependencies. */ + _dl_map_object_deps (new, NULL, 0, 0); + + /* So far, so good. Now check the versions. */ + for (i = 0; i < new->l_searchlist.r_nlist; ++i) + if (new->l_searchlist.r_list[i]->l_versions == NULL) + (void) _dl_check_map_versions (new->l_searchlist.r_list[i], 0, 0); + +#ifdef SCOPE_DEBUG + show_scope (new); +#endif + + /* Only do lazy relocation if `LD_BIND_NOW' is not set. */ + lazy = (mode & RTLD_BINDING_MASK) == RTLD_LAZY && _dl_lazy; + + /* Relocate the objects loaded. We do this in reverse order so that copy + relocs of earlier objects overwrite the data written by later objects. */ + + l = new; + while (l->l_next) + l = l->l_next; + while (1) + { + if (! l->l_relocated) + { +#if 0 +#ifdef SHARED + if (_dl_profile != NULL) + { + /* If this here is the shared object which we want to profile + make sure the profile is started. We can find out whether + this is necessary or not by observing the `_dl_profile_map' + variable. If was NULL but is not NULL afterwars we must + start the profiling. */ + struct link_map *old_profile_map = _dl_profile_map; + + _dl_relocate_object (l, l->l_scope, 1, 1); + + if (old_profile_map == NULL && _dl_profile_map != NULL) + /* We must prepare the profiling. */ + _dl_start_profile (_dl_profile_map, _dl_profile_output); + } + else +#endif +#endif + _dl_relocate_object (l, l->l_scope, lazy, 0); + } + + if (l == new) + break; + l = l->l_prev; + } + + /* Increment the open count for all dependencies. If the file is + not loaded as a dependency here add the search list of the newly + loaded object to the scope. */ + for (i = 0; i < new->l_searchlist.r_nlist; ++i) + if (++new->l_searchlist.r_list[i]->l_opencount > 1 + && new->l_searchlist.r_list[i]->l_type == lt_loaded) + { + struct link_map *imap = new->l_searchlist.r_list[i]; + struct r_scope_elem **runp = imap->l_scope; + size_t cnt = 0; + + while (*runp != NULL) + { + /* This can happen if imap was just loaded, but during + relocation had l_opencount bumped because of relocation + dependency. Avoid duplicates in l_scope. */ + if (__builtin_expect (*runp == &new->l_searchlist, 0)) + break; + + ++cnt; + ++runp; + } + + if (*runp != NULL) + /* Avoid duplicates. */ + continue; + + if (__builtin_expect (cnt + 1 >= imap->l_scope_max, 0)) + { + /* The 'r_scope' array is too small. Allocate a new one + dynamically. */ + struct r_scope_elem **newp; + size_t new_size = imap->l_scope_max * 2; + + if (imap->l_scope == imap->l_scope_mem) + { + 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")); + imap->l_scope = memcpy (newp, imap->l_scope, + cnt * sizeof (imap->l_scope[0])); + } + else + { + newp = (struct r_scope_elem **) + realloc (imap->l_scope, + new_size * sizeof (struct r_scope_elem *)); + if (newp == NULL) + _dl_signal_error (ENOMEM, "dlopen", NULL, + N_("cannot create scope list")); + imap->l_scope = newp; + } + + imap->l_scope_max = new_size; + } + + imap->l_scope[cnt++] = &new->l_searchlist; + imap->l_scope[cnt] = NULL; + } + + /* Run the initializer functions of new objects. */ + _dl_init (new, __libc_argc, __libc_argv, environ); + + /* 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; + + /* Mark the object as not deletable if the RTLD_NODELETE flags was + passed. */ + if (__builtin_expect (mode & RTLD_NODELETE, 0)) + new->l_flags_1 |= DF_1_NODELETE; + + /* Let the user know about the opencount. */ + if (__builtin_expect (_dl_debug_mask & DL_DEBUG_FILES, 0)) + _dl_debug_printf ("opening file=%s; opencount == %u\n\n", + new->l_name, new->l_opencount); +} + + +void * +internal_function +_dl_open (const char *file, int mode, const void *caller) +{ + struct dl_open_args args; + const char *objname; + const char *errstring; + int errcode; + + if ((mode & RTLD_BINDING_MASK) == 0) + /* One of the flags must be set. */ + _dl_signal_error (EINVAL, file, NULL, N_("invalid mode for dlopen()")); + + /* Make sure we are alone. */ +#ifdef HAVE_DD_LOCK + __lock_acquire_recursive(_dl_load_lock); +#endif + + args.file = file; + args.mode = mode; + args.caller = caller; + args.map = NULL; + errcode = _dl_catch_error (&objname, &errstring, dl_open_worker, &args); + +#ifndef MAP_COPY + /* We must munmap() the cache file. */ + _dl_unload_cache (); +#endif + + /* Release the lock. */ +#ifdef HAVE_DD_LOCK + __lock_release_recursive(_dl_load_lock); +#endif + + + if (errstring) + { + /* Some error occurred during loading. */ + char *local_errstring; + size_t len_errstring; + + /* Remove the object from memory. It may be in an inconsistent + state if relocation failed, for example. */ + if (args.map) + { + unsigned int i; + + /* Increment open counters for all objects since this has + not happened yet. */ + for (i = 0; i < args.map->l_searchlist.r_nlist; ++i) + ++args.map->l_searchlist.r_list[i]->l_opencount; + + _dl_close (args.map); + } + + /* Make a local copy of the error string so that we can release the + memory allocated for it. */ + len_errstring = strlen (errstring) + 1; + if (objname == errstring + len_errstring) + { + size_t total_len = len_errstring + strlen (objname) + 1; + local_errstring = alloca (total_len); + memcpy (local_errstring, errstring, total_len); + objname = local_errstring + len_errstring; + } + else + { + local_errstring = alloca (len_errstring); + memcpy (local_errstring, errstring, len_errstring); + } + + if (errstring != _dl_out_of_memory) + free ((char *) errstring); + + /* Reraise the error. */ + _dl_signal_error (errcode, objname, NULL, local_errstring); + } + +#ifndef SHARED + DL_STATIC_INIT (args.map); +#endif + + return args.map; +} + + +#ifdef SCOPE_DEBUG +#include <unistd.h> + +static void +show_scope (struct link_map *new) +{ + int scope_cnt; + + for (scope_cnt = 0; new->l_scope[scope_cnt] != NULL; ++scope_cnt) + { + char numbuf[2]; + unsigned int cnt; + + numbuf[0] = '0' + scope_cnt; + numbuf[1] = '\0'; + _dl_printf ("scope %s:", numbuf); + + for (cnt = 0; cnt < new->l_scope[scope_cnt]->r_nlist; ++cnt) + if (*new->l_scope[scope_cnt]->r_list[cnt]->l_name) + _dl_printf (" %s", new->l_scope[scope_cnt]->r_list[cnt]->l_name); + else + _dl_printf (" <main>"); + + _dl_printf ("\n"); + } +} +#endif |