diff --git a/libc/config/linux/CMakeLists.txt b/libc/config/linux/CMakeLists.txt --- a/libc/config/linux/CMakeLists.txt +++ b/libc/config/linux/CMakeLists.txt @@ -9,3 +9,9 @@ DEPENDS libc.src.__support.common ) + +add_header( + app_h + HDR + app.h +) diff --git a/libc/config/linux/app.h b/libc/config/linux/app.h new file mode 100644 --- /dev/null +++ b/libc/config/linux/app.h @@ -0,0 +1,33 @@ +//===-- Classes to capture properites of linux applications -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_CONFIG_LINUX_APP_H +#define LLVM_LIBC_CONFIG_LINUX_APP_H + +#include + +namespace __llvm_libc { + +struct TLS { + uintptr_t address; + uintptr_t size; + uintptr_t align; +}; + +struct AppProperties { + uintptr_t pageSize; + TLS tls; +}; + +// Creates and initializes the TLS area for the current thread. Should not +// be called before app.tls has been initialized. +void initTLS(); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_CONFIG_LINUX_APP_H diff --git a/libc/loader/linux/x86_64/CMakeLists.txt b/libc/loader/linux/x86_64/CMakeLists.txt --- a/libc/loader/linux/x86_64/CMakeLists.txt +++ b/libc/loader/linux/x86_64/CMakeLists.txt @@ -4,7 +4,10 @@ start.cpp DEPENDS libc.config.linux.linux_syscall_h + libc.config.linux.app_h libc.include.sys_syscall + libc.src.string.memcpy + libc.src.sys.mman.mmap COMPILE_OPTIONS -fno-omit-frame-pointer -ffreestanding # To avoid compiler warnings about calling the main function. diff --git a/libc/loader/linux/x86_64/start.cpp b/libc/loader/linux/x86_64/start.cpp --- a/libc/loader/linux/x86_64/start.cpp +++ b/libc/loader/linux/x86_64/start.cpp @@ -6,14 +6,76 @@ // //===----------------------------------------------------------------------===// +#include "config/linux/app.h" #include "config/linux/syscall.h" +#include "include/sys/mman.h" #include "include/sys/syscall.h" +#include "src/string/memcpy.h" +#include "src/sys/mman/mmap.h" +#include #include +#include #include extern "C" int main(int, char **, char **); +namespace __llvm_libc { + +#ifdef SYS_mmaps2 +static constexpr long mmapSyscallNumber = SYS_mmap2; +#elif SYS_mmap +static constexpr long mmapSyscallNumber = SYS_mmap; +#else +#error "Target platform does not have SYS_mmap or SYS_mmap2 defined" +#endif + +// TODO: Declare var an extern var in config/linux/app.h so that other +// libc functions can make use of the application wide information. For +// example, mmap can pick up the page size from here. +AppProperties app; + +// TODO: The function is x86_64 specific. Move it to config/linux/app.h +// and generalize it. +void initTLS() { + if (app.tls.size == 0) + return; + + uintptr_t tlsSize = app.tls.size; + uintptr_t misAlign = tlsSize % app.tls.align; + tlsSize += (misAlign ? app.tls.align - misAlign : 0); + + // 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); + + // 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 + // use errno before TLS is setup. + long mmapRetVal = __llvm_libc::syscall( + mmapSyscallNumber, nullptr, tlsSizeWithAddr, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (mmapRetVal < 0 && static_cast(mmapRetVal) > -app.pageSize) + __llvm_libc::syscall(SYS_exit, 1); + uintptr_t *tlsAddr = reinterpret_cast(mmapRetVal); + + // x86_64 TLS faces down from the thread pointer with the first entry + // pointing to the address of the first real TLS byte. + uintptr_t endPtr = reinterpret_cast(tlsAddr) + tlsSize; + *reinterpret_cast(endPtr) = + reinterpret_cast(tlsAddr) + tlsSize; + + __llvm_libc::memcpy(tlsAddr, reinterpret_cast(app.tls.address), + app.tls.size); + if (__llvm_libc::syscall(SYS_arch_prctl, ARCH_SET_FS, endPtr) == -1) + __llvm_libc::syscall(SYS_exit, 1); +} + +} // namespace __llvm_libc + +using __llvm_libc::app; + struct Args { // At the language level, argc is an int. But we use uint64_t as the x86_64 // ABI specifies it as an 8 byte value. @@ -53,12 +115,37 @@ // After the env array, is the aux-vector. The end of the aux-vector is // denoted by an AT_NULL entry. + Elf64_Phdr *programHdrTable = nullptr; + uintptr_t programHdrCount; for (AuxEntry *aux_entry = reinterpret_cast(env_end_marker + 1); aux_entry->type != AT_NULL; ++aux_entry) { - // TODO: Read the aux vector and store necessary information in a libc wide - // data structure. + switch (aux_entry->type) { + case AT_PHDR: + programHdrTable = reinterpret_cast(aux_entry->value); + break; + case AT_PHNUM: + programHdrCount = aux_entry->value; + break; + case AT_PAGESZ: + app.pageSize = aux_entry->value; + break; + default: + break; // TODO: Read other useful entries from the aux vector. + } } + for (uintptr_t i = 0; i < programHdrCount; ++i) { + Elf64_Phdr *phdr = programHdrTable + i; + if (phdr->p_type != PT_TLS) + continue; + // TODO: p_vaddr value has to be adjusted for static-pie executables. + app.tls.address = phdr->p_vaddr; + app.tls.size = phdr->p_memsz; + app.tls.align = phdr->p_align; + } + + __llvm_libc::initTLS(); + __llvm_libc::syscall(SYS_exit, main(args->argc, reinterpret_cast(args->argv), reinterpret_cast(env_ptr))); diff --git a/libc/test/loader/linux/CMakeLists.txt b/libc/test/loader/linux/CMakeLists.txt --- a/libc/test/loader/linux/CMakeLists.txt +++ b/libc/test/loader/linux/CMakeLists.txt @@ -35,3 +35,17 @@ DEPENDS libc.loader.linux.crt1 ) + +add_loader_test( + loader_tls_test + SRC + tls_test.cpp + DEPENDS + libc.config.linux.app_h + libc.include.errno + libc.include.sys_mman + libc.loader.linux.crt1 + libc.src.assert.__assert_fail + libc.src.errno.__errno_location + libc.src.sys.mman.mmap +) diff --git a/libc/test/loader/linux/tls_test.cpp b/libc/test/loader/linux/tls_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/loader/linux/tls_test.cpp @@ -0,0 +1,40 @@ +//===-- Loader test to check if tls size is read correctly ----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/errno.h" +#include "include/sys/mman.h" + +#undef NDEBUG +#include "src/assert/assert.h" + +#include "src/errno/llvmlibc_errno.h" +#include "src/sys/mman/mmap.h" + +constexpr int threadLocalDataSize = 101; +_Thread_local int a[threadLocalDataSize] = {123}; + +int main(int argc, char **argv, char **envp) { + assert(a[0] == 123); + + for (int i = 1; i < threadLocalDataSize; ++i) + a[i] = i; + for (int i = 1; i < threadLocalDataSize; ++i) + assert(a[i] == i); + + // Call mmap with bad params so that an error value is + // set in errno. Since errno is implemented using a thread + // local var, this helps us test setting of errno and + // reading it back. + assert(llvmlibc_errno == 0); + void *addr = __llvm_libc::mmap(nullptr, 0, PROT_READ, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + assert(addr == MAP_FAILED); + assert(llvmlibc_errno == EINVAL); + + return 0; +}