From 6630516b0a000e0ac9769eceda72881f788b23b0 Mon Sep 17 00:00:00 2001 From: Carmelo Amoroso Date: Wed, 7 Nov 2007 15:14:50 +0000 Subject: Added support for GNU hash style into dynamic linker --- ldso/ldso/dl-hash.c | 191 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 29 deletions(-) (limited to 'ldso/ldso') diff --git a/ldso/ldso/dl-hash.c b/ldso/ldso/dl-hash.c index 2d5ecddbb..c85035ba8 100644 --- a/ldso/ldso/dl-hash.c +++ b/ldso/ldso/dl-hash.c @@ -53,6 +53,19 @@ struct dyn_elf *_dl_symbol_tables = NULL; */ struct dyn_elf *_dl_handles = NULL; +#ifdef __LDSO_GNU_HASH_SUPPORT__ +/* This is the new hash function that is used by the ELF linker to generate the + * GNU hash table that each executable and library will have if --hash-style=[gnu,both] + * is passed to the linker. We need it to decode the GNU hash table. */ +static inline Elf_Symndx _dl_gnu_hash (const unsigned char *name) +{ + unsigned long h = 5381; + unsigned char c; + for (c = *name; c != '\0'; c = *++name) + h = h * 33 + c; + return h & 0xffffffff; +} +#endif /* This is the hash function that is used by the ELF linker to generate the * hash table that each executable and library is required to have. We need @@ -108,6 +121,29 @@ struct elf_resolve *_dl_add_elf_hash_table(const char *libname, tpnt->libname = _dl_strdup(libname); tpnt->dynamic_addr = (ElfW(Dyn) *)dynamic_addr; tpnt->libtype = loaded_file; + +#ifdef __LDSO_GNU_HASH_SUPPORT__ + if (dynamic_info[DT_GNU_HASH_IDX] != 0) { + + Elf32_Word *hash32 = (Elf_Symndx*)dynamic_info[DT_GNU_HASH_IDX]; + + tpnt->nbucket = *hash32++; + Elf32_Word symbias = *hash32++; + Elf32_Word bitmask_nwords = *hash32++; + /* Must be a power of two. */ + _dl_assert ((bitmask_nwords & (bitmask_nwords - 1)) == 0); + tpnt->l_gnu_bitmask_idxbits = bitmask_nwords - 1; + tpnt->l_gnu_shift = *hash32++; + + tpnt->l_gnu_bitmask = (ElfW(Addr) *) hash32; + hash32 += __ELF_NATIVE_CLASS / 32 * bitmask_nwords; + + tpnt->l_gnu_buckets = hash32; + hash32 += tpnt->nbucket; + tpnt->l_gnu_chain_zero = hash32 - symbias; + } else + /* Fall using old SysV hash table if GNU hash is not present */ +#endif if (dynamic_info[DT_HASH] != 0) { hash_addr = (Elf_Symndx*)dynamic_info[DT_HASH]; @@ -124,22 +160,117 @@ struct elf_resolve *_dl_add_elf_hash_table(const char *libname, } +/* Routine to check whether the symbol matches. */ +static __attribute_noinline__ const ElfW(Sym) * +check_match (const ElfW(Sym) *sym, char *strtab, const char* undef_name, int type_class) { + + if (type_class & (sym->st_shndx == SHN_UNDEF)) + /* undefined symbol itself */ + return NULL; + + if (sym->st_value == 0) + /* No value */ + return NULL; + + if (ELF_ST_TYPE(sym->st_info) > STT_FUNC + && ELF_ST_TYPE(sym->st_info) != STT_COMMON) + /* Ignore all but STT_NOTYPE, STT_OBJECT, STT_FUNC + * and STT_COMMON entries since these are no + * code/data definitions + */ + return NULL; + + if (_dl_strcmp(strtab + sym->st_name, undef_name) != 0) + return NULL; + + /* This is the matching symbol */ + return sym; +} + + +#ifdef __LDSO_GNU_HASH_SUPPORT__ + +static __always_inline const ElfW(Sym) * +_dl_lookup_gnu_hash(struct elf_resolve *tpnt, ElfW(Sym) *symtab, unsigned long hash, + const char* undef_name, int type_class) { + + Elf_Symndx symidx; + const ElfW(Sym) *sym; + char *strtab; + + const ElfW(Addr) *bitmask = tpnt->l_gnu_bitmask; + + ElfW(Addr) bitmask_word = bitmask[(hash / __ELF_NATIVE_CLASS) & tpnt->l_gnu_bitmask_idxbits]; + + unsigned int hashbit1 = hash & (__ELF_NATIVE_CLASS - 1); + unsigned int hashbit2 = ((hash >> tpnt->l_gnu_shift) & (__ELF_NATIVE_CLASS - 1)); + + _dl_assert (bitmask != NULL); + + if (unlikely((bitmask_word >> hashbit1) & (bitmask_word >> hashbit2) & 1)) { + + Elf32_Word bucket = tpnt->l_gnu_buckets[hash % tpnt->nbucket]; + + if (bucket != 0) { + const Elf32_Word *hasharr = &tpnt->l_gnu_chain_zero[bucket]; + do { + if (((*hasharr ^ hash) >> 1) == 0) { + symidx = hasharr - tpnt->l_gnu_chain_zero; + strtab = (char *) (tpnt->dynamic_info[DT_STRTAB]); + sym = check_match (&symtab[symidx], strtab, undef_name, type_class); + if (sym != NULL) + return sym; + } + } while ((*hasharr++ & 1u) == 0); + } + } + /* No symbol found. */ + return NULL; +} +#endif + +static __always_inline const ElfW(Sym) * +_dl_lookup_sysv_hash(struct elf_resolve *tpnt, ElfW(Sym) *symtab, unsigned long hash, const char* undef_name, int type_class) { + + unsigned long hn; + char *strtab; + const ElfW(Sym) *sym; + Elf_Symndx symidx; + + /* Avoid calling .urem here. */ + do_rem(hn, hash, tpnt->nbucket); + strtab = (char *) (tpnt->dynamic_info[DT_STRTAB]); + + _dl_assert(tpnt->elf_buckets != NULL); + + for (symidx = tpnt->elf_buckets[hn]; symidx != STN_UNDEF; symidx = tpnt->chains[symidx]) { + sym = check_match (&symtab[symidx], strtab, undef_name, type_class); + if (sym != NULL) + /* At this point the symbol is that we are looking for */ + return sym; + } + /* No symbol found into the current module*/ + return NULL; +} + /* * This function resolves externals, and this is either called when we process * relocations or when we call an entry in the PLT table for the first time. */ char *_dl_find_hash(const char *name, struct dyn_elf *rpnt, struct elf_resolve *mytpnt, int type_class) { - struct elf_resolve *tpnt; - int si; - char *strtab; + struct elf_resolve *tpnt = NULL; ElfW(Sym) *symtab; - unsigned long elf_hash_number, hn; - const ElfW(Sym) *sym; - char *weak_result = NULL; - elf_hash_number = _dl_elf_hash((const unsigned char *)name); + unsigned long elf_hash_number = 0xffffffff; + const ElfW(Sym) *sym = NULL; + + char *weak_result = NULL; +#ifdef __LDSO_GNU_HASH_SUPPORT__ + unsigned long gnu_hash_number = _dl_gnu_hash((const unsigned char *)name); +#endif + for (; rpnt; rpnt = rpnt->next) { tpnt = rpnt->dyn; @@ -165,29 +296,32 @@ char *_dl_find_hash(const char *name, struct dyn_elf *rpnt, struct elf_resolve * if (tpnt->nbucket == 0) continue; - /* Avoid calling .urem here. */ - do_rem(hn, elf_hash_number, tpnt->nbucket); - symtab = (ElfW(Sym) *) tpnt->dynamic_info[DT_SYMTAB]; - strtab = (char *) (tpnt->dynamic_info[DT_STRTAB]); - - for (si = tpnt->elf_buckets[hn]; si != STN_UNDEF; si = tpnt->chains[si]) { - sym = &symtab[si]; + symtab = (ElfW(Sym) *) (intptr_t) (tpnt->dynamic_info[DT_SYMTAB]); + +#ifdef __LDSO_GNU_HASH_SUPPORT__ + /* Prefer GNU hash style, if any */ + if(tpnt->l_gnu_bitmask) { + if((sym = _dl_lookup_gnu_hash(tpnt, symtab, gnu_hash_number, name, type_class)) != NULL) + /* If sym has been found, do not search further */ + break; + } else { +#endif + /* Use the old SysV-style hash table */ + + /* Calculate the old sysv hash number only once */ + if(elf_hash_number == 0xffffffff) + elf_hash_number = _dl_elf_hash((const unsigned char *)name); - if (type_class & (sym->st_shndx == SHN_UNDEF)) - continue; - if (sym->st_value == 0) - continue; - if (ELF_ST_TYPE(sym->st_info) > STT_FUNC - && ELF_ST_TYPE(sym->st_info) != STT_COMMON) - /* Ignore all but STT_NOTYPE, STT_OBJECT, STT_FUNC - * and STT_COMMON entries since these are no - * code/data definitions - */ - continue; - if (_dl_strcmp(strtab + sym->st_name, name) != 0) - continue; + if((sym = _dl_lookup_sysv_hash(tpnt, symtab, elf_hash_number, name, type_class)) != NULL ) + break; +#ifdef __LDSO_GNU_HASH_SUPPORT__ + } +#endif + } /* end of for (; rpnt; rpnt = rpnt->next) { */ - switch (ELF_ST_BIND(sym->st_info)) { + if(sym) { + /* At this point we have found the requested symbol, do binding */ + switch (ELF_ST_BIND(sym->st_info)) { case STB_WEAK: #if 0 /* Perhaps we should support old style weak symbol handling @@ -200,7 +334,6 @@ char *_dl_find_hash(const char *name, struct dyn_elf *rpnt, struct elf_resolve * return (char*) DL_RELOC_ADDR(tpnt->loadaddr, sym->st_value); default: /* Local symbols not handled here */ break; - } } } return weak_result; -- cgit v1.2.3