Merge "Add additional dl_phdr_info fields"
diff --git a/libc/bionic/dl_iterate_phdr_static.cpp b/libc/bionic/dl_iterate_phdr_static.cpp
index bbce1fc..7b44810 100644
--- a/libc/bionic/dl_iterate_phdr_static.cpp
+++ b/libc/bionic/dl_iterate_phdr_static.cpp
@@ -32,6 +32,10 @@
#include <sys/types.h>
#include <link.h>
+#include "private/bionic_elf_tls.h"
+#include "private/bionic_globals.h"
+#include "pthread_internal.h"
+
/* ld provides this to us in the default link script */
extern "C" void* __executable_start;
@@ -52,6 +56,21 @@
exe_info.dlpi_name = NULL;
exe_info.dlpi_phdr = reinterpret_cast<ElfW(Phdr)*>(reinterpret_cast<uintptr_t>(ehdr) + ehdr->e_phoff);
exe_info.dlpi_phnum = ehdr->e_phnum;
+ exe_info.dlpi_adds = 0;
+ exe_info.dlpi_subs = 0;
+
+ const TlsModules& tls_modules = __libc_shared_globals()->tls_modules;
+ if (tls_modules.module_count == 0) {
+ exe_info.dlpi_tls_modid = 0;
+ exe_info.dlpi_tls_data = nullptr;
+ } else {
+ const size_t kExeModuleId = 1;
+ const StaticTlsLayout& layout = __libc_shared_globals()->static_tls_layout;
+ const TlsModule& tls_module = tls_modules.module_table[__tls_module_id_to_idx(kExeModuleId)];
+ char* static_tls = reinterpret_cast<char*>(__get_bionic_tcb()) - layout.offset_bionic_tcb();
+ exe_info.dlpi_tls_modid = kExeModuleId;
+ exe_info.dlpi_tls_data = static_tls + tls_module.static_offset;
+ }
// Try the executable first.
int rc = cb(&exe_info, sizeof(exe_info), data);
@@ -71,6 +90,10 @@
vdso_info.dlpi_name = NULL;
vdso_info.dlpi_phdr = reinterpret_cast<ElfW(Phdr)*>(reinterpret_cast<char*>(ehdr_vdso) + ehdr_vdso->e_phoff);
vdso_info.dlpi_phnum = ehdr_vdso->e_phnum;
+ vdso_info.dlpi_adds = 0;
+ vdso_info.dlpi_subs = 0;
+ vdso_info.dlpi_tls_modid = 0;
+ vdso_info.dlpi_tls_data = nullptr;
for (size_t i = 0; i < vdso_info.dlpi_phnum; ++i) {
if (vdso_info.dlpi_phdr[i].p_type == PT_LOAD) {
vdso_info.dlpi_addr = (ElfW(Addr)) ehdr_vdso - vdso_info.dlpi_phdr[i].p_vaddr;
diff --git a/libc/include/link.h b/libc/include/link.h
index 072f093..bd430f5 100644
--- a/libc/include/link.h
+++ b/libc/include/link.h
@@ -47,6 +47,12 @@
const char* dlpi_name;
const ElfW(Phdr)* dlpi_phdr;
ElfW(Half) dlpi_phnum;
+
+ // These fields were added in Android R.
+ unsigned long long dlpi_adds;
+ unsigned long long dlpi_subs;
+ size_t dlpi_tls_modid;
+ void* dlpi_tls_data;
};
#if defined(__arm__)
diff --git a/linker/linker.cpp b/linker/linker.cpp
index d581dd8..f428490 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -46,8 +46,8 @@
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
-
#include <async_safe/log.h>
+#include <bionic/pthread_internal.h>
// Private C library headers.
@@ -86,6 +86,9 @@
static LinkerTypeAllocator<android_namespace_t> g_namespace_allocator;
static LinkerTypeAllocator<LinkedListEntry<android_namespace_t>> g_namespace_list_allocator;
+static uint64_t g_module_load_counter = 0;
+static uint64_t g_module_unload_counter = 0;
+
static const char* const kLdConfigArchFilePath = "/system/etc/ld.config." ABI_STRING ".txt";
static const char* const kLdConfigFilePath = "/system/etc/ld.config.txt";
@@ -424,6 +427,24 @@
return true;
}
+// Returns the address of the current thread's copy of a TLS module. If the current thread doesn't
+// have a copy yet, allocate one on-demand if should_alloc is true, and return nullptr otherwise.
+static inline void* get_tls_block_for_this_thread(const soinfo_tls* si_tls, bool should_alloc) {
+ const TlsModule& tls_mod = get_tls_module(si_tls->module_id);
+ if (tls_mod.static_offset != SIZE_MAX) {
+ const StaticTlsLayout& layout = __libc_shared_globals()->static_tls_layout;
+ char* static_tls = reinterpret_cast<char*>(__get_bionic_tcb()) - layout.offset_bionic_tcb();
+ return static_tls + tls_mod.static_offset;
+ } else if (should_alloc) {
+ const TlsIndex ti { si_tls->module_id, 0 };
+ return TLS_GET_ADDR(&ti);
+ } else {
+ TlsDtv* dtv = __get_tcb_dtv(__get_bionic_tcb());
+ if (dtv->generation < tls_mod.first_generation) return nullptr;
+ return dtv->modules[__tls_module_id_to_idx(si_tls->module_id)];
+ }
+}
+
#if defined(__arm__)
// For a given PC, find the .so that it belongs to.
@@ -453,6 +474,16 @@
dl_info.dlpi_name = si->link_map_head.l_name;
dl_info.dlpi_phdr = si->phdr;
dl_info.dlpi_phnum = si->phnum;
+ dl_info.dlpi_adds = g_module_load_counter;
+ dl_info.dlpi_subs = g_module_unload_counter;
+ if (soinfo_tls* tls_module = si->get_tls()) {
+ dl_info.dlpi_tls_modid = tls_module->module_id;
+ dl_info.dlpi_tls_data = get_tls_block_for_this_thread(tls_module, /*should_alloc=*/false);
+ } else {
+ dl_info.dlpi_tls_modid = 0;
+ dl_info.dlpi_tls_data = nullptr;
+ }
+
rv = cb(&dl_info, sizeof(dl_phdr_info), data);
if (rv != 0) {
break;
@@ -2043,6 +2074,7 @@
"... dlclose: unloading \"%s\"@%p ...",
si->get_realpath(),
si);
+ ++g_module_unload_counter;
notify_gdb_of_unload(si);
unregister_soinfo_tls(si);
if (__libc_shared_globals()->unload_hook) {
@@ -2429,18 +2461,15 @@
if ((bind == STB_GLOBAL || bind == STB_WEAK) && sym->st_shndx != 0) {
if (type == STT_TLS) {
// For a TLS symbol, dlsym returns the address of the current thread's
- // copy of the symbol. This function may allocate a DTV and/or storage
- // for the source TLS module. (Allocating a DTV isn't necessary if the
- // symbol is part of static TLS, but it's simpler to reuse
- // __tls_get_addr.)
- soinfo_tls* tls_module = found->get_tls();
+ // copy of the symbol.
+ const soinfo_tls* tls_module = found->get_tls();
if (tls_module == nullptr) {
DL_ERR("TLS symbol \"%s\" in solib \"%s\" with no TLS segment",
sym_name, found->get_realpath());
return false;
}
- const TlsIndex ti { tls_module->module_id, sym->st_value };
- *symbol = TLS_GET_ADDR(&ti);
+ void* tls_block = get_tls_block_for_this_thread(tls_module, /*should_alloc=*/true);
+ *symbol = static_cast<char*>(tls_block) + sym->st_value;
} else {
*symbol = reinterpret_cast<void*>(found->resolve_symbol_address(sym));
}
@@ -4106,6 +4135,7 @@
}
}
+ ++g_module_load_counter;
notify_gdb_of_load(this);
set_image_linked();
return true;
diff --git a/tests/elftls_dl_test.cpp b/tests/elftls_dl_test.cpp
index 6d88880..012aad7 100644
--- a/tests/elftls_dl_test.cpp
+++ b/tests/elftls_dl_test.cpp
@@ -28,6 +28,7 @@
#include <dlfcn.h>
#include <gtest/gtest.h>
+#include <link.h>
#include <thread>
@@ -335,3 +336,58 @@
ASSERT_STREQ(nullptr, info.dli_sname);
ASSERT_EQ(nullptr, info.dli_saddr);
}
+
+TEST(elftls_dl, dl_iterate_phdr) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+
+ auto get_var_addr = reinterpret_cast<void*(*)()>(dlsym(lib, "get_large_tls_var_addr"));
+ ASSERT_NE(nullptr, get_var_addr);
+
+ struct TlsInfo {
+ bool found;
+ size_t modid;
+ void* data;
+ size_t memsz;
+ };
+
+ auto get_tls_info = []() {
+ auto callback = [](dl_phdr_info* info, size_t, void* data) {
+ TlsInfo& tls_info = *static_cast<TlsInfo*>(data);
+
+ // This test is also run with glibc, where dlpi_name may have relative path components, so
+ // examine just the basename when searching for the library.
+ if (strcmp(basename(info->dlpi_name), "libtest_elftls_dynamic.so") != 0) return 0;
+
+ tls_info.found = true;
+ tls_info.modid = info->dlpi_tls_modid;
+ tls_info.data = info->dlpi_tls_data;
+ for (ElfW(Half) i = 0; i < info->dlpi_phnum; ++i) {
+ if (info->dlpi_phdr[i].p_type == PT_TLS) {
+ tls_info.memsz = info->dlpi_phdr[i].p_memsz;
+ }
+ }
+ EXPECT_NE(static_cast<size_t>(0), tls_info.memsz);
+ return 1;
+ };
+
+ TlsInfo result {};
+ dl_iterate_phdr(callback, &result);
+ return result;
+ };
+
+ // The executable has a TLS segment, so it will use module ID #1, and the DSO's ID will be larger
+ // than 1. Initially, the data field is nullptr, because this thread's instance hasn't been
+ // allocated yet.
+ TlsInfo tls_info = get_tls_info();
+ ASSERT_TRUE(tls_info.found);
+ ASSERT_GT(tls_info.modid, static_cast<size_t>(1));
+ ASSERT_EQ(nullptr, tls_info.data);
+
+ void* var_addr = get_var_addr();
+
+ // Verify that dl_iterate_phdr returns a range of memory covering the allocated TLS variable.
+ tls_info = get_tls_info();
+ ASSERT_TRUE(tls_info.found);
+ ASSERT_GE(var_addr, tls_info.data);
+ ASSERT_LT(var_addr, static_cast<char*>(tls_info.data) + tls_info.memsz);
+}
diff --git a/tests/link_test.cpp b/tests/link_test.cpp
index cf5fc0b..75bb4d6 100644
--- a/tests/link_test.cpp
+++ b/tests/link_test.cpp
@@ -28,6 +28,7 @@
#include <gtest/gtest.h>
+#include <dlfcn.h>
#include <link.h>
#if __has_include(<sys/auxv.h>)
#include <sys/auxv.h>
@@ -80,6 +81,52 @@
ASSERT_EQ(0, dl_iterate_phdr(Functor::Callback, &f));
}
+// Verify that the module load/unload counters from dl_iterate_phdr are incremented.
+TEST(link, dl_iterate_phdr_counters) {
+ struct Counters {
+ bool inited = false;
+ uint64_t adds = 0;
+ uint64_t subs = 0;
+ };
+
+ auto get_adds_subs = []() {
+ auto callback = [](dl_phdr_info* info, size_t size, void* data) {
+ Counters& counters = *static_cast<Counters*>(data);
+ EXPECT_GE(size, sizeof(dl_phdr_info));
+ if (!counters.inited) {
+ counters.inited = true;
+ counters.adds = info->dlpi_adds;
+ counters.subs = info->dlpi_subs;
+ } else {
+ // The counters have the same value for each module.
+ EXPECT_EQ(counters.adds, info->dlpi_adds);
+ EXPECT_EQ(counters.subs, info->dlpi_subs);
+ }
+ return 0;
+ };
+
+ Counters counters {};
+ EXPECT_EQ(0, dl_iterate_phdr(callback, &counters));
+ EXPECT_TRUE(counters.inited);
+ return counters;
+ };
+
+ // dlopen increments the 'adds' counter.
+ const auto before_dlopen = get_adds_subs();
+ void* const handle = dlopen("libtest_empty.so", RTLD_NOW);
+ ASSERT_NE(nullptr, handle);
+ const auto after_dlopen = get_adds_subs();
+ ASSERT_LT(before_dlopen.adds, after_dlopen.adds);
+ ASSERT_EQ(before_dlopen.subs, after_dlopen.subs);
+
+ // dlclose increments the 'subs' counter.
+ const auto before_dlclose = after_dlopen;
+ dlclose(handle);
+ const auto after_dlclose = get_adds_subs();
+ ASSERT_EQ(before_dlclose.adds, after_dlclose.adds);
+ ASSERT_LT(before_dlclose.subs, after_dlclose.subs);
+}
+
struct ProgHdr {
const ElfW(Phdr)* table;
size_t size;