Support symbol versioning
Bug: http://b/20139821
Change-Id: I64122a0fb0960c20b2ce614161b7ab048456b681
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: