2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00

selftests/vDSO: support DT_GNU_HASH

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.

Link: https://lore.kernel.org/r/20241206130724.7944-2-xry111@xry111.site
Signed-off-by: Fangrui Song <i@maskray.me>
Tested-by: Xi Ruoyao <xry111@xry111.site>
Signed-off-by: Xi Ruoyao <xry111@xry111.site> # rebase
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
This commit is contained in:
Fangrui Song 2024-12-06 21:07:25 +08:00 committed by Shuah Khan
parent eed8ecdf12
commit e0746bde6f

View File

@ -53,6 +53,7 @@ static struct vdso_info
/* Symbol table */ /* Symbol table */
ELF(Sym) *symtab; ELF(Sym) *symtab;
const char *symstrings; const char *symstrings;
ELF(Word) *gnu_hash;
ELF_HASH_ENTRY *bucket, *chain; ELF_HASH_ENTRY *bucket, *chain;
ELF_HASH_ENTRY nbucket, nchain; ELF_HASH_ENTRY nbucket, nchain;
@ -81,6 +82,16 @@ static unsigned long elf_hash(const char *name)
return h; 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) void vdso_init_from_sysinfo_ehdr(uintptr_t base)
{ {
size_t i; size_t i;
@ -123,6 +134,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
*/ */
ELF_HASH_ENTRY *hash = 0; ELF_HASH_ENTRY *hash = 0;
vdso_info.symstrings = 0; vdso_info.symstrings = 0;
vdso_info.gnu_hash = 0;
vdso_info.symtab = 0; vdso_info.symtab = 0;
vdso_info.versym = 0; vdso_info.versym = 0;
vdso_info.verdef = 0; vdso_info.verdef = 0;
@ -143,6 +155,11 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
((uintptr_t)dyn[i].d_un.d_ptr ((uintptr_t)dyn[i].d_un.d_ptr
+ vdso_info.load_offset); + vdso_info.load_offset);
break; 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: case DT_VERSYM:
vdso_info.versym = (ELF(Versym) *) vdso_info.versym = (ELF(Versym) *)
((uintptr_t)dyn[i].d_un.d_ptr ((uintptr_t)dyn[i].d_un.d_ptr
@ -155,17 +172,27 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
break; break;
} }
} }
if (!vdso_info.symstrings || !vdso_info.symtab || !hash) if (!vdso_info.symstrings || !vdso_info.symtab ||
(!hash && !vdso_info.gnu_hash))
return; /* Failed */ return; /* Failed */
if (!vdso_info.verdef) if (!vdso_info.verdef)
vdso_info.versym = 0; vdso_info.versym = 0;
/* Parse the hash table header. */ /* Parse the hash table header. */
vdso_info.nbucket = hash[0]; if (vdso_info.gnu_hash) {
vdso_info.nchain = hash[1]; vdso_info.nbucket = vdso_info.gnu_hash[0];
vdso_info.bucket = &hash[2]; /* The bucket array is located after the header (4 uint32) and the bloom
vdso_info.chain = &hash[vdso_info.nbucket + 2]; * 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. */ /* That's all we need. */
vdso_info.valid = true; vdso_info.valid = true;
@ -209,6 +236,26 @@ static bool vdso_match_version(ELF(Versym) ver,
&& !strcmp(name, vdso_info.symstrings + aux->vda_name); && !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) void *vdso_sym(const char *version, const char *name)
{ {
unsigned long ver_hash; unsigned long ver_hash;
@ -216,29 +263,36 @@ void *vdso_sym(const char *version, const char *name)
return 0; return 0;
ver_hash = elf_hash(version); 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]) { if (vdso_info.gnu_hash) {
ELF(Sym) *sym = &vdso_info.symtab[chain]; uint32_t h1 = gnu_hash(name), h2, *hashval;
/* Check for a defined global or weak function w/ right name. */ i = vdso_info.bucket[h1 % vdso_info.nbucket];
if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) if (i == 0)
continue; return 0;
if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL && h1 |= 1;
ELF64_ST_BIND(sym->st_info) != STB_WEAK) hashval = vdso_info.bucket + vdso_info.nbucket +
continue; (i - vdso_info.gnu_hash[1]);
if (sym->st_shndx == SHN_UNDEF) for (;; i++) {
continue; ELF(Sym) *sym = &vdso_info.symtab[i];
if (strcmp(name, vdso_info.symstrings + sym->st_name)) h2 = *hashval++;
continue; if (h1 == (h2 | 1) &&
check_sym(sym, i, name, version, ver_hash))
/* Check symbol version. */ return (void *)(vdso_info.load_offset +
if (vdso_info.versym sym->st_value);
&& !vdso_match_version(vdso_info.versym[chain], if (h2 & 1)
version, ver_hash)) break;
continue; }
} else {
return (void *)(vdso_info.load_offset + sym->st_value); 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; return 0;