blob: 62457f8ed2d6935dd8a9801305314cade97397b3 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "runtime-dynamic-linker.h"
#include <lib/fit/defer.h>
#include <algorithm>
#include <fbl/array.h>
#include "tlsdesc-runtime-dynamic.h"
namespace dl {
void RuntimeDynamicLinker::AddNewModules(ModuleList modules) {
loaded_ += modules.size();
modules_.splice(modules_.end(), modules);
}
RuntimeModule* RuntimeDynamicLinker::FindModule(Soname name) {
auto it = std::ranges::find_if(modules_, name.equal_to());
if (it == modules_.end()) {
return nullptr;
}
// TODO(https://fxbug.dev/328135195): increase reference count.
RuntimeModule& found = *it;
return &found;
}
void* RuntimeDynamicLinker::TlsBlock(const RuntimeModule& module) const {
assert(module.tls_module_id() > 0);
if (module.tls_module_id() <= max_static_tls_modid_) {
// TODO(https://fxbug.dev/403350238): Have the linker hold a reference to
// the passive abi so this could pass in ld::InitialExecOffset to
// ld::TpRelative.
return ld::TpRelative(static_cast<ptrdiff_t>(module.static_tls_bias()));
}
// TODO(https://fxbug.dev/403366387): Introduce a dynamic_tls_index accessor
// method on RuntimeModule
auto dynamic_tls_index = module.tls_module_id() - max_static_tls_modid_ - 1;
DynamicTlsPtr& module_tls = _dl_tlsdesc_runtime_dynamic_blocks[dynamic_tls_index];
return module_tls.contents(module.tls_module()).data();
}
fit::result<Error, void*> RuntimeDynamicLinker::LookupSymbol(const RuntimeModule& root,
const char* ref) {
Diagnostics diag;
// The root module's name is included in symbol not found errors.
ld::ScopedModuleDiagnostics root_diag{diag, root.name().str()};
elfldltl::SymbolName name{ref};
// TODO(https://fxbug.dev/370087572): properly handle weak symbols.
for (const RuntimeModule& module : root.module_tree()) {
if (const auto* sym = name.Lookup(module.symbol_info())) {
bool is_tls = sym->type() == elfldltl::ElfSymType::kTls;
uintptr_t bias = (is_tls ? reinterpret_cast<uintptr_t>(TlsBlock(module))
: static_cast<size_type>(module.load_bias()));
return diag.ok(reinterpret_cast<void*>(sym->value + bias));
}
}
diag.UndefinedSymbol(ref);
return diag.take_error();
}
void RuntimeDynamicLinker::MakeGlobal(const ModuleTree& module_tree) {
// This iterates through the `module_tree`, promoting any modules that are not
// already global. When a module is promoted, it is looked up in the dynamic
// linker's `modules_` list and moved to the back of that doubly-linked list.
// Note, that this loop does not change the ordering of the `module_tree`.
for (const RuntimeModule& loaded_module : module_tree) {
// If the loaded module is already global, then its load order does not
// change in modules_.
if (loaded_module.is_global()) {
continue;
}
// TODO(https://fxbug.dev/374810148): Introduce non-const version of ModuleTree.
RuntimeModule& promoted = const_cast<RuntimeModule&>(loaded_module);
promoted.set_global();
// Move the promoted module to the back of the dynamic linker's modules_
// list.
modules_.push_back(modules_.erase(promoted));
}
}
void RuntimeDynamicLinker::PopulateStartupModules(fbl::AllocChecker& func_ac,
const ld::abi::Abi<>& abi) {
// Arm the function-level AllocChecker with the result of the function.
auto set_result = [&func_ac](bool v) { func_ac.arm(sizeof(RuntimeModule), v); };
ModuleList startup_modules;
for (const AbiModule& abi_module : ld::AbiLoadedModules(abi)) {
fbl::AllocChecker ac;
std::unique_ptr<RuntimeModule> module =
RuntimeModule::Create(ac, Soname{abi_module.link_map.name.get()});
if (!ac.check()) [[unlikely]] {
set_result(false);
return;
}
module->SetStartupModule(abi_module, abi);
startup_modules.push_back(std::move(module));
}
// In a second pass, set the direct dependencies for each startup module.
// This loop will get each DT_NEEDED offset from the ABI module's PT_DYNAMIC
// (provided by `link_map.ld`) and look the offset up in the ABI module's
// symbol table to get the name of the dependency. The module for the dep
// (which should also be in the `startup_modules` list) is then added to the
// depending module's `direct_deps` list.
for (RuntimeModule& module : startup_modules) {
const AbiModule& abi_module = module.module();
for (const Elf::Dyn* dyn_entry = abi_module.link_map.ld.get();
dyn_entry->tag != elfldltl::ElfDynTag::kNull; ++dyn_entry) {
// Skip any entries that are not a DT_NEEDED.
if (dyn_entry->tag != elfldltl::ElfDynTag::kNeeded) {
continue;
}
const char* dep_name = abi_module.symbols.string(dyn_entry->val);
auto found = std::ranges::find_if(startup_modules, Soname{dep_name}.equal_to());
// The ABI should include all the dependencies of every startup module in
// its .loaded_modules list.
assert(found != startup_modules.end());
fbl::AllocChecker ac;
module.direct_deps().push_back(&*found, &ac);
if (!ac.check()) [[unlikely]] {
set_result(false);
return;
}
}
}
AddNewModules(std::move(startup_modules));
set_result(true);
}
// TODO(https://fxbug.dev/342484765): Have this function return fit::result<Error::OutOfMemory>.
std::unique_ptr<RuntimeDynamicLinker> RuntimeDynamicLinker::Create(const ld::abi::Abi<>& abi,
fbl::AllocChecker& ac) {
assert(abi.loaded_modules);
assert(abi.static_tls_modules.size() == abi.static_tls_offsets.size());
// Arm the caller's AllocChecker with the return value of this function.
auto result = [&ac](std::unique_ptr<RuntimeDynamicLinker> v) {
ac.arm(sizeof(RuntimeDynamicLinker), v);
return v;
};
fbl::AllocChecker linker_ac;
std::unique_ptr<RuntimeDynamicLinker> dynamic_linker{new (linker_ac) RuntimeDynamicLinker};
if (linker_ac.check()) [[likely]] {
fbl::AllocChecker populate_ac;
dynamic_linker->PopulateStartupModules(populate_ac, abi);
if (!populate_ac.check()) [[unlikely]] {
return result(nullptr);
}
size_t max_static_tls_modid = abi.static_tls_modules.size();
dynamic_linker->max_static_tls_modid_ = max_static_tls_modid;
dynamic_linker->max_tls_modid_ = max_static_tls_modid;
}
return result(std::move(dynamic_linker));
}
// TODO(https://fxbug.dev/382516279): This needs to handle synchronization
// between locking the modules_ list and running the user callback outside
// of any locks.
int RuntimeDynamicLinker::IteratePhdrInfo(DlIteratePhdrCallback* callback, void* data) const {
for (const RuntimeModule& module : modules_) {
void* tls = module.tls_module_id() == 0 ? nullptr : TlsBlock(module);
dl_phdr_info phdr_info = module.MakeDlPhdrInfo(tls, dl_phdr_info_counts());
// A non-zero return value ends the iteration.
if (int result = callback(&phdr_info, sizeof(phdr_info), data); result != 0) {
return result;
}
}
return 0;
}
[[nodiscard]] fit::result<Error> RuntimeDynamicLinker::PrepareTlsBlocksForThread(void* tp) const {
fbl::AllocChecker ac;
SizedDynamicTlsArray blocks = MakeDynamicTlsArray(ac, DynamicTlsCount());
if (!ac.check()) [[unlikely]] {
dl::Diagnostics diag;
diag.OutOfMemory("dynamic TLS vector", DynamicTlsCount() * sizeof(blocks[0]));
return diag.take_error();
}
// TODO(https://fxbug.dev/403350238): this loop needs to be optimized to only
// loop through TLS modules while avoiding multiple O(N) scans.
// Iterate through every `RuntimeModule` with dynamic TLS and copy its TLS
// data into its respective index in `blocks`.
auto next = blocks.begin();
for (const RuntimeModule& module : modules_) {
// Skip non-tls or static-tls modules.
if (module.tls_module_id() <= max_static_tls_modid_) {
continue;
}
*next++ = DynamicTlsPtr::New(ac, module.tls_module());
if (!ac.check()) [[unlikely]] {
dl::Diagnostics diag;
diag.OutOfMemory("dynamic TLS block", module.tls_module().tls_size());
return diag.take_error();
}
}
assert(next == blocks.end());
UnsizedDynamicTlsArray old_blocks = ExchangeRuntimeDynamicBlocks(std::move(blocks), tp);
assert(!old_blocks);
return fit::ok();
}
fit::result<Error, int> RuntimeDynamicLinker::DlInfo(void* handle, int request, void* info) {
const RuntimeModule& module = RuntimeModule::FromPtr(handle);
switch (request) {
case kLinkMap:
*static_cast<const link_map**>(info) = module.user_link_map();
return fit::ok(0);
case kTlsModid:
*static_cast<size_t*>(info) = module.tls_module_id();
return fit::ok(0);
case kTlsData:
*static_cast<void**>(info) = module.tls_module_id() == 0 ? nullptr : TlsBlock(module);
return fit::ok(0);
case kPhdrs:
*static_cast<ElfW(Phdr)**>(info) = const_cast<ElfW(Phdr)*>(
reinterpret_cast<const ElfW(Phdr)*>(module.module().phdrs.data()));
return fit::ok(static_cast<int>(module.module().phdrs.size()));
default:
return fit::error{Error{"unsupported dlinfo request"}};
};
}
} // namespace dl