On Wed, 2024-08-14 at 20:26 -0700, Fangrui Song wrote:
> glibc added support for DT_GNU_HASH in 2006 and DT_HASH has been
> obsoleted for more than one decade in many Linux distributions.
> 
> Many vDSOs support DT_GNU_HASH. This patch adds selftests support.
> 
> Signed-off-by: Fangrui Song <mask...@google.com>

Tested-by: Xi Ruoyao <xry...@xry111.site>

> ---
>  tools/testing/selftests/vDSO/parse_vdso.c | 105 ++++++++++++++++------
>  1 file changed, 79 insertions(+), 26 deletions(-)
> 
> diff --git a/tools/testing/selftests/vDSO/parse_vdso.c 
> b/tools/testing/selftests/vDSO/parse_vdso.c
> index 4ae417372e9e..35cb545da13e 100644
> --- a/tools/testing/selftests/vDSO/parse_vdso.c
> +++ b/tools/testing/selftests/vDSO/parse_vdso.c
> @@ -47,6 +47,7 @@ static struct vdso_info
>       /* Symbol table */
>       ELF(Sym) *symtab;
>       const char *symstrings;
> +     ELF(Word) *gnu_hash;
>       ELF(Word) *bucket, *chain;
>       ELF(Word) nbucket, nchain;
>  
> @@ -75,6 +76,16 @@ static unsigned long elf_hash(const char *name)
>       return h;
>  }
>  
> +static uint32_t gnu_hash(const char *name)
> +{
> +     const unsigned char *s = (void *)name;
> +     uint32_t h = 5381;
> +
> +     for (; *s; s++)
> +             h += h * 32 + *s;
> +     return h;
> +}
> +
>  void vdso_init_from_sysinfo_ehdr(uintptr_t base)
>  {
>       size_t i;
> @@ -117,6 +128,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
>        */
>       ELF(Word) *hash = 0;
>       vdso_info.symstrings = 0;
> +     vdso_info.gnu_hash = 0;
>       vdso_info.symtab = 0;
>       vdso_info.versym = 0;
>       vdso_info.verdef = 0;
> @@ -137,6 +149,11 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
>                               ((uintptr_t)dyn[i].d_un.d_ptr
>                                + vdso_info.load_offset);
>                       break;
> +             case DT_GNU_HASH:
> +                     vdso_info.gnu_hash =
> +                             (ELF(Word) *)((uintptr_t)dyn[i].d_un.d_ptr +
> +                                           vdso_info.load_offset);
> +                     break;
>               case DT_VERSYM:
>                       vdso_info.versym = (ELF(Versym) *)
>                               ((uintptr_t)dyn[i].d_un.d_ptr
> @@ -149,17 +166,26 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
>                       break;
>               }
>       }
> -     if (!vdso_info.symstrings || !vdso_info.symtab || !hash)
> +     if (!vdso_info.symstrings || !vdso_info.symtab ||
> +         (!hash && !vdso_info.gnu_hash))
>               return;  /* Failed */
>  
>       if (!vdso_info.verdef)
>               vdso_info.versym = 0;
>  
>       /* Parse the hash table header. */
> -     vdso_info.nbucket = hash[0];
> -     vdso_info.nchain = hash[1];
> -     vdso_info.bucket = &hash[2];
> -     vdso_info.chain = &hash[vdso_info.nbucket + 2];
> +     if (vdso_info.gnu_hash) {
> +             vdso_info.nbucket = vdso_info.gnu_hash[0];
> +             /* The bucket array is located after the header (4 uint32) and 
> the bloom
> +                filter (size_t array of gnu_hash[2] elements). */
> +             vdso_info.bucket = vdso_info.gnu_hash + 4 +
> +                                sizeof(size_t) / 4 * vdso_info.gnu_hash[2];
> +     } else {
> +             vdso_info.nbucket = hash[0];
> +             vdso_info.nchain = hash[1];
> +             vdso_info.bucket = &hash[2];
> +             vdso_info.chain = &hash[vdso_info.nbucket + 2];
> +     }
>  
>       /* That's all we need. */
>       vdso_info.valid = true;
> @@ -203,6 +229,26 @@ static bool vdso_match_version(ELF(Versym) ver,
>               && !strcmp(name, vdso_info.symstrings + aux->vda_name);
>  }
>  
> +static bool check_sym(ELF(Sym) *sym, ELF(Word) i, const char *name,
> +                   const char *version, unsigned long ver_hash)
> +{
> +     /* Check for a defined global or weak function w/ right name. */
> +     if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC)
> +             return false;
> +     if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL &&
> +         ELF64_ST_BIND(sym->st_info) != STB_WEAK)
> +             return false;
> +     if (strcmp(name, vdso_info.symstrings + sym->st_name))
> +             return false;
> +
> +     /* Check symbol version. */
> +     if (vdso_info.versym &&
> +         !vdso_match_version(vdso_info.versym[i], version, ver_hash))
> +             return false;
> +
> +     return true;
> +}
> +
>  void *vdso_sym(const char *version, const char *name)
>  {
>       unsigned long ver_hash;
> @@ -210,29 +256,36 @@ void *vdso_sym(const char *version, const char *name)
>               return 0;
>  
>       ver_hash = elf_hash(version);
> -     ELF(Word) chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];
> +     ELF(Word) i;
>  
> -     for (; chain != STN_UNDEF; chain = vdso_info.chain[chain]) {
> -             ELF(Sym) *sym = &vdso_info.symtab[chain];
> +     if (vdso_info.gnu_hash) {
> +             uint32_t h1 = gnu_hash(name), h2, *hashval;
>  
> -             /* Check for a defined global or weak function w/ right name. */
> -             if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC)
> -                     continue;
> -             if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL &&
> -                 ELF64_ST_BIND(sym->st_info) != STB_WEAK)
> -                     continue;
> -             if (sym->st_shndx == SHN_UNDEF)
> -                     continue;
> -             if (strcmp(name, vdso_info.symstrings + sym->st_name))
> -                     continue;
> -
> -             /* Check symbol version. */
> -             if (vdso_info.versym
> -                 && !vdso_match_version(vdso_info.versym[chain],
> -                                        version, ver_hash))
> -                     continue;
> -
> -             return (void *)(vdso_info.load_offset + sym->st_value);
> +             i = vdso_info.bucket[h1 % vdso_info.nbucket];
> +             if (i == 0)
> +                     return 0;
> +             h1 |= 1;
> +             hashval = vdso_info.bucket + vdso_info.nbucket +
> +                       (i - vdso_info.gnu_hash[1]);
> +             for (;; i++) {
> +                     ELF(Sym) *sym = &vdso_info.symtab[i];
> +                     h2 = *hashval++;
> +                     if (h1 == (h2 | 1) &&
> +                         check_sym(sym, i, name, version, ver_hash))
> +                             return (void *)(vdso_info.load_offset +
> +                                             sym->st_value);
> +                     if (h2 & 1)
> +                             break;
> +             }
> +     } else {
> +             i = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];
> +             for (; i; i = vdso_info.chain[i]) {
> +                     ELF(Sym) *sym = &vdso_info.symtab[i];
> +                     if (sym->st_shndx != SHN_UNDEF &&
> +                         check_sym(sym, i, name, version, ver_hash))
> +                             return (void *)(vdso_info.load_offset +
> +                                             sym->st_value);
> +             }
>       }
>  
>       return 0;

-- 
Xi Ruoyao <xry...@xry111.site>
School of Aerospace Science and Technology, Xidian University

Reply via email to