blob: f0327f5e60960bd824b94a39631262ef8aee9e30 [file] [log] [blame] [edit]
// 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 LIB_LD_STARTUP_LOAD_H_
#define LIB_LD_STARTUP_LOAD_H_
#include <lib/elfldltl/link.h>
#include <lib/elfldltl/load.h>
#include <lib/elfldltl/relocation.h>
#include <lib/elfldltl/relro.h>
#include <lib/elfldltl/resolve.h>
#include <lib/elfldltl/static-vector.h>
#include <lib/ld/load-module.h>
#include <lib/ld/load.h>
#include <fbl/intrusive_double_list.h>
#include "allocator.h"
#include "diagnostics.h"
namespace ld {
// 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);
// The startup dynamic linker always uses the default ELF layout.
using Elf = elfldltl::Elf<>;
using Ehdr = typename Elf::Ehdr;
using Phdr = typename Elf::Phdr;
using RelroBounds = decltype(elfldltl::RelroBounds(Phdr{}, 0));
// StartupLoadModule::Load returns this.
struct StartupLoadResult {
// This is the number of DT_NEEDED entries seen. Their strings can't be
// decoded without a second elfldltl::DecodeDynamic scan since the first one
// has to find DT_STRTAB and it might not be first. But the first scan
// counts how many entries there are, so the second scan can be
// short-circuited rather than always doing a full O(n) scan of all entries.
size_t needed_count = 0;
// These are only of interest for the main executable.
uintptr_t entry = 0; // Runtime entry point address.
std::optional<size_t> stack_size; // Requested initial stack size.
};
// StartupLoadModule is the LoadModule type used in the startup dynamic linker.
// Its LoadInfo uses fixed storage bounded by kMaxSegments. The Module is
// allocated separately using the initial-exec allocator.
using StartupLoadModuleBase =
LoadModule<elfldltl::Elf<>, elfldltl::StaticVector<kMaxSegments>::Container,
LoadModuleInline::kNo, LoadModuleRelocInfo::kYes>;
template <class Loader>
struct StartupLoadModule : public StartupLoadModuleBase,
fbl::DoublyLinkedListable<StartupLoadModule<Loader>*> {
public:
StartupLoadModule() = delete;
StartupLoadModule(StartupLoadModule&&) = default;
template <typename... LoaderArgs>
explicit StartupLoadModule(std::string_view name, LoaderArgs&&... loader_args)
: StartupLoadModuleBase{name}, loader_{std::forward<LoaderArgs>(loader_args)...} {}
// This uses the given scratch allocator to create a new module object.
template <class Allocator, typename... LoaderArgs>
static StartupLoadModule* New(Diagnostics& diag, Allocator& allocator, std::string_view name,
LoaderArgs&&... loader_args) {
fbl::AllocChecker ac;
StartupLoadModule* module =
new (allocator, ac) StartupLoadModule{name, std::forward<LoaderArgs>(loader_args)...};
CheckAlloc(diag, ac, "temporary module data structure");
return module;
}
// Read the file and use Loader::Load on it. If at least partially
// successful, this uses the given initial-exec allocator to set up the
// passive ABI module in this->module(). The allocator only guarantees two
// mutable allocations at a time, so the caller must then promptly splice it
// into the link_map list before the next Load call allocates the next one.
template <class Allocator, class File>
StartupLoadResult Load(Diagnostics& diag, Allocator& allocator, File&& file) {
// Diagnostics sent to diag during loading will be prefixed with the module
// name, unless the name is empty as it is for the main executable.
ModuleDiagnostics module_diag(diag, this->name().str());
// Read the file header and program headers into stack buffers.
auto headers = elfldltl::LoadHeadersFromFile<elfldltl::Elf<>>(
diag, file, elfldltl::FixedArrayFromFile<Phdr, kMaxPhdrs>{});
if (!headers) [[unlikely]] {
return {};
}
auto& [ehdr_owner, phdrs_owner] = *headers;
const Ehdr& ehdr = ehdr_owner;
const cpp20::span<const Phdr> phdrs = phdrs_owner;
// Decode phdrs to fill LoadInfo and other things.
std::optional<Phdr> relro_phdr;
auto [dyn_phdr, stack_size] =
DecodeModulePhdrs(diag, phdrs, this->load_info().GetPhdrObserver(loader_.page_size()),
elfldltl::PhdrRelroObserver<elfldltl::Elf<>>(relro_phdr));
set_relro(relro_phdr);
// load_info() now has enough information to actually load the file.
if (!loader_.Load(diag, this->load_info(), file.borrow())) [[unlikely]] {
return {};
}
// Now the module's image is in memory! Allocate the Module object and
// start filling it in with pointers into the loaded image.
fbl::AllocChecker ac;
this->NewModule(allocator, ac);
CheckAlloc(diag, ac, "passive ABI module");
// Though stored as std::string_view, which isn't in general guaranteed to
// be NUL-terminated, the name always comes from a DT_NEEDED string in
// another module's DT_STRTAB, or from an empty string literal. So in fact
// it is already NUL-terminated and can be used as a C string for link_map.
this->module().link_map.name = this->name().c_str();
// This fills in the vaddr bounds and phdrs fields. Note that module.phdrs
// might remain empty if the phdrs aren't in the load image, so keep using
// the stack copy read from the file instead.
SetModuleLoadInfo(this->module(), ehdr, this->load_info(), loader_.load_bias(), memory());
// A second phdr scan is needed to decode notes now that they can be
// accessed in memory.
std::optional<elfldltl::ElfNote> build_id;
elfldltl::DecodePhdrs(diag, phdrs,
elfldltl::PhdrMemoryNoteObserver(elfldltl::Elf<>{}, memory(),
elfldltl::ObserveBuildIdNote(build_id)));
if (build_id) {
this->module().build_id = build_id->desc;
}
// Now that there is a Memory object to use, decode the dynamic section.
size_t needed_count = DecodeDynamic(diag, dyn_phdr);
// Everything is now prepared to proceed with loading dependencies
// and performing relocation.
return {
.needed_count = needed_count,
.entry = ehdr.entry + loader_.load_bias(),
.stack_size = stack_size,
};
}
void set_relro(std::optional<Phdr> relro_phdr) {
if (relro_phdr) {
relro_ = elfldltl::RelroBounds(*relro_phdr, loader_.page_size());
}
}
void ProtectRelro(Diagnostics& diag) {
ModuleDiagnostics module_diag{diag, this->name().str()};
std::ignore = loader_.ProtectRelro(diag, relro_);
}
void RelocateRelative(Diagnostics& diag) {
elfldltl::RelocateRelative(diag, memory(), reloc_info(), load_bias());
}
// Returns number of DT_NEEDED entries. See StartupLoadResult, for why that is useful.
size_t DecodeDynamic(Diagnostics& diag, const std::optional<typename Elf::Phdr>& dyn_phdr) {
elfldltl::DynamicTagCountObserver<Elf, elfldltl::ElfDynTag::kNeeded> needed;
DecodeModuleDynamic(module(), diag, memory(), dyn_phdr, needed,
elfldltl::DynamicRelocationInfoObserver(reloc_info()));
return needed.count();
}
Loader& loader() { return loader_; }
decltype(auto) memory() { return loader_.memory(); }
private:
Loader loader_; // Must be initialized by constructor.
RelroBounds relro_{};
};
} // namespace ld
#endif // LIB_LD_STARTUP_LOAD_H_