blob: f4753b85cb61ed8ff884a041037069d4ed002a7c [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.
#include <fcntl.h>
#include <lib/elfldltl/posix.h>
#include <lib/ld/memory.h>
#include <lib/stdcompat/string_view.h>
#include <lib/trivial-allocator/new.h>
#include <lib/trivial-allocator/posix.h>
#include <sys/mman.h>
#include <zircon/compiler.h>
#include <array>
#include <cassert>
#include <cerrno>
#include <cstring>
#include "allocator.h"
#include "bootstrap.h"
#include "posix.h"
#include "startup-diagnostics.h"
namespace ld {
namespace {
constexpr std::string_view kLdDebugPrefix = "LD_DEBUG=";
// This is defined in assembly, and doesn't actually use the C calling
// convention. It calls StartLd.
extern "C" [[noreturn]] void _start();
using UniqueFdFile = elfldltl::UniqueFdFile<Diagnostics>;
using Module = abi::Abi<>::Module;
using SystemPageAllocator = trivial_allocator::PosixMmap;
constexpr auto MakeStartupSystemPageAllocator(StartupData& startup) {
return SystemPageAllocator{startup.page_size};
}
auto MakeStartupScratchAllocator(SystemPageAllocator system) {
return MakeScratchAllocator(system);
}
using ScratchAllocator = decltype(MakeStartupScratchAllocator(SystemPageAllocator{}));
auto MakeStartupInitialExecAllocator(SystemPageAllocator system) {
return MakeInitialExecAllocator(system);
}
using InitialExecAllocator = decltype(MakeStartupInitialExecAllocator(SystemPageAllocator{}));
// This "loads" the executable image that's already in memory such that the
// StartupModule returned is fully populated like loading another module does.
std::pair<StartupModule*, size_t> LoadExecutable(Diagnostics& diag, StartupData& startup,
ScratchAllocator& scratch,
InitialExecAllocator& initial_exec,
uintptr_t entry, uintptr_t phdr, uintptr_t phnum) {
if (entry == reinterpret_cast<uintptr_t>(&_start)) [[unlikely]] {
// The dynamic linker was started directly as a standalone program,
// rather than via PT_INTERP.
//
// TODO(mcgrathr): The glibc dynamic linker has the useful semantics of
// handling command line args for various behavior options and for the name
// of an executable to load as if it had been executed with a PT_INTERP
// pointing at this dynamic linker.
diag.FormatError("dynamic linker executed directly, not via PT_INTERP");
__builtin_trap();
}
cpp20::span phdrs{reinterpret_cast<const Phdr*>(phdr), phnum};
auto no_phdrs = [&diag]() { diag.FormatError("no PT_PHDR in preloaded main executable!"); };
// The phdrs and entry point from auxv describe the main executable, which
// was loaded into the address space before its PT_INTERP file (this
// dynamic linker itself).
if (phdrs.empty()) [[unlikely]] {
no_phdrs();
}
// Allocate the StartupModule for the main executable.
StartupModule* main_executable =
StartupModule::New(diag, scratch, abi::Abi<>::kExecutableName, startup.page_size);
fbl::AllocChecker ac;
main_executable->NewModule(0, initial_exec, ac);
CheckAlloc(diag, ac, "passive ABI module");
Module& module = main_executable->decoded().module();
// We already have the direct pointer to the phdrs in the load image, from
// AT_PHDR even though we never see the Ehdr::phoff value.
module.phdrs = phdrs;
// Scan the phdrs for both the vaddr bounds that would normally be
// determined before loading a module and for everything that would
// normally be picked up from phdrs after loading a module.
using PhdrPhdrObserver =
elfldltl::PhdrSingletonObserver<elfldltl::Elf<>, elfldltl::ElfPhdrType::kPhdr>;
std::optional<Phdr> phdr_phdr;
auto phdr_info = DecodeModulePhdrs(
diag, phdrs, main_executable->decoded().load_info().GetPhdrObserver(startup.page_size),
PhdrPhdrObserver{phdr_phdr});
if (!phdr_info) {
return {};
}
main_executable->decoded().set_relro(phdr_info->relro_phdr, startup.page_size);
// The PT_PHDR gives the link-time view of the p_vaddr of the phdrs. Since
// we never saw the Ehdr, we can't use Ehdr::phoff to locate it among the
// segments, so comparing that p_vaddr to the AT_PHDR value is the only way
// to find the load bias.
if (!phdr_phdr) [[unlikely]] {
no_phdrs();
__builtin_unreachable();
}
SetModuleVaddrBounds(module, main_executable->load_info(), phdr - phdr_phdr->vaddr);
// The module now has enough information to use ModuleMemory. The common
// code expects StartupModule::loader() to have the image and load bias,
// so set its memory() to match. This means that destruction would munmap
// the executable image, but the object will never be destroyed anyway.
main_executable->memory() = ModuleMemory{module};
// A second phdr scan is needed to decode notes now that they can be
// accessed in memory.
elfldltl::DecodePhdrs(diag, phdrs, PhdrMemoryBuildIdObserver(main_executable->memory(), module));
if (phdr_info->tls_phdr) {
main_executable->decoded().SetTls(diag, main_executable->memory(), *phdr_info->tls_phdr, 1);
}
size_t needed_count = 0;
main_executable->set_dynamic(*main_executable->decoded().DecodeDynamic( //
diag, main_executable->memory(), phdr_info->dyn_phdr,
StartupModule::NeededCountObserver(needed_count)));
return {main_executable, needed_count};
}
[[maybe_unused]] void ProtectData(Diagnostics& diag, size_t page_size) {
auto [data_start, data_size] = DataBounds(page_size);
if (mprotect(reinterpret_cast<void*>(data_start), data_size, PROT_READ) != 0) [[unlikely]] {
diag.SystemError("cannot mprotect dynamic linker data pages", elfldltl::PosixError{errno});
}
}
} // namespace
// This gets called from the entry-point written in assembly, which just
// passes the incoming SP value as the first argument. It returns the main
// executable entry point address to jump to after unwinding the stack back
// to the starting state.
extern "C" uintptr_t StartLd(StartupStack& stack) {
StartupData startup = {
.argc = stack.argc,
.argv = stack.argv,
.envp = stack.envp(),
};
// First scan the auxv for important pointers and values.
uintptr_t phdr = 0, phnum = 0, entry = 0;
const void* vdso = nullptr;
for (const auto* av = stack.GetAuxv(); av->front() != 0; ++av) {
const auto& [tag, value] = *av;
switch (static_cast<AuxvTag>(tag)) {
case AuxvTag::kPagesz:
startup.page_size = value;
break;
case AuxvTag::kEntry:
entry = value;
break;
case AuxvTag::kSysinfoEhdr:
vdso = reinterpret_cast<const void*>(value);
break;
case AuxvTag::kPhdr:
phdr = value;
break;
case AuxvTag::kPhnum:
phnum = value;
break;
case AuxvTag::kPhent:
assert(value == sizeof(Phdr));
break;
default:
break;
}
}
// First thing, bootstrap our own dynamic linking against ourselves and the
// vDSO. For this, nothing should go wrong so use a diagnostics object that
// crashes the process at the first error. But we can still use direct
// system calls to write error messages.
auto bootstrap_diag = elfldltl::Diagnostics{
DiagnosticsReport(startup),
elfldltl::DiagnosticsPanicFlags{},
};
BootstrapModule vdso_module = BootstrapVdsoModule(bootstrap_diag, vdso, startup.page_size);
BootstrapModule self_module = BootstrapSelfModule(bootstrap_diag, vdso_module.module);
CompleteBootstrapModule(self_module.module, startup.page_size);
// Check for the LD_DEBUG environment variable.
for (char** ep = startup.envp; *ep; ++ep) {
std::string_view str = *ep;
if (str.size() > kLdDebugPrefix.size() && cpp20::starts_with(str, kLdDebugPrefix)) {
startup.ld_debug = true;
break;
}
}
// Now that things are bootstrapped, set up the main diagnostics object.
Diagnostics diag{startup};
// Set up the allocators.
auto system_page_allocator = MakeStartupSystemPageAllocator(startup);
auto scratch = MakeStartupScratchAllocator(system_page_allocator);
auto initial_exec = MakeStartupInitialExecAllocator(system_page_allocator);
// "Load" the main executable. It's usually already loaded by the system
// program loader before the dynamic linker was loaded, so this really
// consists of doing the bookkeeping that loading other modules does.
auto [main_executable, needed_count] =
LoadExecutable(diag, startup, scratch, initial_exec, entry, phdr, phnum);
auto open_file = [&diag](const elfldltl::Soname<>& soname) -> std::optional<UniqueFdFile> {
if (fbl::unique_fd fd{open(soname.c_str(), O_RDONLY)}) {
return UniqueFdFile{std::move(fd), diag};
}
if (errno != ENOENT) {
diag.SystemError("cannot open dependency ", soname.str(), ": ", elfldltl::PosixError{errno});
}
return {};
};
StartupModule::LinkModules(diag, scratch, initial_exec, main_executable, open_file,
{vdso_module, self_module}, needed_count, startup.page_size);
// Bail out before relocation if there were any loading errors.
CheckErrors(diag);
if constexpr (kProtectData) {
// Now that startup is completed, protect not only the RELRO, but also all
// the data and bss.
ProtectData(diag, startup.page_size);
}
// Bail out before handoff if any errors have been detected.
CheckErrors(diag);
return entry;
}
} // namespace ld