diff mbox series

selftests/vDSO: support DT_GNU_HASH

Message ID 20240815032614.2747224-1-maskray@google.com (mailing list archive)
State New
Headers show
Series selftests/vDSO: support DT_GNU_HASH | expand

Commit Message

Fangrui Song Aug. 15, 2024, 3:26 a.m. UTC
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>
---
 tools/testing/selftests/vDSO/parse_vdso.c | 105 ++++++++++++++++------
 1 file changed, 79 insertions(+), 26 deletions(-)

Comments

Xi Ruoyao Aug. 15, 2024, 5:39 a.m. UTC | #1
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>

Tested-by: Xi Ruoyao <xry111@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 Aug. 26, 2024, 6:07 a.m. UTC | #2
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;
Shuah Khan Aug. 27, 2024, 1:12 p.m. UTC | #3
On 8/26/24 00:07, Xi Ruoyao wrote:
> 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.
> 

I am seeing several checkpatch errors - please fix them and send me v2.

thanks,
-- Shuah
Fangrui Song Aug. 27, 2024, 1:37 p.m. UTC | #4
On Tue, Aug 27, 2024 at 10:12 PM Shuah Khan <skhan@linuxfoundation.org> wrote:
>
> On 8/26/24 00:07, Xi Ruoyao wrote:
> > 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.
> >
>
> I am seeing several checkpatch errors - please fix them and send me v2.
>
> thanks,
> -- Shuah
>

The applicable change is:

--- i/tools/testing/selftests/vDSO/parse_vdso.c
+++ w/tools/testing/selftests/vDSO/parse_vdso.c
@@ -177,7 +177,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
        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). */
+                * 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 {


Other checkpatch.pl output is not actionable. `ELF(Sym) *sym` instead
of `ELF(Sym) * sym` has the correct spacing (used in this file and
elsewhere ElfW in the code base).
Shuah Khan Sept. 3, 2024, 9:24 p.m. UTC | #5
On 8/27/24 07:37, Fangrui Song wrote:
> On Tue, Aug 27, 2024 at 10:12 PM Shuah Khan <skhan@linuxfoundation.org> wrote:
>>
>> On 8/26/24 00:07, Xi Ruoyao wrote:
>>> 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.
>>>
>>
>> I am seeing several checkpatch errors - please fix them and send me v2.
>>
>> thanks,
>> -- Shuah
>>
> 
> The applicable change is:
> 
> --- i/tools/testing/selftests/vDSO/parse_vdso.c
> +++ w/tools/testing/selftests/vDSO/parse_vdso.c
> @@ -177,7 +177,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
>          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). */
> +                * 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 {
> 
> 
> Other checkpatch.pl output is not actionable. `ELF(Sym) *sym` instead
> of `ELF(Sym) * sym` has the correct spacing (used in this file and
> elsewhere ElfW in the code base).
> 
> 

Okay. Send v2 with the actionable change.

thanks,
-- Shuah
diff mbox series

Patch

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;