From c63fe0e1f1503af1124fb7abaa0915e5b2d46132 Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Wed, 5 Jul 2023 11:28:59 +0200 Subject: [PATCH] Tests/LibELF: Test loading libraries with dynamic TLS The setup is a bit peculiar: both the definition and the use site of these TLS variables have to be in a shared library, otherwise the linker might relax the global-dynamic access mode to something that doesn't require a `__tls_get_addr` call. --- Tests/LibELF/CMakeLists.txt | 27 ++++++++++++++++----- Tests/LibELF/TLSDef.cpp | 21 ++++++++++++++++ Tests/LibELF/TLSUse.cpp | 48 +++++++++++++++++++++++++++++++++++++ Tests/LibELF/TestTLS.cpp | 16 +++++++++++++ 4 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 Tests/LibELF/TLSDef.cpp create mode 100644 Tests/LibELF/TLSUse.cpp create mode 100644 Tests/LibELF/TestTLS.cpp diff --git a/Tests/LibELF/CMakeLists.txt b/Tests/LibELF/CMakeLists.txt index b417824fac7..6baeb7c375a 100644 --- a/Tests/LibELF/CMakeLists.txt +++ b/Tests/LibELF/CMakeLists.txt @@ -1,15 +1,21 @@ set(CMAKE_SKIP_RPATH FALSE) -macro(add_dlopen_lib NAME FUNCTION) - add_library(${NAME} SHARED Dynlib.cpp) - target_compile_definitions(${NAME} PRIVATE -DFUNCTION=${FUNCTION}) - # LibLine is not special, just an "external" dependency - target_link_libraries(${NAME} PRIVATE LibLine) + +macro(add_test_lib NAME FILE) + add_library(${NAME} SHARED ${FILE}) serenity_set_implicit_links(${NAME}) - # Avoid execution by the test runner + # Avoid execution by the test runner install(TARGETS ${NAME} DESTINATION usr/Tests/LibELF PERMISSIONS OWNER_READ GROUP_READ WORLD_READ OWNER_WRITE GROUP_WRITE) endmacro() + +macro(add_dlopen_lib NAME FUNCTION) + add_test_lib(${NAME} Dynlib.cpp) + target_compile_definitions(${NAME} PRIVATE -DFUNCTION=${FUNCTION}) + # LibLine is not special, just an "external" dependency + target_link_libraries(${NAME} PRIVATE LibLine) +endmacro() + add_dlopen_lib(DynlibA dynliba_function) add_dlopen_lib(DynlibB dynlibb_function) @@ -22,8 +28,17 @@ unset(CMAKE_INSTALL_RPATH) set(TEST_SOURCES test-elf.cpp TestDlOpen.cpp + TestTLS.cpp ) foreach(source IN LISTS TEST_SOURCES) serenity_test("${source}" LibELF) endforeach() + +add_test_lib(TLSDef TLSDef.cpp) +add_test_lib(TLSUse TLSUse.cpp) +target_compile_options(TLSUse PRIVATE -ftls-model=global-dynamic) +target_link_libraries(TLSUse PRIVATE LibCore LibTest LibThreading TLSDef) +set_target_properties(TLSUse PROPERTIES INSTALL_RPATH "$ORIGIN") +target_link_libraries(TestTLS PRIVATE TLSUse) +set_target_properties(TestTLS PROPERTIES INSTALL_RPATH "$ORIGIN") diff --git a/Tests/LibELF/TLSDef.cpp b/Tests/LibELF/TLSDef.cpp new file mode 100644 index 00000000000..e54af9a9e74 --- /dev/null +++ b/Tests/LibELF/TLSDef.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023, Daniel Bertalan + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +__thread int one = 1; +__thread int two = 2; +[[gnu::tls_model("initial-exec")]] __thread int three = 3; +[[gnu::tls_model("initial-exec")]] __thread int four = 4; + +void check_increment_worked(); +void check_increment_worked() +{ + EXPECT_EQ(one, 2); + EXPECT_EQ(two, 3); + EXPECT_EQ(three, 4); + EXPECT_EQ(four, 5); +} diff --git a/Tests/LibELF/TLSUse.cpp b/Tests/LibELF/TLSUse.cpp new file mode 100644 index 00000000000..79f67a03f02 --- /dev/null +++ b/Tests/LibELF/TLSUse.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023, Daniel Bertalan + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +// Defined in TLSDef.cpp +extern __thread int one; +extern __thread int two; +extern __thread int three; +[[gnu::tls_model("initial-exec")]] extern __thread int four; +extern void check_increment_worked(); + +static void check_initial() +{ + EXPECT_EQ(one, 1); + EXPECT_EQ(two, 2); + EXPECT_EQ(three, 3); + EXPECT_EQ(four, 4); +} + +// This checks the basic functionality of thread-local variables: +// - TLS variables with a static initializer have the correct value on program startup +// - TLS variables are set to their initial values in a new thread +// - relocations refer to the correct variables +// - accessing an initial-exec variable from a DSO works even if +// it's not declared as initial-exec at the use site +// FIXME: Test C++11 thread_local variables with dynamic initializers +void run_test(); +void run_test() +{ + check_initial(); + ++one; + ++two; + ++three; + ++four; + check_increment_worked(); + + auto second_thread = Threading::Thread::construct([] { + check_initial(); + return 0; + }); + second_thread->start(); + (void)second_thread->join(); +} diff --git a/Tests/LibELF/TestTLS.cpp b/Tests/LibELF/TestTLS.cpp new file mode 100644 index 00000000000..76e6982bb8d --- /dev/null +++ b/Tests/LibELF/TestTLS.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023, Daniel Bertalan + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +// When linking an executable, TLS relaxations might be relaxed to different +// access modes than intended. Hence, the actual logic has been moved to a +// shared library, and this executable just calls it. +extern void run_test(); +TEST_CASE(basic) +{ + run_test(); +}