blob: 8d47bf6922ebc402494bbc0dfdc98fefc1b2a218 [file] [log] [blame]
// Copyright 2023 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.
#ifndef SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_RESOLVE_H_
#define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_RESOLVE_H_
#include <lib/fit/result.h>
#include <type_traits>
#include <utility>
#include "diagnostics.h"
#include "link.h"
#include "symbol.h"
namespace elfldltl {
// This type implements a Definition which can be used as the return type for
// the `resolve` parameter for RelocateSymbolic. See link.h for more details.
// The Module type must have the following methods:
//
// * const SymbolInfo& symbol_info() const
// Returns the SymbolInfo type associated with this module. This is used
// to call SymbolInfo::Lookup().
//
// * size_type load_bias() const
// Returns the load bias for symbol addresses in this module.
//
// * size_type tls_module_id() const
// Returns the TLS module ID number for this module.
// This will be zero for a module with no PT_TLS segment.
// It's always one in the main executable if has a PT_TLS segment,
// but may be one in a different module if the main executable has none.
//
// * bool uses_static_tls() const
// This module may have TLS relocations for IE or LE model accesses.
//
// * size_type static_tls_bias() const
// Returns the static TLS layout bias for the defining module.
//
// * fit::result<bool, TlsDescGot> tls_desc(Diagnostics&, const Sym&, Addend addend)
// * fit::result<bool, TlsDescGot> tls_desc(Diagnostics&)
// See elfldltl::RelocateSymbolic API comments about the two overloads.
// This implements that method but for some particular defined symbol in
// `.symbols_info().symtab()`.
//
template <class Module, typename TlsDescResolver>
struct ResolverDefinition {
using Elf = typename std::decay_t<decltype(std::declval<Module>().symbol_info())>::Elf;
using Addr = typename Elf::Addr;
using Addend = typename Elf::Addend;
using Sym = typename Elf::Sym;
using TlsDescGot = typename Elf::TlsDescGot;
static constexpr ResolverDefinition UndefinedWeak(TlsDescResolver* tlsdesc_resolver = nullptr) {
static_assert(ResolverDefinition{}.undefined_weak());
return {.tlsdesc_resolver_ = tlsdesc_resolver};
}
// This should be called before any other method to check if this Definition is valid.
constexpr bool undefined_weak() const { return !symbol_; }
constexpr const Sym& symbol() const { return *symbol_; }
constexpr auto bias() const { return module_->load_bias(); }
constexpr auto tls_module_id() const { return module_->tls_module_id(); }
constexpr bool uses_static_tls() const { return module_->uses_static_tls(); }
constexpr auto static_tls_bias() const { return module_->static_tls_bias(); }
template <
class Diagnostics, typename T = TlsDescResolver,
typename = std::enable_if_t<std::is_invocable_v<T, Diagnostics&, const ResolverDefinition&>>>
constexpr auto tls_desc(Diagnostics& diag) const {
return (*tlsdesc_resolver_)(diag, *this);
}
template <class Diagnostics, typename T = TlsDescResolver,
typename = std::enable_if_t<
std::is_invocable_v<T, Diagnostics&, const ResolverDefinition&, Addend>>>
constexpr auto tls_desc(Diagnostics& diag, Addend addend) const {
return (*tlsdesc_resolver_)(diag, *this, addend);
}
template <typename T = TlsDescResolver, typename = std::enable_if_t<std::is_invocable_v<T>>>
constexpr TlsDescGot tls_desc_undefined_weak() const {
return (*tlsdesc_resolver_)();
}
template <typename T = TlsDescResolver,
typename = std::enable_if_t<std::is_invocable_v<T, Addend>>>
constexpr TlsDescGot tls_desc_undefined_weak(Addend addend) const {
return (*tlsdesc_resolver_)(addend);
}
const Sym* symbol_ = nullptr;
const Module* module_ = nullptr;
TlsDescResolver* tlsdesc_resolver_ = nullptr;
};
enum class ResolverPolicy : bool {
// The first symbol found takes precedence, searching ends after finding the
// first.
kStrictLinkOrder,
// This follows LD_DYNAMIC_WEAK=1 semantics, the resolver will resolve to the
// first STB_GLOBAL symbol even if an STB_WEAK symbol was seen earlier.
// If no global symbol was found the first STB_WEAK symbol will prevail.
kStrongOverWeak,
};
// Returns a callable object which can be used for RelocateSymbolic's `resolve`
// argument. This takes some Module object (as described above) whose
// symbol_info() contains the symbol given by RelocateSymbolic. The `modules`
// argument is a list of modules from where symbolic definitions can be
// resolved, this list is in order of precedence. The ModuleList type is a
// forward iterable range or container. diag is a diagnostics object for
// reporting errors. The TlsDescResolver is a callable object that's called as
// `fit::result<bool, TlsDescGot>(Diagnostics&, const Definition&, Addend)` or
// `fit::result<bool, TlsDescGot>(Diagnostics&, const Definition&)` for a
// TLSDESDC relocation resolved to a defined symbol; and as `TlsDescGot()` or
// `TlsDescGot(Addend)` for one resolved as an undefined weak reference.
//
// All references passed to elfldltl::MakeSymbolResolver should outlive the
// returned object, which in turn must outlive its return values (Definition
// objects). The tlsdesc_resolver reference is saved in Definition objects so
// it can be called from the RelocateSymbolic callbacks.
template <class Module, class ModuleList, class Diagnostics, typename TlsDescResolver>
constexpr auto MakeSymbolResolver(const Module& ref_module, ModuleList& modules, Diagnostics& diag,
TlsDescResolver& tlsdesc_resolver,
ResolverPolicy policy = ResolverPolicy::kStrictLinkOrder) {
using Definition = ResolverDefinition<Module, TlsDescResolver>;
return [&ref_module, &modules, &diag, &tlsdesc_resolver, policy](
const auto& ref, RelocateTls tls_type) -> fit::result<bool, Definition> {
if (ref.runtime_local()) {
// The symbol just resolves to itself in the referring module. Usually
// this would have been replaced with an R_*_RELATIVE reloc (and then
// folded into DT_RELR), but it doesn't have to be. In practice, this
// comes up for TLS relocations which still need to have their specific
// reloc type but can be for purely module-local references.
return fit::ok(Definition{&ref, &ref_module});
}
SymbolName name{ref_module.symbol_info(), ref};
// Return the chosen Definition after some checking.
auto use = [&ref_module, tls_type, &diag, &tlsdesc_resolver,
&name](Definition def) -> fit::result<bool, Definition> {
switch (tls_type) {
case RelocateTls::kNone:
if (def.symbol_->type() == ElfSymType::kTls) [[unlikely]] {
return fit::error{
diag.FormatError("non-TLS relocation resolves to STT_TLS symbol ", name)};
}
break;
case RelocateTls::kStatic:
// If the referring module itself must be in the initial exec set
// then it's fine for it to use IE relocs for any of its references.
// If the referring module itself does not have DF_STATIC_TLS set to
// prevent it from being loaded outside the initial exec set, then
// the defining module must be guaranteed to be in the initial exec
// set. Note that we expect a main executable module to always
// return true for uses_static_tls() even though the linker doesn't
// set DF_STATIC_TLS when generating relocs in an executable
// (including PIE), so we're really using uses_static_tls() here as a
// proxy for "is in initial exec set".
if (!ref_module.uses_static_tls() && !def.module_->uses_static_tls()) [[unlikely]] {
return fit::error{diag.FormatError(
"TLS Initial Exec relocation resolves to STT_TLS symbol in module without DF_STATIC_TLS: ",
name)};
}
[[fallthrough]];
case RelocateTls::kDynamic:
case RelocateTls::kDesc:
if (def.symbol_->type() != ElfSymType::kTls) [[unlikely]] {
return fit::error{
diag.FormatError("TLS relocation resolves to non-STT_TLS symbol: ", name)};
}
break;
}
if (tls_type == RelocateTls::kDesc) {
def.tlsdesc_resolver_ = &tlsdesc_resolver;
}
return fit::ok(def);
};
if (name.empty()) [[unlikely]] {
return fit::error{diag.FormatError("Symbol had invalid st_name")};
}
Definition weak_def = Definition::UndefinedWeak(&tlsdesc_resolver);
for (const auto& module : modules) {
if (const auto* sym = name.Lookup(module.symbol_info())) {
const Definition module_def{sym, &module};
switch (sym->bind()) {
case ElfSymBind::kWeak:
// In kStrongOverWeak policy the first weak definition will prevail
// if no strong definition is found later.
if (policy == ResolverPolicy::kStrongOverWeak) {
if (weak_def.undefined_weak()) {
weak_def = module_def;
}
continue;
}
[[fallthrough]];
case ElfSymBind::kGlobal:
// The first (strong) global always prevails regardless of policy.
return use(module_def);
case ElfSymBind::kLocal:
// Local symbols are never matched by name.
if (!diag.FormatWarning("STB_LOCAL found in hash table")) {
return fit::error{false};
}
continue;
case ElfSymBind::kUnique:
if (!diag.FormatError("STB_GNU_UNIQUE not supported")) {
return fit::error{false};
}
break;
default:
if (!diag.FormatError("Unknown symbol binding type",
static_cast<unsigned>(sym->bind()))) {
return fit::error{false};
}
break;
}
// That returned a definition or continued to look for another so this
// is only reached for the error cases where the Diagnostics object
// said to keep going.
[[unlikely]] return fit::error{true};
}
}
if (!weak_def.undefined_weak()) {
// The only definition found was weak in kStrongOverWeak mode.
return use(weak_def);
}
// Undefined weak is a valid return value for an STB_WEAK reference.
if (ref.bind() == ElfSymBind::kWeak) [[likely]] {
return fit::ok(weak_def);
}
return fit::error{diag.UndefinedSymbol(name)};
};
}
} // namespace elfldltl
#endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_RESOLVE_H_