runtime_loader: Add support for DT_GNU_HASH.

This is an alternative to DT_HASH (SystemV/SVR4 hash tables.) Notably,
it uses a Bloom filter to allow an entire image to be skipped
at once rather than searching the actual symbol hash.

We don't currently build anything with DT_GNU_HASH support.
You can test this by adding -Wl,--hash-style=both to HAIKU_LINKFLAGS.
(It seems to increase image sizes by not too much: libroot goes
from 1347139 to 1367691 bytes (20.55 KB) in my build.)

Change-Id: I4a91276490fcd136db175833ee48b36e06ceed47
Reviewed-on: https://review.haiku-os.org/c/haiku/+/7855
Reviewed-by: waddlesplash <waddlesplash@gmail.com>
This commit is contained in:
Augustin Cavalier 2024-07-23 17:48:00 -04:00 committed by waddlesplash
parent 90a0982dcb
commit ffc1a5219d
5 changed files with 110 additions and 12 deletions

View File

@ -544,6 +544,8 @@ typedef struct {
#define DT_PREINIT_ARRAY 32 /* preinitialization array */
#define DT_PREINIT_ARRAYSZ 33 /* preinitialization array size */
#define DT_GNU_HASH 0x6ffffef5 /* GNU-style hash table */
#define DT_VERSYM 0x6ffffff0 /* symbol version table */
#define DT_VERDEF 0x6ffffffc /* version definition table */
#define DT_VERDEFNUM 0x6ffffffd /* number of version definitions */

View File

@ -107,16 +107,32 @@ typedef struct image_t {
addr_t term_routine;
addr_t dynamic_ptr; // pointer to the dynamic section
// pointer to symbol participation data structures
// needed images
uint32 num_needed;
struct image_t **needed;
// symbol participation data structures
uint32 *symhash;
elf_sym *syms;
char *strtab;
struct {
uint32 mask_words_count_mask;
uint32 shift2;
uint32 bucket_count;
elf_addr *bloom;
uint32 *buckets;
uint32 *chain0;
} gnuhash;
// relocation information
elf_rel *rel;
int rel_len;
elf_rela *rela;
int rela_len;
elf_rel *pltrel;
int pltrel_len;
// init/term functions
addr_t *init_array;
int init_array_len;
addr_t *preinit_array;
@ -124,11 +140,6 @@ typedef struct image_t {
addr_t *term_array;
int term_array_len;
unsigned dso_tls_id;
uint32 num_needed;
struct image_t **needed;
// versioning related structures
uint32 num_version_definitions;
elf_verdef *version_definitions;
@ -138,6 +149,9 @@ typedef struct image_t {
elf_version_info *versions;
uint32 num_versions;
// thread-local storage
unsigned dso_tls_id;
#ifdef __cplusplus
elf_sym* (*find_undefined_symbol)(struct image_t* rootImage,
struct image_t* image,

View File

@ -321,6 +321,23 @@ parse_dynamic_segment(image_t* image)
case DT_SONAME:
sonameOffset = d[i].d_un.d_val;
break;
case DT_GNU_HASH:
{
uint32* gnuhash = (uint32*)
(d[i].d_un.d_ptr + image->regions[0].delta);
const uint32 bucketCount = gnuhash[0];
const uint32 symIndex = gnuhash[1];
const uint32 maskWordsCount = gnuhash[2];
const uint32 bloomSize = maskWordsCount * (sizeof(elf_addr) / 4);
image->gnuhash.mask_words_count_mask = maskWordsCount - 1;
image->gnuhash.shift2 = gnuhash[3];
image->gnuhash.bucket_count = bucketCount;
image->gnuhash.bloom = (elf_addr*)(gnuhash + 4);
image->gnuhash.buckets = gnuhash + 4 + bloomSize;
image->gnuhash.chain0 = image->gnuhash.buckets + bucketCount - symIndex;
break;
}
case DT_VERSYM:
image->symbol_versions = (elf_versym*)
(d[i].d_un.d_ptr + image->regions[0].delta);

View File

@ -31,7 +31,7 @@
\return \c true, iff \a name is non-NULL and matches the name of \a image.
*/
static bool
equals_image_name(image_t* image, const char* name)
equals_image_name(const image_t* image, const char* name)
{
if (name == NULL)
return false;
@ -62,6 +62,19 @@ elf_hash(const char* _name)
}
uint32
elf_gnuhash(const char* _name)
{
const uint8* name = (const uint8*)_name;
uint32 h = 5381;
for (uint8 c = *name; c != '\0'; c = *++name)
h = (h * 33) + c;
return h;
}
struct match_result {
elf_sym* symbol;
elf_sym* versioned_symbol;
@ -72,7 +85,7 @@ struct match_result {
static bool
match_symbol(image_t* image, const SymbolLookupInfo& lookupInfo, uint32 symIdx,
match_symbol(const image_t* image, const SymbolLookupInfo& lookupInfo, uint32 symIdx,
match_result& result)
{
elf_sym* symbol = &image->syms[symIdx];
@ -188,8 +201,43 @@ match_symbol(image_t* image, const SymbolLookupInfo& lookupInfo, uint32 symIdx,
}
elf_sym*
find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo)
static elf_sym*
find_symbol_gnuhash(const image_t* image, const SymbolLookupInfo& lookupInfo)
{
// Test against the Bloom filter.
const uint32 wordSize = sizeof(elf_addr) * 8;
const uint32 firstHash = lookupInfo.gnuhash & (wordSize - 1);
const uint32 secondHash = lookupInfo.gnuhash >> image->gnuhash.shift2;
const uint32 index = (lookupInfo.gnuhash / wordSize) & image->gnuhash.mask_words_count_mask;
const elf_addr bloomWord = image->gnuhash.bloom[index];
if (((bloomWord >> firstHash) & (bloomWord >> secondHash) & 1) == 0)
return NULL;
// Locate hash chain and corresponding value element.
const uint32 bucket = image->gnuhash.buckets[lookupInfo.gnuhash % image->gnuhash.bucket_count];
if (bucket == 0)
return NULL;
match_result result;
const uint32* chain0 = image->gnuhash.chain0;
const uint32* hashValue = &chain0[bucket];
do {
if (((*hashValue ^ lookupInfo.gnuhash) >> 1) != 0)
continue;
uint32 symIndex = hashValue - chain0;
if (match_symbol(image, lookupInfo, symIndex, result))
return result.symbol;
} while ((*hashValue++ & 1) == 0);
if (result.versioned_symbol_count == 1)
return result.versioned_symbol;
return NULL;
}
static elf_sym*
find_symbol_sysv(const image_t* image, const SymbolLookupInfo& lookupInfo)
{
if (image->dynamic_ptr == 0)
return NULL;
@ -209,6 +257,21 @@ find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo)
}
elf_sym*
find_symbol(image_t* image, const SymbolLookupInfo& lookupInfo)
{
if (image->gnuhash.buckets != NULL) {
if (lookupInfo.gnuhash == 0)
const_cast<uint32&>(lookupInfo.gnuhash) = elf_gnuhash(lookupInfo.name);
return find_symbol_gnuhash(image, lookupInfo);
}
if (lookupInfo.hash == 0)
const_cast<uint32&>(lookupInfo.hash) = elf_hash(lookupInfo.name);
return find_symbol_sysv(image, lookupInfo);
}
void
patch_defined_symbol(image_t* image, const char* name, void** symbol,
int32* type)

View File

@ -17,12 +17,13 @@
uint32 elf_hash(const char* name);
uint32 elf_gnuhash(const char* name);
struct SymbolLookupInfo {
const char* name;
int32 type;
uint32 hash;
uint32 hash, gnuhash;
uint32 flags;
const elf_version_info* version;
elf_sym* requestingSymbol;
@ -33,7 +34,8 @@ struct SymbolLookupInfo {
:
name(name),
type(type),
hash(elf_hash(name)),
hash(0),
gnuhash(0),
flags(flags),
version(version),
requestingSymbol(requestingSymbol)