Add GNU hash support for rtld.

Based on dragonflybsd support for GNU hash by John Marino <draco marino st>
Reviewed by:	kan
Tested by:	bapt
MFC after:	2 weeks
This commit is contained in:
Konstantin Belousov 2012-04-30 13:31:10 +00:00
parent 34cb87ba95
commit f62651920d
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=234841
9 changed files with 163 additions and 19 deletions

View File

@ -132,7 +132,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
* limited amounts of stack available so we cannot use alloca().
*/
if (obj != obj_rtld) {
cache = calloc(obj->nchains, sizeof(SymCache));
cache = calloc(obj->dynsymcount, sizeof(SymCache));
/* No need to check for NULL here */
} else
cache = NULL;

View File

@ -328,7 +328,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
* The dynamic loader may be called from a thread, we have
* limited amounts of stack available so we cannot use alloca().
*/
cache = calloc(obj->nchains, sizeof(SymCache));
cache = calloc(obj->dynsymcount, sizeof(SymCache));
/* No need to check for NULL here */
rellim = (const Elf_Rel *)((caddr_t)obj->rel + obj->relsize);

View File

@ -133,7 +133,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
* limited amounts of stack available so we cannot use alloca().
*/
if (obj != obj_rtld) {
cache = calloc(obj->nchains, sizeof(SymCache));
cache = calloc(obj->dynsymcount, sizeof(SymCache));
/* No need to check for NULL here */
} else
cache = NULL;

View File

@ -104,7 +104,7 @@ alloc_fptrs(Obj_Entry *obj, bool mapped)
struct fptr **fptrs;
size_t fbytes;
fbytes = obj->nchains * sizeof(struct fptr *);
fbytes = obj->dynsymcount * sizeof(struct fptr *);
/*
* Avoid malloc, if requested. Happens when relocating
@ -138,7 +138,7 @@ free_fptrs(Obj_Entry *obj, bool mapped)
if (fptrs == NULL)
return;
fbytes = obj->nchains * sizeof(struct fptr *);
fbytes = obj->dynsymcount * sizeof(struct fptr *);
if (mapped)
munmap(fptrs, fbytes);
else
@ -348,7 +348,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
const Elf_Rela *relalim;
const Elf_Rela *rela;
SymCache *cache;
int bytes = obj->nchains * sizeof(SymCache);
int bytes = obj->dynsymcount * sizeof(SymCache);
int r = -1;
/*

View File

@ -299,7 +299,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
* limited amounts of stack available so we cannot use alloca().
*/
if (obj != obj_rtld) {
cache = calloc(obj->nchains, sizeof(SymCache));
cache = calloc(obj->dynsymcount, sizeof(SymCache));
/* No need to check for NULL here */
} else
cache = NULL;

View File

@ -287,7 +287,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
const Elf_Rela *relalim;
const Elf_Rela *rela;
SymCache *cache;
int bytes = obj->nchains * sizeof(SymCache);
int bytes = obj->dynsymcount * sizeof(SymCache);
int r = -1;
/*

View File

@ -133,7 +133,8 @@ static int symlook_global(SymLook *, DoneList *);
static void symlook_init_from_req(SymLook *, const SymLook *);
static int symlook_list(SymLook *, const Objlist *, DoneList *);
static int symlook_needed(SymLook *, const Needed_Entry *, DoneList *);
static int symlook_obj1(SymLook *, const Obj_Entry *);
static int symlook_obj1_sysv(SymLook *, const Obj_Entry *);
static int symlook_obj1_gnu(SymLook *, const Obj_Entry *);
static void trace_loaded_objects(Obj_Entry *);
static void unlink_object(Obj_Entry *);
static void unload_object(Obj_Entry *);
@ -150,6 +151,7 @@ static int object_match_name(const Obj_Entry *, const char *);
static void ld_utrace_log(int, void *, void *, size_t, int, const char *);
static void rtld_fill_dl_phdr_info(const Obj_Entry *obj,
struct dl_phdr_info *phdr_info);
static uint32_t gnu_hash(const char *);
static bool matched_symbol(SymLook *, const Obj_Entry *, Sym_Match_Result *,
const unsigned long);
@ -488,6 +490,9 @@ _rtld(Elf_Addr *sp, func_ptr_type *exit_proc, Obj_Entry **objp)
}
digest_dynamic(obj_main, 0);
dbg("%s valid_hash_sysv %d valid_hash_gnu %d dynsymcount %d",
obj_main->path, obj_main->valid_hash_sysv, obj_main->valid_hash_gnu,
obj_main->dynsymcount);
linkmap_add(obj_main);
linkmap_add(&obj_rtld);
@ -825,6 +830,11 @@ digest_dynamic1(Obj_Entry *obj, int early, const Elf_Dyn **dyn_rpath,
Needed_Entry **needed_tail = &obj->needed;
Needed_Entry **needed_filtees_tail = &obj->needed_filtees;
Needed_Entry **needed_aux_filtees_tail = &obj->needed_aux_filtees;
const Elf_Hashelt *hashtab;
const Elf32_Word *hashval;
Elf32_Word bkt, nmaskwords;
int bloom_size32;
bool nmw_power2;
int plttype = DT_REL;
*dyn_rpath = NULL;
@ -914,12 +924,35 @@ digest_dynamic1(Obj_Entry *obj, int early, const Elf_Dyn **dyn_rpath,
case DT_HASH:
{
const Elf_Hashelt *hashtab = (const Elf_Hashelt *)
(obj->relocbase + dynp->d_un.d_ptr);
hashtab = (const Elf_Hashelt *)(obj->relocbase +
dynp->d_un.d_ptr);
obj->nbuckets = hashtab[0];
obj->nchains = hashtab[1];
obj->buckets = hashtab + 2;
obj->chains = obj->buckets + obj->nbuckets;
obj->valid_hash_sysv = obj->nbuckets > 0 && obj->nchains > 0 &&
obj->buckets != NULL;
}
break;
case DT_GNU_HASH:
{
hashtab = (const Elf_Hashelt *)(obj->relocbase +
dynp->d_un.d_ptr);
obj->nbuckets_gnu = hashtab[0];
obj->symndx_gnu = hashtab[1];
nmaskwords = hashtab[2];
bloom_size32 = (__ELF_WORD_SIZE / 32) * nmaskwords;
/* Number of bitmask words is required to be power of 2 */
nmw_power2 = ((nmaskwords & (nmaskwords - 1)) == 0);
obj->maskwords_bm_gnu = nmaskwords - 1;
obj->shift2_gnu = hashtab[3];
obj->bloom_gnu = (Elf_Addr *) (hashtab + 4);
obj->buckets_gnu = hashtab + 4 + bloom_size32;
obj->chain_zero_gnu = obj->buckets_gnu + obj->nbuckets_gnu -
obj->symndx_gnu;
obj->valid_hash_gnu = nmw_power2 && obj->nbuckets_gnu > 0 &&
obj->buckets_gnu != NULL;
}
break;
@ -1096,6 +1129,22 @@ digest_dynamic1(Obj_Entry *obj, int early, const Elf_Dyn **dyn_rpath,
obj->pltrelasize = obj->pltrelsize;
obj->pltrelsize = 0;
}
/* Determine size of dynsym table (equal to nchains of sysv hash) */
if (obj->valid_hash_sysv)
obj->dynsymcount = obj->nchains;
else if (obj->valid_hash_gnu) {
obj->dynsymcount = 0;
for (bkt = 0; bkt < obj->nbuckets_gnu; bkt++) {
if (obj->buckets_gnu[bkt] == 0)
continue;
hashval = &obj->chain_zero_gnu[obj->buckets_gnu[bkt]];
do
obj->dynsymcount++;
while ((*hashval++ & 1u) == 0);
}
obj->dynsymcount += obj->symndx_gnu;
}
}
static void
@ -1311,6 +1360,22 @@ elf_hash(const char *name)
return h;
}
/*
* The GNU hash function is the Daniel J. Bernstein hash clipped to 32 bits
* unsigned in case it's implemented with a wider type.
*/
static uint32_t
gnu_hash(const char *s)
{
uint32_t h;
unsigned char c;
h = 5381;
for (c = *s; c != '\0'; c = *++s)
h = h * 33 + c;
return (h & 0xffffffff);
}
/*
* Find the library with the given name, and return its full pathname.
* The returned string is dynamically allocated. Generates an error
@ -1387,7 +1452,7 @@ find_symdef(unsigned long symnum, const Obj_Entry *refobj,
* If we have already found this symbol, get the information from
* the cache.
*/
if (symnum >= refobj->nchains)
if (symnum >= refobj->dynsymcount)
return NULL; /* Bad object */
if (cache != NULL && cache[symnum].sym != NULL) {
*defobj_out = cache[symnum].obj;
@ -1885,6 +1950,8 @@ do_load_object(int fd, const char *name, char *path, struct stat *sbp,
object_add_name(obj, name);
obj->path = path;
digest_dynamic(obj, 0);
dbg("%s valid_hash_sysv %d valid_hash_gnu %d dynsymcount %d", obj->path,
obj->valid_hash_sysv, obj->valid_hash_gnu, obj->dynsymcount);
if (obj->z_noopen && (flags & (RTLD_LO_DLOPEN | RTLD_LO_TRACE)) ==
RTLD_LO_DLOPEN) {
dbg("refusing to load non-loadable \"%s\"", obj->path);
@ -2168,8 +2235,8 @@ relocate_objects(Obj_Entry *first, bool bind_now, Obj_Entry *rtldobj,
if (obj != rtldobj)
dbg("relocating \"%s\"", obj->path);
if (obj->nbuckets == 0 || obj->nchains == 0 || obj->buckets == NULL ||
obj->symtab == NULL || obj->strtab == NULL) {
if (obj->symtab == NULL || obj->strtab == NULL ||
!(obj->valid_hash_sysv || obj->valid_hash_gnu)) {
_rtld_error("%s: Shared object has no run-time symbol table",
obj->path);
return -1;
@ -2841,7 +2908,7 @@ dladdr(const void *addr, Dl_info *info)
* Walk the symbol list looking for the symbol whose address is
* closest to the address sent in.
*/
for (symoffset = 0; symoffset < obj->nchains; symoffset++) {
for (symoffset = 0; symoffset < obj->dynsymcount; symoffset++) {
def = obj->symtab + symoffset;
/*
@ -3412,7 +3479,15 @@ symlook_obj(SymLook *req, const Obj_Entry *obj)
SymLook req1;
int flags, res, mres;
mres = symlook_obj1(req, obj);
/*
* There is at least one valid hash at this point, and we prefer to use
* the faster GNU version if available.
*/
if (obj->valid_hash_gnu)
mres = symlook_obj1_gnu(req, obj);
else
mres = symlook_obj1_sysv(req, obj);
if (mres == 0) {
if (obj->needed_filtees != NULL) {
flags = (req->flags & SYMLOOK_EARLY) ? RTLD_LO_EARLY : 0;
@ -3553,8 +3628,13 @@ matched_symbol(SymLook *req, const Obj_Entry *obj, Sym_Match_Result *result,
return (true);
}
/*
* Search for symbol using SysV hash function.
* obj->buckets is known not to be NULL at this point; the test for this was
* performed with the obj->valid_hash_sysv assignment.
*/
static int
symlook_obj1(SymLook *req, const Obj_Entry *obj)
symlook_obj1_sysv(SymLook *req, const Obj_Entry *obj)
{
unsigned long symnum;
Sym_Match_Result matchres;
@ -3582,6 +3662,56 @@ symlook_obj1(SymLook *req, const Obj_Entry *obj)
return (ESRCH);
}
/* Search for symbol using GNU hash function */
static int
symlook_obj1_gnu(SymLook *req, const Obj_Entry *obj)
{
Elf_Addr bloom_word;
const Elf32_Word *hashval;
Elf32_Word bucket;
Sym_Match_Result matchres;
unsigned int h1, h2;
unsigned long symnum;
matchres.sym_out = NULL;
matchres.vsymp = NULL;
matchres.vcount = 0;
/* Pick right bitmask word from Bloom filter array */
bloom_word = obj->bloom_gnu[(req->hash_gnu / __ELF_WORD_SIZE) &
obj->maskwords_bm_gnu];
/* Calculate modulus word size of gnu hash and its derivative */
h1 = req->hash_gnu & (__ELF_WORD_SIZE - 1);
h2 = ((req->hash_gnu >> obj->shift2_gnu) & (__ELF_WORD_SIZE - 1));
/* Filter out the "definitely not in set" queries */
if (((bloom_word >> h1) & (bloom_word >> h2) & 1) == 0)
return (ESRCH);
/* Locate hash chain and corresponding value element*/
bucket = obj->buckets_gnu[req->hash_gnu % obj->nbuckets_gnu];
if (bucket == 0)
return (ESRCH);
hashval = &obj->chain_zero_gnu[bucket];
do {
if (((*hashval ^ req->hash_gnu) >> 1) == 0) {
symnum = hashval - obj->chain_zero_gnu;
if (matched_symbol(req, obj, &matchres, symnum)) {
req->sym_out = matchres.sym_out;
req->defobj_out = obj;
return (0);
}
}
} while ((*hashval++ & 1) == 0);
if (matchres.vcount == 1) {
req->sym_out = matchres.vsymp;
req->defobj_out = obj;
return (0);
}
return (ESRCH);
}
static void
trace_loaded_objects(Obj_Entry *obj)
{
@ -4365,6 +4495,7 @@ symlook_init(SymLook *dst, const char *name)
bzero(dst, sizeof(*dst));
dst->name = name;
dst->hash = elf_hash(name);
dst->hash_gnu = gnu_hash(name);
}
static void
@ -4373,6 +4504,7 @@ symlook_init_from_req(SymLook *dst, const SymLook *src)
dst->name = src->name;
dst->hash = src->hash;
dst->hash_gnu = src->hash_gnu;
dst->ventry = src->ventry;
dst->flags = src->flags;
dst->defobj_out = NULL;

View File

@ -210,7 +210,16 @@ typedef struct Struct_Obj_Entry {
const Elf_Hashelt *buckets; /* Hash table buckets array */
unsigned long nbuckets; /* Number of buckets */
const Elf_Hashelt *chains; /* Hash table chain array */
unsigned long nchains; /* Number of chains */
unsigned long nchains; /* Number of entries in chain array */
Elf32_Word nbuckets_gnu; /* Number of GNU hash buckets*/
Elf32_Word symndx_gnu; /* 1st accessible symbol on dynsym table */
Elf32_Word maskwords_bm_gnu; /* Bloom filter words - 1 (bitmask) */
Elf32_Word shift2_gnu; /* Bloom filter shift count */
Elf32_Word dynsymcount; /* Total entries in dynsym table */
Elf_Addr *bloom_gnu; /* Bloom filter used by GNU hash func */
const Elf_Hashelt *buckets_gnu; /* GNU hash table bucket array */
const Elf_Hashelt *chain_zero_gnu; /* GNU hash table value array (Zeroed) */
char *rpath; /* Search path specified in object */
Needed_Entry *needed; /* Shared objects needed by this one (%) */
@ -257,6 +266,8 @@ typedef struct Struct_Obj_Entry {
bool irelative : 1; /* Object has R_MACHDEP_IRELATIVE relocs */
bool gnu_ifunc : 1; /* Object has references to STT_GNU_IFUNC */
bool crt_no_init : 1; /* Object' crt does not call _init/_fini */
bool valid_hash_sysv : 1; /* A valid System V hash hash tag is available */
bool valid_hash_gnu : 1; /* A valid GNU hash tag is available */
struct link_map linkmap; /* For GDB and dlinfo() */
Objlist dldags; /* Object belongs to these dlopened DAGs (%) */
@ -316,6 +327,7 @@ struct Struct_RtldLockState {
typedef struct Struct_SymLook {
const char *name;
unsigned long hash;
uint32_t hash_gnu;
const Ver_Entry *ventry;
int flags;
const Obj_Entry *defobj_out;

View File

@ -305,7 +305,7 @@ reloc_non_plt(Obj_Entry *obj, Obj_Entry *obj_rtld, int flags,
* limited amounts of stack available so we cannot use alloca().
*/
if (obj != obj_rtld) {
cache = calloc(obj->nchains, sizeof(SymCache));
cache = calloc(obj->dynsymcount, sizeof(SymCache));
/* No need to check for NULL here */
} else
cache = NULL;