Add additional dl_phdr_info fields
Previously, Bionic's dl_phdr_info only included the first four
dl_iterate_phdr fields. Several other libc's have these additional fields:
unsigned long long dlpi_adds -- incremented when a library is loaded
unsigned long long dlpi_subs -- incremented when a library is unloaded
size_t dlpi_tls_modid -- TLS module ID
void* dlpi_tls_data -- pointer to current thread's TLS block or NULL
These extra fields are also exposed by glibc, musl, and FreeBSD. The
unwinder in libgcc.a, linked into shipping Android DSOs, has a
PC->eh_frame cache that activates if dl_phdr_info has the dlpi_adds and
dlpi_subs fields (indicated at run-time by a sufficiently-large size
argument to the callback).
Bug: https://github.com/android-ndk/ndk/issues/1062
Test: bionic unit tests
Change-Id: I6f0bab548cf8c828af2ddab9eb01c5c6d70cd81f
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;