Add tests for dynamic ELF TLS
Bug: http://b/78026329
Test: bionic unit tests
Merged-In: I508fa38b331eeec7dae53039b4b1ec6cedea3034
Change-Id: I508fa38b331eeec7dae53039b4b1ec6cedea3034
diff --git a/tests/elftls_dl_test.cpp b/tests/elftls_dl_test.cpp
index 0a97c28..e908fb9 100644
--- a/tests/elftls_dl_test.cpp
+++ b/tests/elftls_dl_test.cpp
@@ -34,6 +34,10 @@
#include "gtest_globals.h"
#include "utils.h"
+#if defined(__BIONIC__)
+#include "bionic/pthread_internal.h"
+#endif
+
// Access libtest_elftls_shared_var.so's TLS variable using an IE access.
__attribute__((tls_model("initial-exec"))) extern "C" __thread int elftls_shared_var;
@@ -78,3 +82,175 @@
eth.SetArgs({ helper.c_str(), nullptr });
eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, error.c_str());
}
+
+// Use a GD access (__tls_get_addr or TLSDESC) to modify a variable in static
+// TLS memory.
+TEST(elftls_dl, access_static_tls) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+
+ auto bump_shared_var = reinterpret_cast<int(*)()>(dlsym(lib, "bump_shared_var"));
+ ASSERT_NE(nullptr, bump_shared_var);
+
+ ASSERT_EQ(21, ++elftls_shared_var);
+ ASSERT_EQ(22, bump_shared_var());
+
+ std::thread([bump_shared_var] {
+ ASSERT_EQ(21, ++elftls_shared_var);
+ ASSERT_EQ(22, bump_shared_var());
+ }).join();
+}
+
+TEST(elftls_dl, bump_local_vars) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+
+ auto bump_local_vars = reinterpret_cast<int(*)()>(dlsym(lib, "bump_local_vars"));
+ ASSERT_NE(nullptr, bump_local_vars);
+
+ ASSERT_EQ(42, bump_local_vars());
+ std::thread([bump_local_vars] {
+ ASSERT_EQ(42, bump_local_vars());
+ }).join();
+}
+
+// The behavior of accessing an unresolved weak TLS symbol using a dynamic TLS
+// relocation depends on which kind of implementation the target uses. With
+// TLSDESC, the result is NULL. With __tls_get_addr, the result is the
+// generation count (or maybe undefined behavior)? This test only tests TLSDESC.
+TEST(elftls_dl, missing_weak) {
+#if defined(__aarch64__)
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+
+ auto missing_weak_dyn_tls_addr = reinterpret_cast<int*(*)()>(dlsym(lib, "missing_weak_dyn_tls_addr"));
+ ASSERT_NE(nullptr, missing_weak_dyn_tls_addr);
+
+ ASSERT_EQ(nullptr, missing_weak_dyn_tls_addr());
+ std::thread([missing_weak_dyn_tls_addr] {
+ ASSERT_EQ(nullptr, missing_weak_dyn_tls_addr());
+ }).join();
+#else
+ GTEST_LOG_(INFO) << "This test is only run on TLSDESC-based targets.\n";
+#endif
+}
+
+TEST(elftls_dl, dtv_resize) {
+#if defined(__BIONIC__)
+#define LOAD_LIB(soname) ({ \
+ auto lib = dlopen(soname, RTLD_LOCAL | RTLD_NOW); \
+ ASSERT_NE(nullptr, lib); \
+ reinterpret_cast<int(*)()>(dlsym(lib, "bump")); \
+ })
+
+ auto dtv = []() -> TlsDtv* { return __get_tcb_dtv(__get_bionic_tcb()); };
+
+ static_assert(sizeof(TlsDtv) == 3 * sizeof(void*),
+ "This test assumes that the Dtv has a 3-word header");
+
+ // Initially there are 3 modules:
+ // - the main test executable
+ // - libtest_elftls_shared_var
+ // - libtest_elftls_tprel
+
+ // The initial DTV is an empty DTV with no generation and a size of 0.
+ TlsDtv* zero_dtv = dtv();
+ ASSERT_EQ(0u, zero_dtv->count);
+ ASSERT_EQ(nullptr, zero_dtv->next);
+ ASSERT_EQ(kTlsGenerationNone, zero_dtv->generation);
+
+ // Load the fourth module.
+ auto func1 = LOAD_LIB("libtest_elftls_dynamic_filler_1.so");
+ ASSERT_EQ(101, func1());
+
+ // After loading one module, the DTV should be initialized to the next
+ // power-of-2 size (including the header).
+ TlsDtv* initial_dtv = dtv();
+ ASSERT_EQ(5u, initial_dtv->count);
+ ASSERT_EQ(zero_dtv, initial_dtv->next);
+ ASSERT_LT(0u, initial_dtv->generation);
+
+ // Load module 5.
+ auto func2 = LOAD_LIB("libtest_elftls_dynamic_filler_2.so");
+ ASSERT_EQ(102, func1());
+ ASSERT_EQ(201, func2());
+ ASSERT_EQ(initial_dtv, dtv());
+ ASSERT_EQ(5u, initial_dtv->count);
+
+ // Load module 6.
+ auto func3 = LOAD_LIB("libtest_elftls_dynamic_filler_3.so");
+ ASSERT_EQ(103, func1());
+ ASSERT_EQ(202, func2());
+
+#if defined(__aarch64__)
+ // The arm64 TLSDESC resolver doesn't update the DTV if it is new enough for
+ // the given access.
+ ASSERT_EQ(5u, dtv()->count);
+#else
+ // __tls_get_addr updates the DTV anytime the generation counter changes.
+ ASSERT_EQ(13u, dtv()->count);
+#endif
+
+ ASSERT_EQ(301, func3());
+
+ TlsDtv* new_dtv = dtv();
+ ASSERT_EQ(13u, new_dtv->count);
+ ASSERT_NE(initial_dtv, new_dtv);
+ ASSERT_EQ(initial_dtv, new_dtv->next);
+
+#undef LOAD_LIB
+#else
+ GTEST_LOG_(INFO) << "This test is skipped for glibc because it tests Bionic internals.";
+#endif
+}
+
+// Verify that variables are reset to their initial values after the library
+// containing them is closed.
+TEST(elftls_dl, dlclose_resets_values) {
+ for (int round = 0; round < 2; ++round) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+
+ auto bump_local_vars = reinterpret_cast<int(*)()>(dlsym(lib, "bump_local_vars"));
+ ASSERT_NE(nullptr, bump_local_vars);
+
+ ASSERT_EQ(42, bump_local_vars());
+ ASSERT_EQ(44, bump_local_vars());
+
+ ASSERT_EQ(0, dlclose(lib));
+ }
+}
+
+// Calling dlclose should remove the entry for the solib from the global list of
+// ELF TLS modules. Test that repeatedly loading and unloading a library doesn't
+// increase the DTV size.
+TEST(elftls_dl, dlclose_removes_entry) {
+#if defined(__BIONIC__)
+ auto dtv = []() -> TlsDtv* { return __get_tcb_dtv(__get_bionic_tcb()); };
+
+ bool first = true;
+ size_t count = 0;
+
+ // Use a large number of rounds in case the DTV is initially larger than
+ // expected.
+ for (int round = 0; round < 32; ++round) {
+ void* lib = dlopen("libtest_elftls_dynamic.so", RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+
+ auto bump_local_vars = reinterpret_cast<int(*)()>(dlsym(lib, "bump_local_vars"));
+ ASSERT_NE(nullptr, bump_local_vars);
+
+ ASSERT_EQ(42, bump_local_vars());
+ if (first) {
+ first = false;
+ count = dtv()->count;
+ } else {
+ ASSERT_EQ(count, dtv()->count);
+ }
+
+ dlclose(lib);
+ }
+#else
+ GTEST_LOG_(INFO) << "This test is skipped for glibc because it tests Bionic internals.";
+#endif
+}