| /* Look up a symbol in the loaded objects. |
| Copyright (C) 1995-2005, 2006, 2007 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 <alloca.h> |
| #include <libintl.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <ldsodefs.h> |
| #include <dl-hash.h> |
| #include <dl-machine.h> |
| #include <sysdep-cancel.h> |
| #include <bits/libc-lock.h> |
| #include <tls.h> |
| |
| #include <assert.h> |
| |
| #define VERSTAG(tag) (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX (tag)) |
| |
| /* We need this string more than once. */ |
| static const char undefined_msg[] = "undefined symbol: "; |
| |
| |
| struct sym_val |
| { |
| const ElfW(Sym) *s; |
| struct link_map *m; |
| }; |
| |
| |
| #define make_string(string, rest...) \ |
| ({ \ |
| const char *all[] = { string, ## rest }; \ |
| size_t len, cnt; \ |
| char *result, *cp; \ |
| \ |
| len = 1; \ |
| for (cnt = 0; cnt < sizeof (all) / sizeof (all[0]); ++cnt) \ |
| len += strlen (all[cnt]); \ |
| \ |
| cp = result = alloca (len); \ |
| for (cnt = 0; cnt < sizeof (all) / sizeof (all[0]); ++cnt) \ |
| cp = __stpcpy (cp, all[cnt]); \ |
| \ |
| result; \ |
| }) |
| |
| /* Statistics function. */ |
| #ifdef SHARED |
| # define bump_num_relocations() ++GL(dl_num_relocations) |
| #else |
| # define bump_num_relocations() ((void) 0) |
| #endif |
| |
| |
| /* The actual lookup code. */ |
| #include "do-lookup.h" |
| |
| |
| static uint_fast32_t |
| dl_new_hash (const char *s) |
| { |
| uint_fast32_t h = 5381; |
| for (unsigned char c = *s; c != '\0'; c = *++s) |
| h = h * 33 + c; |
| return h & 0xffffffff; |
| } |
| |
| |
| /* Add extra dependency on MAP to UNDEF_MAP. */ |
| static int |
| internal_function |
| add_dependency (struct link_map *undef_map, struct link_map *map, int flags) |
| { |
| struct link_map *runp; |
| unsigned int i; |
| int result = 0; |
| |
| /* Avoid self-references and references to objects which cannot be |
| unloaded anyway. */ |
| if (undef_map == map) |
| return 0; |
| |
| /* Avoid references to objects which cannot be unloaded anyway. */ |
| assert (map->l_type == lt_loaded); |
| if ((map->l_flags_1 & DF_1_NODELETE) != 0) |
| return 0; |
| |
| struct link_map_reldeps *l_reldeps |
| = atomic_forced_read (undef_map->l_reldeps); |
| |
| /* Make sure l_reldeps is read before l_initfini. */ |
| atomic_read_barrier (); |
| |
| /* Determine whether UNDEF_MAP already has a reference to MAP. First |
| look in the normal dependencies. */ |
| struct link_map **l_initfini = atomic_forced_read (undef_map->l_initfini); |
| if (l_initfini != NULL) |
| { |
| for (i = 0; l_initfini[i] != NULL; ++i) |
| if (l_initfini[i] == map) |
| return 0; |
| } |
| |
| /* No normal dependency. See whether we already had to add it |
| to the special list of dynamic dependencies. */ |
| unsigned int l_reldepsact = 0; |
| if (l_reldeps != NULL) |
| { |
| struct link_map **list = &l_reldeps->list[0]; |
| l_reldepsact = l_reldeps->act; |
| for (i = 0; i < l_reldepsact; ++i) |
| if (list[i] == map) |
| return 0; |
| } |
| |
| /* Save serial number of the target MAP. */ |
| unsigned long long serial = map->l_serial; |
| |
| /* Make sure nobody can unload the object while we are at it. */ |
| if (__builtin_expect (flags & DL_LOOKUP_GSCOPE_LOCK, 0)) |
| { |
| /* We can't just call __rtld_lock_lock_recursive (GL(dl_load_lock)) |
| here, that can result in ABBA deadlock. */ |
| THREAD_GSCOPE_RESET_FLAG (); |
| __rtld_lock_lock_recursive (GL(dl_load_lock)); |
| /* While MAP value won't change, after THREAD_GSCOPE_RESET_FLAG () |
| it can e.g. point to unallocated memory. So avoid the optimizer |
| treating the above read from MAP->l_serial as ensurance it |
| can safely dereference it. */ |
| map = atomic_forced_read (map); |
| |
| /* From this point on it is unsafe to dereference MAP, until it |
| has been found in one of the lists. */ |
| |
| /* Redo the l_initfini check in case undef_map's l_initfini |
| changed in the mean time. */ |
| if (undef_map->l_initfini != l_initfini |
| && undef_map->l_initfini != NULL) |
| { |
| l_initfini = undef_map->l_initfini; |
| for (i = 0; l_initfini[i] != NULL; ++i) |
| if (l_initfini[i] == map) |
| goto out_check; |
| } |
| |
| /* Redo the l_reldeps check if undef_map's l_reldeps changed in |
| the mean time. */ |
| if (undef_map->l_reldeps != NULL) |
| { |
| if (undef_map->l_reldeps != l_reldeps) |
| { |
| struct link_map **list = &undef_map->l_reldeps->list[0]; |
| l_reldepsact = undef_map->l_reldeps->act; |
| for (i = 0; i < l_reldepsact; ++i) |
| if (list[i] == map) |
| goto out_check; |
| } |
| else if (undef_map->l_reldeps->act > l_reldepsact) |
| { |
| struct link_map **list |
| = &undef_map->l_reldeps->list[0]; |
| i = l_reldepsact; |
| l_reldepsact = undef_map->l_reldeps->act; |
| for (; i < l_reldepsact; ++i) |
| if (list[i] == map) |
| goto out_check; |
| } |
| } |
| } |
| else |
| __rtld_lock_lock_recursive (GL(dl_load_lock)); |
| |
| /* The object is not yet in the dependency list. Before we add |
| it make sure just one more time the object we are about to |
| reference is still available. There is a brief period in |
| which the object could have been removed since we found the |
| definition. */ |
| runp = GL(dl_ns)[undef_map->l_ns]._ns_loaded; |
| while (runp != NULL && runp != map) |
| runp = runp->l_next; |
| |
| if (runp != NULL) |
| { |
| /* The object is still available. */ |
| |
| /* MAP could have been dlclosed, freed and then some other dlopened |
| library could have the same link_map pointer. */ |
| if (map->l_serial != serial) |
| goto out_check; |
| |
| /* 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) |
| 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) |
| { |
| map->l_flags_1 |= DF_1_NODELETE; |
| goto out; |
| } |
| |
| /* Add the reference now. */ |
| if (__builtin_expect (l_reldepsact >= undef_map->l_reldepsmax, 0)) |
| { |
| /* Allocate more memory for the dependency list. Since this |
| can never happen during the startup phase we can use |
| `realloc'. */ |
| struct link_map_reldeps *newp; |
| unsigned int max |
| = undef_map->l_reldepsmax ? undef_map->l_reldepsmax * 2 : 10; |
| |
| newp = malloc (sizeof (*newp) + max * sizeof (struct link_map *)); |
| if (newp == NULL) |
| { |
| /* If we didn't manage to allocate memory for the list this is |
| 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; |
| goto out; |
| } |
| else |
| { |
| if (l_reldepsact) |
| memcpy (&newp->list[0], &undef_map->l_reldeps->list[0], |
| l_reldepsact * sizeof (struct link_map *)); |
| newp->list[l_reldepsact] = map; |
| newp->act = l_reldepsact + 1; |
| atomic_write_barrier (); |
| void *old = undef_map->l_reldeps; |
| undef_map->l_reldeps = newp; |
| undef_map->l_reldepsmax = max; |
| if (old) |
| _dl_scope_free (old); |
| } |
| } |
| else |
| { |
| undef_map->l_reldeps->list[l_reldepsact] = map; |
| atomic_write_barrier (); |
| undef_map->l_reldeps->act = l_reldepsact + 1; |
| } |
| |
| /* Display information if we are debugging. */ |
| if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_FILES, 0)) |
| _dl_debug_printf ("\ |
| \nfile=%s [%lu]; needed by %s [%lu] (relocation dependency)\n\n", |
| map->l_name[0] ? map->l_name : rtld_progname, |
| map->l_ns, |
| undef_map->l_name[0] |
| ? undef_map->l_name : rtld_progname, |
| undef_map->l_ns); |
| } |
| else |
| /* Whoa, that was bad luck. We have to search again. */ |
| result = -1; |
| |
| out: |
| /* Release the lock. */ |
| __rtld_lock_unlock_recursive (GL(dl_load_lock)); |
| |
| if (__builtin_expect (flags & DL_LOOKUP_GSCOPE_LOCK, 0)) |
| THREAD_GSCOPE_SET_FLAG (); |
| |
| return result; |
| |
| out_check: |
| if (map->l_serial != serial) |
| result = -1; |
| goto out; |
| } |
| |
| static void |
| internal_function |
| _dl_debug_bindings (const char *undef_name, struct link_map *undef_map, |
| const ElfW(Sym) **ref, struct sym_val *value, |
| const struct r_found_version *version, int type_class, |
| int protected); |
| |
| |
| /* Search loaded objects' symbol tables for a definition of the symbol |
| UNDEF_NAME, perhaps with a requested version for the symbol. |
| |
| We must never have calls to the audit functions inside this function |
| or in any function which gets called. If this would happen the audit |
| code might create a thread which can throw off all the scope locking. */ |
| lookup_t |
| internal_function |
| _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map, |
| const ElfW(Sym) **ref, |
| struct r_scope_elem *symbol_scope[], |
| const struct r_found_version *version, |
| int type_class, int flags, struct link_map *skip_map) |
| { |
| const uint_fast32_t new_hash = dl_new_hash (undef_name); |
| unsigned long int old_hash = 0xffffffff; |
| struct sym_val current_value = { NULL, NULL }; |
| struct r_scope_elem **scope = symbol_scope; |
| |
| 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); |
| |
| size_t i = 0; |
| if (__builtin_expect (skip_map != NULL, 0)) |
| /* Search the relevant loaded objects for a definition. */ |
| while ((*scope)->r_list[i] != skip_map) |
| ++i; |
| |
| /* Search the relevant loaded objects for a definition. */ |
| for (size_t start = i; *scope != NULL; start = 0, ++scope) |
| { |
| int res = do_lookup_x (undef_name, new_hash, &old_hash, *ref, |
| ¤t_value, *scope, start, version, flags, |
| skip_map, type_class); |
| if (res > 0) |
| break; |
| |
| if (__builtin_expect (res, 0) < 0 && skip_map == NULL) |
| { |
| /* Oh, oh. The file named in the relocation entry does not |
| contain the needed symbol. This code is never reached |
| for unversioned lookups. */ |
| assert (version != NULL); |
| const char *reference_name = undef_map ? undef_map->l_name : NULL; |
| |
| /* XXX We cannot translate the message. */ |
| _dl_signal_cerror (0, (reference_name[0] |
| ? reference_name |
| : (rtld_progname ?: "<main program>")), |
| N_("relocation error"), |
| make_string ("symbol ", undef_name, ", version ", |
| version->name, |
| " not defined in file ", |
| version->filename, |
| " with link time reference", |
| res == -2 |
| ? " (no version symbols)" : "")); |
| *ref = NULL; |
| return 0; |
| } |
| } |
| |
| if (__builtin_expect (current_value.s == NULL, 0)) |
| { |
| if ((*ref == NULL || ELFW(ST_BIND) ((*ref)->st_info) != STB_WEAK) |
| && skip_map == NULL) |
| { |
| /* We could find no value for a strong reference. */ |
| const char *reference_name = undef_map ? undef_map->l_name : ""; |
| const char *versionstr = version ? ", version " : ""; |
| const char *versionname = (version && version->name |
| ? version->name : ""); |
| |
| /* XXX We cannot translate the message. */ |
| _dl_signal_cerror (0, (reference_name[0] |
| ? reference_name |
| : (rtld_progname ?: "<main program>")), |
| N_("symbol lookup error"), |
| make_string (undefined_msg, undef_name, |
| versionstr, versionname)); |
| } |
| *ref = NULL; |
| return 0; |
| } |
| |
| int protected = (*ref |
| && ELFW(ST_VISIBILITY) ((*ref)->st_other) == STV_PROTECTED); |
| if (__builtin_expect (protected != 0, 0)) |
| { |
| /* It is very tricky. We need to figure out what value to |
| return for the protected symbol. */ |
| if (type_class == ELF_RTYPE_CLASS_PLT) |
| { |
| if (current_value.s != NULL && current_value.m != undef_map) |
| { |
| current_value.s = *ref; |
| current_value.m = undef_map; |
| } |
| } |
| else |
| { |
| struct sym_val protected_value = { NULL, NULL }; |
| |
| for (scope = symbol_scope; *scope != NULL; i = 0, ++scope) |
| if (do_lookup_x (undef_name, new_hash, &old_hash, *ref, |
| &protected_value, *scope, i, version, flags, |
| skip_map, ELF_RTYPE_CLASS_PLT) != 0) |
| break; |
| |
| if (protected_value.s != NULL && protected_value.m != undef_map) |
| { |
| current_value.s = *ref; |
| current_value.m = undef_map; |
| } |
| } |
| } |
| |
| /* We have to check whether this would bind UNDEF_MAP to an object |
| in the global scope which was dynamically loaded. In this case |
| we have to prevent the latter from being unloaded unless the |
| UNDEF_MAP object is also unloaded. */ |
| if (__builtin_expect (current_value.m->l_type == lt_loaded, 0) |
| /* Don't do this for explicit lookups as opposed to implicit |
| runtime lookups. */ |
| && (flags & DL_LOOKUP_ADD_DEPENDENCY) != 0 |
| /* Add UNDEF_MAP to the dependencies. */ |
| && add_dependency (undef_map, current_value.m, flags) < 0) |
| /* Something went wrong. Perhaps the object we tried to reference |
| was just removed. Try finding another definition. */ |
| return _dl_lookup_symbol_x (undef_name, undef_map, ref, |
| (flags & DL_LOOKUP_GSCOPE_LOCK) |
| ? undef_map->l_scope : symbol_scope, |
| version, type_class, flags, skip_map); |
| |
| /* The object is used. */ |
| current_value.m->l_used = 1; |
| |
| if (__builtin_expect (GLRO(dl_debug_mask) |
| & (DL_DEBUG_BINDINGS|DL_DEBUG_PRELINK), 0)) |
| _dl_debug_bindings (undef_name, undef_map, ref, |
| ¤t_value, version, type_class, protected); |
| |
| *ref = current_value.s; |
| return LOOKUP_VALUE (current_value.m); |
| } |
| |
| |
| /* Cache the location of MAP's hash table. */ |
| |
| void |
| internal_function |
| _dl_setup_hash (struct link_map *map) |
| { |
| Elf_Symndx *hash; |
| Elf_Symndx nchain; |
| |
| if (__builtin_expect (map->l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM |
| + DT_THISPROCNUM + DT_VERSIONTAGNUM |
| + DT_EXTRANUM + DT_VALNUM] != NULL, 1)) |
| { |
| Elf32_Word *hash32 |
| = (void *) D_PTR (map, l_info[DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM |
| + DT_THISPROCNUM + DT_VERSIONTAGNUM |
| + DT_EXTRANUM + DT_VALNUM]); |
| map->l_nbuckets = *hash32++; |
| Elf32_Word symbias = *hash32++; |
| Elf32_Word bitmask_nwords = *hash32++; |
| /* Must be a power of two. */ |
| assert ((bitmask_nwords & (bitmask_nwords - 1)) == 0); |
| map->l_gnu_bitmask_idxbits = bitmask_nwords - 1; |
| map->l_gnu_shift = *hash32++; |
| |
| map->l_gnu_bitmask = (ElfW(Addr) *) hash32; |
| hash32 += __ELF_NATIVE_CLASS / 32 * bitmask_nwords; |
| |
| map->l_gnu_buckets = hash32; |
| hash32 += map->l_nbuckets; |
| map->l_gnu_chain_zero = hash32 - symbias; |
| return; |
| } |
| |
| if (!map->l_info[DT_HASH]) |
| return; |
| hash = (void *) D_PTR (map, l_info[DT_HASH]); |
| |
| map->l_nbuckets = *hash++; |
| nchain = *hash++; |
| map->l_buckets = hash; |
| hash += map->l_nbuckets; |
| map->l_chain = hash; |
| } |
| |
| |
| static void |
| internal_function |
| _dl_debug_bindings (const char *undef_name, struct link_map *undef_map, |
| const ElfW(Sym) **ref, struct sym_val *value, |
| const struct r_found_version *version, int type_class, |
| int protected) |
| { |
| const char *reference_name = undef_map->l_name; |
| |
| if (GLRO(dl_debug_mask) & DL_DEBUG_BINDINGS) |
| { |
| _dl_debug_printf ("binding file %s [%lu] to %s [%lu]: %s symbol `%s'", |
| (reference_name[0] |
| ? reference_name |
| : (rtld_progname ?: "<main program>")), |
| undef_map->l_ns, |
| value->m->l_name[0] ? value->m->l_name : rtld_progname, |
| value->m->l_ns, |
| protected ? "protected" : "normal", undef_name); |
| if (version) |
| _dl_debug_printf_c (" [%s]\n", version->name); |
| else |
| _dl_debug_printf_c ("\n"); |
| } |
| #ifdef SHARED |
| if (GLRO(dl_debug_mask) & DL_DEBUG_PRELINK) |
| { |
| int conflict = 0; |
| struct sym_val val = { NULL, NULL }; |
| |
| if ((GLRO(dl_trace_prelink_map) == NULL |
| || GLRO(dl_trace_prelink_map) == GL(dl_ns)[LM_ID_BASE]._ns_loaded) |
| && undef_map != GL(dl_ns)[LM_ID_BASE]._ns_loaded) |
| { |
| const uint_fast32_t new_hash = dl_new_hash (undef_name); |
| unsigned long int old_hash = 0xffffffff; |
| |
| do_lookup_x (undef_name, new_hash, &old_hash, *ref, &val, |
| undef_map->l_local_scope[0], 0, version, 0, NULL, |
| type_class); |
| |
| if (val.s != value->s || val.m != value->m) |
| conflict = 1; |
| } |
| |
| if (value->s |
| && (__builtin_expect (ELFW(ST_TYPE) (value->s->st_info) |
| == STT_TLS, 0))) |
| type_class = 4; |
| |
| if (conflict |
| || GLRO(dl_trace_prelink_map) == undef_map |
| || GLRO(dl_trace_prelink_map) == NULL |
| || type_class == 4) |
| { |
| _dl_printf ("%s 0x%0*Zx 0x%0*Zx -> 0x%0*Zx 0x%0*Zx ", |
| conflict ? "conflict" : "lookup", |
| (int) sizeof (ElfW(Addr)) * 2, |
| (size_t) undef_map->l_map_start, |
| (int) sizeof (ElfW(Addr)) * 2, |
| (size_t) (((ElfW(Addr)) *ref) - undef_map->l_map_start), |
| (int) sizeof (ElfW(Addr)) * 2, |
| (size_t) (value->s ? value->m->l_map_start : 0), |
| (int) sizeof (ElfW(Addr)) * 2, |
| (size_t) (value->s ? value->s->st_value : 0)); |
| |
| if (conflict) |
| _dl_printf ("x 0x%0*Zx 0x%0*Zx ", |
| (int) sizeof (ElfW(Addr)) * 2, |
| (size_t) (val.s ? val.m->l_map_start : 0), |
| (int) sizeof (ElfW(Addr)) * 2, |
| (size_t) (val.s ? val.s->st_value : 0)); |
| |
| _dl_printf ("/%x %s\n", type_class, undef_name); |
| } |
| } |
| #endif |
| } |