diff --git a/elf.cc b/elf.cc
index 6c7aaa8289ff3f2439d290206b6cdc383d3b0967..e567b564ee4c6f81c671572b42a3c7272169341e 100644
--- a/elf.cc
+++ b/elf.cc
@@ -316,7 +316,7 @@ namespace elf {
         return h;
     }
 
-    Elf64_Sym* elf_object::lookup_symbol(const char* name)
+    Elf64_Sym* elf_object::lookup_symbol_old(const char* name)
     {
         auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
         auto strtab = dynamic_ptr<char>(DT_STRTAB);
@@ -335,6 +335,60 @@ namespace elf {
         return nullptr;
     }
 
+    uint_fast32_t
+    dl_new_hash(const char *s)
+    {
+        uint_fast32_t h = 5381;
+        for (unsigned char c = *s; c != '\0'; c = *++s) {
+            h = h * 33 + c;
+        }
+        return h & 0xffffffff;
+    }
+
+    Elf64_Sym* elf_object::lookup_symbol_gnu(const char* name)
+    {
+        auto symtab = dynamic_ptr<Elf64_Sym>(DT_SYMTAB);
+        auto strtab = dynamic_ptr<char>(DT_STRTAB);
+        auto hashtab = dynamic_ptr<Elf64_Word>(DT_GNU_HASH);
+        auto nbucket = hashtab[0];
+        auto symndx = hashtab[1];
+        auto maskwords = hashtab[2];
+        auto shift2 = hashtab[3];
+        auto bloom = reinterpret_cast<const Elf64_Xword*>(hashtab + 4);
+        auto C = sizeof(*bloom) * 8;
+        auto hashval = dl_new_hash(name);
+        if (!((bloom[(hashval / C) % maskwords] >> (hashval % C)) & 1)) {
+            return nullptr;
+        }
+        if (!((bloom[(hashval / C) >> shift2] >> (hashval % C)) & 1)) {
+            return nullptr;
+        }
+        auto buckets = reinterpret_cast<const Elf64_Word*>(bloom + maskwords);
+        auto chains = buckets + nbucket - symndx;
+        auto idx = hashval % nbucket;
+        if (idx == 0) {
+            return nullptr;
+        }
+        do {
+            if ((chains[idx] & ~1) != (hashval & ~1)) {
+                continue;
+            }
+            if (strcmp(&strtab[symtab[idx].st_name], name) == 0) {
+                return &symtab[idx];
+            }
+        } while ((chains[symndx + idx++] & 1) == 0);
+        return nullptr;
+    }
+
+    Elf64_Sym* elf_object::lookup_symbol(const char* name)
+    {
+        if (dynamic_exists(DT_GNU_HASH)) {
+            return lookup_symbol_gnu(name);
+        } else {
+            return lookup_symbol_old(name);
+        }
+    }
+
     void elf_object::load_needed()
     {
         auto needed = dynamic_str_array(DT_NEEDED);
diff --git a/elf.hh b/elf.hh
index 96550141b6f7f91b82bb315a1d19693f04cc04bd..f74923ea5bc98732c136faffd869634af600a176 100644
--- a/elf.hh
+++ b/elf.hh
@@ -145,6 +145,7 @@ namespace elf {
         DT_LOPROC = 0x70000000, // Defines a range of dynamic table tags that are reserved for
           // processor-specific use.
         DT_HIPROC = 0x7FFFFFFF, //
+        DT_GNU_HASH = 0x6ffffef5,
     };
 
     enum {
@@ -223,6 +224,8 @@ namespace elf {
     protected:
         virtual void load_segment(const Elf64_Phdr& segment) = 0;
     private:
+        Elf64_Sym* lookup_symbol_old(const char* name);
+        Elf64_Sym* lookup_symbol_gnu(const char* name);
 	template <typename T>
         T* dynamic_ptr(unsigned tag);
         Elf64_Xword dynamic_val(unsigned tag);