| // Copyright 2022 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_LOAD_H_ |
| #define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_LOAD_H_ |
| |
| #include <cassert> |
| #include <functional> |
| #include <optional> |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| |
| #include "constants.h" |
| #include "internal/load-segment-types.h" |
| #include "layout.h" |
| #include "phdr.h" |
| |
| namespace elfldltl { |
| |
| // Read the ELF file header (Ehdr) from an ELF file using the File API (see |
| // memory.h), and validate it for loading on this machine. |
| // |
| // The first template parameter gives the elfldltl::Elf<...> layout to use, |
| // i.e. usually elfldltl::Elf<> for native loading. The rest are deduced. If |
| // the optional final argument is passed, it can be std::nullopt to accept any |
| // machine or another ElfMachine value to match rather than the native machine. |
| // |
| // This returns the return value of calling file.ReadFromFile<Ehdr>, |
| // i.e. some type std::optional<E> where `const Ehdr& ehdr = <E object>;` |
| // works and the E object owns the storage ehdr points into. |
| template <class Elf, class Diagnostics, class File> |
| constexpr auto LoadEhdrFromFile(Diagnostics& diagnostics, File& file, |
| std::optional<ElfMachine> machine = ElfMachine::kNative) |
| -> decltype(file.template ReadFromFile<typename Elf::Ehdr>(0)) { |
| using namespace std::literals::string_view_literals; |
| |
| using Ehdr = typename Elf::Ehdr; |
| |
| if (auto read_ehdr = file.template ReadFromFile<Ehdr>(0)) [[likely]] { |
| const Ehdr& ehdr = *read_ehdr; |
| if (ehdr.Loadable(diagnostics, machine)) [[likely]] { |
| return read_ehdr; |
| } |
| } else { |
| diagnostics.FormatError("cannot read ELF file header"sv); |
| } |
| |
| return {}; |
| } |
| |
| // Read the ELF file header (Ehdr) and program headers (Phdr) from an ELF file |
| // using the File API (see memory.h), and validate for loading on this machine. |
| // |
| // This just combines LoadEhdrFromFile and ReadPhdrsFromFile (see phdr.h). |
| // It returns std::optional<std::pair<E, P>> where LoadEhdrFromFile returns |
| // std::optional<E> and ReadPhdrsFromFile returns std::optional<P>. e.g., |
| // ``` |
| // auto headers = elfldltl::LoadHeadersFromFile<elfldltl::Elf<>>(...); |
| // if (headers) { |
| // auto [ehdr_owner, phdrs_owner] = *headers; |
| // const Ehdr& ehdr = ehdr_owner; |
| // const cpp20::span<const Phdr> phdrs = phdrs_owner; |
| // ... |
| // } |
| // ``` |
| template <class Elf, class Diagnostics, class File, typename PhdrAllocator> |
| constexpr auto LoadHeadersFromFile(Diagnostics& diagnostics, File& file, |
| PhdrAllocator&& phdr_allocator, |
| std::optional<ElfMachine> machine = ElfMachine::kNative) |
| -> decltype(std::make_optional(std::make_pair( |
| *file.template ReadFromFile<typename Elf::Ehdr>(0), |
| *file.template ReadArrayFromFile<typename Elf::Phdr>(0, phdr_allocator, 0)))) { |
| if (auto read_ehdr = LoadEhdrFromFile<Elf>(diagnostics, file, machine)) [[likely]] { |
| const typename Elf::Ehdr& ehdr = *read_ehdr; |
| if (auto read_phdrs = ReadPhdrsFromFile(diagnostics, file, |
| std::forward<PhdrAllocator>(phdr_allocator), ehdr)) { |
| return std::make_pair(*std::move(read_ehdr), *std::move(read_phdrs)); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| // This does the same work as LoadHeadersFromFile, but handles different |
| // ELFCLASS (and optionally, ELFDATA) formats in the ELF file. It returns |
| // false if the file is invalid, or else returns the result of invoking the |
| // callback as `bool(const Ehdr&, cpp20::span<const Phdr>)` for the particular |
| // Elf64<...> or Elf32<...> instantiation chosen. |
| // |
| // If the optional expected_data argument is provided, it can be std::nullopt |
| // to permit callbacks with either data format (byte order) as well as either |
| // class. The final optional argument gives the machine architecture to match, |
| // and likewise can be std::nullopt to accept any machine. |
| template <template <typename> class PhdrAllocator, class Diagnostics, class File, typename Callback> |
| constexpr bool WithLoadHeadersFromFile(Diagnostics& diagnostics, File& file, Callback&& callback, |
| std::optional<ElfData> expected_data = ElfData::kNative, |
| std::optional<ElfMachine> machine = ElfMachine::kNative) { |
| using namespace std::literals::string_view_literals; |
| |
| using Ehdr64Lsb = typename Elf64<ElfData::k2Lsb>::Ehdr; |
| using Ehdr64Msb = typename Elf64<ElfData::k2Msb>::Ehdr; |
| |
| // Below we'll call this with Elf<...>::Ehdr depending on the format. |
| auto load_headers = [&](const auto& ehdr) -> bool { |
| using Ehdr = std::decay_t<decltype(ehdr)>; |
| using Elf = typename Ehdr::ElfLayout; |
| using Phdr = typename Elf::Phdr; |
| if (!ehdr.Loadable(diagnostics, machine)) [[unlikely]] { |
| return false; |
| } |
| PhdrAllocator<Phdr> phdr_allocator; |
| auto read_phdrs = ReadPhdrsFromFile(diagnostics, file, phdr_allocator, ehdr); |
| if (!read_phdrs) [[unlikely]] { |
| return false; |
| } |
| cpp20::span<const Phdr> phdrs = *read_phdrs; |
| return std::invoke(std::forward<Callback>(callback), ehdr, phdrs); |
| }; |
| |
| // Below we'll call this with Elf64<...>::Ehdr depending on the byte order. |
| auto check_class = [&](const auto& probe_ehdr) -> bool { |
| using ProbeEhdr = std::decay_t<decltype(probe_ehdr)>; |
| using ProbeElf = typename ProbeEhdr::ElfLayout; |
| using Ehdr32 = typename Elf32<ProbeElf::kData>::Ehdr; |
| // If the EI_CLASS field is invalid, it doesn't matter which one we use |
| // because it won't get past Valid() either way. |
| return probe_ehdr.elfclass == ElfClass::k64 |
| ? load_headers(probe_ehdr) |
| : load_headers(reinterpret_cast<const Ehdr32&>(probe_ehdr)); |
| }; |
| |
| // Read an ELFCLASS64 header, which is larger. We'll only examine the |
| // e_ident fields that are common to all the header layouts until we've |
| // determined the right Ehdr type to use. |
| auto read_probe_ehdr = file.template ReadFromFile<Elf64<>::Ehdr>(0); |
| if (!read_probe_ehdr) [[unlikely]] { |
| diagnostics.FormatError("cannot read ELF file header"sv); |
| return false; |
| } |
| const Elf64<>::Ehdr& probe_ehdr = *read_probe_ehdr; |
| |
| if (!expected_data) { |
| // If the EI_DATA field is invalid, it doesn't matter which one we use |
| // because won't get past Valid() either way. |
| return probe_ehdr.elfdata == ElfData::k2Lsb |
| ? check_class(reinterpret_cast<const Ehdr64Lsb&>(probe_ehdr)) |
| : check_class(reinterpret_cast<const Ehdr64Msb&>(probe_ehdr)); |
| } |
| |
| // The caller accepts only one byte order, so only use that instantiation. |
| switch (*expected_data) { |
| case ElfData::k2Lsb: |
| return check_class(reinterpret_cast<const Ehdr64Lsb&>(probe_ehdr)); |
| case ElfData::k2Msb: |
| return check_class(reinterpret_cast<const Ehdr64Msb&>(probe_ehdr)); |
| } |
| } |
| |
| // The default template parameter for LoadInfo; see below. |
| template <class SegmentType> |
| using NoSegmentWrapper = SegmentType; |
| |
| // elfldltl::LoadInfo<Elf, Container, ...> holds all the information an ELF |
| // loader needs to know. It holds representations of the PT_LOAD segments |
| // in terms that matter to loading, using Container<Segment, ...>. The |
| // number of PT_LOAD segments and segments().size() do not necessarily |
| // match exactly. Rather, each Segment is a normalized loading step. |
| // |
| // Container is a vector-like template, such as from an adapter like |
| // elfldltl::StdContainer<std::vector>::Container (container.h) or a special |
| // implementation like elfldltl::StaticVector<N>::Container (static-vector.h). |
| // It must support emplace_back, emplace, and erase methods. |
| // |
| // Segment is just an alias for std::variant<...> of the several specific |
| // types. All segment types have vaddr() and memsz(); most have offset() and |
| // filesz(). All these are normalized to whole pages. Segments with memsz == 0 |
| // will always be elided. |
| // |
| // * ConstantSegment is loaded directly from the file (or was relocated); |
| // * It also has `bool readable()` and `bool executable()`. |
| // * It has `bool relro()` that only arises after ApplyRelro(..., true). |
| // |
| // * DataSegment is loaded from the file but writable (usually copy-on-write). |
| // |
| // * DataWithZeroFillSegment is the same but has memsz() > filesz(). |
| // * The bytes past filesz() are zero-fill, not from the file. |
| // |
| // * ZeroFillSegment has only vaddr() and memsz(). |
| // |
| // The optional SegmentWrapper template template parameter can be provided to |
| // modify the segment types above. Each of the named segment types will be the |
| // instantiation of that template with the original built-in segment type. |
| // This can override the methods and/or add additional ones. The segments will |
| // be constructed only as the built-in types are constructed, so subclasses |
| // should just delegate to the base class constructors (`using Base::Base;`) |
| // with any new members default-constructed. elfldltl::NoSegmentWrapper is a |
| // do-nothing template alias that's the default for SegmentWrapper. |
| // |
| // In addition to the standard members, a segment subclass should override the |
| // (templated) `bool CanMerge(const auto&) const` method to return false if |
| // the segment cannot be merged with an adjacent semantic of compatible flags. |
| // Such merging may reconstruct a new segment object and remove the old two, |
| // default-constructing any subclass members and losing such state. |
| // |
| // The default segment types are copyable, but the SegmentWrapper types can be |
| // move-only. Each *Segment type also has a method: |
| // ``` |
| // static fit::result<bool, Type> Copy(Diagnostics& diag, const OtherSegment& other_segment); |
| // ``` |
| |
| // This constructs *Segment by copying from a segment of the corresponding |
| // *Segment type possibly from some different LoadInfo<...> instantiation. In |
| // the default case, this is no different from copy construction. But a |
| // segment subclass must override this with its own Copy method that also |
| // copies its additional state. Copying can fail, using the Diagnostics object |
| // and returning the fit::error<bool> of whatever its *Error calls return. |
| // |
| // The CopyFrom method copies each segment that way to initialize the object |
| // from any other LoadInfo<...> instantiation whose segments are accepted by |
| // copied-to the SegmentWrapper types' Copy methods. That could be just its |
| // own types that are move-only but can be copied with costlier work that might |
| // fail, or it could be a different compatible SegmentWrapper. The default |
| // *Segment::Copy functions will accept any corresponding segment type but will |
| // just ignore any extra state added by the other SegmentWrapper. |
| // |
| // The GetPhdrObserver method is used with elfldltl::DecodePhdrs (see phdr.h) |
| // to call AddSegment, which can also be called directly with a valid sequence |
| // of PT_LOAD segments. |
| // |
| // The RelroBounds function returns the normalized [start, end) of RELRO so |
| // protection can be applied there. The returned LoadInfo::Region object also |
| // has size() and empty() for convenience. |
| // |
| // The ApplyRelro method uses this to adjust the segments for uses where |
| // relocation precedes loading, after all segments have been added. |
| // |
| // After adjustment, VisitSegments can be used to iterate over segments() |
| // using std::visit. |
| // |
| template <class Elf, template <typename> class Container, |
| PhdrLoadPolicy Policy = PhdrLoadPolicy::kBasic, |
| template <class SegmentType> class SegmentWrapper = NoSegmentWrapper> |
| class LoadInfo { |
| private: |
| using Types = internal::LoadSegmentTypes<typename Elf::size_type>; |
| |
| public: |
| using size_type = typename Elf::size_type; |
| using Region = typename Types::Region; |
| using Phdr = typename Elf::Phdr; |
| |
| using ConstantSegment = SegmentWrapper<typename Types::template ConstantSegment<Policy>>; |
| using DataSegment = SegmentWrapper<typename Types::template DataSegment<Policy>>; |
| using DataWithZeroFillSegment = |
| SegmentWrapper<typename Types::template DataWithZeroFillSegment<Policy>>; |
| using ZeroFillSegment = SegmentWrapper<typename Types::ZeroFillSegment>; |
| |
| using Segment = |
| std::variant<ConstantSegment, DataSegment, DataWithZeroFillSegment, ZeroFillSegment>; |
| |
| static_assert(std::is_move_constructible_v<Segment> || std::is_copy_constructible_v<Segment>); |
| |
| // LoadInfo is default-constructible. Whether it's movable depends on the |
| // Container type. Whether it's copyable depends both on the Container type |
| // and on the SegmentWrapper types. |
| // |
| // The CopyFrom method initializes a default-constructed LoadInfo by copying |
| // from another LoadInfo. If this LoadInfo instantiation is copyable, then |
| // there's probably no need to use it. However, it can be used to copy |
| // either from this LoadInfo type when it's not copyable, or from a different |
| // LoadInfo<Elf, ...> instantiation that can use a different container and/or |
| // different SegmentWrapper types. This object's SegmentWrapper types must |
| // support Copy calls with the corresponding other Loadinfo::*Segment type. |
| template <class Diagnostics, |
| // The remaining template parameters are deduced from the |
| // LoadInfo<...> instantiation of the argument, which could differ. |
| template <typename> class OtherContainer, PhdrLoadPolicy OtherPolicy, |
| template <class> class OtherWrapper> |
| bool CopyFrom(Diagnostics& diag, |
| const LoadInfo<Elf, OtherContainer, OtherPolicy, OtherWrapper>& other) { |
| using Other = LoadInfo<Elf, OtherContainer, OtherPolicy, OtherWrapper>; |
| |
| vaddr_start_ = other.vaddr_start(); |
| vaddr_size_ = other.vaddr_size(); |
| |
| segments_.clear(); |
| segments_.reserve(other.segments().size()); |
| auto copy_segment = [this, &diag](const auto& other_segment) -> bool { |
| using OtherSegment = std::decay_t<decltype(other_segment)>; |
| using NewSegment = SegmentTypeFromOther<Other, OtherSegment>; |
| auto new_segment = NewSegment::Copy(diag, other_segment); |
| if (new_segment.is_error()) [[unlikely]] { |
| return new_segment.error_value(); |
| } |
| return this->AddSegment(diag, *std::move(new_segment)); |
| }; |
| return other.VisitSegments(copy_segment); |
| } |
| |
| constexpr Container<Segment>& segments() { return segments_; } |
| constexpr const Container<Segment>& segments() const { return segments_; } |
| |
| constexpr size_type vaddr_start() const { return vaddr_start_; } |
| |
| constexpr size_type vaddr_size() const { return vaddr_size_; } |
| |
| // Add a PT_LOAD segment. Merge with the preceding segment if they are |
| // adjacent and compatible, unless merge=false. |
| template <class Diagnostics> |
| constexpr bool AddSegment(Diagnostics& diagnostics, size_type page_size, const Phdr& phdr, |
| bool merge = true) { |
| // Normalize the file and memory bounds to whole pages. |
| auto [offset, filesz] = PageBounds(page_size, phdr.offset, phdr.filesz); |
| auto [vaddr, memsz] = PageBounds(page_size, phdr.vaddr, phdr.memsz); |
| |
| // Choose which type of segment this should be. |
| |
| if (memsz == 0) [[unlikely]] { |
| return true; |
| } |
| if (!(phdr.flags() & Phdr::kWrite)) { |
| return AddSegment(diagnostics, ConstantSegment(offset, vaddr, memsz, phdr.flags), merge); |
| } |
| if (phdr.filesz == 0) { |
| return AddSegment(diagnostics, ZeroFillSegment(vaddr, memsz), merge); |
| } |
| if (phdr.memsz > phdr.filesz) { |
| return AddSegment( |
| diagnostics, |
| DataWithZeroFillSegment(offset, vaddr, memsz, phdr.offset + phdr.filesz - offset), merge); |
| } |
| return AddSegment(diagnostics, DataSegment(offset, vaddr, memsz, filesz), merge); |
| } |
| |
| // Add a Segment or *Segment object already constructed. |
| template <class Diagnostics, class NewSegment> |
| constexpr bool AddSegment(Diagnostics& diagnostics, NewSegment new_segment, bool merge = true) { |
| // Merge with the last segment if possible, or else append a new one. |
| // SegmentMerger::Merge overloads match each specific type as it's created |
| // below. It only merges when it could copy, it never merges when the new |
| // segment would have to be moved. So it takes const& even when the |
| // segment type is move-only. |
| if ((merge && !segments_.empty() && SegmentMerger::Merge(segments_.back(), new_segment)) || |
| segments_.emplace_back(diagnostics, internal::kTooManyLoads, std::move(new_segment))) { |
| // If this call didn't come from the PhdrObserver then it might be |
| // extending the original vaddr_size_. |
| RecomputeVaddrSize(); |
| return true; |
| } |
| return false; |
| } |
| |
| // Get an ephemeral object to pass to elfldltl::DecodePhdrs. The returned |
| // observer object must not outlive this LoadInfo object. The optional |
| // merge=false argument prevents merging adjacent segments that are |
| // apparently compatible. This can be avoided if it will be done later after |
| // possibly changing segments' mergeability, as when ApplyRelro is used. |
| constexpr auto GetPhdrObserver(size_type page_size, bool merge = true) { |
| auto add_segment = [this, page_size, merge](auto& diagnostics, const Phdr& phdr) { |
| return this->AddSegment(diagnostics, page_size, phdr, merge); |
| }; |
| return GetPhdrObserver(page_size, add_segment); |
| } |
| |
| constexpr Segment RemoveLastSegment() { |
| assert(!segments_.empty()); |
| Segment segment = std::move(segments_.back()); |
| segments_.pop_back(); |
| RecomputeVaddrSize(); |
| return segment; |
| } |
| |
| // Iterate over segments() by calling std::visit(visitor, segment). |
| // Return false the first time the visitor returns false. |
| template <typename T> |
| constexpr bool VisitSegments(T&& visitor) { |
| return VisitAllOf(std::forward<T>(visitor), segments_); |
| } |
| |
| template <typename T> |
| constexpr bool VisitSegments(T&& visitor) const { |
| return VisitAllOf(std::forward<T>(visitor), segments_); |
| } |
| |
| template <typename T> |
| static constexpr bool VisitSegment(T&& visitor, const Segment& segment) { |
| return internal::Visit(visitor, segment); |
| } |
| |
| constexpr typename Container<Segment>::const_iterator FindSegment(size_type vaddr) const { |
| auto within_bounds = [vaddr](const auto& segment) { |
| return segment.vaddr() <= vaddr && vaddr < segment.vaddr() + segment.memsz(); |
| }; |
| |
| auto it = std::lower_bound( |
| segments_.begin(), segments_.end(), vaddr, [&](const auto& segment, size_type vaddr) { |
| // Pass over segments where its entire vaddr range is less than vaddr, |
| // subtracting 1 from vaddr + memsz to exclude the vaddr of the next segment. |
| // This relies on the guarantee from LoadInfo::AddSegment that all segments' memsz > 0. |
| return internal::Visit( |
| [vaddr](const auto& s) { return s.vaddr() + s.memsz() - 1 < vaddr; }, segment); |
| }); |
| |
| if (it != segments_.end() && !internal::Visit(within_bounds, *it)) { |
| it = segments_.end(); |
| } |
| |
| return it; |
| } |
| |
| // When loading before relocation, the RelroBounds() region can just be made |
| // read-only in memory after relocation. Partial pages in the RELRO region are |
| // excluded from RelroBounds, as protections can only be applied per-page. |
| // TODO(https://fxbug.dev/42074388): Address the discrepancy between our round-up behavior and |
| // glibc's round-down behavior for RELRO start. |
| static constexpr Region RelroBounds(const std::optional<Phdr>& relro, size_type page_size) { |
| Region region; |
| if (relro) { |
| region.start = relro->vaddr; |
| if (region.start & (page_size - 1)) { |
| region.start = (region.start + page_size - 1) & -page_size; |
| } |
| region.end = (relro->vaddr + relro->memsz) & -page_size; |
| } |
| return region; |
| } |
| |
| // Apply RELRO for loading after relocation, adjusting the segments to |
| // promote RELRO to read-only. If the merge_ro flag is true, then the RELRO |
| // segment can be merged with an adjacent true read-only segment. This is |
| // appropriate if relocations have been applied in place to the same image; |
| // whether merged or not, the RELRO segment will be part of a ConstantSegment |
| // with relro() false. If already-relocated segments are stored separately |
| // from true read-only segments (such as in a COW mirror of the original |
| // portion of the file), the RELRO segment instead becomes a ConstantSegment |
| // with relro() true. |
| template <class Diagnostics> |
| constexpr bool ApplyRelro(Diagnostics& diagnostics, const std::optional<Phdr>& relro, |
| size_type page_size, bool merge_ro) { |
| using namespace std::literals::string_view_literals; |
| |
| constexpr std::string_view kMissing = "PT_GNU_RELRO not in any data segment"; |
| |
| const Region region = RelroBounds(relro, page_size); |
| if (region.empty()) { |
| return true; |
| } |
| |
| auto is_relro = [region](const auto& segment) { |
| return region.start >= segment.vaddr() && region.end <= segment.vaddr() + segment.memsz(); |
| }; |
| |
| for (auto it = segments().begin(); it != segments().end(); ++it) { |
| Segment& segment = *it; |
| |
| // This does the same work in two different instantiations for |
| // DataSegment and DataWithZeroFillSegment. |
| auto check_relro = [&](auto& data) -> std::optional<bool> { |
| // There is only one RELRO region and segments are in ascending order. |
| // If we've passed it, there isn't one to find. |
| if (data.vaddr() >= region.end) [[unlikely]] { |
| return diagnostics.FormatError(kMissing); |
| } |
| |
| if (data.vaddr() + data.memsz() <= region.start) { |
| return std::nullopt; // Keep looking. |
| } |
| |
| if (region.start > data.vaddr()) [[unlikely]] { |
| return diagnostics.FormatError("PT_GNU_RELRO not at segment start"sv); |
| } |
| |
| // This is the segment containing RELRO. Passing both it and data is |
| // here redundant in that they're the same underlying Segment pointer; |
| // but this dispatches to the correct FixupRelro instantiation for the |
| // two data segment types. |
| return FixupRelro(diagnostics, it, data, region.size(), merge_ro); |
| }; |
| |
| std::optional<bool> done; |
| if (auto* data = std::get_if<DataSegment>(&segment)) { |
| done = check_relro(*data); |
| } else if (auto* bss = std::get_if<DataWithZeroFillSegment>(&segment)) { |
| done = check_relro(*bss); |
| } else if (diagnostics.extra_checking() && internal::Visit(is_relro, segment)) { |
| return diagnostics.FormatError("PT_GNU_RELRO applied to non-data segment"sv); |
| } |
| if (done) { |
| return *done; |
| } |
| } |
| |
| return diagnostics.FormatError(kMissing); |
| } |
| |
| // This uses the symbolizer_markup::Writer API to emit the contextual |
| // elements describing this ELF module. The ID number should be unique among |
| // modules in the same address space, i.e. since the last Reset() in the same |
| // markup output stream. |
| template <class Writer> |
| Writer& SymbolizerContext(Writer& writer, unsigned int id, std::string_view name, |
| cpp20::span<const std::byte> build_id, size_type load_address, |
| std::string_view prefix = {}) const { |
| writer.Prefix(prefix).ElfModule(id, name, build_id).Newline(); |
| VisitSegments([&](const auto& segment) { |
| size_type load_vaddr = segment.vaddr() - vaddr_start() + load_address; |
| writer.Prefix(prefix) |
| .LoadImageMmap(load_vaddr, segment.memsz(), id, |
| {.read = segment.readable(), |
| .write = segment.writable(), |
| .execute = segment.executable()}, |
| segment.vaddr()) |
| .Newline(); |
| return true; |
| }); |
| return writer; |
| } |
| |
| private: |
| using SegmentMerger = internal::SegmentMerger<LoadInfo>; |
| |
| static constexpr size_t kTypeCount = std::variant_size_v<Segment>; |
| |
| // This has a Type alias for the *Segment type that corresponds to |
| // OtherSegment where OtherVariant is the other LoadInfo<...>::Segment type. |
| template <class OtherVariant, class OtherSegment, size_t I> |
| struct SegmentTypeFromOtherHelper { |
| using TryOther = std::variant_alternative_t<I, OtherVariant>; |
| static constexpr bool kMatch = std::is_same_v<TryOther, OtherSegment>; |
| |
| using This = std::variant_alternative_t<I, Segment>; |
| using Next = SegmentTypeFromOtherHelper<OtherVariant, OtherSegment, I + 1>; |
| |
| using Type = std::conditional_t<kMatch, This, typename Next::Type>; |
| }; |
| |
| // This specialization prevents attempting an invalid index. It will only be |
| // instantiated in the not-chosen argument of the std::conditional_t, but its |
| // Type alias still has to be a valid type for that. |
| template <class OtherVariant, class OtherSegment> |
| struct SegmentTypeFromOtherHelper<OtherVariant, OtherSegment, kTypeCount> { |
| using Type = void; |
| }; |
| |
| // This is used by Copy. |
| template <class Other, class OtherSegment> |
| using SegmentTypeFromOther = |
| typename SegmentTypeFromOtherHelper<typename Other::Segment, OtherSegment, 0>::Type; |
| |
| // Making this static with a universal reference parameter avoids having to |
| // repeat the actual body in the const and non-const methods that call it. |
| template <typename T, class C> |
| static constexpr bool VisitAllOf(T&& visitor, C&& container) { |
| for (auto& elt : container) { |
| if (!internal::Visit(visitor, elt)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static constexpr std::pair<size_type, size_type> PageBounds(size_type page_size, size_type start, |
| size_type size) { |
| size_type end = (start + size + page_size - 1) & -page_size; |
| start &= -page_size; |
| return {start, end - start}; |
| } |
| |
| template <typename T> |
| constexpr auto GetPhdrObserver(size_type page_size, T&& add_segment) { |
| return MakePhdrLoadObserver<Elf, Policy>(page_size, vaddr_start_, vaddr_size_, |
| std::forward<T>(add_segment)); |
| } |
| |
| constexpr void RecomputeVaddrSize() { |
| if (!segments_.empty()) { |
| VisitSegment( |
| [this](const auto& segment) -> std::true_type { |
| vaddr_size_ = segment.vaddr() - vaddr_start_ + segment.memsz(); |
| return {}; |
| }, |
| segments_.back()); |
| } |
| } |
| |
| template <class SegmentType> |
| constexpr std::pair<ConstantSegment, std::optional<Segment>> SplitRelro( |
| const SegmentType& segment, size_type relro_size, bool merge_ro) { |
| ConstantSegment relro_segment{ |
| segment.offset(), |
| segment.vaddr(), |
| relro_size, |
| Phdr::kRead | (merge_ro ? 0 : static_cast<uint32_t>(Phdr::kWrite)), |
| }; |
| |
| assert(relro_size <= segment.memsz()); |
| if (relro_size == segment.memsz()) { |
| return {std::move(relro_segment), std::nullopt}; |
| } |
| |
| auto offset = segment.offset() + relro_size; |
| auto vaddr = segment.vaddr() + relro_size; |
| auto memsz = segment.memsz() - relro_size; |
| auto filesz = segment.filesz() - relro_size; |
| |
| if (std::is_same_v<SegmentType, DataSegment>) { |
| return {std::move(relro_segment), DataSegment{offset, vaddr, memsz, filesz}}; |
| } |
| |
| assert((std::is_same_v<SegmentType, DataWithZeroFillSegment>)); |
| if (segment.filesz() - relro_size == 0) { |
| return {std::move(relro_segment), ZeroFillSegment{vaddr, memsz}}; |
| } |
| return {std::move(relro_segment), DataWithZeroFillSegment{offset, vaddr, memsz, filesz}}; |
| } |
| |
| // Complete ApplyRelro once the specific segment has been found. This is |
| // instantiated separately for DataSegment and DataWithZeroFillSegment. It |
| // firsts creates a new ConstantSegment spanning relro_size. If relro_size |
| // is less than the full segment size a second segment will be inserted after |
| // the relro segment. Merging is attempted on the two new segments. Note |
| // that because the two new segments will never be mergeable together we only |
| // need to try merging the relro segment with the segment behind it, and the |
| // split segment with the one in front of it. AddSegment will always merge |
| // if possible, so no new merging opportunities will become available after |
| // those two attempted merges have taken place. This is useful in cases |
| // where the segments will be copied after applying relro like in out of |
| // process dynamic linking. This can reduce the number of segments that need |
| // to be copied over into that processes address space. It doesn't make sense |
| // for traditional dynamic linking because all segments have already been |
| // mapped in before relocation can take place, so there is no efficiency to |
| // be found using this. |
| template <class Diagnostics, class SegmentType> |
| constexpr bool FixupRelro( // |
| Diagnostics& diagnostics, |
| // This iterator points to (the std::variant containing) this segment, |
| // but segment's type provides the deduced template parameter and saves |
| // repeating the iterator dereference and the std::get from the variant. |
| typename Container<Segment>::iterator it, const SegmentType& segment, // |
| size_type relro_size, bool merge_ro) { |
| if (!segment.CanReplace()) { |
| return diagnostics.FormatError("Cannot split segment to apply PT_GNU_RELRO protections"); |
| } |
| |
| auto [relro_segment, split_segment] = SplitRelro(segment, relro_size, merge_ro); |
| |
| auto merge = [this](auto it1, auto it2) { |
| if (!SegmentMerger::Merge(*it1, *it2)) { |
| return it2; |
| } |
| // Distances are used in place of iterators because they can be |
| // invalidated by insert/erase. |
| auto dist = std::distance(segments().begin(), it1); |
| segments().erase(it2); |
| return segments().begin() + dist; |
| }; |
| |
| // Replace the current segment instead of erase + insert. |
| *it = std::move(relro_segment); |
| |
| if (it != segments().begin()) { |
| it = merge(it - 1, it); |
| } |
| |
| if (split_segment) { |
| it += 1; |
| auto it_or_err = |
| segments().emplace(diagnostics, internal::kTooManyLoads, it, *std::move(split_segment)); |
| if (!it_or_err) { |
| return false; |
| } |
| it = *it_or_err; |
| } |
| |
| if (it + 1 < segments().end()) { |
| merge(it, it + 1); |
| } |
| |
| return true; |
| } |
| |
| Container<Segment> segments_; |
| size_type vaddr_start_ = 0, vaddr_size_ = 0; |
| }; |
| |
| // Given some `LoadInfo::...Segment` type, this is true if and only if its |
| // writable() method ever returns true. |
| template <class SegmentType> |
| inline constexpr bool kSegmentWritable = |
| !std::is_same_v<decltype(std::declval<SegmentType>().writable()), std::false_type>; |
| |
| // Given some `LoadInfo::...Segment` type, this is true if and only if its |
| // filesz() can ever return nonzero. |
| template <class SegmentType> |
| inline constexpr bool kSegmentHasFilesz = |
| std::is_same_v<decltype(std::declval<SegmentType>().filesz()), typename SegmentType::size_type>; |
| |
| } // namespace elfldltl |
| |
| #endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_LOAD_H_ |