1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:37:35 +00:00

LibC+LibELF: Support loading shared libraries compiled with dynamic TLS

This is a prerequisite for upstreaming our LLVM patches, as our current
hack forcing `-ftls-model=initial-exec` in the Clang driver is not
acceptable upstream.

Currently, our kernel-managed TLS implementation limits us to only
having a single block of storage for all thread-local variables that's
initialized at load time. This PR merely implements the dynamic TLS
interface (`__tls_get_addr` and TLSDESC) on top of our static TLS
infrastructure. The current model's limitations still stand:
- a single static TLS block is reserved at load time, `dlopen()`-ing
  shared libraries that define thread-local variables might cause us to
  run out of space.
- the initial TLS image is not changeable post-load, so `dlopen()`-ing
  libraries with non-zero-initialized TLS variables is not supported.

The way we repurpose `ti_module` to mean "offset within static TLS
block" instead of "module index" is not ABI-compliant.
This commit is contained in:
Daniel Bertalan 2023-07-05 23:58:24 +02:00 committed by Jelle Raaijmakers
parent 192ee4594c
commit ad9e674fa0
6 changed files with 166 additions and 21 deletions

View file

@ -2,7 +2,7 @@
* Copyright (c) 2019-2020, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Daniel Bertalan <dani@danielbertalan.dev>
* Copyright (c) 2022-2023, Daniel Bertalan <dani@danielbertalan.dev>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -35,6 +35,13 @@ static void* mmap_with_name(void* addr, size_t length, int prot, int flags, int
# define MAP_RANDOMIZED 0
#endif
#if ARCH(AARCH64)
# define HAS_TLSDESC_SUPPORT
extern "C" {
void* __tlsdesc_static(void*);
}
#endif
namespace ELF {
Result<NonnullRefPtr<DynamicLoader>, DlErrorMessage> DynamicLoader::try_create(int fd, DeprecatedString filepath)
@ -527,6 +534,23 @@ DynamicLoader::RelocationResult DynamicLoader::do_direct_relocation(DynamicObjec
return VirtualAddress { reinterpret_cast<DynamicObject::IfuncResolver>(address.get())() };
};
struct ResolvedTLSSymbol {
DynamicObject const& dynamic_object;
FlatPtr value;
};
auto resolve_tls_symbol = [](DynamicObject::Relocation const& relocation) -> Optional<ResolvedTLSSymbol> {
if (relocation.symbol_index() == 0)
return ResolvedTLSSymbol { relocation.dynamic_object(), 0 };
auto res = lookup_symbol(relocation.symbol());
if (!res.has_value())
return {};
VERIFY(relocation.symbol().type() != STT_GNU_IFUNC);
VERIFY(res.value().dynamic_object != nullptr);
return ResolvedTLSSymbol { *res.value().dynamic_object, res.value().value };
};
switch (relocation.type()) {
case R_X86_64_NONE:
@ -601,30 +625,51 @@ DynamicLoader::RelocationResult DynamicLoader::do_direct_relocation(DynamicObjec
}
case R_AARCH64_TLS_TPREL:
case R_X86_64_TPOFF64: {
auto symbol = relocation.symbol();
FlatPtr symbol_value;
DynamicObject const* dynamic_object_of_symbol;
if (relocation.symbol_index() != 0) {
auto res = lookup_symbol(symbol);
if (!res.has_value())
break;
VERIFY(symbol.type() != STT_GNU_IFUNC);
symbol_value = res.value().value;
dynamic_object_of_symbol = res.value().dynamic_object;
} else {
symbol_value = 0;
dynamic_object_of_symbol = &relocation.dynamic_object();
}
VERIFY(dynamic_object_of_symbol);
size_t addend = relocation.addend_used() ? relocation.addend() : *patch_ptr;
auto maybe_resolution = resolve_tls_symbol(relocation);
if (!maybe_resolution.has_value())
break;
auto [dynamic_object_of_symbol, symbol_value] = maybe_resolution.value();
*patch_ptr = addend + dynamic_object_of_symbol->tls_offset().value() + symbol_value;
size_t addend = relocation.addend_used() ? relocation.addend() : *patch_ptr;
*patch_ptr = addend + dynamic_object_of_symbol.tls_offset().value() + symbol_value;
// At offset 0 there's the thread's ThreadSpecificData structure, we don't want to collide with it.
VERIFY(static_cast<ssize_t>(*patch_ptr) < 0);
break;
}
case R_X86_64_DTPMOD64: {
auto maybe_resolution = resolve_tls_symbol(relocation);
if (!maybe_resolution.has_value())
break;
// We repurpose the module index to store the TLS block's TP offset. This is fine
// because we currently only support a single static TLS block.
*patch_ptr = maybe_resolution->dynamic_object.tls_offset().value();
break;
}
case R_X86_64_DTPOFF64: {
auto maybe_resolution = resolve_tls_symbol(relocation);
if (!maybe_resolution.has_value())
break;
size_t addend = relocation.addend_used() ? relocation.addend() : *patch_ptr;
*patch_ptr = addend + maybe_resolution->value;
break;
}
#ifdef HAS_TLSDESC_SUPPORT
case R_AARCH64_TLSDESC: {
auto maybe_resolution = resolve_tls_symbol(relocation);
if (!maybe_resolution.has_value())
break;
auto [dynamic_object_of_symbol, symbol_value] = maybe_resolution.value();
size_t addend = relocation.addend_used() ? relocation.addend() : *patch_ptr;
patch_ptr[0] = (FlatPtr)__tlsdesc_static;
patch_ptr[1] = addend + dynamic_object_of_symbol.tls_offset().value() + symbol_value;
break;
}
#endif
case R_AARCH64_IRELATIVE:
case R_X86_64_IRELATIVE: {
if (should_call_ifunc_resolver == ShouldCallIfuncResolver::No)