Browse Source

Added support for GNU hash style into dynamic linker

Carmelo Amoroso 18 years ago
parent
commit
6630516b0a
8 changed files with 247 additions and 32 deletions
  1. 10 0
      Rules.mak
  2. 15 0
      extra/Configs/Config.in
  3. 2 0
      include/elf.h
  4. 19 2
      ldso/include/dl-elf.h
  5. 25 0
      ldso/include/dl-hash.h
  6. 9 1
      ldso/include/ldso.h
  7. 162 29
      ldso/ldso/dl-hash.c
  8. 5 0
      test/Rules.mak

+ 10 - 0
Rules.mak

@@ -408,6 +408,16 @@ ifeq ($(UCLIBC_BUILD_NOW),y)
 LDFLAGS_NOSTRIP+=-Wl,-z,now
 LDFLAGS_NOSTRIP+=-Wl,-z,now
 endif
 endif
 
 
+ifeq ($(LDSO_GNU_HASH_SUPPORT),y)
+# Be sure that binutils support it
+LDFLAGS_GNUHASH :=$(call check_ld,--hash-style=gnu)
+ifeq ($(LDFLAGS_GNUHASH),)
+$(error Your binutils don't support --hash-style option, while you want to use it)
+else
+LDFLAGS_NOSTRIP += -Wl,$(LDFLAGS_GNUHASH)
+endif
+endif
+
 LDFLAGS:=$(LDFLAGS_NOSTRIP) -Wl,-z,defs
 LDFLAGS:=$(LDFLAGS_NOSTRIP) -Wl,-z,defs
 ifeq ($(DODEBUG),y)
 ifeq ($(DODEBUG),y)
 #CFLAGS += -g3
 #CFLAGS += -g3

+ 15 - 0
extra/Configs/Config.in

@@ -322,6 +322,21 @@ config UCLIBC_CTOR_DTOR
 	  or dtors and want your binaries to be as small as possible, then
 	  or dtors and want your binaries to be as small as possible, then
 	  answer N.
 	  answer N.
 
 
+config LDSO_GNU_HASH_SUPPORT
+	bool "Enable GNU hash style support"
+	depends on HAVE_SHARED
+	default n
+	help
+	  Newest binutils support a new hash style named GNU-hash. The dynamic
+	  linker will use the new GNU-hash section (.gnu.hash) for symbol lookup 
+	  if present into the ELF binaries, otherwise it will use the old SysV 
+	  hash style (.hash). This ensures that it is completely backward compatible.
+	  Further, being the hash table implementation self-contained into each
+	  executable and shared libraries, objects with mixed hash style can
+	  peacefully coexist in the same process.
+	  
+	  If you want to use this new feature, answer Y  
+
 config HAS_NO_THREADS
 config HAS_NO_THREADS
 	bool
 	bool
 	default n
 	default n

+ 2 - 0
include/elf.h

@@ -431,6 +431,7 @@ typedef struct
 #define SHT_SYMTAB_SHNDX  18		/* Extended section indeces */
 #define SHT_SYMTAB_SHNDX  18		/* Extended section indeces */
 #define	SHT_NUM		  19		/* Number of defined types.  */
 #define	SHT_NUM		  19		/* Number of defined types.  */
 #define SHT_LOOS	  0x60000000	/* Start OS-specific */
 #define SHT_LOOS	  0x60000000	/* Start OS-specific */
+#define SHT_GNU_HASH	  0x6ffffff6	/* GNU-style hash table.  */
 #define SHT_GNU_LIBLIST	  0x6ffffff7	/* Prelink library list */
 #define SHT_GNU_LIBLIST	  0x6ffffff7	/* Prelink library list */
 #define SHT_CHECKSUM	  0x6ffffff8	/* Checksum for DSO content.  */
 #define SHT_CHECKSUM	  0x6ffffff8	/* Checksum for DSO content.  */
 #define SHT_LOSUNW	  0x6ffffffa	/* Sun-specific low bound.  */
 #define SHT_LOSUNW	  0x6ffffffa	/* Sun-specific low bound.  */
@@ -813,6 +814,7 @@ typedef struct
    If any adjustment is made to the ELF object after it has been
    If any adjustment is made to the ELF object after it has been
    built these entries will need to be adjusted.  */
    built these entries will need to be adjusted.  */
 #define DT_ADDRRNGLO	0x6ffffe00
 #define DT_ADDRRNGLO	0x6ffffe00
+#define DT_GNU_HASH	0x6ffffef5	/* GNU-style hash table.  */
 #define DT_GNU_CONFLICT	0x6ffffef8	/* Start of conflict section */
 #define DT_GNU_CONFLICT	0x6ffffef8	/* Start of conflict section */
 #define DT_GNU_LIBLIST	0x6ffffef9	/* Library list */
 #define DT_GNU_LIBLIST	0x6ffffef9	/* Library list */
 #define DT_CONFIG	0x6ffffefa	/* Configuration information.  */
 #define DT_CONFIG	0x6ffffefa	/* Configuration information.  */

+ 19 - 2
ldso/include/dl-elf.h

@@ -84,8 +84,11 @@ extern void _dl_protect_relro (struct elf_resolve *l);
 #endif
 #endif
 
 
 /* OS and/or GNU dynamic extensions */
 /* OS and/or GNU dynamic extensions */
-#define OS_NUM 1
-#define DT_RELCONT_IDX DT_NUM
+#ifdef __LDSO_GNU_HASH_SUPPORT__
+# define OS_NUM 2 /* for DT_RELOCCOUNT and DT_GNU_HASH entries */
+#else
+# define OS_NUM 1 /* for DT_RELOCCOUNT entry */
+#endif
 
 
 #ifndef ARCH_DYNAMIC_INFO
 #ifndef ARCH_DYNAMIC_INFO
   /* define in arch specific code, if needed */
   /* define in arch specific code, if needed */
@@ -93,6 +96,13 @@ extern void _dl_protect_relro (struct elf_resolve *l);
 #endif
 #endif
 
 
 #define DYNAMIC_SIZE (DT_NUM+OS_NUM+ARCH_NUM)
 #define DYNAMIC_SIZE (DT_NUM+OS_NUM+ARCH_NUM)
+/* Keep ARCH specific entries into dynamic section at the end of the array */
+#define DT_RELCONT_IDX (DYNAMIC_SIZE - OS_NUM - ARCH_NUM)
+
+#ifdef __LDSO_GNU_HASH_SUPPORT__
+/* GNU hash comes just after the relocation count */
+# define DT_GNU_HASH_IDX (DT_RELCONT_IDX + 1)
+#endif
 
 
 extern void _dl_parse_dynamic_info(ElfW(Dyn) *dpnt, unsigned long dynamic_info[],
 extern void _dl_parse_dynamic_info(ElfW(Dyn) *dpnt, unsigned long dynamic_info[],
                                    void *debug_addr, DL_LOADADDR_TYPE load_off);
                                    void *debug_addr, DL_LOADADDR_TYPE load_off);
@@ -131,6 +141,10 @@ void __dl_parse_dynamic_info(ElfW(Dyn) *dpnt, unsigned long dynamic_info[],
 			if (dpnt->d_tag == DT_FLAGS_1 &&
 			if (dpnt->d_tag == DT_FLAGS_1 &&
 			    (dpnt->d_un.d_val & DF_1_NOW))
 			    (dpnt->d_un.d_val & DF_1_NOW))
 				dynamic_info[DT_BIND_NOW] = 1;
 				dynamic_info[DT_BIND_NOW] = 1;
+#ifdef __LDSO_GNU_HASH_SUPPORT__				
+			if (dpnt->d_tag == DT_GNU_HASH)
+				dynamic_info[DT_GNU_HASH_IDX] = dpnt->d_un.d_ptr;
+#endif				
 		}
 		}
 #ifdef ARCH_DYNAMIC_INFO
 #ifdef ARCH_DYNAMIC_INFO
 		else {
 		else {
@@ -149,6 +163,9 @@ void __dl_parse_dynamic_info(ElfW(Dyn) *dpnt, unsigned long dynamic_info[],
 	ADJUST_DYN_INFO(DT_SYMTAB, load_off);
 	ADJUST_DYN_INFO(DT_SYMTAB, load_off);
 	ADJUST_DYN_INFO(DT_RELOC_TABLE_ADDR, load_off);
 	ADJUST_DYN_INFO(DT_RELOC_TABLE_ADDR, load_off);
 	ADJUST_DYN_INFO(DT_JMPREL, load_off);
 	ADJUST_DYN_INFO(DT_JMPREL, load_off);
+#ifdef __LDSO_GNU_HASH_SUPPORT__
+	ADJUST_DYN_INFO(DT_GNU_HASH_IDX, load_off);
+#endif	
 #undef ADJUST_DYN_INFO
 #undef ADJUST_DYN_INFO
 }
 }
 
 

+ 25 - 0
ldso/include/dl-hash.h

@@ -40,14 +40,39 @@ struct elf_resolve {
   unsigned short int init_flag;
   unsigned short int init_flag;
   unsigned long rtld_flags; /* RTLD_GLOBAL, RTLD_NOW etc. */
   unsigned long rtld_flags; /* RTLD_GLOBAL, RTLD_NOW etc. */
   Elf_Symndx nbucket;
   Elf_Symndx nbucket;
+  
+#ifdef __LDSO_GNU_HASH_SUPPORT__
+  /* Data needed to support GNU hash style */
+  Elf32_Word l_gnu_bitmask_idxbits;
+  Elf32_Word l_gnu_shift;
+  const ElfW(Addr) *l_gnu_bitmask;
+
+  union
+  {
+    const Elf32_Word *l_gnu_chain_zero;
+    const Elf_Symndx *elf_buckets;
+  };
+#else
   Elf_Symndx *elf_buckets;
   Elf_Symndx *elf_buckets;
+#endif
+  
   struct init_fini_list *init_fini;
   struct init_fini_list *init_fini;
   struct init_fini_list *rtld_local; /* keep tack of RTLD_LOCAL libs in same group */
   struct init_fini_list *rtld_local; /* keep tack of RTLD_LOCAL libs in same group */
   /*
   /*
    * These are only used with ELF style shared libraries
    * These are only used with ELF style shared libraries
    */
    */
   Elf_Symndx nchain;
   Elf_Symndx nchain;
+
+#ifdef __LDSO_GNU_HASH_SUPPORT__
+  union
+  {
+    const Elf32_Word *l_gnu_buckets;
+    const Elf_Symndx *chains;
+  };
+#else	
   Elf_Symndx *chains;
   Elf_Symndx *chains;
+#endif  
+  
   unsigned long dynamic_info[DYNAMIC_SIZE];
   unsigned long dynamic_info[DYNAMIC_SIZE];
 
 
   unsigned long n_phent;
   unsigned long n_phent;

+ 9 - 1
ldso/include/ldso.h

@@ -66,9 +66,17 @@ extern int   _dl_debug_file;
 	_dl_dprintf(_dl_debug_file, "%s:%i: " fmt, __FUNCTION__, __LINE__, ## args);
 	_dl_dprintf(_dl_debug_file, "%s:%i: " fmt, __FUNCTION__, __LINE__, ## args);
 # define _dl_if_debug_dprint(fmt, args...) \
 # define _dl_if_debug_dprint(fmt, args...) \
 	do { if (_dl_debug) __dl_debug_dprint(fmt, ## args); } while (0)
 	do { if (_dl_debug) __dl_debug_dprint(fmt, ## args); } while (0)
+# define _dl_assert(expr)						\
+	do {								\
+		if (!(expr)) {						\
+			__dl_debug_dprint("assert(%s)\n", #expr);	\
+			_dl_exit(45);					\
+		}							\
+	} while (0)
 #else
 #else
-# define _dl_debug_dprint(fmt, args...)
+# define __dl_debug_dprint(fmt, args...)
 # define _dl_if_debug_dprint(fmt, args...)
 # define _dl_if_debug_dprint(fmt, args...)
+# define _dl_assert(expr)
 # define _dl_debug_file 2
 # define _dl_debug_file 2
 #endif /* __SUPPORT_LD_DEBUG__ */
 #endif /* __SUPPORT_LD_DEBUG__ */
 
 

+ 162 - 29
ldso/ldso/dl-hash.c

@@ -53,6 +53,19 @@ struct dyn_elf *_dl_symbol_tables = NULL;
  */
  */
 struct dyn_elf *_dl_handles = 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
 /* 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
  * 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->libname = _dl_strdup(libname);
 	tpnt->dynamic_addr = (ElfW(Dyn) *)dynamic_addr;
 	tpnt->dynamic_addr = (ElfW(Dyn) *)dynamic_addr;
 	tpnt->libtype = loaded_file;
 	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) {
 	if (dynamic_info[DT_HASH] != 0) {
 		hash_addr = (Elf_Symndx*)dynamic_info[DT_HASH];
 		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
  * 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.
  * 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)
 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;
 	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) {
 	for (; rpnt; rpnt = rpnt->next) {
 		tpnt = rpnt->dyn;
 		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)
 		if (tpnt->nbucket == 0)
 			continue;
 			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:
 			case STB_WEAK:
 #if 0
 #if 0
 /* Perhaps we should support old style weak symbol handling
 /* 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);
 				return (char*) DL_RELOC_ADDR(tpnt->loadaddr, sym->st_value);
 			default:	/* Local symbols not handled here */
 			default:	/* Local symbols not handled here */
 				break;
 				break;
-			}
 		}
 		}
 	}
 	}
 	return weak_result;
 	return weak_result;

+ 5 - 0
test/Rules.mak

@@ -107,6 +107,11 @@ ifeq ($(findstring -static,$(LDFLAGS)),)
 	LDFLAGS += -Wl,--dynamic-linker,$(UCLIBC_LDSO_ABSPATH)/$(UCLIBC_LDSO)
 	LDFLAGS += -Wl,--dynamic-linker,$(UCLIBC_LDSO_ABSPATH)/$(UCLIBC_LDSO)
 endif
 endif
 
 
+ifeq ($(LDSO_GNU_HASH_SUPPORT),y)
+# Check for binutils support is done on root Rules.mak
+LDFLAGS += -Wl,${LDFLAGS_GNUHASH}
+endif
+
 
 
 # Filter output
 # Filter output
 MAKEFLAGS += --no-print-directory
 MAKEFLAGS += --no-print-directory