diff --git a/libc/include/elf.h b/libc/include/elf.h index 801d9ff72..df768ba52 100644 --- a/libc/include/elf.h +++ b/libc/include/elf.h @@ -194,4 +194,10 @@ typedef struct { #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 5ed88914c..057c2173c 100644 --- a/linker/dlfcn.cpp +++ b/linker/dlfcn.cpp @@ -100,7 +100,7 @@ void* dlsym(void* handle, const char* symbol) { } 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 a72b73ccd..8003dbf84 100644 --- a/linker/linked_list.h +++ b/linker/linked_list.h @@ -136,6 +136,17 @@ class LinkedList { } } + template + T* find_if(F predicate) const { + for (LinkedListEntry* 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* e = head_; sz < array_length && e != nullptr; e = e->next) { diff --git a/linker/linker.cpp b/linker/linker.cpp index 3c8ba76e6..e029dbdbf 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 @@ static const char* const kDefaultLdPaths[] = { nullptr }; +static const ElfW(Versym) kVersymNotNeeded = 0; +static const ElfW(Versym) kVersymGlobal = 1; + static std::vector g_ld_library_paths; static std::vector g_ld_preload_names; @@ -379,8 +382,128 @@ int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), void 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 +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_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(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 @@ static bool is_symbol_global_and_defined(const soinfo* si, const ElfW(Sym)* s) { 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 @@ ElfW(Sym)* soinfo::gnu_lookup(SymbolName& symbol_name) { 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(base)); @@ -411,7 +552,7 @@ ElfW(Sym)* soinfo::gnu_lookup(SymbolName& symbol_name) { TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p", symbol_name.get_name(), get_soname(), reinterpret_cast(base)); - return nullptr; + return true; } // bloom test says "probably yes"... @@ -421,43 +562,77 @@ ElfW(Sym)* soinfo::gnu_lookup(SymbolName& symbol_name) { TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p", symbol_name.get_name(), get_soname(), reinterpret_cast(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(s->st_value), static_cast(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(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(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(s->st_value), static_cast(s->st_size)); - return s; + *symbol_index = n; + return true; } } @@ -465,7 +640,8 @@ ElfW(Sym)* soinfo::elf_lookup(SymbolName& symbol_name) { symbol_name.get_name(), get_soname(), reinterpret_cast(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 @@ uint32_t SymbolName::gnu_hash() { 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 @@ ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found */ 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 @@ ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found // 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 @@ ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found 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 @@ ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found 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 @@ ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found return true; }); + + if (error) { + return false; + } } if (s != nullptr) { @@ -590,7 +788,8 @@ ElfW(Sym)* soinfo_do_lookup(soinfo* si_from, const char* name, soinfo** si_found reinterpret_cast((*si_found_in)->load_bias)); } - return s; + *symbol = s; + return true; } class ProtectedDataGuard { @@ -735,13 +934,16 @@ static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_s // 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 @@ ElfW(Sym)* dlsym_handle_lookup(soinfo* si, soinfo** found, const char* name) { 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)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* caller, } } - 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 @@ ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* caller, 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 @@ static ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr) { 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_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; jvn_cnt; ++j) { + const ElfW(Vernaux)* vernaux = reinterpret_cast(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 @@ static ElfW(Addr) get_addend(ElfW(Rel)* rel, ElfW(Addr) reloc_addr) { template 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 @@ bool soinfo::relocate(ElfRelIteratorT&& rel_iterator, const soinfo_list_t& globa 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 @@ soinfo::soinfo_list_t& soinfo::get_children() { 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 @@ soinfo::soinfo_list_t& soinfo::get_parents() { 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 @@ bool soinfo::prelink_image() { case DT_BIND_NOW: break; - // Ignore: bionic does not support symbol versioning... case DT_VERSYM: + versym_ = reinterpret_cast(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 7482581d8..dae3972c0 100644 --- a/linker/linker.h +++ b/linker/linker.h @@ -40,6 +40,7 @@ #include "linked_list.h" #include +#include #define DL_ERR(fmt, x...) \ do { \ @@ -142,6 +143,32 @@ class SymbolName { 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_infos; + + DISALLOW_COPY_AND_ASSIGN(VersionTracker); +}; + struct soinfo { public: typedef LinkedList soinfo_list_t; @@ -260,11 +287,16 @@ struct soinfo { 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 @@ struct soinfo { 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 @@ struct soinfo { 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 @@ void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path); 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 14f6a1bfb..c162111e9 100644 --- a/linker/linker_mips.cpp +++ b/linker/linker_mips.cpp @@ -50,6 +50,12 @@ template 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 @@ bool soinfo::relocate(ElfRelIteratorT&& rel_iterator, 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 @@ bool soinfo::mips_relocate_got(const soinfo_list_t& global_group, // 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 708e2cd5f..1023644c0 100644 --- a/tests/dlfcn_test.cpp +++ b/tests/dlfcn_test.cpp @@ -925,3 +925,63 @@ TEST(dlfcn, dlopen_dlopen_from_ctor) { 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(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(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(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(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(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 000000000..f3a637424 --- /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 da3fb1e46..3d5b060de 100644 --- a/tests/libs/Android.mk +++ b/tests/libs/Android.mk @@ -26,6 +26,7 @@ common_additional_dependencies := \ $(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 # ----------------------------------------------------------------------------- @@ -197,6 +198,11 @@ include $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_siblings.mk # ----------------------------------------------------------------------------- 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 # diff --git a/tests/libs/versioned_lib_other.cpp b/tests/libs/versioned_lib_other.cpp new file mode 100644 index 000000000..60fa99abb --- /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 000000000..752686def --- /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 000000000..c81cbf1ea --- /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 000000000..dbda3276c --- /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 000000000..d7d413f36 --- /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 000000000..bb381029d --- /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 000000000..f4740a46e --- /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 000000000..5b1ce5983 --- /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 000000000..96eb7c3b4 --- /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 000000000..10bc9ced5 --- /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;