Support symbol versioning

Bug: http://b/20139821
Change-Id: I64122a0fb0960c20b2ce614161b7ab048456b681
diff --git a/libc/include/elf.h b/libc/include/elf.h
index 801d9ff..df768ba 100644
--- a/libc/include/elf.h
+++ b/libc/include/elf.h
@@ -194,4 +194,10 @@
 
 #define NT_GNU_BUILD_ID 3
 
+#define VER_FLG_BASE 0x1
+#define VER_FLG_WEAK 0x2
+
+#define VER_NDX_LOCAL  0
+#define VER_NDX_GLOBAL 1
+
 #endif /* _ELF_H */
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
index 5ed8891..057c217 100644
--- a/linker/dlfcn.cpp
+++ b/linker/dlfcn.cpp
@@ -100,7 +100,7 @@
   }
 
   soinfo* found = nullptr;
-  ElfW(Sym)* sym = nullptr;
+  const ElfW(Sym)* sym = nullptr;
   void* caller_addr = __builtin_return_address(0);
   soinfo* caller = find_containing_library(caller_addr);
 
diff --git a/linker/linked_list.h b/linker/linked_list.h
index a72b73c..8003dbf 100644
--- a/linker/linked_list.h
+++ b/linker/linked_list.h
@@ -136,6 +136,17 @@
     }
   }
 
+  template<typename F>
+  T* find_if(F predicate) const {
+    for (LinkedListEntry<T>* e = head_; e != nullptr; e = e->next) {
+      if (predicate(e->element)) {
+        return e->element;
+      }
+    }
+
+    return nullptr;
+  }
+
   size_t copy_to_array(T* array[], size_t array_length) const {
     size_t sz = 0;
     for (LinkedListEntry<T>* e = head_; sz < array_length && e != nullptr; e = e->next) {
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 3c8ba76..e029dbd 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2009 The Android Open Source Project
+ * Copyright (C) 2008 The Android Open Source Project
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -100,6 +100,9 @@
   nullptr
 };
 
+static const ElfW(Versym) kVersymNotNeeded = 0;
+static const ElfW(Versym) kVersymGlobal = 1;
+
 static std::vector<std::string> g_ld_library_paths;
 static std::vector<std::string> g_ld_preload_names;
 
@@ -379,8 +382,128 @@
   return rv;
 }
 
-ElfW(Sym)* soinfo::find_symbol_by_name(SymbolName& symbol_name) {
-  return is_gnu_hash() ? gnu_lookup(symbol_name) : elf_lookup(symbol_name);
+const ElfW(Versym)* soinfo::get_versym(size_t n) const {
+  if (has_min_version(2) && versym_ != nullptr) {
+    return versym_ + n;
+  }
+
+  return nullptr;
+}
+
+ElfW(Addr) soinfo::get_verneed_ptr() const {
+  if (has_min_version(2)) {
+    return verneed_ptr_;
+  }
+
+  return 0;
+}
+
+size_t soinfo::get_verneed_cnt() const {
+  if (has_min_version(2)) {
+    return verneed_cnt_;
+  }
+
+  return 0;
+}
+
+ElfW(Addr) soinfo::get_verdef_ptr() const {
+  if (has_min_version(2)) {
+    return verdef_ptr_;
+  }
+
+  return 0;
+}
+
+size_t soinfo::get_verdef_cnt() const {
+  if (has_min_version(2)) {
+    return verdef_cnt_;
+  }
+
+  return 0;
+}
+
+template<typename F>
+static bool for_each_verdef(const soinfo* si, F functor) {
+  if (!si->has_min_version(2)) {
+    return true;
+  }
+
+  uintptr_t verdef_ptr = si->get_verdef_ptr();
+  if (verdef_ptr == 0) {
+    return true;
+  }
+
+  size_t offset = 0;
+
+  size_t verdef_cnt = si->get_verdef_cnt();
+  for (size_t i = 0; i<verdef_cnt; ++i) {
+    const ElfW(Verdef)* verdef = reinterpret_cast<ElfW(Verdef)*>(verdef_ptr + offset);
+    size_t verdaux_offset = offset + verdef->vd_aux;
+    offset += verdef->vd_next;
+
+    if (verdef->vd_version != 1) {
+      DL_ERR("unsupported verdef[%zd] vd_version: %d (expected 1)", i, verdef->vd_version);
+      return false;
+    }
+
+    if ((verdef->vd_flags & VER_FLG_BASE) != 0) {
+      // "this is the version of the file itself.  It must not be used for
+      //  matching a symbol. It can be used to match references."
+      //
+      // http://www.akkadia.org/drepper/symbol-versioning
+      continue;
+    }
+
+    if (verdef->vd_cnt == 0) {
+      DL_ERR("invalid verdef[%zd] vd_cnt == 0 (version without a name)", i);
+      return false;
+    }
+
+    const ElfW(Verdaux)* verdaux = reinterpret_cast<ElfW(Verdaux)*>(verdef_ptr + verdaux_offset);
+
+    if (functor(i, verdef, verdaux) == true) {
+      break;
+    }
+  }
+
+  return true;
+}
+
+bool soinfo::find_verdef_version_index(const version_info* vi, ElfW(Versym)* versym) const {
+  if (vi == nullptr) {
+    *versym = kVersymNotNeeded;
+    return true;
+  }
+
+  *versym = kVersymGlobal;
+
+  return for_each_verdef(this,
+    [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) {
+      if (verdef->vd_hash == vi->elf_hash &&
+          strcmp(vi->name, get_string(verdaux->vda_name)) == 0) {
+        *versym = verdef->vd_ndx;
+        return true;
+      }
+
+      return false;
+    }
+  );
+}
+
+bool soinfo::find_symbol_by_name(SymbolName& symbol_name,
+                                 const version_info* vi,
+                                 const ElfW(Sym)** symbol) const {
+  uint32_t symbol_index;
+  bool success =
+      is_gnu_hash() ?
+      gnu_lookup(symbol_name, vi, &symbol_index) :
+      elf_lookup(symbol_name, vi, &symbol_index);
+
+  if (success) {
+    *symbol = symbol_index == 0 ? nullptr : symtab_ + symbol_index;
+  }
+
+  return success;
 }
 
 static bool is_symbol_global_and_defined(const soinfo* si, const ElfW(Sym)* s) {
@@ -395,7 +518,23 @@
   return false;
 }
 
-ElfW(Sym)* soinfo::gnu_lookup(SymbolName& symbol_name) {
+static const ElfW(Versym) kVersymHiddenBit = 0x8000;
+
+static inline bool is_versym_hidden(const ElfW(Versym)* versym) {
+  // the symbol is hidden if bit 15 of versym is set.
+  return versym != nullptr && (*versym & kVersymHiddenBit) != 0;
+}
+
+static inline bool check_symbol_version(const ElfW(Versym) verneed,
+                                        const ElfW(Versym)* verdef) {
+  return verneed == kVersymNotNeeded ||
+      verdef == nullptr ||
+      verneed == (*verdef & ~kVersymHiddenBit);
+}
+
+bool soinfo::gnu_lookup(SymbolName& symbol_name,
+                        const version_info* vi,
+                        uint32_t* symbol_index) const {
   uint32_t hash = symbol_name.gnu_hash();
   uint32_t h2 = hash >> gnu_shift2_;
 
@@ -403,6 +542,8 @@
   uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_;
   ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];
 
+  *symbol_index = 0;
+
   TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p (gnu)",
       symbol_name.get_name(), get_soname(), reinterpret_cast<void*>(base));
 
@@ -411,7 +552,7 @@
     TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p",
         symbol_name.get_name(), get_soname(), reinterpret_cast<void*>(base));
 
-    return nullptr;
+    return true;
   }
 
   // bloom test says "probably yes"...
@@ -421,43 +562,77 @@
     TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p",
         symbol_name.get_name(), get_soname(), reinterpret_cast<void*>(base));
 
-    return nullptr;
+    return true;
+  }
+
+  // lookup versym for the version definition in this library
+  // note the difference between "version is not requested" (vi == nullptr)
+  // and "version not found". In the first case verneed is kVersymNotNeeded
+  // which implies that the default version can be accepted; the second case results in
+  // verneed = 1 (kVersymGlobal) and implies that we should ignore versioned symbols
+  // for this library and consider only *global* ones.
+  ElfW(Versym) verneed = 0;
+  if (!find_verdef_version_index(vi, &verneed)) {
+    return false;
   }
 
   do {
     ElfW(Sym)* s = symtab_ + n;
+    const ElfW(Versym)* verdef = get_versym(n);
+    // skip hidden versions when verneed == kVersymNotNeeded (0)
+    if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) {
+        continue;
+    }
     if (((gnu_chain_[n] ^ hash) >> 1) == 0 &&
+        check_symbol_version(verneed, verdef) &&
         strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
         is_symbol_global_and_defined(this, s)) {
       TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
           symbol_name.get_name(), get_soname(), reinterpret_cast<void*>(s->st_value),
           static_cast<size_t>(s->st_size));
-      return s;
+      *symbol_index = n;
+      return true;
     }
   } while ((gnu_chain_[n++] & 1) == 0);
 
   TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p",
              symbol_name.get_name(), get_soname(), reinterpret_cast<void*>(base));
 
-  return nullptr;
+  return true;
 }
 
-ElfW(Sym)* soinfo::elf_lookup(SymbolName& symbol_name) {
+bool soinfo::elf_lookup(SymbolName& symbol_name,
+                        const version_info* vi,
+                        uint32_t* symbol_index) const {
   uint32_t hash = symbol_name.elf_hash();
 
   TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p h=%x(elf) %zd",
              symbol_name.get_name(), get_soname(),
              reinterpret_cast<void*>(base), hash, hash % nbucket_);
 
+  ElfW(Versym) verneed = 0;
+  if (!find_verdef_version_index(vi, &verneed)) {
+    return false;
+  }
+
   for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
     ElfW(Sym)* s = symtab_ + n;
-    if (strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
+    const ElfW(Versym)* verdef = get_versym(n);
+
+    // skip hidden versions when verneed == 0
+    if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) {
+        continue;
+    }
+
+    if (check_symbol_version(verneed, verdef) &&
+        strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
         is_symbol_global_and_defined(this, s)) {
       TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
                  symbol_name.get_name(), get_soname(),
                  reinterpret_cast<void*>(s->st_value),
                  static_cast<size_t>(s->st_size));
-      return s;
+      *symbol_index = n;
+      return true;
     }
   }
 
@@ -465,7 +640,8 @@
              symbol_name.get_name(), get_soname(),
              reinterpret_cast<void*>(base), hash, hash % nbucket_);
 
-  return nullptr;
+  *symbol_index = 0;
+  return true;
 }
 
 soinfo::soinfo(const char* realpath, const struct stat* file_stat,
@@ -523,10 +699,11 @@
   return gnu_hash_;
 }
 
-ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found_in,
-    const soinfo::soinfo_list_t& global_group, const soinfo::soinfo_list_t& local_group) {
+bool soinfo_do_lookup(soinfo* si_from, const char* name, const version_info* vi,
+                      soinfo** si_found_in, const soinfo::soinfo_list_t& global_group,
+                      const soinfo::soinfo_list_t& local_group, const ElfW(Sym)** symbol) {
   SymbolName symbol_name(name);
-  ElfW(Sym)* s = nullptr;
+  const ElfW(Sym)* s = nullptr;
 
   /* "This element's presence in a shared object library alters the dynamic linker's
    * symbol resolution algorithm for references within the library. Instead of starting
@@ -541,7 +718,10 @@
    */
   if (si_from->has_DT_SYMBOLIC) {
     DEBUG("%s: looking up %s in local scope (DT_SYMBOLIC)", si_from->get_soname(), name);
-    s = si_from->find_symbol_by_name(symbol_name);
+    if (!si_from->find_symbol_by_name(symbol_name, vi, &s)) {
+      return false;
+    }
+
     if (s != nullptr) {
       *si_found_in = si_from;
     }
@@ -549,10 +729,15 @@
 
   // 1. Look for it in global_group
   if (s == nullptr) {
+    bool error = false;
     global_group.visit([&](soinfo* global_si) {
       DEBUG("%s: looking up %s in %s (from global group)",
           si_from->get_soname(), name, global_si->get_soname());
-      s = global_si->find_symbol_by_name(symbol_name);
+      if (!global_si->find_symbol_by_name(symbol_name, vi, &s)) {
+        error = true;
+        return false;
+      }
+
       if (s != nullptr) {
         *si_found_in = global_si;
         return false;
@@ -560,10 +745,15 @@
 
       return true;
     });
+
+    if (error) {
+      return false;
+    }
   }
 
   // 2. Look for it in the local group
   if (s == nullptr) {
+    bool error = false;
     local_group.visit([&](soinfo* local_si) {
       if (local_si == si_from && si_from->has_DT_SYMBOLIC) {
         // we already did this - skip
@@ -572,7 +762,11 @@
 
       DEBUG("%s: looking up %s in %s (from local group)",
           si_from->get_soname(), name, local_si->get_soname());
-      s = local_si->find_symbol_by_name(symbol_name);
+      if (!local_si->find_symbol_by_name(symbol_name, vi, &s)) {
+        error = true;
+        return false;
+      }
+
       if (s != nullptr) {
         *si_found_in = local_si;
         return false;
@@ -580,6 +774,10 @@
 
       return true;
     });
+
+    if (error) {
+      return false;
+    }
   }
 
   if (s != nullptr) {
@@ -590,7 +788,8 @@
                reinterpret_cast<void*>((*si_found_in)->load_bias));
   }
 
-  return s;
+  *symbol = s;
+  return true;
 }
 
 class ProtectedDataGuard {
@@ -735,13 +934,16 @@
 
 // This is used by dlsym(3).  It performs symbol lookup only within the
 // specified soinfo object and its dependencies in breadth first order.
-ElfW(Sym)* dlsym_handle_lookup(soinfo* si, soinfo** found, const char* name) {
-  ElfW(Sym)* result = nullptr;
+const ElfW(Sym)* dlsym_handle_lookup(soinfo* si, soinfo** found, const char* name) {
+  const ElfW(Sym)* result = nullptr;
   SymbolName symbol_name(name);
 
-
   walk_dependencies_tree(&si, 1, [&](soinfo* current_soinfo) {
-    result = current_soinfo->find_symbol_by_name(symbol_name);
+    if (!current_soinfo->find_symbol_by_name(symbol_name, nullptr, &result)) {
+      result = nullptr;
+      return false;
+    }
+
     if (result != nullptr) {
       *found = current_soinfo;
       return false;
@@ -758,7 +960,10 @@
    beginning of the global solist. Otherwise the search starts at the
    specified soinfo (for RTLD_NEXT).
  */
-ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* caller, void* handle) {
+const ElfW(Sym)* dlsym_linear_lookup(const char* name,
+                                     soinfo** found,
+                                     soinfo* caller,
+                                     void* handle) {
   SymbolName symbol_name(name);
 
   soinfo* start = solist;
@@ -771,13 +976,16 @@
     }
   }
 
-  ElfW(Sym)* s = nullptr;
+  const ElfW(Sym)* s = nullptr;
   for (soinfo* si = start; si != nullptr; si = si->next) {
     if ((si->get_rtld_flags() & RTLD_GLOBAL) == 0) {
       continue;
     }
 
-    s = si->find_symbol_by_name(symbol_name);
+    if (!si->find_symbol_by_name(symbol_name, nullptr, &s)) {
+      return nullptr;
+    }
+
     if (s != nullptr) {
       *found = si;
       break;
@@ -800,7 +1008,10 @@
         break;
       }
 
-      s = si->find_symbol_by_name(symbol_name);
+      if (!si->find_symbol_by_name(symbol_name, nullptr, &s)) {
+        return nullptr;
+      }
+
       if (s != nullptr) {
         *found = si;
         break;
@@ -1444,6 +1655,93 @@
   return ifunc_addr;
 }
 
+const version_info* VersionTracker::get_version_info(ElfW(Versym) source_symver) const {
+  if (source_symver < 2 ||
+      source_symver >= version_infos.size() ||
+      version_infos[source_symver].name == nullptr) {
+    return nullptr;
+  }
+
+  return &version_infos[source_symver];
+}
+
+void VersionTracker::add_version_info(size_t source_index,
+                                      ElfW(Word) elf_hash,
+                                      const char* ver_name,
+                                      const soinfo* target_si) {
+  if (source_index >= version_infos.size()) {
+    version_infos.resize(source_index+1);
+  }
+
+  version_infos[source_index].elf_hash = elf_hash;
+  version_infos[source_index].name = ver_name;
+  version_infos[source_index].target_si = target_si;
+}
+
+bool VersionTracker::init_verneed(const soinfo* si_from) {
+  uintptr_t verneed_ptr = si_from->get_verneed_ptr();
+
+  if (verneed_ptr == 0) {
+    return true;
+  }
+
+  size_t verneed_cnt = si_from->get_verneed_cnt();
+
+  for (size_t i = 0, offset = 0; i<verneed_cnt; ++i) {
+    const ElfW(Verneed)* verneed = reinterpret_cast<ElfW(Verneed)*>(verneed_ptr + offset);
+    size_t vernaux_offset = offset + verneed->vn_aux;
+    offset += verneed->vn_next;
+
+    if (verneed->vn_version != 1) {
+      DL_ERR("unsupported verneed[%zd] vn_version: %d (expected 1)", i, verneed->vn_version);
+      return false;
+    }
+
+    const char* target_soname = si_from->get_string(verneed->vn_file);
+    // find it in dependencies
+    soinfo* target_si = si_from->get_children().find_if([&](const soinfo* si) {
+      return strcmp(si->get_soname(), target_soname) == 0;
+    });
+
+    if (target_si == nullptr) {
+      DL_ERR("cannot find \"%s\" from verneed[%zd] in DT_NEEDED list for \"%s\"",
+          target_soname, i, si_from->get_soname());
+      return false;
+    }
+
+    for (size_t j = 0; j<verneed->vn_cnt; ++j) {
+      const ElfW(Vernaux)* vernaux = reinterpret_cast<ElfW(Vernaux)*>(verneed_ptr + vernaux_offset);
+      vernaux_offset += vernaux->vna_next;
+
+      const ElfW(Word) elf_hash = vernaux->vna_hash;
+      const char* ver_name = si_from->get_string(vernaux->vna_name);
+      ElfW(Half) source_index = vernaux->vna_other;
+
+      add_version_info(source_index, elf_hash, ver_name, target_si);
+    }
+  }
+
+  return true;
+}
+
+bool VersionTracker::init_verdef(const soinfo* si_from) {
+  return for_each_verdef(si_from,
+    [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) {
+      add_version_info(verdef->vd_ndx, verdef->vd_hash,
+          si_from->get_string(verdaux->vda_name), si_from);
+      return false;
+    }
+  );
+}
+
+bool VersionTracker::init(const soinfo* si_from) {
+  if (!si_from->has_min_version(2)) {
+    return true;
+  }
+
+  return init_verneed(si_from) && init_verdef(si_from);
+}
+
 #if !defined(__mips__)
 #if defined(USE_RELA)
 static ElfW(Addr) get_addend(ElfW(Rela)* rela, ElfW(Addr) reloc_addr __unused) {
@@ -1462,6 +1760,12 @@
 template<typename ElfRelIteratorT>
 bool soinfo::relocate(ElfRelIteratorT&& rel_iterator, const soinfo_list_t& global_group,
                       const soinfo_list_t& local_group) {
+  VersionTracker version_tracker;
+
+  if (!version_tracker.init(this)) {
+    return false;
+  }
+
   for (size_t idx = 0; rel_iterator.has_next(); ++idx) {
     const auto rel = rel_iterator.next();
     if (rel == nullptr) {
@@ -1481,12 +1785,32 @@
       continue;
     }
 
-    ElfW(Sym)* s = nullptr;
+    const ElfW(Sym)* s = nullptr;
     soinfo* lsi = nullptr;
 
     if (sym != 0) {
       sym_name = get_string(symtab_[sym].st_name);
-      s = soinfo_do_lookup(this, sym_name, &lsi, global_group,local_group);
+      const ElfW(Versym)* sym_ver_ptr = get_versym(sym);
+      ElfW(Versym) sym_ver = sym_ver_ptr == nullptr ? 0 : *sym_ver_ptr;
+
+      if (sym_ver == VER_NDX_LOCAL || sym_ver == VER_NDX_GLOBAL) {
+        // there is no version info for this one
+        if (!soinfo_do_lookup(this, sym_name, nullptr, &lsi, global_group, local_group, &s)) {
+          return false;
+        }
+      } else {
+        const version_info* vi = version_tracker.get_version_info(sym_ver);
+
+        if (vi == nullptr) {
+          DL_ERR("cannot find verneed/verdef for version index=%d "
+              "referenced by symbol \"%s\" at \"%s\"", sym_ver, sym_name, get_soname());
+          return false;
+        }
+
+        if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) {
+          return false;
+        }
+      }
       if (s == nullptr) {
         // We only allow an undefined symbol if this is a weak reference...
         s = &symtab_[sym];
@@ -1977,6 +2301,14 @@
   return g_empty_list;
 }
 
+const soinfo::soinfo_list_t& soinfo::get_children() const {
+  if (has_min_version(0)) {
+    return children_;
+  }
+
+  return g_empty_list;
+}
+
 soinfo::soinfo_list_t& soinfo::get_parents() {
   if (has_min_version(0)) {
     return parents_;
@@ -1985,7 +2317,7 @@
   return g_empty_list;
 }
 
-ElfW(Addr) soinfo::resolve_symbol_address(ElfW(Sym)* s) {
+ElfW(Addr) soinfo::resolve_symbol_address(const ElfW(Sym)* s) const {
   if (ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC) {
     return call_ifunc_resolver(s->st_value + load_bias);
   }
@@ -2452,12 +2784,23 @@
       case DT_BIND_NOW:
         break;
 
-      // Ignore: bionic does not support symbol versioning...
       case DT_VERSYM:
+        versym_ = reinterpret_cast<ElfW(Versym)*>(load_bias + d->d_un.d_ptr);
+        break;
+
       case DT_VERDEF:
+        verdef_ptr_ = load_bias + d->d_un.d_ptr;
+        break;
       case DT_VERDEFNUM:
+        verdef_cnt_ = d->d_un.d_val;
+        break;
+
       case DT_VERNEED:
+        verneed_ptr_ = load_bias + d->d_un.d_ptr;
+        break;
+
       case DT_VERNEEDNUM:
+        verneed_cnt_ = d->d_un.d_val;
         break;
 
       default:
diff --git a/linker/linker.h b/linker/linker.h
index 7482581..dae3972 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -40,6 +40,7 @@
 #include "linked_list.h"
 
 #include <string>
+#include <vector>
 
 #define DL_ERR(fmt, x...) \
     do { \
@@ -142,6 +143,32 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(SymbolName);
 };
 
+struct version_info {
+  version_info() : elf_hash(0), name(nullptr), target_si(nullptr) {}
+
+  uint32_t elf_hash;
+  const char* name;
+  const soinfo* target_si;
+};
+
+// Class used construct version dependency graph.
+class VersionTracker {
+ public:
+  VersionTracker() = default;
+  bool init(const soinfo* si_from);
+
+  const version_info* get_version_info(ElfW(Versym) source_symver) const;
+ private:
+  bool init_verneed(const soinfo* si_from);
+  bool init_verdef(const soinfo* si_from);
+  void add_version_info(size_t source_index, ElfW(Word) elf_hash,
+      const char* ver_name, const soinfo* target_si);
+
+  std::vector<version_info> version_infos;
+
+  DISALLOW_COPY_AND_ASSIGN(VersionTracker);
+};
+
 struct soinfo {
  public:
   typedef LinkedList<soinfo, SoinfoListAllocator> soinfo_list_t;
@@ -260,11 +287,16 @@
   void set_dt_flags_1(uint32_t dt_flags_1);
 
   soinfo_list_t& get_children();
+  const soinfo_list_t& get_children() const;
+
   soinfo_list_t& get_parents();
 
-  ElfW(Sym)* find_symbol_by_name(SymbolName& symbol_name);
+  bool find_symbol_by_name(SymbolName& symbol_name,
+                           const version_info* vi,
+                           const ElfW(Sym)** symbol) const;
+
   ElfW(Sym)* find_symbol_by_address(const void* addr);
-  ElfW(Addr) resolve_symbol_address(ElfW(Sym)* s);
+  ElfW(Addr) resolve_symbol_address(const ElfW(Sym)* s) const;
 
   const char* get_string(ElfW(Word) index) const;
   bool can_unload() const;
@@ -292,11 +324,18 @@
 
   const char* get_soname() const;
   const char* get_realpath() const;
+  const ElfW(Versym)* get_versym(size_t n) const;
+  ElfW(Addr) get_verneed_ptr() const;
+  size_t get_verneed_cnt() const;
+  ElfW(Addr) get_verdef_ptr() const;
+  size_t get_verdef_cnt() const;
+
+  bool find_verdef_version_index(const version_info* vi, ElfW(Versym)* versym) const;
 
  private:
-  ElfW(Sym)* elf_lookup(SymbolName& symbol_name);
+  bool elf_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
   ElfW(Sym)* elf_addr_lookup(const void* addr);
-  ElfW(Sym)* gnu_lookup(SymbolName& symbol_name);
+  bool gnu_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
   ElfW(Sym)* gnu_addr_lookup(const void* addr);
 
   void call_array(const char* array_name, linker_function_t* functions, size_t count, bool reverse);
@@ -341,11 +380,20 @@
   const char* soname_;
   std::string realpath_;
 
+  const ElfW(Versym)* versym_;
+
+  ElfW(Addr) verdef_ptr_;
+  size_t verdef_cnt_;
+
+  ElfW(Addr) verneed_ptr_;
+  size_t verneed_cnt_;
+
   friend soinfo* get_libdl_info();
 };
 
-ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found_in,
-    const soinfo::soinfo_list_t& global_group, const soinfo::soinfo_list_t& local_group);
+bool soinfo_do_lookup(soinfo* si_from, const char* name, const version_info* vi,
+                      soinfo** si_found_in, const soinfo::soinfo_list_t& global_group,
+                      const soinfo::soinfo_list_t& local_group, const ElfW(Sym)** symbol);
 
 enum RelocationKind {
   kRelocAbsolute = 0,
@@ -364,10 +412,10 @@
 soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo);
 void do_dlclose(soinfo* si);
 
-ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* caller, void* handle);
+const ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* caller, void* handle);
 soinfo* find_containing_library(const void* addr);
 
-ElfW(Sym)* dlsym_handle_lookup(soinfo* si, soinfo** found, const char* name);
+const ElfW(Sym)* dlsym_handle_lookup(soinfo* si, soinfo** found, const char* name);
 
 void debuggerd_init();
 extern "C" abort_msg_t* g_abort_message;
diff --git a/linker/linker_mips.cpp b/linker/linker_mips.cpp
index 14f6a1b..c162111 100644
--- a/linker/linker_mips.cpp
+++ b/linker/linker_mips.cpp
@@ -50,6 +50,12 @@
 bool soinfo::relocate(ElfRelIteratorT&& rel_iterator,
                       const soinfo_list_t& global_group,
                       const soinfo_list_t& local_group) {
+  VersionTracker version_tracker;
+
+  if (!version_tracker.init(this)) {
+    return false;
+  }
+
   for (size_t idx = 0; rel_iterator.has_next(); ++idx) {
     const auto rel = rel_iterator.next();
 
@@ -69,12 +75,33 @@
       continue;
     }
 
-    ElfW(Sym)* s = nullptr;
+    const ElfW(Sym)* s = nullptr;
     soinfo* lsi = nullptr;
 
     if (sym != 0) {
       sym_name = get_string(symtab_[sym].st_name);
-      s = soinfo_do_lookup(this, sym_name, &lsi, global_group,local_group);
+      const ElfW(Versym)* sym_ver_ptr = get_versym(sym);
+      ElfW(Versym) sym_ver = sym_ver_ptr == nullptr ? 0 : *sym_ver_ptr;
+
+      if (sym_ver == VER_NDX_LOCAL || sym_ver == VER_NDX_GLOBAL) {
+        // there is no version info for this one
+        if (!soinfo_do_lookup(this, sym_name, nullptr, &lsi, global_group, local_group, &s)) {
+          return false;
+        }
+      } else {
+        const version_info* vi = version_tracker.get_version_info(sym_ver);
+
+        if (vi == nullptr) {
+          DL_ERR("cannot find verneed/verdef for version index=%d "
+              "referenced by symbol \"%s\" at \"%s\"", sym_ver, sym_name, get_soname());
+          return false;
+        }
+
+        if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) {
+          return false;
+        }
+      }
+
       if (s == nullptr) {
         // mips does not support relocation with weak-undefined symbols
         DL_ERR("cannot locate symbol \"%s\" referenced by \"%s\"...", sym_name, get_soname());
@@ -147,7 +174,11 @@
     // This is an undefined reference... try to locate it.
     const char* sym_name = get_string(sym->st_name);
     soinfo* lsi = nullptr;
-    ElfW(Sym)* s = soinfo_do_lookup(this, sym_name, &lsi, global_group, local_group);
+    const ElfW(Sym)* s = nullptr;
+    if (!soinfo_do_lookup(this, sym_name, nullptr, &lsi, global_group, local_group, &s)) {
+      return false;
+    }
+
     if (s == nullptr) {
       // We only allow an undefined symbol if this is a weak reference.
       s = &symtab_[g];
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index 708e2cd..1023644 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -925,3 +925,63 @@
   GTEST_LOG_(INFO) << "This test is disabled for glibc (glibc segfaults if you try to call dlopen from a constructor).\n";
 #endif
 }
+
+TEST(dlfcn, symbol_versioning_use_v1) {
+  void* handle = dlopen("libtest_versioned_uselibv1.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+  typedef int (*fn_t)();
+  fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version"));
+  ASSERT_TRUE(fn != nullptr) << dlerror();
+  ASSERT_EQ(1, fn());
+  dlclose(handle);
+}
+
+TEST(dlfcn, symbol_versioning_use_v2) {
+  void* handle = dlopen("libtest_versioned_uselibv2.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+  typedef int (*fn_t)();
+  fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version"));
+  ASSERT_TRUE(fn != nullptr) << dlerror();
+  ASSERT_EQ(2, fn());
+  dlclose(handle);
+}
+
+TEST(dlfcn, symbol_versioning_use_other_v2) {
+  void* handle = dlopen("libtest_versioned_uselibv2_other.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+  typedef int (*fn_t)();
+  fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version"));
+  ASSERT_TRUE(fn != nullptr) << dlerror();
+  ASSERT_EQ(20, fn());
+  dlclose(handle);
+}
+
+TEST(dlfcn, symbol_versioning_use_other_v3) {
+  void* handle = dlopen("libtest_versioned_uselibv3_other.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+  typedef int (*fn_t)();
+  fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "get_function_version"));
+  ASSERT_TRUE(fn != nullptr) << dlerror();
+  ASSERT_EQ(3, fn());
+  dlclose(handle);
+}
+
+TEST(dlfcn, symbol_versioning_default_via_dlsym) {
+  void* handle = dlopen("libtest_versioned_lib.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+  typedef int (*fn_t)();
+  fn_t fn = reinterpret_cast<fn_t>(dlsym(handle, "versioned_function"));
+  ASSERT_TRUE(fn != nullptr) << dlerror();
+  ASSERT_EQ(3, fn()); // the default version is 3
+  dlclose(handle);
+}
+
+// This preempts the implementation from libtest_versioned_lib.so
+extern "C" int version_zero_function() {
+  return 0;
+}
+
+// This preempts the implementation from libtest_versioned_uselibv*.so
+extern "C" int version_zero_function2() {
+  return 0;
+}
diff --git a/tests/libs/Android.build.versioned_lib.mk b/tests/libs/Android.build.versioned_lib.mk
new file mode 100644
index 0000000..f3a6374
--- /dev/null
+++ b/tests/libs/Android.build.versioned_lib.mk
@@ -0,0 +1,120 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# -----------------------------------------------------------------------------
+# Libraries used to test versioned symbols
+# -----------------------------------------------------------------------------
+libtest_versioned_uselibv1_src_files := versioned_uselib.cpp
+
+libtest_versioned_uselibv1_shared_libraries := \
+    libtest_versioned_libv1
+
+module := libtest_versioned_uselibv1
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+libtest_versioned_uselibv2_src_files := \
+    versioned_uselib.cpp
+
+libtest_versioned_uselibv2_shared_libraries := \
+    libtest_versioned_libv2
+
+libtest_versioned_uselibv2_ldflags := \
+    -Wl,--version-script,$(LOCAL_PATH)/versioned_uselib.map
+
+module := libtest_versioned_uselibv2
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+libtest_versioned_uselibv2_other_src_files := \
+    versioned_uselib.cpp
+
+libtest_versioned_uselibv2_other_shared_libraries := \
+    libtest_versioned_otherlib_empty libtest_versioned_libv2
+
+module := libtest_versioned_uselibv2_other
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+libtest_versioned_uselibv3_other_src_files := \
+    versioned_uselib.cpp
+
+libtest_versioned_uselibv3_other_shared_libraries := \
+    libtest_versioned_otherlib_empty libtest_versioned_lib
+
+module := libtest_versioned_uselibv3_other
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+# lib v1 - this one used during static linking but never used at runtime
+# which forces libtest_versioned_uselibv1 to use function v1 from
+# libtest_versioned_lib.so
+# -----------------------------------------------------------------------------
+libtest_versioned_libv1_src_files := \
+    versioned_lib_v1.cpp
+
+libtest_versioned_libv1_ldflags := \
+    -Wl,--version-script,$(LOCAL_PATH)/versioned_lib_v1.map \
+    -Wl,-soname,libtest_versioned_lib.so
+
+module := libtest_versioned_libv1
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+# lib v2 - to make libtest_versioned_uselibv2.so use version 2 of versioned_function()
+# -----------------------------------------------------------------------------
+libtest_versioned_libv2_src_files := \
+    versioned_lib_v2.cpp
+
+libtest_versioned_libv2_ldflags := \
+    -Wl,--version-script,$(LOCAL_PATH)/versioned_lib_v2.map \
+    -Wl,-soname,libtest_versioned_lib.so
+
+module := libtest_versioned_libv2
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+
+# -----------------------------------------------------------------------------
+# last version - this one is used at the runtime and exports 3 versions
+# of versioned_symbol().
+# -----------------------------------------------------------------------------
+libtest_versioned_lib_src_files := \
+    versioned_lib_v3.cpp
+
+libtest_versioned_lib_ldflags := \
+    -Wl,--version-script,$(LOCAL_PATH)/versioned_lib_v3.map
+
+module := libtest_versioned_lib
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+# This library is empty, the actual implementation will provide an unversioned
+# symbol for versioned_function().
+# -----------------------------------------------------------------------------
+libtest_versioned_otherlib_empty_src_files := empty.cpp
+
+libtest_versioned_otherlib_empty_ldflags := -Wl,-soname,libtest_versioned_otherlib.so
+module := libtest_versioned_otherlib_empty
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
+libtest_versioned_otherlib_src_files := versioned_lib_other.cpp
+
+libtest_versioned_otherlib_ldflags := \
+    -Wl,--version-script,$(LOCAL_PATH)/versioned_lib_other.map
+
+module := libtest_versioned_otherlib
+include $(LOCAL_PATH)/Android.build.testlib.mk
diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk
index da3fb1e..3d5b060 100644
--- a/tests/libs/Android.mk
+++ b/tests/libs/Android.mk
@@ -26,6 +26,7 @@
     $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_siblings.mk \
     $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_main_executable.mk \
     $(LOCAL_PATH)/Android.build.testlib.mk \
+    $(LOCAL_PATH)/Android.build.versioned_lib.mk \
     $(TEST_PATH)/Android.build.mk
 
 # -----------------------------------------------------------------------------
@@ -198,6 +199,11 @@
 include $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_main_executable.mk
 
 # -----------------------------------------------------------------------------
+# Build libtest_versioned_lib.so with its dependencies.
+# -----------------------------------------------------------------------------
+include $(LOCAL_PATH)/Android.build.versioned_lib.mk
+
+# -----------------------------------------------------------------------------
 # Library with dependency loop used by dlfcn tests
 #
 # libtest_with_dependency_loop -> a -> b -> c -> a
diff --git a/tests/libs/versioned_lib_other.cpp b/tests/libs/versioned_lib_other.cpp
new file mode 100644
index 0000000..60fa99a
--- /dev/null
+++ b/tests/libs/versioned_lib_other.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" int versioned_function_v2() {
+  return 20;
+}
+
+__asm__(".symver versioned_function_v2,versioned_function@@TESTLIB_V2");
diff --git a/tests/libs/versioned_lib_other.map b/tests/libs/versioned_lib_other.map
new file mode 100644
index 0000000..752686d
--- /dev/null
+++ b/tests/libs/versioned_lib_other.map
@@ -0,0 +1,9 @@
+TESTLIB_V0 {
+  local:
+    versioned_function_v*;
+};
+
+TESTLIB_V2 {
+  global:
+    versioned_function;
+} TESTLIB_V0;
diff --git a/tests/libs/versioned_lib_v1.cpp b/tests/libs/versioned_lib_v1.cpp
new file mode 100644
index 0000000..c81cbf1
--- /dev/null
+++ b/tests/libs/versioned_lib_v1.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" {
+  int versioned_function_v1(); // __attribute__((visibility("hidden")));
+  int version_zero_function();
+}
+
+int versioned_function_v1() {
+  return 1;
+}
+
+int version_zero_function() {
+  return 100;
+}
+
+__asm__(".symver versioned_function_v1,versioned_function@@TESTLIB_V1");
diff --git a/tests/libs/versioned_lib_v1.map b/tests/libs/versioned_lib_v1.map
new file mode 100644
index 0000000..dbda327
--- /dev/null
+++ b/tests/libs/versioned_lib_v1.map
@@ -0,0 +1,12 @@
+TESTLIB_V0 {
+  global:
+    version_zero_function;
+  local:
+    versioned_function_v*;
+};
+
+TESTLIB_V1 {
+  global:
+    versioned_function;
+} TESTLIB_V0;
+
diff --git a/tests/libs/versioned_lib_v2.cpp b/tests/libs/versioned_lib_v2.cpp
new file mode 100644
index 0000000..d7d413f
--- /dev/null
+++ b/tests/libs/versioned_lib_v2.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" {
+  int versioned_function_v1(); // __attribute__((visibility("hidden")));
+  int versioned_function_v2(); // __attribute__((visibility("hidden")));
+  int version_zero_function();
+}
+
+int versioned_function_v1() {
+  return 1;
+}
+
+int versioned_function_v2() {
+  return 2;
+}
+
+int version_zero_function() {
+  return 200;
+}
+__asm__(".symver versioned_function_v1,versioned_function@TESTLIB_V1");
+__asm__(".symver versioned_function_v2,versioned_function@@TESTLIB_V2");
diff --git a/tests/libs/versioned_lib_v2.map b/tests/libs/versioned_lib_v2.map
new file mode 100644
index 0000000..bb38102
--- /dev/null
+++ b/tests/libs/versioned_lib_v2.map
@@ -0,0 +1,16 @@
+TESTLIB_V0 {
+  global:
+    version_zero_function;
+  local:
+    versioned_function_v*;
+};
+
+TESTLIB_V1 {
+  global:
+    versioned_function;
+} TESTLIB_V0;
+
+TESTLIB_V2 {
+  global:
+    versioned_function;
+} TESTLIB_V1;
diff --git a/tests/libs/versioned_lib_v3.cpp b/tests/libs/versioned_lib_v3.cpp
new file mode 100644
index 0000000..f4740a4
--- /dev/null
+++ b/tests/libs/versioned_lib_v3.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" {
+  int versioned_function_v1(); // __attribute__((visibility("hidden")));
+  int versioned_function_v2(); // __attribute__((visibility("hidden")));
+  int versioned_function_v3(); // __attribute__((visibility("hidden")));
+  int version_zero_function();
+}
+
+int versioned_function_v1() {
+  return 1;
+}
+
+int versioned_function_v2() {
+  return 2;
+}
+
+int versioned_function_v3() {
+  return 3;
+}
+
+int version_zero_function() {
+  return 1000;
+}
+
+__asm__(".symver versioned_function_v1,versioned_function@TESTLIB_V1");
+__asm__(".symver versioned_function_v2,versioned_function@TESTLIB_V2");
+__asm__(".symver versioned_function_v3,versioned_function@@TESTLIB_V3");
diff --git a/tests/libs/versioned_lib_v3.map b/tests/libs/versioned_lib_v3.map
new file mode 100644
index 0000000..5b1ce59
--- /dev/null
+++ b/tests/libs/versioned_lib_v3.map
@@ -0,0 +1,21 @@
+TESTLIB_V0 {
+  global:
+    version_zero_function;
+  local:
+    versioned_function_v*;
+};
+
+TESTLIB_V1 {
+  global:
+    versioned_function;
+} TESTLIB_V0;
+
+TESTLIB_V2 {
+  global:
+    versioned_function;
+} TESTLIB_V1;
+
+TESTLIB_V3 {
+  global:
+    versioned_function;
+} TESTLIB_V2;
diff --git a/tests/libs/versioned_uselib.cpp b/tests/libs/versioned_uselib.cpp
new file mode 100644
index 0000000..96eb7c3
--- /dev/null
+++ b/tests/libs/versioned_uselib.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" {
+  int versioned_function();
+
+  int get_function_version();
+  int version_zero_function();
+  int version_zero_function2() __attribute__((weak));
+}
+
+int get_function_version() {
+  return version_zero_function2() + version_zero_function() + versioned_function();
+}
+
+// we expect this function to be preempted by main executable.
+int version_zero_function2() {
+  return 40000;
+}
diff --git a/tests/libs/versioned_uselib.map b/tests/libs/versioned_uselib.map
new file mode 100644
index 0000000..10bc9ce
--- /dev/null
+++ b/tests/libs/versioned_uselib.map
@@ -0,0 +1,9 @@
+TESTLIB_NONE {
+  global:
+    get_function_version;
+};
+
+TESTLIB_ZERO {
+  global:
+    version_zero_function2;
+} TESTLIB_NONE;