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@google.com
Ping.
Some context: I'd change LoongArch vDSO to use the toolchain default instead of forcing DT_HASH (note that LoongArch is launched decades after all major distros switched to DT_GNU_HASH), but without the selftest support we'll lose test coverage.
And now ARM64 has already lost test coverage after commit 48f6430505c0.
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;