Skip to content

Commit

Permalink
[libc] Linux threads - Setup TLS area of a new thread and cleanup at …
Browse files Browse the repository at this point in the history
…exit.

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D129543
  • Loading branch information
Siva Chandra Reddy committed Jul 13, 2022
1 parent 50c627b commit 859c189
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 49 deletions.
32 changes: 26 additions & 6 deletions libc/config/linux/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

namespace __llvm_libc {

// Data structure to capture properties of the linux/ELF TLS.
struct TLS {
// Data structure to capture properties of the linux/ELF TLS image.
struct TLSImage {
// The load address of the TLS.
uintptr_t address;

Expand Down Expand Up @@ -68,18 +68,38 @@ struct AppProperties {

Args *args;

// The properties of an application's TLS.
TLS tls;
// The properties of an application's TLS image.
TLSImage tls;

// Environment data.
uint64_t *envPtr;
};

extern AppProperties app;

// Creates and initializes the TLS area for the current thread. Should not
// The descriptor of a thread's TLS area.
struct TLSDescriptor {
// The size of the TLS area.
uintptr_t size = 0;

// The address of the TLS area. This address can be passed to cleanup
// functions like munmap.
uintptr_t addr = 0;

// The value the thread pointer register should be initialized to.
// Note that, dependending the target architecture ABI, it can be the
// same as |addr| or something else.
uintptr_t tp = 0;

constexpr TLSDescriptor() = default;
};

// Create and initialize the TLS area for the current thread. Should not
// be called before app.tls has been initialized.
void initTLS();
void init_tls(TLSDescriptor &tls);

// Cleanup the TLS area as described in |tls_descriptor|.
void cleanup_tls(uintptr_t tls_addr, uintptr_t tls_size);

} // namespace __llvm_libc

Expand Down
42 changes: 29 additions & 13 deletions libc/loader/linux/aarch64/start.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

#include <linux/auxvec.h>
#include <linux/elf.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
Expand All @@ -36,9 +35,12 @@ static constexpr long MMAP_SYSCALL_NUMBER = SYS_mmap;

AppProperties app;

void initTLS() {
if (app.tls.size == 0)
void init_tls(TLSDescriptor &tls_descriptor) {
if (app.tls.size == 0) {
tls_descriptor.size = 0;
tls_descriptor.tp = 0;
return;
}

// aarch64 follows the variant 1 TLS layout:
//
Expand All @@ -49,14 +51,14 @@ void initTLS() {
//
// The thread pointer points to the first entry.

const size_t size_of_pointers = 2 * sizeof(uintptr_t);
size_t padding = 0;
const size_t ALIGNMENT_MASK = app.tls.align - 1;
size_t diff = size_of_pointers & ALIGNMENT_MASK;
const uintptr_t size_of_pointers = 2 * sizeof(uintptr_t);
uintptr_t padding = 0;
const uintptr_t ALIGNMENT_MASK = app.tls.align - 1;
uintptr_t diff = size_of_pointers & ALIGNMENT_MASK;
if (diff != 0)
padding += (ALIGNMENT_MASK - diff) + 1;

size_t alloc_size = size_of_pointers + padding + app.tls.size;
uintptr_t alloc_size = size_of_pointers + padding + app.tls.size;

// We cannot call the mmap function here as the functions set errno on
// failure. Since errno is implemented via a thread local variable, we cannot
Expand All @@ -73,9 +75,19 @@ void initTLS() {
__llvm_libc::inline_memcpy(reinterpret_cast<char *>(tls_addr),
reinterpret_cast<const char *>(app.tls.address),
app.tls.init_size);
__arm_wsr64("tpidr_el0", thread_ptr);
tls_descriptor.size = alloc_size;
tls_descriptor.addr = thread_ptr;
tls_descriptor.tp = thread_ptr;
}

void cleanup_tls(uintptr_t addr, uintptr_t size) {
if (size == 0)
return;
__llvm_libc::syscall(SYS_munmap, addr, size);
}

static void set_thread_ptr(uintptr_t val) { __arm_wsr64("tpidr_el0", val); }

} // namespace __llvm_libc

using __llvm_libc::app;
Expand Down Expand Up @@ -134,9 +146,13 @@ extern "C" void _start() {
app.tls.align = phdr->p_align;
}

__llvm_libc::initTLS();
__llvm_libc::TLSDescriptor tls;
__llvm_libc::init_tls(tls);
if (tls.size != 0)
__llvm_libc::set_thread_ptr(tls.tp);

__llvm_libc::syscall(SYS_exit, main(app.args->argc,
reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr)));
int retval = main(app.args->argc, reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr));
__llvm_libc::cleanup_tls(tls.addr, tls.size);
__llvm_libc::syscall(SYS_exit, retval);
}
1 change: 1 addition & 0 deletions libc/loader/linux/x86_64/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ add_loader_object(
COMPILE_OPTIONS
-fno-omit-frame-pointer
-ffreestanding # To avoid compiler warnings about calling the main function.
-fno-builtin
)
38 changes: 29 additions & 9 deletions libc/loader/linux/x86_64/start.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ AppProperties app;

// TODO: The function is x86_64 specific. Move it to config/linux/app.h
// and generalize it. Also, dynamic loading is not handled currently.
void initTLS() {
if (app.tls.size == 0)
void init_tls(TLSDescriptor &tls_descriptor) {
if (app.tls.size == 0) {
tls_descriptor.size = 0;
tls_descriptor.tp = 0;
return;
}

// We will assume the alignment is always a power of two.
uintptr_t tlsSize = app.tls.size & -app.tls.align;
Expand All @@ -45,7 +48,7 @@ void initTLS() {
// Per the x86_64 TLS ABI, the entry pointed to by the thread pointer is the
// address of the TLS block. So, we add more size to accomodate this address
// entry.
size_t tlsSizeWithAddr = tlsSize + sizeof(uintptr_t);
uintptr_t tlsSizeWithAddr = tlsSize + sizeof(uintptr_t);

// We cannot call the mmap function here as the functions set errno on
// failure. Since errno is implemented via a thread local variable, we cannot
Expand All @@ -67,8 +70,21 @@ void initTLS() {
__llvm_libc::inline_memcpy(reinterpret_cast<char *>(tlsAddr),
reinterpret_cast<const char *>(app.tls.address),
app.tls.init_size);
if (__llvm_libc::syscall(SYS_arch_prctl, ARCH_SET_FS, endPtr) == -1)
__llvm_libc::syscall(SYS_exit, 1);

tls_descriptor = {tlsSizeWithAddr, uintptr_t(tlsAddr), endPtr};
return;
}

void cleanup_tls(uintptr_t addr, uintptr_t size) {
if (size == 0)
return;
__llvm_libc::syscall(SYS_munmap, addr, size);
}

// Sets the thread pointer to |val|. Returns true on success, false on failure.
static bool set_thread_ptr(uintptr_t val) {
return __llvm_libc::syscall(SYS_arch_prctl, ARCH_SET_FS, val) == -1 ? false
: true;
}

} // namespace __llvm_libc
Expand Down Expand Up @@ -144,9 +160,13 @@ extern "C" void _start() {
app.tls.align = phdr->p_align;
}

__llvm_libc::initTLS();
__llvm_libc::TLSDescriptor tls;
__llvm_libc::init_tls(tls);
if (tls.size != 0 && !__llvm_libc::set_thread_ptr(tls.tp))
__llvm_libc::syscall(SYS_exit, 1);

__llvm_libc::syscall(SYS_exit, main(app.args->argc,
reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr)));
int retval = main(app.args->argc, reinterpret_cast<char **>(app.args->argv),
reinterpret_cast<char **>(env_ptr));
__llvm_libc::cleanup_tls(tls.addr, tls.size);
__llvm_libc::syscall(SYS_exit, retval);
}
1 change: 1 addition & 0 deletions libc/src/__support/threads/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_object_library(
thread.cpp
DEPENDS
.futex_word_type
libc.config.linux.app_h
libc.include.sys_syscall
libc.src.__support.CPP.atomic
libc.src.__support.CPP.error
Expand Down
40 changes: 25 additions & 15 deletions libc/src/__support/threads/linux/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "src/__support/threads/thread.h"
#include "config/linux/app.h"
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/error.h"
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
Expand Down Expand Up @@ -42,11 +43,10 @@ static constexpr unsigned CLONE_SYSCALL_FLAGS =
| CLONE_THREAD // Same thread group as the parent.
| CLONE_SYSVSEM // Share a single list of System V semaphore adjustment
// values
| CLONE_PARENT_SETTID // Set child thread ID in |ptid| of the parent.
| CLONE_CHILD_CLEARTID; // Let the kernel clear the tid address
// wake the joining thread.
// TODO: Add the CLONE_SETTLS flag and setup the TLS area correctly
// when making the clone syscall.
| CLONE_PARENT_SETTID // Set child thread ID in |ptid| of the parent.
| CLONE_CHILD_CLEARTID // Let the kernel clear the tid address
// wake the joining thread.
| CLONE_SETTLS; // Setup the thread pointer of the new thread.

static inline cpp::ErrorOr<void *> alloc_stack(size_t size) {
long mmap_result =
Expand Down Expand Up @@ -80,6 +80,14 @@ struct alignas(STACK_ALIGNMENT) StartArgs {
void *arg;
};

static void cleanup_thread_resources(ThreadAttributes *attrib) {
// Cleanup the TLS before the stack as the TLS information is stored on
// the stack.
cleanup_tls(attrib->tls, attrib->tls_size);
if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stack_size);
}

__attribute__((always_inline)) inline uintptr_t get_start_args_addr() {
// NOTE: For __builtin_frame_address to work reliably across compilers,
// architectures and various optimization levels, the TU including this file
Expand Down Expand Up @@ -118,8 +126,7 @@ static void start_thread() {
if (!attrib->detach_state.compare_exchange_strong(
joinable_state, uint32_t(DetachState::EXITING))) {
// Thread is detached so cleanup the resources.
if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stack_size);
cleanup_thread_resources(attrib);

// Set the CLEAR_TID address to nullptr to prevent the kernel
// from signalling at a non-existent futex location.
Expand All @@ -143,6 +150,9 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
owned_stack = true;
}

TLSDescriptor tls;
init_tls(tls);

// When the new thread is spawned by the kernel, the new thread gets the
// stack we pass to the clone syscall. However, this stack is empty and does
// not have any local vars present in this function. Hence, one cannot
Expand All @@ -167,6 +177,8 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
attrib->stack = stack;
attrib->stack_size = size;
attrib->owned_stack = owned_stack;
attrib->tls = tls.addr;
attrib->tls_size = tls.size;

start_args->thread_attrib = attrib;
start_args->runner = runner;
Expand All @@ -187,14 +199,14 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack,
&attrib->tid, // The address where the child tid is written
&clear_tid->val, // The futex where the child thread status is signalled
0 // Set TLS to null for now.
tls.tp // The thread pointer value for the new thread.
);
#elif defined(LLVM_LIBC_ARCH_AARCH64)
long register clone_result asm("x0");
clone_result = __llvm_libc::syscall(
SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack,
&attrib->tid, // The address where the child tid is written
0, // Set TLS to null for now.
tls.tp, // The thread pointer value for the new thread.
&clear_tid->val // The futex where the child thread status is signalled
);
#else
Expand All @@ -209,8 +221,7 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
#endif
start_thread();
} else if (clone_result < 0) {
if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stack_size);
cleanup_thread_resources(attrib);
return -clone_result;
}

Expand All @@ -225,8 +236,7 @@ int Thread::join(ThreadReturnValue &retval) {
else
retval.stdc_retval = attrib->retval.stdc_retval;

if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stack_size);
cleanup_thread_resources(attrib);

return 0;
}
Expand All @@ -243,8 +253,8 @@ int Thread::detach() {
// and free up resources.
wait();

if (attrib->owned_stack)
free_stack(attrib->stack, attrib->stack_size);
cleanup_thread_resources(attrib);

return int(DetachType::CLEANUP);
}

Expand Down
11 changes: 5 additions & 6 deletions libc/src/__support/threads/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ struct alignas(STACK_ALIGNMENT) ThreadAttributes {
// exits. It will clean up the thread resources once the thread
// exits.
cpp::Atomic<uint32_t> detach_state;
void *stack; // Pointer to the thread stack
void *tls;
void *stack; // Pointer to the thread stack
unsigned long long stack_size; // Size of the stack
uintptr_t tls; // Address to the thread TLS memory
uintptr_t tls_size; // The size of area pointed to by |tls|.
unsigned char owned_stack; // Indicates if the thread owns this stack memory
int tid;
ThreadStyle style;
Expand All @@ -92,10 +93,8 @@ struct alignas(STACK_ALIGNMENT) ThreadAttributes {

constexpr ThreadAttributes()
: detach_state(uint32_t(DetachState::DETACHED)), stack(nullptr),
tls(nullptr), stack_size(0), owned_stack(false), tid(-1),
style(ThreadStyle::POSIX), retval(),
platform_data(nullptr) {
}
stack_size(0), tls(0), tls_size(0), owned_stack(false), tid(-1),
style(ThreadStyle::POSIX), retval(), platform_data(nullptr) {}
};

struct Thread {
Expand Down
12 changes: 12 additions & 0 deletions libc/test/integration/src/__support/threads/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ add_integration_test(
libc.src.__support.threads.mutex
libc.src.__support.threads.thread
)

add_integration_test(
thread_tls_test
SUITE
libc-support-threads-integration-tests
SRCS
thread_tls_test.cpp
LOADER
libc.loader.linux.crt1
DEPENDS
libc.src.__support.threads.thread
)
Loading

0 comments on commit 859c189

Please sign in to comment.