blob: 48d9fb54318172e413d0259b84ccb45bcbbd0bd0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "phys/elf-image.h"
#include <inttypes.h>
#include <lib/arch/cache.h>
#include <lib/elfldltl/diagnostics.h>
#include <lib/elfldltl/dynamic.h>
#include <lib/elfldltl/link.h>
#include <lib/fit/defer.h>
#include <zircon/assert.h>
#include <zircon/limits.h>
#include <ktl/atomic.h>
#include <ktl/move.h>
#include <ktl/variant.h>
#include <phys/address-space.h>
#include <phys/allocation.h>
#include <phys/symbolize.h>
#include <ktl/enforce.h>
namespace {
constexpr ktl::string_view kDiagnosticsPrefix = "Cannot load ELF image: ";
// TODO(mcgrathr): BFD ld produces a spurious empty .eh_frame with its own
// empty PT_LOAD segment. This is harmless enough to the actual layout,
// but triggers a FormatWarning.
#ifdef __clang__
auto GetDiagnostics() { return elfldltl::PanicDiagnostics(kDiagnosticsPrefix); }
#else
constexpr auto kPanicReport = elfldltl::PanicDiagnosticsReport(kDiagnosticsPrefix);
using DiagBase = elfldltl::Diagnostics<decltype(kPanicReport), elfldltl::DiagnosticsPanicFlags>;
struct NoWarnings : public DiagBase {
constexpr NoWarnings() : DiagBase(kPanicReport) {}
static constexpr auto FormatWarning = [](auto&&...) { return true; };
};
auto GetDiagnostics() { return NoWarnings(); }
#endif
} // namespace
fit::result<ElfImage::Error> ElfImage::Init(ElfImage::BootfsDir dir, ktl::string_view name,
bool relocated) {
name_ = name;
auto read_file = [this, &dir]() -> fit::result<Error> {
if (auto found = dir.find(name_); found != dir.end()) {
// Singleton ELF file, no patches.
dir.ignore_error();
image_.set_image(found->data);
return fit::ok();
}
BootfsDir subdir;
if (auto result = dir.subdir(name_); result.is_ok()) {
subdir = ktl::move(result).value();
} else {
return result.take_error();
}
// Find the ELF file in the directory.
auto it = subdir.find(kImageName);
if (it == subdir.end()) {
if (auto result = subdir.take_error(); result.is_error()) {
return result.take_error();
}
return fit::error{Error{
.reason = "ELF file not found in image directory"sv,
.filename = kImageName,
}};
}
subdir.ignore_error();
image_.set_image(it->data);
// Now find the code patches.
if (auto result = patcher_.Init(subdir); result.is_error()) {
return result.take_error();
}
return fit::ok();
};
if (auto result = read_file(); result.is_error()) {
return result;
}
auto diagnostics = GetDiagnostics();
auto phdr_allocator = elfldltl::NoArrayFromFile<elfldltl::Elf<>::Phdr>();
auto headers =
elfldltl::LoadHeadersFromFile<elfldltl::Elf<>>(diagnostics, image_, phdr_allocator);
auto [ehdr, phdrs] = *headers;
ktl::optional<elfldltl::Elf<>::Phdr> relro, dynamic, interp;
elfldltl::DecodePhdrs( //
diagnostics, phdrs, load_info_.GetPhdrObserver(ZX_PAGE_SIZE),
elfldltl::PhdrFileNoteObserver( //
elfldltl::Elf<>(), image_, elfldltl::NoArrayFromFile<ktl::byte>(),
elfldltl::ObserveBuildIdNote(build_id_)),
elfldltl::PhdrRelroObserver<elfldltl::Elf<>>(relro),
elfldltl::PhdrDynamicObserver<elfldltl::Elf<>>(dynamic),
elfldltl::PhdrInterpObserver<elfldltl::Elf<>>(interp));
image_.set_base(load_info_.vaddr_start());
entry_ = ehdr.entry;
if (relocated) {
// In the phys context, all the relocations are done in place before the
// image is considered "loaded". Update the load segments to indicate
// RELRO protections have already been applied.
load_info_.ApplyRelro(diagnostics, relro, ZX_PAGE_SIZE, true);
}
if (dynamic) {
dynamic_ = *image_.ReadArray<elfldltl::Elf<>::Dyn>(
dynamic->offset, dynamic->filesz / sizeof(elfldltl::Elf<>::Dyn));
}
if (interp) {
auto chars = image_.ReadArrayFromFile<char>(interp->offset, elfldltl::NoArrayFromFile<char>(),
interp->filesz);
ZX_ASSERT_MSG(chars, "PT_INTERP has invalid offset range [%#" PRIxPTR ", %#" PRIxPTR ")",
static_cast<uintptr_t>(interp->offset),
static_cast<uintptr_t>(interp->offset + interp->filesz));
ZX_ASSERT_MSG(!chars->empty(), "PT_INTERP has zero filesz");
ZX_ASSERT_MSG(chars->back() == '\0', "PT_INTERP missing NUL terminator");
interp_.emplace(chars->data(), chars->size() - 1);
}
return fit::ok();
}
ktl::span<ktl::byte> ElfImage::GetBytesToPatch(const code_patching::Directive& patch) {
ktl::span<ktl::byte> file = image_.image();
ZX_ASSERT_MSG(patch.range_start >= image_.base() && file.size() >= patch.range_size &&
file.size() - patch.range_size >= patch.range_start - image_.base(),
"Patch ID %#" PRIx32 " range [%#" PRIx64 ", %#" PRIx64
") is outside file bounds [%#" PRIxPTR ", %#" PRIxPTR ")",
patch.id, patch.range_start, patch.range_start + patch.range_size, image_.base(),
image_.base() + file.size());
return file.subspan(static_cast<size_t>(patch.range_start - image_.base()), patch.range_size);
}
Allocation ElfImage::Load(ktl::optional<uint64_t> relocation_address, bool in_place_ok) {
auto endof = [](const auto& last) { return last.offset() + last.filesz(); };
const uint64_t load_size = ktl::visit(endof, load_info_.segments().back());
auto update_load_address_and_symbolize = fit::defer([relocation_address, this]() {
// Update the load address before having emitting otherwise-misleading
// markup.
set_load_address(relocation_address.value_or(physical_load_address()));
gSymbolize->OnLoad(*this);
});
if (in_place_ok && CanLoadInPlace()) {
// TODO(https://fxbug.dev/42065186): Could have a memalloc::Pool feature to
// reclassify the memory range to the new type.
// The full vaddr_size() fits in the pages the BOOTFS file occupies. If
// there is any bss (memsz > filesz), it may overlap with some nonzero file
// contents and not just the BOOTFS page-alignment padding. Zero it all.
ZX_DEBUG_ASSERT(ZBI_BOOTFS_PAGE_ALIGN(image_.image().size_bytes()) >= load_info_.vaddr_size());
memset(image_.image().data() + load_size, 0,
static_cast<size_t>(load_info_.vaddr_size() - load_size));
return {};
}
fbl::AllocChecker ac;
Allocation image =
Allocation::New(ac, memalloc::Type::kPhysElf, load_info_.vaddr_size(), ZX_PAGE_SIZE);
if (!ac.check()) {
ZX_PANIC("cannot allocate phys ELF load image of %#zx bytes",
static_cast<size_t>(load_info_.vaddr_size()));
}
ZX_ASSERT_MSG(load_size <= image.size_bytes(), "load_size %#" PRIx64 " > allocation size %#zx",
load_size, image.size_bytes());
// Copy the full load image into the new allocation. The load_size is
// page-rounded and thus can go past the formal end of the file, indicating
// how mapping from a file would work. To get the equivalent effect, just
// fill the remainder of the allocated page with zero while zeroing any
// following bss (memsz > filesz).
const size_t copy = ktl::min(static_cast<size_t>(load_size), image_.image().size_bytes());
memcpy(image.get(), image_.image().data(), copy);
memset(image.get() + copy, 0, load_info_.vaddr_size() - copy);
// Hereafter image_ refers to the now-loaded image, not the original file.
image_.set_image(image.data());
// Ensure that by the time we return, it's safe to jump into this code.
// Later relocation won't touch executable segments, so additional
// synchronization should not be required unless the image is copied
// elsewhere in physical memory.
ktl::atomic_signal_fence(ktl::memory_order_seq_cst);
arch::GlobalCacheConsistencyContext cache;
cache.SyncRange(physical_load_address(), load_info_.vaddr_size());
return image;
}
void ElfImage::Relocate() {
ZX_DEBUG_ASSERT(load_bias_); // The load address has already been chosen.
if (!dynamic_.empty()) {
auto diagnostics = GetDiagnostics();
elfldltl::RelocationInfo<elfldltl::Elf<>> reloc_info;
elfldltl::DecodeDynamic(diagnostics, image_, dynamic_,
elfldltl::DynamicRelocationInfoObserver(reloc_info));
ZX_ASSERT(reloc_info.rel_symbolic().empty());
ZX_ASSERT(reloc_info.rela_symbolic().empty());
bool relocated = elfldltl::RelocateRelative(diagnostics, image_, reloc_info, *load_bias_);
ZX_ASSERT(relocated);
// Make sure everything is written before the image is used as code.
ktl::atomic_signal_fence(ktl::memory_order_seq_cst);
}
}
fit::result<AddressSpace::MapError> ElfImage::MapInto(AddressSpace& aspace) {
fit::result<AddressSpace::MapError> result = fit::ok();
load_info().VisitSegments([&](const auto& segment) {
uint64_t vaddr = segment.vaddr() + load_bias();
uint64_t paddr = physical_load_address() + segment.offset();
const AddressSpace::MapSettings settings = {
.access =
{
.readable = segment.readable(),
.writable = segment.writable(),
.executable = segment.executable(),
},
.memory = kArchNormalMemoryType,
};
result = aspace.Map(vaddr, segment.memsz(), paddr, settings);
return result.is_ok();
});
return result;
}
void ElfImage::AssertInterpMatchesBuildId(ktl::string_view prefix,
const elfldltl::ElfNote& build_id_note) {
ZX_DEBUG_ASSERT(build_id_note.IsBuildId());
ZX_ASSERT_MSG(build_id_note.desc.size() <= kMaxBuildIdLen,
"%.*s: reference build ID of %zu bytes > max supported %zu",
static_cast<int>(prefix.size()), prefix.data(), //
build_id_note.desc.size(), kMaxBuildIdLen);
char build_id_hex_buffer[kMaxBuildIdLen * 2];
ktl::string_view build_id_hex = build_id_note.HexString(build_id_hex_buffer);
ZX_ASSERT_MSG(interp_, "%.*s: ELF image has no PT_INTERP (expected %.*s)",
static_cast<int>(prefix.size()), prefix.data(),
static_cast<int>(build_id_hex.size()), build_id_hex.data());
ZX_ASSERT_MSG(*interp_ == build_id_hex,
"%.*s: ELF image PT_INTERP (size %zu) %.*s != expected (size %zu) %.*s",
static_cast<int>(prefix.size()), prefix.data(), //
interp_->size(), //
static_cast<int>(interp_->size()), interp_->data(), //
build_id_hex.size(), //
static_cast<int>(build_id_hex.size()), build_id_hex.data());
}
void ElfImage::InitSelf(ktl::string_view name, elfldltl::DirectMemory& memory, uintptr_t load_bias,
const elfldltl::Elf<>::Phdr& load_segment,
ktl::span<const ktl::byte> build_id_note) {
image_.set_image(memory.image());
image_.set_base(memory.base());
load_bias_ = load_bias;
auto diag = elfldltl::PanicDiagnostics();
ZX_ASSERT(load_info_.AddSegment(diag, ZX_PAGE_SIZE, load_segment));
elfldltl::ElfNoteSegment<> notes(build_id_note);
ZX_DEBUG_ASSERT(notes.begin() != notes.end());
ZX_DEBUG_ASSERT(++notes.begin() == notes.end());
build_id_ = *notes.begin();
name_ = name;
OnHandoff();
}
void ElfImage::PrintPatch(const code_patching::Directive& patch,
ktl::initializer_list<ktl::string_view> strings) const {
printf("%s: code-patching on %.*s: ", gSymbolize->name(), static_cast<int>(name_.size()),
name_.data());
for (ktl::string_view str : strings) {
stdout->Write(str);
}
printf(": [%#" PRIx64 ", %#" PRIx64 ")\n", patch.range_start,
patch.range_start + patch.range_size);
}