aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Prichard <rprichard@google.com>2024-03-04 23:54:57 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-03-04 23:54:57 +0000
commite6a4943dfac3afb483433d362b6beccb491fb223 (patch)
tree6d9904d3918dbc1d8d771ae4ac8017b20382ffc2
parent2c0e9a58ead53aff66f0978c51142d8331ffffb0 (diff)
parent98731dc343c347cf7db75125abb560687543cd22 (diff)
downloadbionic-e6a4943dfac3afb483433d362b6beccb491fb223.tar.gz
Merge "Revamp the elftls_dl.dtv_resize test" into main
-rw-r--r--tests/Android.bp3
-rw-r--r--tests/elftls_dl_test.cpp73
-rw-r--r--tests/libs/Android.bp33
-rw-r--r--tests/libs/elftls_dtv_resize_helper.cpp224
4 files changed, 266 insertions, 67 deletions
diff --git a/tests/Android.bp b/tests/Android.bp
index 4e9192e85..89d226772 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -835,6 +835,7 @@ cc_defaults {
"cfi_test_helper",
"cfi_test_helper2",
"elftls_dlopen_ie_error_helper",
+ "elftls_dtv_resize_helper",
"exec_linker_helper",
"exec_linker_helper_lib",
"heap_tagging_async_helper",
@@ -937,6 +938,8 @@ cc_defaults {
"libtest_elftls_dynamic_filler_1",
"libtest_elftls_dynamic_filler_2",
"libtest_elftls_dynamic_filler_3",
+ "libtest_elftls_dynamic_filler_4",
+ "libtest_elftls_dynamic_filler_5",
"libtest_elftls_shared_var",
"libtest_elftls_shared_var_ie",
"libtest_elftls_tprel",
diff --git a/tests/elftls_dl_test.cpp b/tests/elftls_dl_test.cpp
index e2fa3a087..bcb2b40b3 100644
--- a/tests/elftls_dl_test.cpp
+++ b/tests/elftls_dl_test.cpp
@@ -29,10 +29,9 @@
#include <dlfcn.h>
#include <link.h>
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
+#include <string>
#include <thread>
#include "gtest_globals.h"
@@ -155,71 +154,11 @@ TEST(elftls_dl, tlsdesc_missing_weak) {
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 4 modules (5 w/ hwasan):
- // - the main test executable
- // - libc
- // - libtest_elftls_shared_var
- // - libtest_elftls_tprel
- // - w/ hwasan: libclang_rt.hwasan
-
- // 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 module 5 (6 w/ hwasan).
- 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(running_with_hwasan() ? 13u : 5u, dtv()->count);
- ASSERT_EQ(zero_dtv, initial_dtv->next);
- ASSERT_LT(0u, initial_dtv->generation);
-
- // Load module 6 (7 w/ hwasan).
- auto func2 = LOAD_LIB("libtest_elftls_dynamic_filler_2.so");
- ASSERT_EQ(102, func1());
-
-#if defined(__aarch64__)
- // The arm64 TLSDESC resolver doesn't update the DTV if it is new enough for
- // the given access.
- ASSERT_EQ(running_with_hwasan() ? 13u : 5u, dtv()->count);
-#else
- // __tls_get_addr updates the DTV anytime the generation counter changes.
- ASSERT_EQ(13u, dtv()->count);
-#endif
-
- ASSERT_EQ(201, func2());
- TlsDtv* new_dtv = dtv();
- if (!running_with_hwasan()) {
- ASSERT_NE(initial_dtv, new_dtv);
- ASSERT_EQ(initial_dtv, new_dtv->next);
- }
- ASSERT_EQ(13u, new_dtv->count);
-
- // Load module 7 (8 w/ hwasan).
- auto func3 = LOAD_LIB("libtest_elftls_dynamic_filler_3.so");
- ASSERT_EQ(103, func1());
- ASSERT_EQ(202, func2());
- ASSERT_EQ(301, func3());
-
- ASSERT_EQ(new_dtv, dtv());
-
-#undef LOAD_LIB
+ std::string helper = GetTestlibRoot() + "/elftls_dtv_resize_helper";
+ chmod(helper.c_str(), 0755); // TODO: "x" lost in CTS, b/34945607
+ ExecTestHelper eth;
+ eth.SetArgs({helper.c_str(), nullptr});
+ eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, nullptr);
#else
GTEST_SKIP() << "test doesn't apply to glibc";
#endif
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 06ee132d4..f64055213 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -123,6 +123,39 @@ cc_test_library {
],
}
+cc_test_library {
+ name: "libtest_elftls_dynamic_filler_4",
+ defaults: ["bionic_testlib_defaults"],
+ srcs: ["elftls_dynamic_filler.cpp"],
+ cflags: [
+ "-DTLS_FILLER=400",
+ ],
+}
+
+cc_test_library {
+ name: "libtest_elftls_dynamic_filler_5",
+ defaults: ["bionic_testlib_defaults"],
+ srcs: ["elftls_dynamic_filler.cpp"],
+ cflags: [
+ "-DTLS_FILLER=500",
+ ],
+}
+
+cc_test {
+ name: "elftls_dtv_resize_helper",
+ defaults: [
+ "bionic_testlib_defaults",
+ "bionic_targets_only",
+ ],
+ srcs: ["elftls_dtv_resize_helper.cpp"],
+ include_dirs: [
+ "bionic/libc",
+ ],
+ static_libs: [
+ "libbase",
+ ],
+}
+
// -----------------------------------------------------------------------------
// Library to test gnu-styled hash
// -----------------------------------------------------------------------------
diff --git a/tests/libs/elftls_dtv_resize_helper.cpp b/tests/libs/elftls_dtv_resize_helper.cpp
new file mode 100644
index 000000000..125f8e84f
--- /dev/null
+++ b/tests/libs/elftls_dtv_resize_helper.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <functional>
+#include <iostream>
+
+#include <android-base/test_utils.h>
+
+#include "bionic/pthread_internal.h"
+
+constexpr bool kDumpModulesForDebugging = false;
+
+// The old external/libcxx doesn't have operator<< for nullptr.
+// TODO(b/175635923): Remove this hack after upgrading libc++.
+template <class T>
+T fix_nullptr(T&& arg) {
+ return arg;
+}
+void* fix_nullptr(nullptr_t arg) {
+ return static_cast<void*>(arg);
+}
+
+template <class Val1, class Val2, class Compare>
+void check(int line, const char* val1_expr, Val1&& val1, const char* val2_expr, Val2&& val2,
+ Compare compare) {
+ if (!compare(val1, val2)) {
+ std::cerr << __FILE__ << ":" << line << ": assertion failed: LHS(" << val1_expr << ") is "
+ << fix_nullptr(val1) << ", RHS(" << val2_expr << ") is " << fix_nullptr(val2) << "\n"
+ << std::flush;
+ abort();
+ }
+}
+
+#define ASSERT_EQ(val1, val2) check(__LINE__, #val1, val1, #val2, val2, std::equal_to())
+#define ASSERT_NE(val1, val2) check(__LINE__, #val1, val1, #val2, val2, std::not_equal_to())
+#define ASSERT_LT(val1, val2) check(__LINE__, #val1, val1, #val2, val2, std::less())
+#define ASSERT_LE(val1, val2) check(__LINE__, #val1, val1, #val2, val2, std::less_equal())
+
+static size_t highest_loaded_modid() {
+ size_t result = 0;
+ auto update_result = [](struct dl_phdr_info* info, size_t size __unused, void* data) {
+ size_t& result = *reinterpret_cast<size_t*>(data);
+ if (kDumpModulesForDebugging) {
+ fprintf(stderr, "module %s: TLS modid %zu\n", info->dlpi_name, info->dlpi_tls_modid);
+ }
+ result = std::max(result, info->dlpi_tls_modid);
+ return 0;
+ };
+ dl_iterate_phdr(update_result, &result);
+ return result;
+}
+
+static TlsDtv* dtv() {
+ return __get_tcb_dtv(__get_bionic_tcb());
+}
+
+static size_t highest_modid_in_dtv() {
+ TlsDtv* current_dtv = dtv();
+ size_t result = 0;
+ for (size_t i = 0; i < current_dtv->count; ++i) {
+ if (current_dtv->modules[i] != nullptr) {
+ result = __tls_module_idx_to_id(i);
+ }
+ }
+ return result;
+}
+
+// Unused, but ensures that the test executable has a TLS segment. With a
+// new-enough libc++_static.a, the test executable will tend to has a TLS
+// segment to hold the libc++ EH globals pointer.
+__thread int g_tls_var_placeholder = 42;
+
+int main() {
+ // Prevent this TLS variable from being optimized away.
+ ASSERT_EQ(42, g_tls_var_placeholder);
+
+ auto load_lib = [](const char* soname) {
+ void* lib = dlopen(soname, RTLD_LOCAL | RTLD_NOW);
+ ASSERT_NE(nullptr, lib);
+ auto func = reinterpret_cast<int (*)()>(dlsym(lib, "bump"));
+ ASSERT_NE(nullptr, func);
+ return func;
+ };
+
+ static_assert(sizeof(TlsDtv) == 3 * sizeof(void*),
+ "This test assumes that the Dtv has a 3-word header");
+
+ // Initially there are 2-4 modules:
+ // - 1: test executable
+ // - 2: libc
+ // - 3: libc++ (when using a new-enough libc++)
+ // - 4: libclang_rt.hwasan (when running with HWASan)
+ size_t first_filler_modid = highest_loaded_modid() + 1;
+ ASSERT_LE(2, highest_loaded_modid());
+ ASSERT_LE(highest_loaded_modid(), 4);
+
+ // 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 a module. The DTV is still empty unless the TLS variable is accessed.
+ auto func1 = load_lib("libtest_elftls_dynamic_filler_1.so");
+ ASSERT_EQ(zero_dtv, dtv());
+ ASSERT_EQ(first_filler_modid, highest_loaded_modid());
+
+ // After accessing a TLS variable, the DTV should be initialized. It should be
+ // 8 words in size, with a 5-entry capacity.
+ ASSERT_EQ(101, func1());
+ TlsDtv* initial_dtv = dtv();
+ ASSERT_EQ(5u, dtv()->count);
+ ASSERT_EQ(zero_dtv, initial_dtv->next);
+ ASSERT_LT(0u, initial_dtv->generation);
+ ASSERT_EQ(first_filler_modid, highest_modid_in_dtv());
+ ASSERT_NE(nullptr, initial_dtv->modules[__tls_module_id_to_idx(first_filler_modid)]);
+
+ size_t current_generation = initial_dtv->generation;
+
+ // Fill the rest of the DTV up. (i.e. Ensure that exactly 5 modules with TLS
+ // segments are loaded.)
+ auto fill_entry = [&](size_t modid, const char* soname, int tls_var_value) {
+ if (highest_modid_in_dtv() == modid - 1) {
+ auto func = load_lib(soname);
+
+ // Loading the module doesn't affect the DTV yet.
+ ASSERT_EQ(initial_dtv, dtv());
+ ASSERT_EQ(modid, highest_loaded_modid());
+ ASSERT_EQ(modid - 1, highest_modid_in_dtv());
+ ASSERT_EQ(current_generation, initial_dtv->generation);
+
+ // Access the TLS variable, which will allocate it in the DTV.
+ ASSERT_EQ(tls_var_value, func());
+
+ // Verify allocation and a bumped generation.
+ ASSERT_EQ(initial_dtv, dtv());
+ ASSERT_EQ(modid, highest_modid_in_dtv());
+ ASSERT_LT(current_generation, initial_dtv->generation);
+ current_generation = initial_dtv->generation;
+ }
+ };
+
+ fill_entry(4u, "libtest_elftls_dynamic_filler_2.so", 201);
+ fill_entry(5u, "libtest_elftls_dynamic_filler_3.so", 301);
+ ASSERT_EQ(5u, highest_modid_in_dtv());
+
+ // Load module 6, which will require doubling the size of the DTV.
+ auto func4 = load_lib("libtest_elftls_dynamic_filler_4.so");
+ ASSERT_EQ(6u, highest_loaded_modid());
+ ASSERT_EQ(5u, highest_modid_in_dtv());
+ ASSERT_EQ(initial_dtv, dtv());
+
+ // Access a TLS variable from the first filler module.
+ ASSERT_EQ(102, func1());
+ ASSERT_EQ(5u, highest_modid_in_dtv());
+#if defined(__aarch64__)
+ // The arm64 TLSDESC resolver doesn't update the DTV if it is new enough for
+ // the given access.
+ ASSERT_EQ(initial_dtv, dtv());
+ ASSERT_EQ(5u, dtv()->count);
+ ASSERT_EQ(current_generation, dtv()->generation);
+#else
+ // __tls_get_addr updates the DTV anytime the generation counter changes, but
+ // the highest modid in the DTV is still 5, because module 6 hasn't been
+ // allocated yet.
+ ASSERT_NE(initial_dtv, dtv());
+ ASSERT_EQ(13u, dtv()->count);
+ ASSERT_LT(current_generation, dtv()->generation);
+#endif
+
+ // Accessing the TLS variable in the latest module will always expand the DTV.
+ ASSERT_EQ(401, func4());
+ TlsDtv* new_dtv = dtv();
+ ASSERT_NE(initial_dtv, new_dtv);
+ ASSERT_EQ(initial_dtv, new_dtv->next);
+ ASSERT_EQ(13u, new_dtv->count);
+ ASSERT_LT(current_generation, new_dtv->generation);
+ ASSERT_EQ(6u, highest_modid_in_dtv());
+ current_generation = new_dtv->generation;
+
+ // Load one more filler, module 7.
+ auto func5 = load_lib("libtest_elftls_dynamic_filler_5.so");
+ ASSERT_EQ(103, func1());
+ ASSERT_EQ(402, func4());
+ ASSERT_EQ(6u, highest_modid_in_dtv());
+ ASSERT_EQ(501, func5());
+ ASSERT_EQ(7u, highest_modid_in_dtv());
+
+ // Verify that no new DTV has been allocated.
+ ASSERT_EQ(new_dtv, dtv());
+ ASSERT_EQ(13u, new_dtv->count);
+ ASSERT_LT(current_generation, new_dtv->generation);
+
+ return 0;
+}