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.
This commit is contained in:
Daniel Bertalan 2023-07-05 11:28:59 +02:00 committed by Jelle Raaijmakers
parent ad9e674fa0
commit c63fe0e1f1
Notes: sideshowbarker 2024-07-17 02:56:25 +09:00
4 changed files with 106 additions and 6 deletions

View file

@ -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")

21
Tests/LibELF/TLSDef.cpp Normal file
View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2023, Daniel Bertalan <dani@danielbertalan.dev>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/Macros.h>
__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);
}

48
Tests/LibELF/TLSUse.cpp Normal file
View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2023, Daniel Bertalan <dani@danielbertalan.dev>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/Macros.h>
#include <LibThreading/Thread.h>
// 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();
}

16
Tests/LibELF/TestTLS.cpp Normal file
View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2023, Daniel Bertalan <dani@danielbertalan.dev>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
// 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();
}