blob: bbb7692d5210b1350c3eef5971d16554dfd0f16f [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.
#ifndef LIB_DL_MODULE_H_
#define LIB_DL_MODULE_H_
#include <lib/elfldltl/alloc-checker-container.h>
#include <lib/elfldltl/dynamic.h>
#include <lib/elfldltl/load.h>
#include <lib/elfldltl/memory.h>
#include <lib/elfldltl/resolve.h>
#include <lib/elfldltl/soname.h>
#include <lib/elfldltl/static-vector.h>
#include <lib/fit/result.h>
#include <lib/ld/decoded-module-in-memory.h>
#include <lib/ld/load-module.h>
#include <lib/ld/load.h>
#include <lib/ld/memory.h>
#include <lib/ld/module.h>
#include <fbl/alloc_checker.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/vector.h>
#include "diagnostics.h"
namespace dl {
using Elf = elfldltl::Elf<>;
using Soname = elfldltl::Soname<>;
// TODO(https://fxbug.dev/324136435): These KMax* variables are copied from
// //sdk/lib/ld/startup-load.h, we should share these symbols instead.
// Usually there are fewer than five segments, so this seems like a reasonable
// upper bound to support.
inline constexpr size_t kMaxSegments = 8;
// There can be quite a few metadata phdrs in addition to a PT_LOAD for each
// segment, so allow a fair few more.
inline constexpr size_t kMaxPhdrs = 32;
static_assert(kMaxPhdrs > kMaxSegments);
template <class ModuleType>
using ModuleList = fbl::DoublyLinkedList<std::unique_ptr<ModuleType>>;
// TODO(https://fxbug.dev/324136831): comment on how ModuleHandle relates to
// startup modules when the latter is supported.
// TODO(https://fxbug.dev/328135195): comment on the reference counting when
// that gets implemented.
// A ModuleHandle is created for every unique ELF file object loaded either
// directly or indirectly as a dependency of another module. It holds the
// ld::abi::Abi<...>::Module data structure that describes the module in the
// passive ABI (see //sdk/lib/ld/module.h).
// A ModuleHandle is created by its corresponding LoadModule (see below) when
// an ELF file is first loaded by `dlopen`. Whereas a LoadModule is ephemeral
// and lives only as long as it takes to load a module and its dependencies in
// `dlopen`, the ModuleHandle is a "permanent" data structure that is kept alive
// in the RuntimeDynamicLinker's `loaded_modules` list until the module is
// unloaded.
// While this is an internal API, a ModuleHandle* is the void* handle returned
// by the public <dlfcn.h> API.
class ModuleHandle : public fbl::DoublyLinkedListable<std::unique_ptr<ModuleHandle>> {
public:
using Addr = Elf::Addr;
using SymbolInfo = elfldltl::SymbolInfo<Elf>;
using AbiModule = ld::AbiModule<>;
// Not copyable, but movable.
ModuleHandle(const ModuleHandle&) = delete;
ModuleHandle(ModuleHandle&&) = default;
// See unmap-[posix|zircon|.cc for the dtor. On destruction, the module's load
// image is unmapped per the semantics of the OS implementation.
~ModuleHandle();
// The name of the module handle is set to the filename passed to dlopen() to
// create the module handle. This is usually the same as the DT_SONAME of the
// AbiModule, but that is not guaranteed. When performing an equality check,
// match against both possible name values.
constexpr bool operator==(const Soname& name) const {
return name == name_ || name == abi_module_.soname;
}
constexpr const Soname& name() const { return name_; }
// TODO(https://fxbug.dev/333920495): pass in the symbolizer_modid.
[[nodiscard]] static std::unique_ptr<ModuleHandle> Create(Soname name, fbl::AllocChecker& ac) {
std::unique_ptr<ModuleHandle> module{new (ac) ModuleHandle};
if (module) [[likely]] {
module->name_ = name;
}
return module;
}
constexpr AbiModule& module() { return abi_module_; }
constexpr const AbiModule& module() const { return abi_module_; }
constexpr Addr load_bias() const { return abi_module_.link_map.addr; }
const SymbolInfo& symbol_info() const { return abi_module_.symbols; }
size_t vaddr_size() const { return abi_module_.vaddr_end - abi_module_.vaddr_start; }
private:
// A ModuleHandle can only be created with Module::Create...).
ModuleHandle() = default;
static void Unmap(uintptr_t vaddr, size_t len);
Soname name_;
AbiModule abi_module_;
};
// Use a AllocCheckerContainer that supports fallible allocations; methods return
// a boolean value to signify allocation success or failure.
template <typename T>
using Vector = elfldltl::AllocCheckerContainer<fbl::Vector>::Container<T>;
// LoadModule is the temporary data structure created to load a file; a
// LoadModule is created when a file needs to be loaded, and is destroyed after
// the file module and all its dependencies have been loaded, decoded, symbols
// resolved, and relro protected.
template <class Loader>
class LoadModule : public ld::LoadModule<ld::DecodedModuleInMemory<>>,
public fbl::DoublyLinkedListable<std::unique_ptr<LoadModule<Loader>>> {
public:
using Relro = typename Loader::Relro;
using Phdr = Elf::Phdr;
using Dyn = Elf::Dyn;
using LoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StaticVector<kMaxSegments>::Container>;
// TODO(https://fxbug.dev/331421403): Implement TLS.
struct NoTlsDesc {
using TlsDescGot = typename Elf::TlsDescGot;
constexpr TlsDescGot operator()() const {
assert(false && "TLS is not supported");
return {};
}
template <class Diagnostics, class Definition>
constexpr fit::result<bool, TlsDescGot> operator()(Diagnostics& diag,
const Definition& defn) const {
assert(false && "TLS is not supported");
return fit::error{false};
}
};
// This is the observer used to collect DT_NEEDED offsets from the dynamic phdr.
static const constexpr std::string_view kNeededError{"DT_NEEDED offsets"};
using NeededObserver = elfldltl::DynamicValueCollectionObserver< //
Elf, elfldltl::ElfDynTag::kNeeded, Vector<size_type>, kNeededError>;
// The LoadModule::Create(...) creates and takes temporary ownership of the
// ModuleHandle for the file in order to set information on the data structure
// during the loading process. When the loading process has completed,
// `take_module()` should be called on the load module before it's destroyed
// to transfer ownership of the module handle to the caller. Otherwise, if an
// error occurs during loading, the load module will clean up the module
// handle in its own destruction.
// A fbl::AllocChecker& caller_ac is passed in to require the caller to check
// for allocation success/failure. This function will arm the caller_ac with
// the allocation results of its local calls.
[[nodiscard]] static std::unique_ptr<LoadModule> Create(Soname name,
fbl::AllocChecker& caller_ac) {
fbl::AllocChecker ac;
auto module = ModuleHandle::Create(name, ac);
if (!ac.check()) [[unlikely]] {
caller_ac.arm(sizeof(ModuleHandle), false);
return nullptr;
}
std::unique_ptr<LoadModule> load_module{new (ac) LoadModule};
if (!ac.check()) [[unlikely]] {
caller_ac.arm(sizeof(LoadModule), false);
return nullptr;
}
// TODO(https://fxbug.dev/335921712): Have ModuleHandle own the name string.
load_module->set_name(name);
// Have the underlying DecodedModule (see <lib/ld/decoded-module.h>) point to
// the ABIModule embedded in the ModuleHandle, so that its information will
// be filled out during decoding operations.
load_module->decoded().set_module(module->module());
load_module->module_ = std::move(module);
// Signal to the caller all allocations have succeeded.
caller_ac.arm(sizeof(LoadModule), true);
return std::move(load_module);
}
// This must be the last method called on LoadModule and can only be called
// with `std::move(load_module).take_module();`.
// Calling this method indicates that the module has been loaded successfully
// and will give the caller ownership of the module, handing off the
// responsibility for managing its lifetime and unmapping.
std::unique_ptr<ModuleHandle> take_module() && { return std::move(module_); }
// Load `file` into the system image, decode phdrs and save the metadata in
// the the ABI module. Decode the module's dependencies (if any), and
// return a vector their so names.
template <class File>
std::optional<Vector<Soname>> Load(Diagnostics& diag, File&& file) {
// Read the file header and program headers into stack buffers and map in
// the image. This fills in load_info() as well as the module vaddr bounds
// and phdrs fields.
Loader loader;
auto headers = decoded().LoadFromFile(diag, loader, std::move(file));
if (!headers) [[unlikely]] {
return std::nullopt;
}
Vector<size_type> needed_offsets;
// TODO(https://fxbug.dev/331421403): TLS is not supported yet.
size_type max_tls_modid = 0;
if (!decoded().DecodeFromMemory( //
diag, loader.memory(), loader.page_size(), *headers, max_tls_modid,
elfldltl::DynamicRelocationInfoObserver(decoded().reloc_info()),
NeededObserver(needed_offsets))) [[unlikely]] {
return std::nullopt;
}
// After successfully loading the file, finalize the module's mapping by
// calling `Commit` on the loader. Save the returned relro capability
// that will be used to apply relro protections later.
loader_relro_ = std::move(loader).Commit(decoded().relro_bounds());
// TODO(https://fxbug.dev/324136435): The code that parses the names from
// the symbol table be shared with <lib/ld/remote-decoded-module.h>.
if (Vector<Soname> needed_names;
needed_names.reserve(diag, kNeededError, needed_offsets.size())) [[likely]] {
for (size_type offset : needed_offsets) {
std::string_view name = this->symbol_info().string(offset);
if (name.empty()) [[unlikely]] {
diag.FormatError("DT_NEEDED has DT_STRTAB offset ", offset, " with DT_STRSZ ",
this->symbol_info().strtab().size());
return std::nullopt;
}
if (!needed_names.push_back(diag, kNeededError, Soname{name})) [[unlikely]] {
return std::nullopt;
}
}
return std::move(needed_names);
}
return std::nullopt;
}
// Perform relative and symbolic relocations, resolving symbols from the
// list of modules as needed.
bool Relocate(Diagnostics& diag, ModuleList<LoadModule>& modules) {
constexpr NoTlsDesc kNoTlsDesc{};
auto memory = ld::ModuleMemory{module()};
auto resolver = elfldltl::MakeSymbolResolver(*this, modules, diag, kNoTlsDesc);
return elfldltl::RelocateRelative(diag, memory, reloc_info(), load_bias()) &&
elfldltl::RelocateSymbolic(memory, diag, reloc_info(), symbol_info(), load_bias(),
resolver);
}
private:
// A LoadModule can only be created with LoadModule::Create...).
LoadModule() = default;
std::unique_ptr<ModuleHandle> module_;
Relro loader_relro_;
};
} // namespace dl
#endif // LIB_DL_MODULE_H_