From 06d2d790adc8cc487881dc56234bb55c1c8c6901 Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Wed, 23 Jan 2019 23:19:19 -0800 Subject: [PATCH] Add tests for dynamic ELF TLS Bug: http://b/78026329 Test: bionic unit tests Merged-In: I508fa38b331eeec7dae53039b4b1ec6cedea3034 Change-Id: I508fa38b331eeec7dae53039b4b1ec6cedea3034 --- tests/Android.bp | 5 +- tests/dlfcn_test.cpp | 13 -- tests/elftls_dl_test.cpp | 176 +++++++++++++++++++++++++++ tests/elftls_test.cpp | 14 +++ tests/libs/Android.bp | 37 ++++-- tests/libs/elf_tls_test_library.cpp | 19 --- tests/libs/elftls_dynamic.cpp | 55 +++++++++ tests/libs/elftls_dynamic_filler.cpp | 33 +++++ 8 files changed, 311 insertions(+), 41 deletions(-) delete mode 100644 tests/libs/elf_tls_test_library.cpp create mode 100644 tests/libs/elftls_dynamic.cpp create mode 100644 tests/libs/elftls_dynamic_filler.cpp diff --git a/tests/Android.bp b/tests/Android.bp index e123f9e6e..b979f322e 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -533,7 +533,6 @@ cc_test { "libdl_preempt_test_1", "libdl_preempt_test_2", "libdl_test_df_1_global", - "libelf-tls-library", "libgnu-hash-table-library", "libsysv-hash-table-library", "libtestshared", @@ -571,6 +570,10 @@ cc_test { "libtest_dlsym_from_this", "libtest_dlsym_weak_func", "libtest_dt_runpath_d", + "libtest_elftls_dynamic", + "libtest_elftls_dynamic_filler_1", + "libtest_elftls_dynamic_filler_2", + "libtest_elftls_dynamic_filler_3", "libtest_elftls_shared_var", "libtest_elftls_shared_var_ie", "libtest_elftls_tprel", diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp index e3ba22718..8a3b6f36d 100644 --- a/tests/dlfcn_test.cpp +++ b/tests/dlfcn_test.cpp @@ -1082,19 +1082,6 @@ TEST(dlfcn, dlopen_library_with_only_sysv_hash) { ASSERT_SUBSTR("libsysv-hash-table-library.so", dlinfo.dli_fname); } -TEST(dlfcn, dlopen_library_with_ELF_TLS) { -// TODO: Remove this test. Once ELF TLS is implemented, this test will be -// replaced with a larger set of tests. Removing the test requires matching CLs -// in CTS and in internal test suites. -#if 0 - dlerror(); // Clear any pending errors. - void* handle = dlopen("libelf-tls-library.so", RTLD_NOW); - ASSERT_TRUE(handle == nullptr); - ASSERT_SUBSTR("unknown reloc type ", dlerror()); -#endif - GTEST_LOG_(INFO) << "This test is disabled pending replacement with dynamic ELF TLS tests.\n"; -} - TEST(dlfcn, dlopen_bad_flags) { dlerror(); // Clear any pending errors. void* handle; diff --git a/tests/elftls_dl_test.cpp b/tests/elftls_dl_test.cpp index 0a97c281d..e908fb930 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 @@ TEST(elftls_dl, dlopen_ie_error) { 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(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(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(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(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(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(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 +} diff --git a/tests/elftls_test.cpp b/tests/elftls_test.cpp index 11d41ce43..2d83d7080 100644 --- a/tests/elftls_test.cpp +++ b/tests/elftls_test.cpp @@ -83,3 +83,17 @@ TEST(elftls, tprel_addend) { ASSERT_EQ(8, bump_static_tls_var_2()); }).join(); } + +// Because this C++ source file is built with -fpic, the compiler will access +// this variable using a GD model. Typically, the static linker will relax the +// GD to LE, but the arm32 linker doesn't do TLS relaxations, so we can test +// calling __tls_get_addr in a static executable. The static linker knows that +// the main executable's TlsIndex::module_id is 1 and writes that into the GOT. +__thread int tlsvar_general = 30; + +TEST(elftls, general) { + ASSERT_EQ(31, ++tlsvar_general); + std::thread([] { + ASSERT_EQ(31, ++tlsvar_general); + }).join(); +} diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp index 05d1ed2f9..d58b6b8a8 100644 --- a/tests/libs/Android.bp +++ b/tests/libs/Android.bp @@ -42,14 +42,6 @@ cc_defaults { // ----------------------------------------------------------------------------- // Libraries and helper binaries for ELF TLS // ----------------------------------------------------------------------------- -cc_test_library { - name: "libelf-tls-library", - defaults: ["bionic_testlib_defaults"], - srcs: ["elf_tls_test_library.cpp"], - cflags: ["-fno-emulated-tls"], - allow_undefined_symbols: true, // __tls_get_addr is undefined. -} - cc_test_library { name: "libtest_elftls_shared_var", defaults: ["bionic_testlib_defaults"], @@ -79,6 +71,35 @@ cc_test { ldflags: ["-Wl,--rpath,${ORIGIN}/.."], } +cc_test_library { + name: "libtest_elftls_dynamic", + defaults: ["bionic_testlib_defaults"], + srcs: ["elftls_dynamic.cpp"], + cflags: ["-fno-emulated-tls"], + shared_libs: ["libtest_elftls_shared_var"], +} + +cc_test_library { + name: "libtest_elftls_dynamic_filler_1", + defaults: ["bionic_testlib_defaults"], + srcs: ["elftls_dynamic_filler.cpp"], + cflags: ["-fno-emulated-tls", "-DTLS_FILLER=100"], +} + +cc_test_library { + name: "libtest_elftls_dynamic_filler_2", + defaults: ["bionic_testlib_defaults"], + srcs: ["elftls_dynamic_filler.cpp"], + cflags: ["-fno-emulated-tls", "-DTLS_FILLER=200"], +} + +cc_test_library { + name: "libtest_elftls_dynamic_filler_3", + defaults: ["bionic_testlib_defaults"], + srcs: ["elftls_dynamic_filler.cpp"], + cflags: ["-fno-emulated-tls", "-DTLS_FILLER=300"], +} + // ----------------------------------------------------------------------------- // Library to test gnu-styled hash // ----------------------------------------------------------------------------- diff --git a/tests/libs/elf_tls_test_library.cpp b/tests/libs/elf_tls_test_library.cpp deleted file mode 100644 index 56d0171ae..000000000 --- a/tests/libs/elf_tls_test_library.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -thread_local int elf_tls_variable; - -extern "C" int* get() { return &elf_tls_variable; } diff --git a/tests/libs/elftls_dynamic.cpp b/tests/libs/elftls_dynamic.cpp new file mode 100644 index 000000000..7fa239c58 --- /dev/null +++ b/tests/libs/elftls_dynamic.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 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. + */ + +// This shared object test library is dlopen'ed by the main test executable. +// This variable comes from libtest_elftls_shared_var.so, which is part of +// static TLS. Verify that a GD-model access can access the variable. +// +// Accessing the static TLS variable from an solib prevents the static linker +// from relaxing the GD access to IE and lets us test that __tls_get_addr and +// the tlsdesc resolver handle a static TLS variable. +extern "C" __thread int elftls_shared_var; + +extern "C" int bump_shared_var() { + return ++elftls_shared_var; +} + +// The static linker denotes the current module by omitting the symbol from +// the DTPMOD/TLSDESC relocations. +static __thread int local_var_1 = 15; +static __thread int local_var_2 = 25; + +extern "C" int bump_local_vars() { + return ++local_var_1 + ++local_var_2; +} + +__attribute__((weak)) extern "C" __thread int missing_weak_dyn_tls; + +extern "C" int* missing_weak_dyn_tls_addr() { + return &missing_weak_dyn_tls; +} diff --git a/tests/libs/elftls_dynamic_filler.cpp b/tests/libs/elftls_dynamic_filler.cpp new file mode 100644 index 000000000..9c00ab069 --- /dev/null +++ b/tests/libs/elftls_dynamic_filler.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 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. + */ + +__thread int var = TLS_FILLER; + +extern "C" int bump() { + return ++var; +}