-3 Removals
+88 Additions
/*/*
* Copyright (C) 2008 The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
* are met: * are met:
* * Redistributions of source code must retain the above copyright * * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright * * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in * notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the * the documentation and/or other materials provided with the
* distribution. * distribution.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
#include <android/api-level.h>#include <android/api-level.h>
#include <dlfcn.h>#include <dlfcn.h>
#include <errno.h>#include <errno.h>
#include <fcntl.h>#include <fcntl.h>
#include <inttypes.h>#include <inttypes.h>
#include <pthread.h>#include <pthread.h>
#include <stdio.h>#include <stdio.h>
#include <stdlib.h>#include <stdlib.h>
#include <string.h>#include <string.h>
#include <sys/mman.h>#include <sys/mman.h>
#include <sys/param.h>#include <sys/param.h>
#include <sys/prctl.h>
#include <unistd.h>#include <unistd.h>
#include <new>#include <new>
#include <string>#include <string>
#include <vector>#include <vector>
// Private C library headers.// Private C library headers.
#include "private/bionic_tls.h"#include "private/bionic_tls.h"
#include "private/KernelArgumentBlock.h"#include "private/KernelArgumentBlock.h"
#include "private/ScopedPthreadMutexLocker.h"#include "private/ScopedPthreadMutexLocker.h"
#include "private/ScopeGuard.h"#include "private/ScopeGuard.h"
#include "private/UniquePtr.h"#include "private/UniquePtr.h"
#include "linker.h"#include "linker.h"
#include "linker_block_allocator.h"#include "linker_block_allocator.h"
#include "linker_debug.h"#include "linker_debug.h"
#include "linker_sleb128.h"#include "linker_sleb128.h"
#include "linker_phdr.h"#include "linker_phdr.h"
#include "linker_relocs.h"#include "linker_relocs.h"
#include "linker_reloc_iterators.h"#include "linker_reloc_iterators.h"
#include "ziparchive/zip_archive.h"#include "ziparchive/zip_archive.h"
extern void __libc_init_AT_SECURE(KernelArgumentBlock&);extern void __libc_init_AT_SECURE(KernelArgumentBlock&);
// Override macros to use C++ style casts.// Override macros to use C++ style casts.
#undef ELF_ST_TYPE#undef ELF_ST_TYPE
#define ELF_ST_TYPE(x) (static_cast<uint32_t>(x) & 0xf)#define ELF_ST_TYPE(x) (static_cast<uint32_t>(x) & 0xf)
static ElfW(Addr) get_elf_exec_load_bias(const ElfW(Ehdr)* elf);static ElfW(Addr) get_elf_exec_load_bias(const ElfW(Ehdr)* elf);
static LinkerTypeAllocator<soinfo> g_soinfo_allocator;static LinkerTypeAllocator<soinfo> g_soinfo_allocator;
static LinkerTypeAllocator<LinkedListEntry<soinfo>> g_soinfo_links_allocator;static LinkerTypeAllocator<LinkedListEntry<soinfo>> g_soinfo_links_allocator;
static soinfo* solist;static soinfo* solist;
static soinfo* sonext;static soinfo* sonext;
static soinfo* somain; // main process, always the one after libdl_infostatic soinfo* somain; // main process, always the one after libdl_info
static const char* const kDefaultLdPaths[] = {static const char* const kDefaultLdPaths[] = {
#if defined(__LP64__)#if defined(__LP64__)
"/vendor/lib64", "/vendor/lib64",
"/system/lib64", "/system/lib64",
#else#else
"/vendor/lib", "/vendor/lib",
"/system/lib", "/system/lib",
#endif#endif
nullptr nullptr
};};
static const ElfW(Versym) kVersymNotNeeded = 0;static const ElfW(Versym) kVersymNotNeeded = 0;
static const ElfW(Versym) kVersymGlobal = 1;static const ElfW(Versym) kVersymGlobal = 1;
static std::vector<std::string> g_ld_library_paths;static std::vector<std::string> g_ld_library_paths;
static std::vector<std::string> g_ld_preload_names;static std::vector<std::string> g_ld_preload_names;
static std::vector<soinfo*> g_ld_preloads;static std::vector<soinfo*> g_ld_preloads;
__LIBC_HIDDEN__ int g_ld_debug_verbosity;__LIBC_HIDDEN__ int g_ld_debug_verbosity;
__LIBC_HIDDEN__ abort_msg_t* g_abort_message = nullptr; // For debuggerd.__LIBC_HIDDEN__ abort_msg_t* g_abort_message = nullptr; // For debuggerd.
#if STATS#if STATS
struct linker_stats_t {struct linker_stats_t {
int count[kRelocMax]; int count[kRelocMax];
};};
static linker_stats_t linker_stats;static linker_stats_t linker_stats;
void count_relocation(RelocationKind kind) {void count_relocation(RelocationKind kind) {
++linker_stats.count[kind]; ++linker_stats.count[kind];
}}
#else#else
void count_relocation(RelocationKind) {void count_relocation(RelocationKind) {
}}
#endif#endif
#if COUNT_PAGES#if COUNT_PAGES
uint32_t bitmask[4096];uint32_t bitmask[4096];
#endif#endif
static char __linker_dl_err_buf[768];static char __linker_dl_err_buf[768];
char* linker_get_error_buffer() {char* linker_get_error_buffer() {
return &__linker_dl_err_buf[0]; return &__linker_dl_err_buf[0];
}}
size_t linker_get_error_buffer_size() {size_t linker_get_error_buffer_size() {
return sizeof(__linker_dl_err_buf); return sizeof(__linker_dl_err_buf);
}}
// This function is an empty stub where GDB locates a breakpoint to get notified// This function is an empty stub where GDB locates a breakpoint to get notified
// about linker activity.// about linker activity.
extern "C"extern "C"
void __attribute__((noinline)) __attribute__((visibility("default"))) rtld_db_dlactivity();void __attribute__((noinline)) __attribute__((visibility("default"))) rtld_db_dlactivity();
static pthread_mutex_t g__r_debug_mutex = PTHREAD_MUTEX_INITIALIZER;static pthread_mutex_t g__r_debug_mutex = PTHREAD_MUTEX_INITIALIZER;
static r_debug _r_debug =static r_debug _r_debug =
{1, nullptr, reinterpret_cast<uintptr_t>(&rtld_db_dlactivity), r_debug::RT_CONSISTENT, 0}; {1, nullptr, reinterpret_cast<uintptr_t>(&rtld_db_dlactivity), r_debug::RT_CONSISTENT, 0};
static link_map* r_debug_tail = 0;static link_map* r_debug_tail = 0;
static void insert_soinfo_into_debug_map(soinfo* info) {static void insert_soinfo_into_debug_map(soinfo* info) {
// Copy the necessary fields into the debug structure. // Copy the necessary fields into the debug structure.
link_map* map = &(info->link_map_head); link_map* map = &(info->link_map_head);
map->l_addr = info->load_bias; map->l_addr = info->load_bias;
// link_map l_name field is not const. // link_map l_name field is not const.
map->l_name = const_cast<char*>(info->get_realpath()); map->l_name = const_cast<char*>(info->get_realpath());
map->l_ld = info->dynamic; map->l_ld = info->dynamic;
// Stick the new library at the end of the list. // Stick the new library at the end of the list.
// gdb tends to care more about libc than it does // gdb tends to care more about libc than it does
// about leaf libraries, and ordering it this way // about leaf libraries, and ordering it this way
// reduces the back-and-forth over the wire. // reduces the back-and-forth over the wire.
if (r_debug_tail) { if (r_debug_tail) {
r_debug_tail->l_next = map; r_debug_tail->l_next = map;
map->l_prev = r_debug_tail; map->l_prev = r_debug_tail;
map->l_next = 0; map->l_next = 0;
} else { } else {
_r_debug.r_map = map; _r_debug.r_map = map;
map->l_prev = 0; map->l_prev = 0;
map->l_next = 0; map->l_next = 0;
} }
r_debug_tail = map; r_debug_tail = map;
}}
static void remove_soinfo_from_debug_map(soinfo* info) {static void remove_soinfo_from_debug_map(soinfo* info) {
link_map* map = &(info->link_map_head); link_map* map = &(info->link_map_head);
if (r_debug_tail == map) { if (r_debug_tail == map) {
r_debug_tail = map->l_prev; r_debug_tail = map->l_prev;
} }
if (map->l_prev) { if (map->l_prev) {
map->l_prev->l_next = map->l_next; map->l_prev->l_next = map->l_next;
} }
if (map->l_next) { if (map->l_next) {
map->l_next->l_prev = map->l_prev; map->l_next->l_prev = map->l_prev;
} }
}}
static void notify_gdb_of_load(soinfo* info) {static void notify_gdb_of_load(soinfo* info) {
if (info->is_main_executable()) { if (info->is_main_executable()) {
// GDB already knows about the main executable // GDB already knows about the main executable
return; return;
} }
ScopedPthreadMutexLocker locker(&g__r_debug_mutex); ScopedPthreadMutexLocker locker(&g__r_debug_mutex);
_r_debug.r_state = r_debug::RT_ADD; _r_debug.r_state = r_debug::RT_ADD;
rtld_db_dlactivity(); rtld_db_dlactivity();
insert_soinfo_into_debug_map(info); insert_soinfo_into_debug_map(info);
_r_debug.r_state = r_debug::RT_CONSISTENT; _r_debug.r_state = r_debug::RT_CONSISTENT;
rtld_db_dlactivity(); rtld_db_dlactivity();
}}
static void notify_gdb_of_unload(soinfo* info) {static void notify_gdb_of_unload(soinfo* info) {
if (info->is_main_executable()) { if (info->is_main_executable()) {
// GDB already knows about the main executable // GDB already knows about the main executable
return; return;
} }
ScopedPthreadMutexLocker locker(&g__r_debug_mutex); ScopedPthreadMutexLocker locker(&g__r_debug_mutex);
_r_debug.r_state = r_debug::RT_DELETE; _r_debug.r_state = r_debug::RT_DELETE;
rtld_db_dlactivity(); rtld_db_dlactivity();
remove_soinfo_from_debug_map(info); remove_soinfo_from_debug_map(info);
_r_debug.r_state = r_debug::RT_CONSISTENT; _r_debug.r_state = r_debug::RT_CONSISTENT;
rtld_db_dlactivity(); rtld_db_dlactivity();
}}
void notify_gdb_of_libraries() {void notify_gdb_of_libraries() {
_r_debug.r_state = r_debug::RT_ADD; _r_debug.r_state = r_debug::RT_ADD;
rtld_db_dlactivity(); rtld_db_dlactivity();
_r_debug.r_state = r_debug::RT_CONSISTENT; _r_debug.r_state = r_debug::RT_CONSISTENT;
rtld_db_dlactivity(); rtld_db_dlactivity();
}}
LinkedListEntry<soinfo>* SoinfoListAllocator::alloc() {LinkedListEntry<soinfo>* SoinfoListAllocator::alloc() {
return g_soinfo_links_allocator.alloc(); return g_soinfo_links_allocator.alloc();
}}
void SoinfoListAllocator::free(LinkedListEntry<soinfo>* entry) {void SoinfoListAllocator::free(LinkedListEntry<soinfo>* entry) {
g_soinfo_links_allocator.free(entry); g_soinfo_links_allocator.free(entry);
}}
static soinfo* soinfo_alloc(const char* name, struct stat* file_stat,static soinfo* soinfo_alloc(const char* name, struct stat* file_stat,
off64_t file_offset, uint32_t rtld_flags) { off64_t file_offset, uint32_t rtld_flags) {
if (strlen(name) >= PATH_MAX) { if (strlen(name) >= PATH_MAX) {
DL_ERR("library name \"%s\" too long", name); DL_ERR("library name \"%s\" too long", name);
return nullptr; return nullptr;
} }
soinfo* si = new (g_soinfo_allocator.alloc()) soinfo(name, file_stat, file_offset, rtld_flags); soinfo* si = new (g_soinfo_allocator.alloc()) soinfo(name, file_stat, file_offset, rtld_flags);
sonext->next = si; sonext->next = si;
sonext = si; sonext = si;
TRACE("name %s: allocated soinfo @ %p", name, si); TRACE("name %s: allocated soinfo @ %p", name, si);
return si; return si;
}}
static void soinfo_free(soinfo* si) {static void soinfo_free(soinfo* si) {
if (si == nullptr) { if (si == nullptr) {
return; return;
} }
if (si->base != 0 && si->size != 0) { if (si->base != 0 && si->size != 0) {
munmap(reinterpret_cast<void*>(si->base), si->size); munmap(reinterpret_cast<void*>(si->base), si->size);
} }
soinfo *prev = nullptr, *trav; soinfo *prev = nullptr, *trav;
TRACE("name %s: freeing soinfo @ %p", si->get_realpath(), si); TRACE("name %s: freeing soinfo @ %p", si->get_realpath(), si);
for (trav = solist; trav != nullptr; trav = trav->next) { for (trav = solist; trav != nullptr; trav = trav->next) {
if (trav == si) { if (trav == si) {
break; break;
} }
prev = trav; prev = trav;
} }
if (trav == nullptr) { if (trav == nullptr) {
// si was not in solist // si was not in solist
DL_ERR("name \"%s\"@%p is not in solist!", si->get_realpath(), si); DL_ERR("name \"%s\"@%p is not in solist!", si->get_realpath(), si);
return; return;
} }
// clear links to/from si // clear links to/from si
si->remove_all_links(); si->remove_all_links();
// prev will never be null, because the first entry in solist is // prev will never be null, because the first entry in solist is
// always the static libdl_info. // always the static libdl_info.
prev->next = si->next; prev->next = si->next;
if (si == sonext) { if (si == sonext) {
sonext = prev; sonext = prev;
} }
si->~soinfo(); si->~soinfo();
g_soinfo_allocator.free(si); g_soinfo_allocator.free(si);
}}
static void parse_path(const char* path, const char* delimiters,static void parse_path(const char* path, const char* delimiters,
std::vector<std::string>* paths) { std::vector<std::string>* paths) {
if (path == nullptr) { if (path == nullptr) {
return; return;
} }
paths->clear(); paths->clear();
for (const char *p = path; ; ++p) { for (const char *p = path; ; ++p) {
size_t len = strcspn(p, delimiters); size_t len = strcspn(p, delimiters);
// skip empty tokens // skip empty tokens
if (len == 0) { if (len == 0) {
continue; continue;
} }
paths->push_back(std::string(p, len)); paths->push_back(std::string(p, len));
p += len; p += len;
if (*p == '\0') { if (*p == '\0') {
break; break;
} }
} }
}}
static void parse_LD_LIBRARY_PATH(const char* path) {static void parse_LD_LIBRARY_PATH(const char* path) {
parse_path(path, ":", &g_ld_library_paths); parse_path(path, ":", &g_ld_library_paths);
}}
static void parse_LD_PRELOAD(const char* path) {static void parse_LD_PRELOAD(const char* path) {
// We have historically supported ':' as well as ' ' in LD_PRELOAD. // We have historically supported ':' as well as ' ' in LD_PRELOAD.
parse_path(path, " :", &g_ld_preload_names); parse_path(path, " :", &g_ld_preload_names);
}}
static bool realpath_fd(int fd, std::string* realpath) {static bool realpath_fd(int fd, std::string* realpath) {
std::vector<char> buf(PATH_MAX), proc_self_fd(PATH_MAX); std::vector<char> buf(PATH_MAX), proc_self_fd(PATH_MAX);
snprintf(&proc_self_fd[0], proc_self_fd.size(), "/proc/self/fd/%d", fd); snprintf(&proc_self_fd[0], proc_self_fd.size(), "/proc/self/fd/%d", fd);
// set DUMPABLE to 1 to access /proc/self/fd
int dumpable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0);
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
auto guard = make_scope_guard([&]() {
// restore dumpable
prctl(PR_SET_DUMPABLE, dumpable, 0, 0, 0);
});
if (readlink(&proc_self_fd[0], &buf[0], buf.size()) == -1) { if (readlink(&proc_self_fd[0], &buf[0], buf.size()) == -1) {
PRINT("readlink('%s') failed: %s [fd=%d]", &proc_self_fd[0], strerror(errno), fd); PRINT("readlink('%s') failed: %s [fd=%d]", &proc_self_fd[0], strerror(errno), fd);
return false; return false;
} }
*realpath = std::string(&buf[0]); *realpath = std::string(&buf[0]);
return true; return true;
}}
#if defined(__arm__)#if defined(__arm__)
// For a given PC, find the .so that it belongs to.// For a given PC, find the .so that it belongs to.
// Returns the base address of the .ARM.exidx section// Returns the base address of the .ARM.exidx section
// for that .so, and the number of 8-byte entries// for that .so, and the number of 8-byte entries
// in that section (via *pcount).// in that section (via *pcount).
////
// Intended to be called by libc's __gnu_Unwind_Find_exidx().// Intended to be called by libc's __gnu_Unwind_Find_exidx().
////
// This function is exposed via dlfcn.cpp and libdl.so.// This function is exposed via dlfcn.cpp and libdl.so.
_Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr pc, int* pcount) {_Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr pc, int* pcount) {
uintptr_t addr = reinterpret_cast<uintptr_t>(pc); uintptr_t addr = reinterpret_cast<uintptr_t>(pc);
for (soinfo* si = solist; si != 0; si = si->next) { for (soinfo* si = solist; si != 0; si = si->next) {
if ((addr >= si->base) && (addr < (si->base + si->size))) { if ((addr >= si->base) && (addr < (si->base + si->size))) {
*pcount = si->ARM_exidx_count; *pcount = si->ARM_exidx_count;
return reinterpret_cast<_Unwind_Ptr>(si->ARM_exidx); return reinterpret_cast<_Unwind_Ptr>(si->ARM_exidx);
} }
} }
*pcount = 0; *pcount = 0;
return nullptr; return nullptr;
}}
#endif#endif
// Here, we only have to provide a callback to iterate across all the// Here, we only have to provide a callback to iterate across all the
// loaded libraries. gcc_eh does the rest.// loaded libraries. gcc_eh does the rest.
int do_dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), void* data) {int do_dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), void* data) {
int rv = 0; int rv = 0;
for (soinfo* si = solist; si != nullptr; si = si->next) { for (soinfo* si = solist; si != nullptr; si = si->next) {
dl_phdr_info dl_info; dl_phdr_info dl_info;
dl_info.dlpi_addr = si->link_map_head.l_addr; dl_info.dlpi_addr = si->link_map_head.l_addr;
dl_info.dlpi_name = si->link_map_head.l_name; dl_info.dlpi_name = si->link_map_head.l_name;
dl_info.dlpi_phdr = si->phdr; dl_info.dlpi_phdr = si->phdr;
dl_info.dlpi_phnum = si->phnum; dl_info.dlpi_phnum = si->phnum;
rv = cb(&dl_info, sizeof(dl_phdr_info), data); rv = cb(&dl_info, sizeof(dl_phdr_info), data);
if (rv != 0) { if (rv != 0) {
break; break;
} }
} }
return rv; return rv;
}}
const ElfW(Versym)* soinfo::get_versym(size_t n) const {const ElfW(Versym)* soinfo::get_versym(size_t n) const {
if (has_min_version(2) && versym_ != nullptr) { if (has_min_version(2) && versym_ != nullptr) {
return versym_ + n; return versym_ + n;
} }
return nullptr; return nullptr;
}}
ElfW(Addr) soinfo::get_verneed_ptr() const {ElfW(Addr) soinfo::get_verneed_ptr() const {
if (has_min_version(2)) { if (has_min_version(2)) {
return verneed_ptr_; return verneed_ptr_;
} }
return 0; return 0;
}}
size_t soinfo::get_verneed_cnt() const {size_t soinfo::get_verneed_cnt() const {
if (has_min_version(2)) { if (has_min_version(2)) {
return verneed_cnt_; return verneed_cnt_;
} }
return 0; return 0;
}}
ElfW(Addr) soinfo::get_verdef_ptr() const {ElfW(Addr) soinfo::get_verdef_ptr() const {
if (has_min_version(2)) { if (has_min_version(2)) {
return verdef_ptr_; return verdef_ptr_;
} }
return 0; return 0;
}}
size_t soinfo::get_verdef_cnt() const {size_t soinfo::get_verdef_cnt() const {
if (has_min_version(2)) { if (has_min_version(2)) {
return verdef_cnt_; return verdef_cnt_;
} }
return 0; return 0;
}}
template<typename F>template<typename F>
static bool for_each_verdef(const soinfo* si, F functor) {static bool for_each_verdef(const soinfo* si, F functor) {
if (!si->has_min_version(2)) { if (!si->has_min_version(2)) {
return true; return true;
} }
uintptr_t verdef_ptr = si->get_verdef_ptr(); uintptr_t verdef_ptr = si->get_verdef_ptr();
if (verdef_ptr == 0) { if (verdef_ptr == 0) {
return true; return true;
} }
size_t offset = 0; size_t offset = 0;
size_t verdef_cnt = si->get_verdef_cnt(); size_t verdef_cnt = si->get_verdef_cnt();
for (size_t i = 0; i<verdef_cnt; ++i) { for (size_t i = 0; i<verdef_cnt; ++i) {
const ElfW(Verdef)* verdef = reinterpret_cast<ElfW(Verdef)*>(verdef_ptr + offset); const ElfW(Verdef)* verdef = reinterpret_cast<ElfW(Verdef)*>(verdef_ptr + offset);
size_t verdaux_offset = offset + verdef->vd_aux; size_t verdaux_offset = offset + verdef->vd_aux;
offset += verdef->vd_next; offset += verdef->vd_next;
if (verdef->vd_version != 1) { if (verdef->vd_version != 1) {
DL_ERR("unsupported verdef[%zd] vd_version: %d (expected 1) library: %s", DL_ERR("unsupported verdef[%zd] vd_version: %d (expected 1) library: %s",
i, verdef->vd_version, si->get_realpath()); i, verdef->vd_version, si->get_realpath());
return false; return false;
} }
if ((verdef->vd_flags & VER_FLG_BASE) != 0) { if ((verdef->vd_flags & VER_FLG_BASE) != 0) {
// "this is the version of the file itself. It must not be used for // "this is the version of the file itself. It must not be used for
// matching a symbol. It can be used to match references." // matching a symbol. It can be used to match references."
// //
// http://www.akkadia.org/drepper/symbol-versioning // http://www.akkadia.org/drepper/symbol-versioning
continue; continue;
} }
if (verdef->vd_cnt == 0) { if (verdef->vd_cnt == 0) {
DL_ERR("invalid verdef[%zd] vd_cnt == 0 (version without a name)", i); DL_ERR("invalid verdef[%zd] vd_cnt == 0 (version without a name)", i);
return false; return false;
} }
const ElfW(Verdaux)* verdaux = reinterpret_cast<ElfW(Verdaux)*>(verdef_ptr + verdaux_offset); const ElfW(Verdaux)* verdaux = reinterpret_cast<ElfW(Verdaux)*>(verdef_ptr + verdaux_offset);
if (functor(i, verdef, verdaux) == true) { if (functor(i, verdef, verdaux) == true) {
break; break;
} }
} }
return true; return true;
}}
bool soinfo::find_verdef_version_index(const version_info* vi, ElfW(Versym)* versym) const {bool soinfo::find_verdef_version_index(const version_info* vi, ElfW(Versym)* versym) const {
if (vi == nullptr) { if (vi == nullptr) {
*versym = kVersymNotNeeded; *versym = kVersymNotNeeded;
return true; return true;
} }
*versym = kVersymGlobal; *versym = kVersymGlobal;
return for_each_verdef(this, return for_each_verdef(this,
[&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) { [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) {
if (verdef->vd_hash == vi->elf_hash && if (verdef->vd_hash == vi->elf_hash &&
strcmp(vi->name, get_string(verdaux->vda_name)) == 0) { strcmp(vi->name, get_string(verdaux->vda_name)) == 0) {
*versym = verdef->vd_ndx; *versym = verdef->vd_ndx;
return true; return true;
} }
return false; return false;
} }
); );
}}
bool soinfo::find_symbol_by_name(SymbolName& symbol_name,bool soinfo::find_symbol_by_name(SymbolName& symbol_name,
const version_info* vi, const version_info* vi,
const ElfW(Sym)** symbol) const { const ElfW(Sym)** symbol) const {
uint32_t symbol_index; uint32_t symbol_index;
bool success = bool success =
is_gnu_hash() ? is_gnu_hash() ?
gnu_lookup(symbol_name, vi, &symbol_index) : gnu_lookup(symbol_name, vi, &symbol_index) :
elf_lookup(symbol_name, vi, &symbol_index); elf_lookup(symbol_name, vi, &symbol_index);
if (success) { if (success) {
*symbol = symbol_index == 0 ? nullptr : symtab_ + symbol_index; *symbol = symbol_index == 0 ? nullptr : symtab_ + symbol_index;
} }
return success; return success;
}}
static bool is_symbol_global_and_defined(const soinfo* si, const ElfW(Sym)* s) {static bool is_symbol_global_and_defined(const soinfo* si, const ElfW(Sym)* s) {
if (ELF_ST_BIND(s->st_info) == STB_GLOBAL || if (ELF_ST_BIND(s->st_info) == STB_GLOBAL ||
ELF_ST_BIND(s->st_info) == STB_WEAK) { ELF_ST_BIND(s->st_info) == STB_WEAK) {
return s->st_shndx != SHN_UNDEF; return s->st_shndx != SHN_UNDEF;
} else if (ELF_ST_BIND(s->st_info) != STB_LOCAL) { } else if (ELF_ST_BIND(s->st_info) != STB_LOCAL) {
DL_WARN("unexpected ST_BIND value: %d for '%s' in '%s'", DL_WARN("unexpected ST_BIND value: %d for '%s' in '%s'",
ELF_ST_BIND(s->st_info), si->get_string(s->st_name), si->get_realpath()); ELF_ST_BIND(s->st_info), si->get_string(s->st_name), si->get_realpath());
} }
return false; return false;
}}
static const ElfW(Versym) kVersymHiddenBit = 0x8000;static const ElfW(Versym) kVersymHiddenBit = 0x8000;
static inline bool is_versym_hidden(const ElfW(Versym)* versym) {static inline bool is_versym_hidden(const ElfW(Versym)* versym) {
// the symbol is hidden if bit 15 of versym is set. // the symbol is hidden if bit 15 of versym is set.
return versym != nullptr && (*versym & kVersymHiddenBit) != 0; return versym != nullptr && (*versym & kVersymHiddenBit) != 0;
}}
static inline bool check_symbol_version(const ElfW(Versym) verneed,static inline bool check_symbol_version(const ElfW(Versym) verneed,
const ElfW(Versym)* verdef) { const ElfW(Versym)* verdef) {
return verneed == kVersymNotNeeded || return verneed == kVersymNotNeeded ||
verdef == nullptr || verdef == nullptr ||
verneed == (*verdef & ~kVersymHiddenBit); verneed == (*verdef & ~kVersymHiddenBit);
}}
bool soinfo::gnu_lookup(SymbolName& symbol_name,bool soinfo::gnu_lookup(SymbolName& symbol_name,
const version_info* vi, const version_info* vi,
uint32_t* symbol_index) const { uint32_t* symbol_index) const {
uint32_t hash = symbol_name.gnu_hash(); uint32_t hash = symbol_name.gnu_hash();
uint32_t h2 = hash >> gnu_shift2_; uint32_t h2 = hash >> gnu_shift2_;
uint32_t bloom_mask_bits = sizeof(ElfW(Addr))*8; uint32_t bloom_mask_bits = sizeof(ElfW(Addr))*8;
uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_; uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_;
ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num]; ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];
*symbol_index = 0; *symbol_index = 0;
TRACE_TYPE(LOOKUP, "SEARCH %s in %[email protected]%p (gnu)", TRACE_TYPE(LOOKUP, "SEARCH %s in %[email protected]%p (gnu)",
symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base));
// test against bloom filter // test against bloom filter
if ((1 & (bloom_word >> (hash % bloom_mask_bits)) & (bloom_word >> (h2 % bloom_mask_bits))) == 0) { if ((1 & (bloom_word >> (hash % bloom_mask_bits)) & (bloom_word >> (h2 % bloom_mask_bits))) == 0) {
TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p", TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p",
symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base));
return true; return true;
} }
// bloom test says "probably yes"... // bloom test says "probably yes"...
uint32_t n = gnu_bucket_[hash % gnu_nbucket_]; uint32_t n = gnu_bucket_[hash % gnu_nbucket_];
if (n == 0) { if (n == 0) {
TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p", TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p",
symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base));
return true; return true;
} }
// lookup versym for the version definition in this library // lookup versym for the version definition in this library
// note the difference between "version is not requested" (vi == nullptr) // note the difference between "version is not requested" (vi == nullptr)
// and "version not found". In the first case verneed is kVersymNotNeeded // 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 // 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 // verneed = 1 (kVersymGlobal) and implies that we should ignore versioned symbols
// for this library and consider only *global* ones. // for this library and consider only *global* ones.
ElfW(Versym) verneed = 0; ElfW(Versym) verneed = 0;
if (!find_verdef_version_index(vi, &verneed)) { if (!find_verdef_version_index(vi, &verneed)) {
return false; return false;
} }
do { do {
ElfW(Sym)* s = symtab_ + n; ElfW(Sym)* s = symtab_ + n;
const ElfW(Versym)* verdef = get_versym(n); const ElfW(Versym)* verdef = get_versym(n);
// skip hidden versions when verneed == kVersymNotNeeded (0) // skip hidden versions when verneed == kVersymNotNeeded (0)
if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) { if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) {
continue; continue;
} }
if (((gnu_chain_[n] ^ hash) >> 1) == 0 && if (((gnu_chain_[n] ^ hash) >> 1) == 0 &&
check_symbol_version(verneed, verdef) && check_symbol_version(verneed, verdef) &&
strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 && strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
is_symbol_global_and_defined(this, s)) { is_symbol_global_and_defined(this, s)) {
TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd", TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(s->st_value), symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(s->st_value),
static_cast<size_t>(s->st_size)); static_cast<size_t>(s->st_size));
*symbol_index = n; *symbol_index = n;
return true; return true;
} }
} while ((gnu_chain_[n++] & 1) == 0); } while ((gnu_chain_[n++] & 1) == 0);
TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p", TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p",
symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base));
return true; return true;
}}
bool soinfo::elf_lookup(SymbolName& symbol_name,bool soinfo::elf_lookup(SymbolName& symbol_name,
const version_info* vi, const version_info* vi,
uint32_t* symbol_index) const { uint32_t* symbol_index) const {
uint32_t hash = symbol_name.elf_hash(); uint32_t hash = symbol_name.elf_hash();
TRACE_TYPE(LOOKUP, "SEARCH %s in %[email protected]%p h=%x(elf) %zd", TRACE_TYPE(LOOKUP, "SEARCH %s in %[email protected]%p h=%x(elf) %zd",
symbol_name.get_name(), get_realpath(), symbol_name.get_name(), get_realpath(),
reinterpret_cast<void*>(base), hash, hash % nbucket_); reinterpret_cast<void*>(base), hash, hash % nbucket_);
ElfW(Versym) verneed = 0; ElfW(Versym) verneed = 0;
if (!find_verdef_version_index(vi, &verneed)) { if (!find_verdef_version_index(vi, &verneed)) {
return false; return false;
} }
for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
ElfW(Sym)* s = symtab_ + n; ElfW(Sym)* s = symtab_ + n;
const ElfW(Versym)* verdef = get_versym(n); const ElfW(Versym)* verdef = get_versym(n);
// skip hidden versions when verneed == 0 // skip hidden versions when verneed == 0
if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) { if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) {
continue; continue;
} }
if (check_symbol_version(verneed, verdef) && if (check_symbol_version(verneed, verdef) &&
strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 && strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
is_symbol_global_and_defined(this, s)) { is_symbol_global_and_defined(this, s)) {
TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd", TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
symbol_name.get_name(), get_realpath(), symbol_name.get_name(), get_realpath(),
reinterpret_cast<void*>(s->st_value), reinterpret_cast<void*>(s->st_value),
static_cast<size_t>(s->st_size)); static_cast<size_t>(s->st_size));
*symbol_index = n; *symbol_index = n;
return true; return true;
} }
} }
TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p %x %zd", TRACE_TYPE(LOOKUP, "NOT FOUND %s in %[email protected]%p %x %zd",
symbol_name.get_name(), get_realpath(), symbol_name.get_name(), get_realpath(),
reinterpret_cast<void*>(base), hash, hash % nbucket_); reinterpret_cast<void*>(base), hash, hash % nbucket_);
*symbol_index = 0; *symbol_index = 0;
return true; return true;
}}
soinfo::soinfo(const char* realpath, const struct stat* file_stat,soinfo::soinfo(const char* realpath, const struct stat* file_stat,
off64_t file_offset, int rtld_flags) { off64_t file_offset, int rtld_flags) {
memset(this, 0, sizeof(*this)); memset(this, 0, sizeof(*this));
if (realpath != nullptr) { if (realpath != nullptr) {
realpath_ = realpath; realpath_ = realpath;
} }
flags_ = FLAG_NEW_SOINFO; flags_ = FLAG_NEW_SOINFO;
version_ = SOINFO_VERSION; version_ = SOINFO_VERSION;
if (file_stat != nullptr) { if (file_stat != nullptr) {
this->st_dev_ = file_stat->st_dev; this->st_dev_ = file_stat->st_dev;
this->st_ino_ = file_stat->st_ino; this->st_ino_ = file_stat->st_ino;
this->file_offset_ = file_offset; this->file_offset_ = file_offset;
} }
this->rtld_flags_ = rtld_flags; this->rtld_flags_ = rtld_flags;
}}
uint32_t SymbolName::elf_hash() {uint32_t SymbolName::elf_hash() {
if (!has_elf_hash_) { if (!has_elf_hash_) {
const uint8_t* name = reinterpret_cast<const uint8_t*>(name_); const uint8_t* name = reinterpret_cast<const uint8_t*>(name_);
uint32_t h = 0, g; uint32_t h = 0, g;
while (*name) { while (*name) {
h = (h << 4) + *name++; h = (h << 4) + *name++;
g = h & 0xf0000000; g = h & 0xf0000000;
h ^= g; h ^= g;
h ^= g >> 24; h ^= g >> 24;
} }
elf_hash_ = h; elf_hash_ = h;
has_elf_hash_ = true; has_elf_hash_ = true;
} }
return elf_hash_; return elf_hash_;
}}
uint32_t SymbolName::gnu_hash() {uint32_t SymbolName::gnu_hash() {
if (!has_gnu_hash_) { if (!has_gnu_hash_) {
uint32_t h = 5381; uint32_t h = 5381;
const uint8_t* name = reinterpret_cast<const uint8_t*>(name_); const uint8_t* name = reinterpret_cast<const uint8_t*>(name_);
while (*name != 0) { while (*name != 0) {
h += (h << 5) + *name++; // h*33 + c = h + h * 32 + c = h + h << 5 + c h += (h << 5) + *name++; // h*33 + c = h + h * 32 + c = h + h << 5 + c
} }
gnu_hash_ = h; gnu_hash_ = h;
has_gnu_hash_ = true; has_gnu_hash_ = true;
} }
return gnu_hash_; return gnu_hash_;
}}
bool soinfo_do_lookup(soinfo* si_from, const char* name, const version_info* vi,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, soinfo** si_found_in, const soinfo::soinfo_list_t& global_group,
const soinfo::soinfo_list_t& local_group, const ElfW(Sym)** symbol) { const soinfo::soinfo_list_t& local_group, const ElfW(Sym)** symbol) {
SymbolName symbol_name(name); SymbolName symbol_name(name);
const ElfW(Sym)* s = nullptr; const ElfW(Sym)* s = nullptr;
/* "This element's presence in a shared object library alters the dynamic linker's /* "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 * symbol resolution algorithm for references within the library. Instead of starting
* a symbol search with the executable file, the dynamic linker starts from the shared * a symbol search with the executable file, the dynamic linker starts from the shared
* object itself. If the shared object fails to supply the referenced symbol, the * object itself. If the shared object fails to supply the referenced symbol, the
* dynamic linker then searches the executable file and other shared objects as usual." * dynamic linker then searches the executable file and other shared objects as usual."
* *
* http://www.sco.com/developers/gabi/2012-12-31/ch5.dynamic.html * http://www.sco.com/developers/gabi/2012-12-31/ch5.dynamic.html
* *
* Note that this is unlikely since static linker avoids generating * Note that this is unlikely since static linker avoids generating
* relocations for -Bsymbolic linked dynamic executables. * relocations for -Bsymbolic linked dynamic executables.
*/ */
if (si_from->has_DT_SYMBOLIC) { if (si_from->has_DT_SYMBOLIC) {
DEBUG("%s: looking up %s in local scope (DT_SYMBOLIC)", si_from->get_realpath(), name); DEBUG("%s: looking up %s in local scope (DT_SYMBOLIC)", si_from->get_realpath(), name);
if (!si_from->find_symbol_by_name(symbol_name, vi, &s)) { if (!si_from->find_symbol_by_name(symbol_name, vi, &s)) {
return false; return false;
} }
if (s != nullptr) { if (s != nullptr) {
*si_found_in = si_from; *si_found_in = si_from;
} }
} }
// 1. Look for it in global_group // 1. Look for it in global_group
if (s == nullptr) { if (s == nullptr) {
bool error = false; bool error = false;
global_group.visit([&](soinfo* global_si) { global_group.visit([&](soinfo* global_si) {
DEBUG("%s: looking up %s in %s (from global group)", DEBUG("%s: looking up %s in %s (from global group)",
si_from->get_realpath(), name, global_si->get_realpath()); si_from->get_realpath(), name, global_si->get_realpath());
if (!global_si->find_symbol_by_name(symbol_name, vi, &s)) { if (!global_si->find_symbol_by_name(symbol_name, vi, &s)) {
error = true; error = true;
return false; return false;
} }
if (s != nullptr) { if (s != nullptr) {
*si_found_in = global_si; *si_found_in = global_si;
return false; return false;
} }
return true; return true;
}); });
if (error) { if (error) {
return false; return false;
} }
} }
// 2. Look for it in the local group // 2. Look for it in the local group
if (s == nullptr) { if (s == nullptr) {
bool error = false; bool error = false;
local_group.visit([&](soinfo* local_si) { local_group.visit([&](soinfo* local_si) {
if (local_si == si_from && si_from->has_DT_SYMBOLIC) { if (local_si == si_from && si_from->has_DT_SYMBOLIC) {
// we already did this - skip // we already did this - skip
return true; return true;
} }
DEBUG("%s: looking up %s in %s (from local group)", DEBUG("%s: looking up %s in %s (from local group)",
si_from->get_realpath(), name, local_si->get_realpath()); si_from->get_realpath(), name, local_si->get_realpath());
if (!local_si->find_symbol_by_name(symbol_name, vi, &s)) { if (!local_si->find_symbol_by_name(symbol_name, vi, &s)) {
error = true; error = true;
return false; return false;
} }
if (s != nullptr) { if (s != nullptr) {
*si_found_in = local_si; *si_found_in = local_si;
return false; return false;
} }
return true; return true;
}); });
if (error) { if (error) {
return false; return false;
} }
} }
if (s != nullptr) { if (s != nullptr) {
TRACE_TYPE(LOOKUP, "si %s sym %s s->st_value = %p, " TRACE_TYPE(LOOKUP, "si %s sym %s s->st_value = %p, "
"found in %s, base = %p, load bias = %p", "found in %s, base = %p, load bias = %p",
si_from->get_realpath(), name, reinterpret_cast<void*>(s->st_value), si_from->get_realpath(), name, reinterpret_cast<void*>(s->st_value),
(*si_found_in)->get_realpath(), reinterpret_cast<void*>((*si_found_in)->base), (*si_found_in)->get_realpath(), reinterpret_cast<void*>((*si_found_in)->base),
reinterpret_cast<void*>((*si_found_in)->load_bias)); reinterpret_cast<void*>((*si_found_in)->load_bias));
} }
*symbol = s; *symbol = s;
return true; return true;
}}
class ProtectedDataGuard {class ProtectedDataGuard {
public: public:
ProtectedDataGuard() { ProtectedDataGuard() {
if (ref_count_++ == 0) { if (ref_count_++ == 0) {
protect_data(PROT_READ | PROT_WRITE); protect_data(PROT_READ | PROT_WRITE);
} }
} }
~ProtectedDataGuard() { ~ProtectedDataGuard() {
if (ref_count_ == 0) { // overflow if (ref_count_ == 0) { // overflow
__libc_fatal("Too many nested calls to dlopen()"); __libc_fatal("Too many nested calls to dlopen()");
} }
if (--ref_count_ == 0) { if (--ref_count_ == 0) {
protect_data(PROT_READ); protect_data(PROT_READ);
} }
} }
private: private:
void protect_data(int protection) { void protect_data(int protection) {
g_soinfo_allocator.protect_all(protection); g_soinfo_allocator.protect_all(protection);
g_soinfo_links_allocator.protect_all(protection); g_soinfo_links_allocator.protect_all(protection);
} }
static size_t ref_count_; static size_t ref_count_;
};};
size_t ProtectedDataGuard::ref_count_ = 0;size_t ProtectedDataGuard::ref_count_ = 0;
// Each size has it's own allocator.// Each size has it's own allocator.
template<size_t size>template<size_t size>
class SizeBasedAllocator {class SizeBasedAllocator {
public: public:
static void* alloc() { static void* alloc() {
return allocator_.alloc(); return allocator_.alloc();
} }
static void free(void* ptr) { static void free(void* ptr) {
allocator_.free(ptr); allocator_.free(ptr);
} }
private: private:
static LinkerBlockAllocator allocator_; static LinkerBlockAllocator allocator_;
};};
template<size_t size>template<size_t size>
LinkerBlockAllocator SizeBasedAllocator<size>::allocator_(size);LinkerBlockAllocator SizeBasedAllocator<size>::allocator_(size);
template<typename T>template<typename T>
class TypeBasedAllocator {class TypeBasedAllocator {
public: public:
static T* alloc() { static T* alloc() {
return reinterpret_cast<T*>(SizeBasedAllocator<sizeof(T)>::alloc()); return reinterpret_cast<T*>(SizeBasedAllocator<sizeof(T)>::alloc());
} }
static void free(T* ptr) { static void free(T* ptr) {
SizeBasedAllocator<sizeof(T)>::free(ptr); SizeBasedAllocator<sizeof(T)>::free(ptr);
} }
};};
class LoadTask {class LoadTask {
public: public:
struct deleter_t { struct deleter_t {
void operator()(LoadTask* t) { void operator()(LoadTask* t) {
TypeBasedAllocator<LoadTask>::free(t); TypeBasedAllocator<LoadTask>::free(t);
} }
}; };
typedef UniquePtr<LoadTask, deleter_t> unique_ptr; typedef UniquePtr<LoadTask, deleter_t> unique_ptr;
static deleter_t deleter; static deleter_t deleter;
static LoadTask* create(const char* name, soinfo* needed_by) { static LoadTask* create(const char* name, soinfo* needed_by) {
LoadTask* ptr = TypeBasedAllocator<LoadTask>::alloc(); LoadTask* ptr = TypeBasedAllocator<LoadTask>::alloc();
return new (ptr) LoadTask(name, needed_by); return new (ptr) LoadTask(name, needed_by);
} }
const char* get_name() const { const char* get_name() const {
return name_; return name_;
} }
soinfo* get_needed_by() const { soinfo* get_needed_by() const {
return needed_by_; return needed_by_;
} }
private: private:
LoadTask(const char* name, soinfo* needed_by) LoadTask(const char* name, soinfo* needed_by)
: name_(name), needed_by_(needed_by) {} : name_(name), needed_by_(needed_by) {}
const char* name_; const char* name_;
soinfo* needed_by_; soinfo* needed_by_;
DISALLOW_IMPLICIT_CONSTRUCTORS(LoadTask); DISALLOW_IMPLICIT_CONSTRUCTORS(LoadTask);
};};
LoadTask::deleter_t LoadTask::deleter;LoadTask::deleter_t LoadTask::deleter;
template <typename T>template <typename T>
using linked_list_t = LinkedList<T, TypeBasedAllocator<LinkedListEntry<T>>>;using linked_list_t = LinkedList<T, TypeBasedAllocator<LinkedListEntry<T>>>;
typedef linked_list_t<soinfo> SoinfoLinkedList;typedef linked_list_t<soinfo> SoinfoLinkedList;
typedef linked_list_t<const char> StringLinkedList;typedef linked_list_t<const char> StringLinkedList;
typedef linked_list_t<LoadTask> LoadTaskList;typedef linked_list_t<LoadTask> LoadTaskList;
static soinfo* find_library(const char* name, int rtld_flags, const android_dlextinfo* extinfo);
// g_ld_all_shim_libs maintains the references to memory as it used
// in the soinfo structures and in the g_active_shim_libs list.
static std::vector<std::string> g_ld_all_shim_libs;
// g_active_shim_libs are all shim libs that are still eligible
// to be loaded. We must remove a shim lib from the list before
// we load the library to avoid recursive loops (load shim libA
// for libB where libA also links against libB).
static linked_list_t<const std::string> g_active_shim_libs;
static void reset_g_active_shim_libs(void) {
g_active_shim_libs.clear();
for (const auto& pair : g_ld_all_shim_libs) {
g_active_shim_libs.push_back(&pair);
}
}
static void parse_LD_SHIM_LIBS(const char* path) {
parse_path(path, " :", &g_ld_all_shim_libs);
reset_g_active_shim_libs();
}
static bool shim_lib_matches(const char *shim_lib, const char *realpath) {
const char *sep = strchr(shim_lib, '|');
return sep != nullptr && strncmp(realpath, shim_lib, sep - shim_lib) == 0;
}
template<typename F>
static void shim_libs_for_each(const char *const path, F action) {
if (path == nullptr) return;
INFO("Finding shim libs for \"%s\"\n", path);
std::vector<const std::string *> matched;
g_active_shim_libs.for_each([&](const std::string *a_pair) {
const char *pair = a_pair->c_str();
if (shim_lib_matches(pair, path)) {
matched.push_back(a_pair);
}
});
g_active_shim_libs.remove_if([&](const std::string *a_pair) {
const char *pair = a_pair->c_str();
return shim_lib_matches(pair, path);
});
for (const auto& one_pair : matched) {
const char* const pair = one_pair->c_str();
const char* sep = strchr(pair, '|');
soinfo *child = find_library(sep+1, RTLD_GLOBAL, nullptr);
if (child) {
INFO("Using shim lib \"%s\"\n", sep+1);
action(child);
} else {
PRINT("Shim lib \"%s\" can not be loaded, ignoring.", sep+1);
}
}
}
// This function walks down the tree of soinfo dependencies// This function walks down the tree of soinfo dependencies
// in breadth-first order and// in breadth-first order and
// * calls action(soinfo* si) for each node, and// * calls action(soinfo* si) for each node, and
// * terminates walk if action returns false.// * terminates walk if action returns false.
////
// walk_dependencies_tree returns false if walk was terminated// walk_dependencies_tree returns false if walk was terminated
// by the action and true otherwise.// by the action and true otherwise.
template<typename F>template<typename F>
static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, F action) {static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, bool do_shims, F action) {
SoinfoLinkedList visit_list; SoinfoLinkedList visit_list;
SoinfoLinkedList visited; SoinfoLinkedList visited;
for (size_t i = 0; i < root_soinfos_size; ++i) { for (size_t i = 0; i < root_soinfos_size; ++i) {
visit_list.push_back(root_soinfos[i]); visit_list.push_back(root_soinfos[i]);
} }
soinfo* si; soinfo* si;
while ((si = visit_list.pop_front()) != nullptr) { while ((si = visit_list.pop_front()) != nullptr) {
if (visited.contains(si)) { if (visited.contains(si)) {
continue; continue;
} }
if (!action(si)) { if (!action(si)) {
return false; return false;
} }
visited.push_back(si); visited.push_back(si);
si->get_children().for_each([&](soinfo* child) { si->get_children().for_each([&](soinfo* child) {
visit_list.push_back(child); visit_list.push_back(child);
}); });
if (do_shims) {
shim_libs_for_each(si->get_realpath(), [&](soinfo* child) {
si->add_child(child);
visit_list.push_back(child);
});
}
} }
return true; return true;
}}
static const ElfW(Sym)* dlsym_handle_lookup(soinfo* root, soinfo* skip_until,static const ElfW(Sym)* dlsym_handle_lookup(soinfo* root, soinfo* skip_until,
soinfo** found, SymbolName& symbol_name) { soinfo** found, SymbolName& symbol_name) {
const ElfW(Sym)* result = nullptr; const ElfW(Sym)* result = nullptr;
bool skip_lookup = skip_until != nullptr; bool skip_lookup = skip_until != nullptr;
walk_dependencies_tree(&root, 1, [&](soinfo* current_soinfo) { walk_dependencies_tree(&root, 1, false, [&](soinfo* current_soinfo) {
if (skip_lookup) { if (skip_lookup) {
skip_lookup = current_soinfo != skip_until; skip_lookup = current_soinfo != skip_until;
return true; return true;
} }
if (!current_soinfo->find_symbol_by_name(symbol_name, nullptr, &result)) { if (!current_soinfo->find_symbol_by_name(symbol_name, nullptr, &result)) {
result = nullptr; result = nullptr;
return false; return false;
} }
if (result != nullptr) { if (result != nullptr) {
*found = current_soinfo; *found = current_soinfo;
return false; return false;
} }
return true; return true;
}); });
return result; return result;
}}
// This is used by dlsym(3). It performs symbol lookup only within the// This is used by dlsym(3). It performs symbol lookup only within the
// specified soinfo object and its dependencies in breadth first order.// specified soinfo object and its dependencies in breadth first order.
const 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) {
// According to man dlopen(3) and posix docs in the case when si is handle // According to man dlopen(3) and posix docs in the case when si is handle
// of the main executable we need to search not only in the executable and its // of the main executable we need to search not only in the executable and its
// dependencies but also in all libraries loaded with RTLD_GLOBAL. // dependencies but also in all libraries loaded with RTLD_GLOBAL.
// //
// Since RTLD_GLOBAL is always set for the main executable and all dt_needed shared // Since RTLD_GLOBAL is always set for the main executable and all dt_needed shared
// libraries and they are loaded in breath-first (correct) order we can just execute // libraries and they are loaded in breath-first (correct) order we can just execute
// dlsym(RTLD_DEFAULT, ...); instead of doing two stage lookup. // dlsym(RTLD_DEFAULT, ...); instead of doing two stage lookup.
if (si == somain) { if (si == somain) {
return dlsym_linear_lookup(name, found, nullptr, RTLD_DEFAULT); return dlsym_linear_lookup(name, found, nullptr, RTLD_DEFAULT);
} }
SymbolName symbol_name(name); SymbolName symbol_name(name);
return dlsym_handle_lookup(si, nullptr, found, symbol_name); return dlsym_handle_lookup(si, nullptr, found, symbol_name);
}}
/* This is used by dlsym(3) to performs a global symbol lookup. If the/* This is used by dlsym(3) to performs a global symbol lookup. If the
start value is null (for RTLD_DEFAULT), the search starts at the start value is null (for RTLD_DEFAULT), the search starts at the
beginning of the global solist. Otherwise the search starts at the beginning of the global solist. Otherwise the search starts at the
specified soinfo (for RTLD_NEXT). specified soinfo (for RTLD_NEXT).
*/ */
const ElfW(Sym)* dlsym_linear_lookup(const char* name,const ElfW(Sym)* dlsym_linear_lookup(const char* name,
soinfo** found, soinfo** found,
soinfo* caller, soinfo* caller,
void* handle) { void* handle) {
SymbolName symbol_name(name); SymbolName symbol_name(name);
soinfo* start = solist; soinfo* start = solist;
if (handle == RTLD_NEXT) { if (handle == RTLD_NEXT) {
if (caller == nullptr) { if (caller == nullptr) {
return nullptr; return nullptr;
} else { } else {
start = caller->next; start = caller->next;
} }
} }
const ElfW(Sym)* s = nullptr; const ElfW(Sym)* s = nullptr;
for (soinfo* si = start; si != nullptr; si = si->next) { for (soinfo* si = start; si != nullptr; si = si->next) {
// Do not skip RTLD_LOCAL libraries in dlsym(RTLD_DEFAULT, ...) // Do not skip RTLD_LOCAL libraries in dlsym(RTLD_DEFAULT, ...)
// if the library is opened by application with target api level <= 22 // if the library is opened by application with target api level <= 22
// See http://b/21565766 // See http://b/21565766
if ((si->get_rtld_flags() & RTLD_GLOBAL) == 0 && si->get_target_sdk_version() > 22) { if ((si->get_rtld_flags() & RTLD_GLOBAL) == 0 && si->get_target_sdk_version() > 22) {
continue; continue;
} }
if (!si->find_symbol_by_name(symbol_name, nullptr, &s)) { if (!si->find_symbol_by_name(symbol_name, nullptr, &s)) {
return nullptr; return nullptr;
} }
if (s != nullptr) { if (s != nullptr) {
*found = si; *found = si;
break; break;
} }
} }
// If not found - use dlsym_handle_lookup for caller's // If not found - use dlsym_handle_lookup for caller's
// local_group unless it is part of the global group in which // local_group unless it is part of the global group in which
// case we already did it. // case we already did it.
if (s == nullptr && caller != nullptr && if (s == nullptr && caller != nullptr &&
(caller->get_rtld_flags() & RTLD_GLOBAL) == 0) { (caller->get_rtld_flags() & RTLD_GLOBAL) == 0) {
return dlsym_handle_lookup(caller->get_local_group_root(), return dlsym_handle_lookup(caller->get_local_group_root(),
(handle == RTLD_NEXT) ? caller : nullptr, found, symbol_name); (handle == RTLD_NEXT) ? caller : nullptr, found, symbol_name);
} }
if (s != nullptr) { if (s != nullptr) {
TRACE_TYPE(LOOKUP, "%s s->st_value = %p, found->base = %p", TRACE_TYPE(LOOKUP, "%s s->st_value = %p, found->base = %p",
name, reinterpret_cast<void*>(s->st_value), reinterpret_cast<void*>((*found)->base)); name, reinterpret_cast<void*>(s->st_value), reinterpret_cast<void*>((*found)->base));
} }
return s; return s;
}}
soinfo* find_containing_library(const void* p) {soinfo* find_containing_library(const void* p) {
ElfW(Addr) address = reinterpret_cast<ElfW(Addr)>(p); ElfW(Addr) address = reinterpret_cast<ElfW(Addr)>(p);
for (soinfo* si = solist; si != nullptr; si = si->next) { for (soinfo* si = solist; si != nullptr; si = si->next) {
if (address >= si->base && address - si->base < si->size) { if (address >= si->base && address - si->base < si->size) {
return si; return si;
} }
} }
return nullptr; return nullptr;
}}
ElfW(Sym)* soinfo::find_symbol_by_address(const void* addr) {ElfW(Sym)* soinfo::find_symbol_by_address(const void* addr) {
return is_gnu_hash() ? gnu_addr_lookup(addr) : elf_addr_lookup(addr); return is_gnu_hash() ? gnu_addr_lookup(addr) : elf_addr_lookup(addr);
}}
static bool symbol_matches_soaddr(const ElfW(Sym)* sym, ElfW(Addr) soaddr) {static bool symbol_matches_soaddr(const ElfW(Sym)* sym, ElfW(Addr) soaddr) {
return sym->st_shndx != SHN_UNDEF && return sym->st_shndx != SHN_UNDEF &&
soaddr >= sym->st_value && soaddr >= sym->st_value &&
soaddr < sym->st_value + sym->st_size; soaddr < sym->st_value + sym->st_size;
}}
ElfW(Sym)* soinfo::gnu_addr_lookup(const void* addr) {ElfW(Sym)* soinfo::gnu_addr_lookup(const void* addr) {
ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias; ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias;
for (size_t i = 0; i < gnu_nbucket_; ++i) { for (size_t i = 0; i < gnu_nbucket_; ++i) {
uint32_t n = gnu_bucket_[i]; uint32_t n = gnu_bucket_[i];
if (n == 0) { if (n == 0) {
continue; continue;
} }
do { do {
ElfW(Sym)* sym = symtab_ + n; ElfW(Sym)* sym = symtab_ + n;
if (symbol_matches_soaddr(sym, soaddr)) { if (symbol_matches_soaddr(sym, soaddr)) {
return sym; return sym;
} }
} while ((gnu_chain_[n++] & 1) == 0); } while ((gnu_chain_[n++] & 1) == 0);
} }
return nullptr; return nullptr;
}}
ElfW(Sym)* soinfo::elf_addr_lookup(const void* addr) {ElfW(Sym)* soinfo::elf_addr_lookup(const void* addr) {
ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias; ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias;
// Search the library's symbol table for any defined symbol which // Search the library's symbol table for any defined symbol which
// contains this address. // contains this address.
for (size_t i = 0; i < nchain_; ++i) { for (size_t i = 0; i < nchain_; ++i) {
ElfW(Sym)* sym = symtab_ + i; ElfW(Sym)* sym = symtab_ + i;
if (symbol_matches_soaddr(sym, soaddr)) { if (symbol_matches_soaddr(sym, soaddr)) {
return sym; return sym;
} }
} }
return nullptr; return nullptr;
}}
static int open_library_in_zipfile(const char* const path,static int open_library_in_zipfile(const char* const path,
off64_t* file_offset) { off64_t* file_offset) {
TRACE("Trying zip file open from path '%s'", path); TRACE("Trying zip file open from path '%s'", path);
// Treat an '!/' separator inside a path as the separator between the name // Treat an '!/' separator inside a path as the separator between the name
// of the zip file on disk and the subdirectory to search within it. // of the zip file on disk and the subdirectory to search within it.
// For example, if path is "foo.zip!/bar/bas/x.so", then we search for // For example, if path is "foo.zip!/bar/bas/x.so", then we search for
// "bar/bas/x.so" within "foo.zip". // "bar/bas/x.so" within "foo.zip".
const char* separator = strstr(path, "!/"); const char* separator = strstr(path, "!/");
if (separator == nullptr) { if (separator == nullptr) {
return -1; return -1;
} }
char buf[512]; char buf[512];
if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) { if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) {
PRINT("Warning: ignoring very long library path: %s", path); PRINT("Warning: ignoring very long library path: %s", path);
return -1; return -1;
} }
buf[separator - path] = '\0'; buf[separator - path] = '\0';
const char* zip_path = buf; const char* zip_path = buf;
const char* file_path = &buf[separator - path + 2]; const char* file_path = &buf[separator - path + 2];
int fd = TEMP_FAILURE_RETRY(open(zip_path, O_RDONLY | O_CLOEXEC)); int fd = TEMP_FAILURE_RETRY(open(zip_path, O_RDONLY | O_CLOEXEC));
if (fd == -1) { if (fd == -1) {
return -1; return -1;
} }
ZipArchiveHandle handle; ZipArchiveHandle handle;
if (OpenArchiveFd(fd, "", &handle, false) != 0) { if (OpenArchiveFd(fd, "", &handle, false) != 0) {
// invalid zip-file (?) // invalid zip-file (?)
close(fd); close(fd);
return -1; return -1;
} }
auto archive_guard = make_scope_guard([&]() { auto archive_guard = make_scope_guard([&]() {
CloseArchive(handle); CloseArchive(handle);
}); });
ZipEntry entry; ZipEntry entry;
if (FindEntry(handle, ZipEntryName(file_path), &entry) != 0) { if (FindEntry(handle, ZipEntryName(file_path), &entry) != 0) {
// Entry was not found. // Entry was not found.
close(fd); close(fd);
return -1; return -1;
} }
// Check if it is properly stored // Check if it is properly stored
if (entry.method != kCompressStored || (entry.offset % PAGE_SIZE) != 0) { if (entry.method != kCompressStored || (entry.offset % PAGE_SIZE) != 0) {
close(fd); close(fd);
return -1; return -1;
} }
*file_offset = entry.offset; *file_offset = entry.offset;
return fd; return fd;
}}
static bool format_path(char* buf, size_t buf_size, const char* path, const char* name) {static bool format_path(char* buf, size_t buf_size, const char* path, const char* name) {
int n = __libc_format_buffer(buf, buf_size, "%s/%s", path, name); int n = __libc_format_buffer(buf, buf_size, "%s/%s", path, name);
if (n < 0 || n >= static_cast<int>(buf_size)) { if (n < 0 || n >= static_cast<int>(buf_size)) {
PRINT("Warning: ignoring very long library path: %s/%s", path, name); PRINT("Warning: ignoring very long library path: %s/%s", path, name);
return false; return false;
} }
return true; return true;
}}
static int open_library_on_default_path(const char* name, off64_t* file_offset) {static int open_library_on_default_path(const char* name, off64_t* file_offset) {
for (size_t i = 0; kDefaultLdPaths[i] != nullptr; ++i) { for (size_t i = 0; kDefaultLdPaths[i] != nullptr; ++i) {
char buf[512]; char buf[512];
if (!format_path(buf, sizeof(buf), kDefaultLdPaths[i], name)) { if (!format_path(buf, sizeof(buf), kDefaultLdPaths[i], name)) {
continue; continue;
} }
int fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC)); int fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC));
if (fd != -1) { if (fd != -1) {
*file_offset = 0; *file_offset = 0;
return fd; return fd;
} }
} }
return -1; return -1;
}}
static int open_library_on_ld_library_path(const char* name, off64_t* file_offset) {static int open_library_on_ld_library_path(const char* name, off64_t* file_offset) {
for (const auto& path_str : g_ld_library_paths) { for (const auto& path_str : g_ld_library_paths) {
char buf[512]; char buf[512];
const char* const path = path_str.c_str(); const char* const path = path_str.c_str();
if (!format_path(buf, sizeof(buf), path, name)) { if (!format_path(buf, sizeof(buf), path, name)) {
continue; continue;
} }
int fd = -1; int fd = -1;
if (strchr(buf, '!') != nullptr) { if (strchr(buf, '!') != nullptr) {
fd = open_library_in_zipfile(buf, file_offset); fd = open_library_in_zipfile(buf, file_offset);
} }
if (fd == -1) { if (fd == -1) {
fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC)); fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC));
if (fd != -1) { if (fd != -1) {
*file_offset = 0; *file_offset = 0;
} }
} }
if (fd != -1) { if (fd != -1) {
return fd; return fd;
} }
} }
return -1; return -1;
}}
static int open_library(const char* name, off64_t* file_offset) {static int open_library(const char* name, off64_t* file_offset) {
TRACE("[ opening %s ]", name); TRACE("[ opening %s ]", name);
// If the name contains a slash, we should attempt to open it directly and not search the paths. // If the name contains a slash, we should attempt to open it directly and not search the paths.
if (strchr(name, '/') != nullptr) { if (strchr(name, '/') != nullptr) {
if (strchr(name, '!') != nullptr) { if (strchr(name, '!') != nullptr) {
int fd = open_library_in_zipfile(name, file_offset); int fd = open_library_in_zipfile(name, file_offset);
if (fd != -1) { if (fd != -1) {
return fd; return fd;
} }
} }
int fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CLOEXEC)); int fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CLOEXEC));
if (fd != -1) { if (fd != -1) {
*file_offset = 0; *file_offset = 0;
} }
return fd; return fd;
} }
// Otherwise we try LD_LIBRARY_PATH first, and fall back to the built-in well known paths. // Otherwise we try LD_LIBRARY_PATH first, and fall back to the built-in well known paths.
int fd = open_library_on_ld_library_path(name, file_offset); int fd = open_library_on_ld_library_path(name, file_offset);
if (fd == -1) { if (fd == -1) {
fd = open_library_on_default_path(name, file_offset); fd = open_library_on_default_path(name, file_offset);
} }
return fd; return fd;
}}
static const char* fix_dt_needed(const char* dt_needed, const char* sopath __unused) {static const char* fix_dt_needed(const char* dt_needed, const char* sopath __unused) {
#if !defined(__LP64__)#if !defined(__LP64__)
// Work around incorrect DT_NEEDED entries for old apps: http://b/21364029 // Work around incorrect DT_NEEDED entries for old apps: http://b/21364029
if (get_application_target_sdk_version() <= 22) { if (get_application_target_sdk_version() <= 22) {
const char* bname = basename(dt_needed); const char* bname = basename(dt_needed);
if (bname != dt_needed) { if (bname != dt_needed) {
DL_WARN("'%s' library has invalid DT_NEEDED entry '%s'", sopath, dt_needed); DL_WARN("'%s' library has invalid DT_NEEDED entry '%s'", sopath, dt_needed);
} }
return bname; return bname;
} }
#endif#endif
return dt_needed; return dt_needed;
}}
template<typename F>template<typename F>
static void for_each_dt_needed(const soinfo* si, F action) {static void for_each_dt_needed(const soinfo* si, F action) {
for (ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) { for (ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) {
if (d->d_tag == DT_NEEDED) { if (d->d_tag == DT_NEEDED) {
action(fix_dt_needed(si->get_string(d->d_un.d_val), si->get_realpath())); action(fix_dt_needed(si->get_string(d->d_un.d_val), si->get_realpath()));
} }
} }
}}
static soinfo* load_library(int fd, off64_t file_offset,static soinfo* load_library(int fd, off64_t file_offset,
LoadTaskList& load_tasks, LoadTaskList& load_tasks,
const char* name, int rtld_flags, const char* name, int rtld_flags,
const android_dlextinfo* extinfo) { const android_dlextinfo* extinfo) {
if ((file_offset % PAGE_SIZE) != 0) { if ((file_offset % PAGE_SIZE) != 0) {
DL_ERR("file offset for the library \"%s\" is not page-aligned: %" PRId64, name, file_offset); DL_ERR("file offset for the library \"%s\" is not page-aligned: %" PRId64, name, file_offset);
return nullptr; return nullptr;
} }
if (file_offset < 0) { if (file_offset < 0) {
DL_ERR("file offset for the library \"%s\" is negative: %" PRId64, name, file_offset); DL_ERR("file offset for the library \"%s\" is negative: %" PRId64, name, file_offset);
return nullptr; return nullptr;
} }
struct stat file_stat; struct stat file_stat;
if (TEMP_FAILURE_RETRY(fstat(fd, &file_stat)) != 0) { if (TEMP_FAILURE_RETRY(fstat(fd, &file_stat)) != 0) {
DL_ERR("unable to stat file for the library \"%s\": %s", name, strerror(errno)); DL_ERR("unable to stat file for the library \"%s\": %s", name, strerror(errno));
return nullptr; return nullptr;
} }
if (file_offset >= file_stat.st_size) { if (file_offset >= file_stat.st_size) {
DL_ERR("file offset for the library \"%s\" >= file size: %" PRId64 " >= %" PRId64, DL_ERR("file offset for the library \"%s\" >= file size: %" PRId64 " >= %" PRId64,
name, file_offset, file_stat.st_size); name, file_offset, file_stat.st_size);
return nullptr; return nullptr;
} }
// Check for symlink and other situations where // Check for symlink and other situations where
// file can have different names, unless ANDROID_DLEXT_FORCE_LOAD is set // file can have different names, unless ANDROID_DLEXT_FORCE_LOAD is set
if (extinfo == nullptr || (extinfo->flags & ANDROID_DLEXT_FORCE_LOAD) == 0) { if (extinfo == nullptr || (extinfo->flags & ANDROID_DLEXT_FORCE_LOAD) == 0) {
for (soinfo* si = solist; si != nullptr; si = si->next) { for (soinfo* si = solist; si != nullptr; si = si->next) {
if (si->get_st_dev() != 0 && if (si->get_st_dev() != 0 &&
si->get_st_ino() != 0 && si->get_st_ino() != 0 &&
si->get_st_dev() == file_stat.st_dev && si->get_st_dev() == file_stat.st_dev &&
si->get_st_ino() == file_stat.st_ino && si->get_st_ino() == file_stat.st_ino &&
si->get_file_offset() == file_offset) { si->get_file_offset() == file_offset) {
TRACE("library \"%s\" is already loaded under different name/path \"%s\" - " TRACE("library \"%s\" is already loaded under different name/path \"%s\" - "
"will return existing soinfo", name, si->get_realpath()); "will return existing soinfo", name, si->get_realpath());
return si; return si;
} }
} }
} }
if ((rtld_flags & RTLD_NOLOAD) != 0) { if ((rtld_flags & RTLD_NOLOAD) != 0) {
DL_ERR("library \"%s\" wasn't loaded and RTLD_NOLOAD prevented it", name); DL_ERR("library \"%s\" wasn't loaded and RTLD_NOLOAD prevented it", name);
return nullptr; return nullptr;
} }
std::string realpath = name; std::string realpath = name;
if (!realpath_fd(fd, &realpath)) { if (!realpath_fd(fd, &realpath)) {
PRINT("warning: unable to get realpath for the library \"%s\". Will use given name.", name); PRINT("warning: unable to get realpath for the library \"%s\". Will use given name.", name);
realpath = name; realpath = name;
} }
// Read the ELF header and load the segments. // Read the ELF header and load the segments.
ElfReader elf_reader(realpath.c_str(), fd, file_offset, file_stat.st_size); ElfReader elf_reader(realpath.c_str(), fd, file_offset, file_stat.st_size);
if (!elf_reader.Load(extinfo)) { if (!elf_reader.Load(extinfo)) {
return nullptr; return nullptr;
} }
soinfo* si = soinfo_alloc(realpath.c_str(), &file_stat, file_offset, rtld_flags); soinfo* si = soinfo_alloc(realpath.c_str(), &file_stat, file_offset, rtld_flags);
if (si == nullptr) { if (si == nullptr) {
return nullptr; return nullptr;
} }
si->base = elf_reader.load_start(); si->base = elf_reader.load_start();
si->size = elf_reader.load_size(); si->size = elf_reader.load_size();
si->load_bias = elf_reader.load_bias(); si->load_bias = elf_reader.load_bias();
si->phnum = elf_reader.phdr_count(); si->phnum = elf_reader.phdr_count();
si->phdr = elf_reader.loaded_phdr(); si->phdr = elf_reader.loaded_phdr();
if (!si->prelink_image()) { if (!si->prelink_image()) {
soinfo_free(si); soinfo_free(si);
return nullptr; return nullptr;
} }
for_each_dt_needed(si, [&] (const char* name) { for_each_dt_needed(si, [&] (const char* name) {
load_tasks.push_back(LoadTask::create(name, si)); load_tasks.push_back(LoadTask::create(name, si));
}); });
return si; return si;
}}
static soinfo* load_library(LoadTaskList& load_tasks,static soinfo* load_library(LoadTaskList& load_tasks,
const char* name, int rtld_flags, const char* name, int rtld_flags,
const android_dlextinfo* extinfo) { const android_dlextinfo* extinfo) {
if (extinfo != nullptr && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) != 0) { if (extinfo != nullptr && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) != 0) {
off64_t file_offset = 0; off64_t file_offset = 0;
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) { if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
file_offset = extinfo->library_fd_offset; file_offset = extinfo->library_fd_offset;
} }
return load_library(extinfo->library_fd, file_offset, load_tasks, name, rtld_flags, extinfo); return load_library(extinfo->library_fd, file_offset, load_tasks, name, rtld_flags, extinfo);
} }
// Open the file. // Open the file.
off64_t file_offset; off64_t file_offset;
int fd = open_library(name, &file_offset); int fd = open_library(name, &file_offset);
if (fd == -1) { if (fd == -1) {
DL_ERR("library \"%s\" not found", name); DL_ERR("library \"%s\" not found", name);
return nullptr; return nullptr;
} }
soinfo* result = load_library(fd, file_offset, load_tasks, name, rtld_flags, extinfo); soinfo* result = load_library(fd, file_offset, load_tasks, name, rtld_flags, extinfo);
close(fd); close(fd);
return result; return result;
}}
// Returns true if library was found and false in 2 cases// Returns true if library was found and false in 2 cases
// 1. The library was found but loaded under different target_sdk_version// 1. The library was found but loaded under different target_sdk_version
// (*candidate != nullptr)// (*candidate != nullptr)
// 2. The library was not found by soname (*candidate is nullptr)// 2. The library was not found by soname (*candidate is nullptr)
static bool find_loaded_library_by_soname(const char* name, soinfo** candidate) {static bool find_loaded_library_by_soname(const char* name, soinfo** candidate) {
*candidate = nullptr; *candidate = nullptr;
// Ignore filename with path. // Ignore filename with path.
if (strchr(name, '/') != nullptr) { if (strchr(name, '/') != nullptr) {
return false; return false;
} }
uint32_t target_sdk_version = get_application_target_sdk_version(); uint32_t target_sdk_version = get_application_target_sdk_version();
for (soinfo* si = solist; si != nullptr; si = si->next) { for (soinfo* si = solist; si != nullptr; si = si->next) {
const char* soname = si->get_soname(); const char* soname = si->get_soname();
if (soname != nullptr && (strcmp(name, soname) == 0)) { if (soname != nullptr && (strcmp(name, soname) == 0)) {
// If the library was opened under different target sdk version // If the library was opened under different target sdk version
// skip this step and try to reopen it. The exceptions are // skip this step and try to reopen it. The exceptions are
// "libdl.so" and global group. There is no point in skipping // "libdl.so" and global group. There is no point in skipping
// them because relocation process is going to use them // them because relocation process is going to use them
// in any case. // in any case.
bool is_libdl = si == solist; bool is_libdl = si == solist;
if (is_libdl || (si->get_dt_flags_1() & DF_1_GLOBAL) != 0 || if (is_libdl || (si->get_dt_flags_1() & DF_1_GLOBAL) != 0 ||
!si->is_linked() || si->get_target_sdk_version() == target_sdk_version) { !si->is_linked() || si->get_target_sdk_version() == target_sdk_version) {
*candidate = si; *candidate = si;
return true; return true;
} else if (*candidate == nullptr) { } else if (*candidate == nullptr) {
// for the different sdk version - remember the first library. // for the different sdk version - remember the first library.
*candidate = si; *candidate = si;
} }
} }
} }
return false; return false;
}}
static soinfo* find_library_internal(LoadTaskList& load_tasks, const char* name,static soinfo* find_library_internal(LoadTaskList& load_tasks, const char* name,
int rtld_flags, const android_dlextinfo* extinfo) { int rtld_flags, const android_dlextinfo* extinfo) {
soinfo* candidate; soinfo* candidate;
if (find_loaded_library_by_soname(name, &candidate)) { if (find_loaded_library_by_soname(name, &candidate)) {
return candidate; return candidate;
} }
// Library might still be loaded, the accurate detection // Library might still be loaded, the accurate detection
// of this fact is done by load_library. // of this fact is done by load_library.
TRACE("[ '%s' find_loaded_library_by_soname returned false (*candidate=%[email protected]%p). Trying harder...]", TRACE("[ '%s' find_loaded_library_by_soname returned false (*candidate=%[email protected]%p). Trying harder...]",
name, candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate); name, candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate);
soinfo* si = load_library(load_tasks, name, rtld_flags, extinfo); soinfo* si = load_library(load_tasks, name, rtld_flags, extinfo);
// In case we were unable to load the library but there // In case we were unable to load the library but there
// is a candidate loaded under the same soname but different // is a candidate loaded under the same soname but different
// sdk level - return it anyways. // sdk level - return it anyways.
if (si == nullptr && candidate != nullptr) { if (si == nullptr && candidate != nullptr) {
si = candidate; si = candidate;
} }
return si; return si;
}}
static void soinfo_unload(soinfo* si);static void soinfo_unload(soinfo* si);
// TODO: this is slightly unusual way to construct// TODO: this is slightly unusual way to construct
// the global group for relocation. Not every RTLD_GLOBAL// the global group for relocation. Not every RTLD_GLOBAL
// library is included in this group for backwards-compatibility// library is included in this group for backwards-compatibility
// reasons.// reasons.
////
// This group consists of the main executable, LD_PRELOADs// This group consists of the main executable, LD_PRELOADs
// and libraries with the DF_1_GLOBAL flag set.// and libraries with the DF_1_GLOBAL flag set.
static soinfo::soinfo_list_t make_global_group() {static soinfo::soinfo_list_t make_global_group() {
soinfo::soinfo_list_t global_group; soinfo::soinfo_list_t global_group;
for (soinfo* si = somain; si != nullptr; si = si->next) { for (soinfo* si = somain; si != nullptr; si = si->next) {
if ((si->get_dt_flags_1() & DF_1_GLOBAL) != 0) { if ((si->get_dt_flags_1() & DF_1_GLOBAL) != 0) {
global_group.push_back(si); global_group.push_back(si);
} }
} }
return global_group; return global_group;
}}
static bool find_libraries(soinfo* start_with, const char* const library_names[],static bool find_libraries(soinfo* start_with, const char* const library_names[],
size_t library_names_count, soinfo* soinfos[], std::vector<soinfo*>* ld_preloads, size_t library_names_count, soinfo* soinfos[], std::vector<soinfo*>* ld_preloads,
size_t ld_preloads_count, int rtld_flags, const android_dlextinfo* extinfo) { size_t ld_preloads_count, int rtld_flags, const android_dlextinfo* extinfo) {
// Step 0: prepare. // Step 0: prepare.
LoadTaskList load_tasks; LoadTaskList load_tasks;
for (size_t i = 0; i < library_names_count; ++i) { for (size_t i = 0; i < library_names_count; ++i) {
const char* name = library_names[i]; const char* name = library_names[i];
load_tasks.push_back(LoadTask::create(name, start_with)); load_tasks.push_back(LoadTask::create(name, start_with));
} }
// Construct global_group. // Construct global_group.
soinfo::soinfo_list_t global_group = make_global_group(); soinfo::soinfo_list_t global_group = make_global_group();
// If soinfos array is null allocate one on stack. // If soinfos array is null allocate one on stack.
// The array is needed in case of failure; for example // The array is needed in case of failure; for example
// when library_names[] = {libone.so, libtwo.so} and libone.so // when library_names[] = {libone.so, libtwo.so} and libone.so
// is loaded correctly but libtwo.so failed for some reason. // is loaded correctly but libtwo.so failed for some reason.
// In this case libone.so should be unloaded on return. // In this case libone.so should be unloaded on return.
// See also implementation of failure_guard below. // See also implementation of failure_guard below.
if (soinfos == nullptr) { if (soinfos == nullptr) {
size_t soinfos_size = sizeof(soinfo*)*library_names_count; size_t soinfos_size = sizeof(soinfo*)*library_names_count;
soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size)); soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size));
memset(soinfos, 0, soinfos_size); memset(soinfos, 0, soinfos_size);
} }
// list of libraries to link - see step 2. // list of libraries to link - see step 2.
size_t soinfos_count = 0; size_t soinfos_count = 0;
auto failure_guard = make_scope_guard([&]() { auto failure_guard = make_scope_guard([&]() {
// Housekeeping // Housekeeping
load_tasks.for_each([] (LoadTask* t) { load_tasks.for_each([] (LoadTask* t) {
LoadTask::deleter(t); LoadTask::deleter(t);
}); });
for (size_t i = 0; i<soinfos_count; ++i) { for (size_t i = 0; i<soinfos_count; ++i) {
soinfo_unload(soinfos[i]); soinfo_unload(soinfos[i]);
} }
}); });
// Step 1: load and pre-link all DT_NEEDED libraries in breadth first order. // Step 1: load and pre-link all DT_NEEDED libraries in breadth first order.
for (LoadTask::unique_ptr task(load_tasks.pop_front()); for (LoadTask::unique_ptr task(load_tasks.pop_front());
task.get() != nullptr; task.reset(load_tasks.pop_front())) { task.get() != nullptr; task.reset(load_tasks.pop_front())) {
soinfo* si = find_library_internal(load_tasks, task->get_name(), rtld_flags, extinfo); soinfo* needed_by = task->get_needed_by();
soinfo* si = find_library_internal(load_tasks, task->get_name(),
rtld_flags, needed_by == nullptr ? extinfo : nullptr);
if (si == nullptr) { if (si == nullptr) {
return false; return false;
} }
soinfo* needed_by = task->get_needed_by();
if (needed_by != nullptr) { if (needed_by != nullptr) {
needed_by->add_child(si); needed_by->add_child(si);
} }
if (si->is_linked()) { if (si->is_linked()) {
si->increment_ref_count(); si->increment_ref_count();
} }
// When ld_preloads is not null, the first // When ld_preloads is not null, the first
// ld_preloads_count libs are in fact ld_preloads. // ld_preloads_count libs are in fact ld_preloads.
if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) { if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) {
// Add LD_PRELOADed libraries to the global group for future runs. // Add LD_PRELOADed libraries to the global group for future runs.
// There is no need to explicitly add them to the global group // There is no need to explicitly add them to the global group
// for this run because they are going to appear in the local // for this run because they are going to appear in the local
// group in the correct order. // group in the correct order.
si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL); si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL);
ld_preloads->push_back(si); ld_preloads->push_back(si);
} }
if (soinfos_count < library_names_count) { if (soinfos_count < library_names_count) {
soinfos[soinfos_count++] = si; soinfos[soinfos_count++] = si;
} }
} }
// Step 2: link libraries. // Step 2: link libraries.
soinfo::soinfo_list_t local_group; soinfo::soinfo_list_t local_group;
walk_dependencies_tree( walk_dependencies_tree(
start_with == nullptr ? soinfos : &start_with, start_with == nullptr ? soinfos : &start_with,
start_with == nullptr ? soinfos_count : 1, start_with == nullptr ? soinfos_count : 1,
true,
[&] (soinfo* si) { [&] (soinfo* si) {
local_group.push_back(si); local_group.push_back(si);
return true; return true;
}); });
// We need to increment ref_count in case // We need to increment ref_count in case
// the root of the local group was not linked. // the root of the local group was not linked.
bool was_local_group_root_linked = local_group.front()->is_linked(); bool was_local_group_root_linked = local_group.front()->is_linked();
bool linked = local_group.visit([&](soinfo* si) { bool linked = local_group.visit([&](soinfo* si) {
if (!si->is_linked()) { if (!si->is_linked()) {
if (!si->link_image(global_group, local_group, extinfo)) { if (!si->link_image(global_group, local_group, extinfo)) {
return false; return false;
} }
si->set_linked(); si->set_linked();
} }
return true; return true;
}); });
if (linked) { if (linked) {
failure_guard.disable(); failure_guard.disable();
} }
if (!was_local_group_root_linked) { if (!was_local_group_root_linked) {
local_group.front()->increment_ref_count(); local_group.front()->increment_ref_count();
} }
return linked; return linked;
}}
static soinfo* find_library(const char* name, int rtld_flags, const android_dlextinfo* extinfo) {static soinfo* find_library(const char* name, int rtld_flags, const android_dlextinfo* extinfo) {
soinfo* si; soinfo* si;
if (name == nullptr) { if (name == nullptr) {
si = somain; si = somain;
} else if (!find_libraries(nullptr, &name, 1, &si, nullptr, 0, rtld_flags, extinfo)) { } else if (!find_libraries(nullptr, &name, 1, &si, nullptr, 0, rtld_flags, extinfo)) {
return nullptr; return nullptr;
} }
return si; return si;
}}
static void soinfo_unload(soinfo* root) {static void soinfo_unload(soinfo* root) {
// Note that the library can be loaded but not linked; // Note that the library can be loaded but not linked;
// in which case there is no root but we still need // in which case there is no root but we still need
// to walk the tree and unload soinfos involved. // to walk the tree and unload soinfos involved.
// //
// This happens on unsuccessful dlopen, when one of // This happens on unsuccessful dlopen, when one of
// the DT_NEEDED libraries could not be linked/found. // the DT_NEEDED libraries could not be linked/found.
if (root->is_linked()) { if (root->is_linked()) {
root = root->get_local_group_root(); root = root->get_local_group_root();
} }
if (!root->can_unload()) { if (!root->can_unload()) {
TRACE("not unloading '%s' - the binary is flagged with NODELETE", root->get_realpath()); TRACE("not unloading '%s' - the binary is flagged with NODELETE", root->get_realpath());
return; return;
} }
size_t ref_count = root->is_linked() ? root->decrement_ref_count() : 0; size_t ref_count = root->is_linked() ? root->decrement_ref_count() : 0;
if (ref_count == 0) { if (ref_count == 0) {
soinfo::soinfo_list_t local_unload_list; soinfo::soinfo_list_t local_unload_list;
soinfo::soinfo_list_t external_unload_list; soinfo::soinfo_list_t external_unload_list;
soinfo::soinfo_list_t depth_first_list; soinfo::soinfo_list_t depth_first_list;
depth_first_list.push_back(root); depth_first_list.push_back(root);
soinfo* si = nullptr; soinfo* si = nullptr;
while ((si = depth_first_list.pop_front()) != nullptr) { while ((si = depth_first_list.pop_front()) != nullptr) {
if (local_unload_list.contains(si)) { if (local_unload_list.contains(si)) {
continue; continue;
} }
local_unload_list.push_back(si); local_unload_list.push_back(si);
if (si->has_min_version(0)) { if (si->has_min_version(0)) {
soinfo* child = nullptr; soinfo* child = nullptr;
while ((child = si->get_children().pop_front()) != nullptr) { while ((child = si->get_children().pop_front()) != nullptr) {
TRACE("%[email protected]%p needs to unload %[email protected]%p", si->get_realpath(), si, TRACE("%[email protected]%p needs to unload %[email protected]%p", si->get_realpath(), si,
child->get_realpath(), child); child->get_realpath(), child);
if (local_unload_list.contains(child)) { if (local_unload_list.contains(child)) {
continue; continue;
} else if (child->is_linked() && child->get_local_group_root() != root) { } else if (child->is_linked() && child->get_local_group_root() != root) {
external_unload_list.push_back(child); external_unload_list.push_back(child);
} else { } else {
depth_first_list.push_front(child); depth_first_list.push_front(child);
} }
} }
} else { } else {
#if !defined(__work_around_b_19059885__)#if !defined(__work_around_b_19059885__)
__libc_fatal("soinfo for \"%s\"@%p has no version", si->get_realpath(), si); __libc_fatal("soinfo for \"%s\"@%p has no version", si->get_realpath(), si);
#else#else
PRINT("warning: soinfo for \"%s\"@%p has no version", si->get_realpath(), si); PRINT("warning: soinfo for \"%s\"@%p has no version", si->get_realpath(), si);
for_each_dt_needed(si, [&] (const char* library_name) { for_each_dt_needed(si, [&] (const char* library_name) {
TRACE("deprecated (old format of soinfo): %s needs to unload %s", TRACE("deprecated (old format of soinfo): %s needs to unload %s",
si->get_realpath(), library_name); si->get_realpath(), library_name);
soinfo* needed = find_library(library_name, RTLD_NOLOAD, nullptr); soinfo* needed = find_library(library_name, RTLD_NOLOAD, nullptr);
if (needed != nullptr) { if (needed != nullptr) {
// Not found: for example if symlink was deleted between dlopen and dlclose // Not found: for example if symlink was deleted between dlopen and dlclose
// Since we cannot really handle errors at this point - print and continue. // Since we cannot really handle errors at this point - print and continue.
PRINT("warning: couldn't find %s needed by %s on unload.", PRINT("warning: couldn't find %s needed by %s on unload.",
library_name, si->get_realpath()); library_name, si->get_realpath());
return; return;
} else if (local_unload_list.contains(needed)) { } else if (local_unload_list.contains(needed)) {
// already visited // already visited
return; return;
} else if (needed->is_linked() && needed->get_local_group_root() != root) { } else if (needed->is_linked() && needed->get_local_group_root() != root) {
// external group // external group
external_unload_list.push_back(needed); external_unload_list.push_back(needed);
} else { } else {
// local group // local group
depth_first_list.push_front(needed); depth_first_list.push_front(needed);
} }
}); });
#endif#endif
} }
} }
local_unload_list.for_each([](soinfo* si) { local_unload_list.for_each([](soinfo* si) {
si->call_destructors(); si->call_destructors();
}); });
while ((si = local_unload_list.pop_front()) != nullptr) { while ((si = local_unload_list.pop_front()) != nullptr) {
notify_gdb_of_unload(si); notify_gdb_of_unload(si);
soinfo_free(si); soinfo_free(si);
} }
while ((si = external_unload_list.pop_front()) != nullptr) { while ((si = external_unload_list.pop_front()) != nullptr) {
soinfo_unload(si); soinfo_unload(si);
} }
} else { } else {
TRACE("not unloading '%s' group, decrementing ref_count to %zd", TRACE("not unloading '%s' group, decrementing ref_count to %zd",
root->get_realpath(), ref_count); root->get_realpath(), ref_count);
} }
}}
void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) {
// Use basic string manipulation calls to avoid snprintf. // Use basic string manipulation calls to avoid snprintf.
// snprintf indirectly calls pthread_getspecific to get the size of a buffer. // snprintf indirectly calls pthread_getspecific to get the size of a buffer.
// When debug malloc is enabled, this call returns 0. This in turn causes // When debug malloc is enabled, this call returns 0. This in turn causes
// snprintf to do nothing, which causes libraries to fail to load. // snprintf to do nothing, which causes libraries to fail to load.
// See b/17302493 for further details. // See b/17302493 for further details.
// Once the above bug is fixed, this code can be modified to use // Once the above bug is fixed, this code can be modified to use
// snprintf again. // snprintf again.
size_t required_len = strlen(kDefaultLdPaths[0]) + strlen(kDefaultLdPaths[1]) + 2; size_t required_len = strlen(kDefaultLdPaths[0]) + strlen(kDefaultLdPaths[1]) + 2;
if (buffer_size < required_len) { if (buffer_size < required_len) {
__libc_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: " __libc_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: "
"buffer len %zu, required len %zu", buffer_size, required_len); "buffer len %zu, required len %zu", buffer_size, required_len);
} }
char* end = stpcpy(buffer, kDefaultLdPaths[0]); char* end = stpcpy(buffer, kDefaultLdPaths[0]);
*end = ':'; *end = ':';
strcpy(end + 1, kDefaultLdPaths[1]); strcpy(end + 1, kDefaultLdPaths[1]);
}}
void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path) {void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path) {
parse_LD_LIBRARY_PATH(ld_library_path); parse_LD_LIBRARY_PATH(ld_library_path);
}}
soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) {soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) {
if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) { if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) {
DL_ERR("invalid flags to dlopen: %x", flags); DL_ERR("invalid flags to dlopen: %x", flags);
return nullptr; return nullptr;
} }
if (extinfo != nullptr) { if (extinfo != nullptr) {
if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) { if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) {
DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags); DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
return nullptr; return nullptr;
} }
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 && if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 &&
(extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) { (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
DL_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without " DL_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without "
"ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags); "ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags);
return nullptr; return nullptr;
} }
} }
ProtectedDataGuard guard; ProtectedDataGuard guard;
reset_g_active_shim_libs();
soinfo* si = find_library(name, flags, extinfo); soinfo* si = find_library(name, flags, extinfo);
if (si != nullptr) { if (si != nullptr) {
si->call_constructors(); si->call_constructors();
} }
return si; return si;
}}
void do_dlclose(soinfo* si) {void do_dlclose(soinfo* si) {
ProtectedDataGuard guard; ProtectedDataGuard guard;
soinfo_unload(si); soinfo_unload(si);
}}
static ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr) {static ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr) {
typedef ElfW(Addr) (*ifunc_resolver_t)(void); typedef ElfW(Addr) (*ifunc_resolver_t)(void);
ifunc_resolver_t ifunc_resolver = reinterpret_cast<ifunc_resolver_t>(resolver_addr); ifunc_resolver_t ifunc_resolver = reinterpret_cast<ifunc_resolver_t>(resolver_addr);
ElfW(Addr) ifunc_addr = ifunc_resolver(); ElfW(Addr) ifunc_addr = ifunc_resolver();
TRACE_TYPE(RELO, "Called [email protected]%p. The result is %p", TRACE_TYPE(RELO, "Called [email protected]%p. The result is %p",
ifunc_resolver, reinterpret_cast<void*>(ifunc_addr)); ifunc_resolver, reinterpret_cast<void*>(ifunc_addr));
return ifunc_addr; return ifunc_addr;
}}
const version_info* VersionTracker::get_version_info(ElfW(Versym) source_symver) const {const version_info* VersionTracker::get_version_info(ElfW(Versym) source_symver) const {
if (source_symver < 2 || if (source_symver < 2 ||
source_symver >= version_infos.size() || source_symver >= version_infos.size() ||
version_infos[source_symver].name == nullptr) { version_infos[source_symver].name == nullptr) {
return nullptr; return nullptr;
} }
return &version_infos[source_symver]; return &version_infos[source_symver];
}}
void VersionTracker::add_version_info(size_t source_index,void VersionTracker::add_version_info(size_t source_index,
ElfW(Word) elf_hash, ElfW(Word) elf_hash,
const char* ver_name, const char* ver_name,
const soinfo* target_si) { const soinfo* target_si) {
if (source_index >= version_infos.size()) { if (source_index >= version_infos.size()) {
version_infos.resize(source_index+1); version_infos.resize(source_index+1);
} }
version_infos[source_index].elf_hash = elf_hash; version_infos[source_index].elf_hash = elf_hash;
version_infos[source_index].name = ver_name; version_infos[source_index].name = ver_name;
version_infos[source_index].target_si = target_si; version_infos[source_index].target_si = target_si;
}}
bool VersionTracker::init_verneed(const soinfo* si_from) {bool VersionTracker::init_verneed(const soinfo* si_from) {
uintptr_t verneed_ptr = si_from->get_verneed_ptr(); uintptr_t verneed_ptr = si_from->get_verneed_ptr();
if (verneed_ptr == 0) { if (verneed_ptr == 0) {
return true; return true;
} }
size_t verneed_cnt = si_from->get_verneed_cnt(); size_t verneed_cnt = si_from->get_verneed_cnt();
for (size_t i = 0, offset = 0; i<verneed_cnt; ++i) { for (size_t i = 0, offset = 0; i<verneed_cnt; ++i) {
const ElfW(Verneed)* verneed = reinterpret_cast<ElfW(Verneed)*>(verneed_ptr + offset); const ElfW(Verneed)* verneed = reinterpret_cast<ElfW(Verneed)*>(verneed_ptr + offset);
size_t vernaux_offset = offset + verneed->vn_aux; size_t vernaux_offset = offset + verneed->vn_aux;
offset += verneed->vn_next; offset += verneed->vn_next;
if (verneed->vn_version != 1) { if (verneed->vn_version != 1) {
DL_ERR("unsupported verneed[%zd] vn_version: %d (expected 1)", i, verneed->vn_version); DL_ERR("unsupported verneed[%zd] vn_version: %d (expected 1)", i, verneed->vn_version);
return false; return false;
} }
const char* target_soname = si_from->get_string(verneed->vn_file); const char* target_soname = si_from->get_string(verneed->vn_file);
// find it in dependencies // find it in dependencies
soinfo* target_si = si_from->get_children().find_if([&](const soinfo* si) { soinfo* target_si = si_from->get_children().find_if([&](const soinfo* si) {
return si->get_soname() != nullptr && strcmp(si->get_soname(), target_soname) == 0; return si->get_soname() != nullptr && strcmp(si->get_soname(), target_soname) == 0;
}); });
if (target_si == nullptr) { if (target_si == nullptr) {
DL_ERR("cannot find \"%s\" from verneed[%zd] in DT_NEEDED list for \"%s\"", DL_ERR("cannot find \"%s\" from verneed[%zd] in DT_NEEDED list for \"%s\"",
target_soname, i, si_from->get_realpath()); target_soname, i, si_from->get_realpath());
return false; return false;
} }
for (size_t j = 0; j<verneed->vn_cnt; ++j) { for (size_t j = 0; j<verneed->vn_cnt; ++j) {
const ElfW(Vernaux)* vernaux = reinterpret_cast<ElfW(Vernaux)*>(verneed_ptr + vernaux_offset); const ElfW(Vernaux)* vernaux = reinterpret_cast<ElfW(Vernaux)*>(verneed_ptr + vernaux_offset);
vernaux_offset += vernaux->vna_next; vernaux_offset += vernaux->vna_next;
const ElfW(Word) elf_hash = vernaux->vna_hash; const ElfW(Word) elf_hash = vernaux->vna_hash;
const char* ver_name = si_from->get_string(vernaux->vna_name); const char* ver_name = si_from->get_string(vernaux->vna_name);
ElfW(Half) source_index = vernaux->vna_other; ElfW(Half) source_index = vernaux->vna_other;
add_version_info(source_index, elf_hash, ver_name, target_si); add_version_info(source_index, elf_hash, ver_name, target_si);
} }
} }
return true; return true;
}}
bool VersionTracker::init_verdef(const soinfo* si_from) {bool VersionTracker::init_verdef(const soinfo* si_from) {
return for_each_verdef(si_from, return for_each_verdef(si_from,
[&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) { [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) {
add_version_info(verdef->vd_ndx, verdef->vd_hash, add_version_info(verdef->vd_ndx, verdef->vd_hash,
si_from->get_string(verdaux->vda_name), si_from); si_from->get_string(verdaux->vda_name), si_from);
return false; return false;
} }
); );
}}
bool VersionTracker::init(const soinfo* si_from) {bool VersionTracker::init(const soinfo* si_from) {
if (!si_from->has_min_version(2)) { if (!si_from->has_min_version(2)) {
return true; return true;
} }
return init_verneed(si_from) && init_verdef(si_from); return init_verneed(si_from) && init_verdef(si_from);
}}
bool soinfo::lookup_version_info(const VersionTracker& version_tracker, ElfW(Word) sym,bool soinfo::lookup_version_info(const VersionTracker& version_tracker, ElfW(Word) sym,
const char* sym_name, const version_info** vi) { const char* sym_name, const version_info** vi) {
const ElfW(Versym)* sym_ver_ptr = get_versym(sym); const ElfW(Versym)* sym_ver_ptr = get_versym(sym);
ElfW(Versym) sym_ver = sym_ver_ptr == nullptr ? 0 : *sym_ver_ptr; ElfW(Versym) sym_ver = sym_ver_ptr == nullptr ? 0 : *sym_ver_ptr;
if (sym_ver != VER_NDX_LOCAL && sym_ver != VER_NDX_GLOBAL) { if (sym_ver != VER_NDX_LOCAL && sym_ver != VER_NDX_GLOBAL) {
*vi = version_tracker.get_version_info(sym_ver); *vi = version_tracker.get_version_info(sym_ver);
if (*vi == nullptr) { if (*vi == nullptr) {
DL_ERR("cannot find verneed/verdef for version index=%d " DL_ERR("cannot find verneed/verdef for version index=%d "
"referenced by symbol \"%s\" at \"%s\"", sym_ver, sym_name, get_realpath()); "referenced by symbol \"%s\" at \"%s\"", sym_ver, sym_name, get_realpath());