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 <maskray@xxxxxxxxxx> Tested-by: Xi Ruoyao <xry111@xxxxxxxxxxx> > --- > 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 <xry111@xxxxxxxxxxx> School of Aerospace Science and Technology, Xidian University