blob: d00de58cd1ee51460cfe93c2c944a9d252ebe09e [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 SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DWARF_EH_FRAME_HDR_H_
#define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DWARF_EH_FRAME_HDR_H_
#include <ios>
#include <iterator>
#if __cpp_impl_three_way_comparison >= 201907L
#include <compare>
#endif
#include "../diagnostics.h"
#include "../layout.h"
#include "encoding.h"
namespace elfldltl::dwarf {
// This is the fixed portion of the GNU .eh_frame_hdr format. It identifies
// the version of the format, and then the encodings of the data that follow.
// Immediately after this header follow in sequence:
//
// * Pointer to .eh_frame section, encoded as eh_frame_ptr says.
//
// * Count of FDEs in the table, encoded as fde_count says.
//
// * That many table entries, each encoded as fde_table says.
// Each entry is a PC value followed by an FDE address, both using
// the same encoding. The table is sorted in ascending PC order.
//
struct EhFrameHdrEncoding {
static constexpr uint8_t kVersion = 1;
uint8_t version = kVersion;
uint8_t eh_frame_ptr = EncodedPtr::kOmit;
uint8_t fde_count = EncodedPtr::kOmit;
uint8_t fde_table = EncodedPtr::kOmit;
};
// The .eh_frame_hdr section (PT_GNU_EH_FRAME at runtime) is an index into the
// .eh_frame section. It provides a sorted table mapping a PC to the FDE whose
// initial_location is that PC. Thus it's easy to do a quick binary search for
// any PC in the module's vaddr range and find the FDE that probably contains
// it. (The last FDE in the table will be found for any PC past the last
// instruction covered by that FDE, but the FDE itself must be decoded--and its
// CIE first--before that can be determined.)
// EhFrameHdrEntry is sortable on PC.
// It can be used as `auto [pc, fde]` in for loops.
template <typename SizeType>
struct EhFrameHdrEntry {
constexpr bool operator==(const EhFrameHdrEntry& other) const {
return pc == other.pc && fde == other.fde;
}
constexpr bool operator!=(const EhFrameHdrEntry& other) const { return !(*this == other); }
#if __cpp_impl_three_way_comparison >= 201907L
constexpr bool operator<=>(const EhFrameHdrEntry& other) const { return pc <=> other.pc; }
constexpr bool operator<=>(SizeType other) const { return pc <=> other.pc; }
#else
constexpr bool operator<(const EhFrameHdrEntry& other) const { return pc < other.pc; }
constexpr bool operator<(SizeType other) const { return pc < other; }
#endif
SizeType pc = 0, fde = 0;
};
// ostream-compatible objects can format EhFrameHdrEntry with the << operator.
template <typename Ostream, typename SizeType>
constexpr decltype(auto) operator<<(Ostream&& ostream, const EhFrameHdrEntry<SizeType>& entry) {
auto flags = ostream.flags();
ostream << std::hex << std::showbase // Use 0x123abc format.
<< "[PC " << entry.pc << " -> FDE " << entry.fde << "]";
ostream.flags(flags);
return std::forward<Ostream>(ostream);
}
// The EhFrameHdr object acts like a container of PC, FDE pairs (Entry) giving
// the vaddr of the corresponding FDE. Its `.find` method does binary search.
// The API is otherwise also similar to `const std::map<uintNN_t, uintNN_t>`,
// except that `operator[]` is an index rather than associative (use `.find`).
//
// This object only handles the index table, not actually decoding FDEs. Once
// an FDE is found in the table, <elfldltl/dwarf/cfi-entry.h> APIs decode it.
//
// Note that the PT_GNU_EH_FRAME p_filesz only covers .eh_frame_hdr, not
// .eh_frame. So the eh_frame_ptr (see below) and the FDE addresses in the
// table will point outside the bounds of memory fetched (or bounded) just to
// decode .eh_frame_hdr. They will both be in the same RODATA segment, so if
// the entire segment is made accessible then both .eh_frame_hdr and entries it
// locates can be gleaned from it can be accessed in a uniform manner.
template <class Elf = Elf<>>
class EhFrameHdr {
public:
// This is usually called `size_type` in toolkit code, but here that refers
// to the size of the element count in the container-style API.
using address_size_type = typename Elf::size_type;
using Phdr = typename Elf::Phdr;
// Standard container API types.
using value_type = EhFrameHdrEntry<address_size_type>;
using size_type = size_t;
using difference_type = ptrdiff_t;
using const_reference = const value_type&;
using reference = value_type&;
using const_pointer = const value_type*;
using pointer = value_type*;
class iterator {
public:
using iterator_category = std::random_access_iterator_tag;
constexpr iterator() = default;
constexpr iterator(const iterator&) = default;
constexpr iterator& operator=(const iterator&) = default;
constexpr bool operator==(const iterator& other) const {
assert(hdr_ == other.hdr_);
return pos_ == other.pos_;
}
constexpr bool operator!=(const iterator& other) const { return !(*this == other); }
#if __cpp_impl_three_way_comparison >= 201907L
constexpr std::strong_ordering operator<=>(const iterator& other) const {
assert(hdr_ == other.hdr_);
return pos_ <=> other.pos_;
}
#else // No operator<=> support.
constexpr bool operator<(const iterator& other) const {
assert(hdr_ == other.hdr_);
return pos_ < other.pos_;
}
constexpr bool operator>(const iterator& other) const {
assert(hdr_ == other.hdr_);
return pos_ > other.pos_;
}
constexpr bool operator<=(const iterator& other) const {
assert(hdr_ == other.hdr_);
return pos_ <= other.pos_;
}
constexpr bool operator>=(const iterator& other) const {
assert(hdr_ == other.hdr_);
return pos_ >= other.pos_;
}
#endif // operator<=> support.
constexpr const value_type& operator*() const { return entry_; }
constexpr const value_type* operator->() const { return &entry_; }
constexpr const value_type& operator[](ptrdiff_t n) { return *(*this + n); }
constexpr iterator& operator++() { // prefix
*this += 1;
return *this;
}
constexpr iterator operator++(int) { // postfix
iterator old = *this;
++*this;
return old;
}
constexpr iterator& operator--() { // prefix
*this -= 1;
return *this;
}
constexpr iterator operator--(int) { // postfix
iterator old = *this;
--*this;
return old;
}
constexpr iterator& operator+=(ptrdiff_t n) {
const size_t table_end_pos = hdr_->fde_table_.size_bytes();
const ptrdiff_t distance = n * hdr_->entry_size_;
if (distance < 0 ? static_cast<size_t>(-distance) < pos_
: (static_cast<size_t>(distance) > table_end_pos ||
table_end_pos - pos_ <= static_cast<size_t>(distance))) {
// This either reaches the end exactly or is invalid, which we make
// reach the end instead of aborting though it's technically undefined
// behavior to advance an iterator past either end of its container.
pos_ = table_end_pos;
} else {
pos_ += distance;
Decode();
}
return *this;
}
constexpr iterator operator+(ptrdiff_t n) {
iterator it = *this;
it += n;
return it;
}
constexpr iterator& operator-=(ptrdiff_t n) { return *this += -n; }
constexpr iterator operator-(ptrdiff_t n) { return *this + -n; }
constexpr iterator operator-(const iterator& other) {
assert(other.hdr_ == hdr_);
const ptrdiff_t distance =
(static_cast<ptrdiff_t>(pos_) - static_cast<ptrdiff_t>(other.pos_));
return distance / hdr_->entry_size_;
}
private:
friend EhFrameHdr;
constexpr iterator(const EhFrameHdr& hdr, size_t pos) : hdr_(&hdr), pos_(pos) {
assert(hdr_->fde_table_.size_bytes() % hdr_->entry_size_ == 0);
if (pos_ < hdr_->fde_table_.size_bytes()) {
Decode();
}
}
constexpr void Decode() {
assert(pos_ < hdr_->fde_table_.size_bytes());
assert(hdr_->fde_table_.size_bytes() - pos_ >= hdr_->entry_size_);
cpp20::span bytes = hdr_->fde_table_.subspan(pos_, hdr_->entry_size_);
auto pc = EncodedPtr::Read<Elf>(hdr_->encoding_.fde_table, bytes);
assert(pc);
assert(pc->encoded_size == hdr_->entry_size_ / 2);
bytes = bytes.subspan(pc->encoded_size);
auto fde = EncodedPtr::Read<Elf>(hdr_->encoding_.fde_table, bytes);
assert(fde);
assert(fde->encoded_size == hdr_->entry_size_ / 2);
if (EncodedPtr::Signed(hdr_->encoding_.fde_table)) {
// The value has already been sign-extended, so the adjustments below
// will work the same as either signed or unsigned. Pro forma no-op to
// use the signed value as unsigned.
pc->ptr = cpp20::bit_cast<uint64_t>(pc->sptr);
}
switch (EncodedPtr::Modifier(hdr_->encoding_.fde_table)) {
case EncodedPtr::kAbs:
break;
case EncodedPtr::kPcrel:
// Each value is relative to its own location inside .eh_frame_hdr.
{
pc->ptr += hdr_->table_offset_ + pos_;
fde->ptr += hdr_->table_offset_ + pos_ + pc->encoded_size;
}
[[fallthrough]]; // Now relative to .eh_frame_hdr.
case EncodedPtr::kDatarel:
// Each value is relative to the beginning of .eh_frame_hdr.
pc->ptr += hdr_->vaddr_;
fde->ptr += hdr_->vaddr_;
break;
default:
// The encoding was vetted in Init.
__builtin_trap();
}
entry_ = {
.pc = static_cast<address_size_type>(pc->ptr),
.fde = static_cast<address_size_type>(fde->ptr),
};
}
const EhFrameHdr* hdr_ = nullptr;
size_t pos_ = 0;
value_type entry_;
};
using const_iterator = iterator;
static constexpr uint8_t kAddressSize = sizeof(address_size_type);
// This is the whole size occupied at the PT_GNU_EH_FRAME vaddr.
constexpr size_t size_bytes() const {
return sizeof(encoding_) + //
EncodedPtr::EncodedSize(encoding_.eh_frame_ptr, kAddressSize) +
EncodedPtr::EncodedSize(encoding_.fde_count, kAddressSize) +
EncodedPtr::EncodedSize(encoding_.fde_table, kAddressSize) + //
fde_table_.size_bytes();
}
// The container-like methods act similarly to std::array<value_type, N>.
constexpr size_t size() const {
return entry_size_ == 0 ? 0 : fde_table_.size_bytes() / entry_size_;
}
constexpr bool empty() const { return size() == 0; }
constexpr iterator begin() const { return {*this, 0}; }
constexpr iterator cbegin() const { return begin(); }
constexpr iterator end() const { return {*this, fde_table_.size_bytes()}; }
constexpr iterator cend() const { return end(); }
constexpr const value_type& operator[](size_t idx) { return begin()[idx]; }
// This just does simple binary search. Note that entries identify only the
// FDE's starting PC and not its PC limit, so this will "find" the last FDE
// in the table for any PC that's above all the entries. The FDE itself must
// be decoded to determine where its PC range ends. (Usually the EhFrameHdr
// for a given module will only be searched for a PC already known to fall
// within that module's vaddr bounds, so only PCs off the end of the last FDE
// in alignment fill or code lacking CFI would "wrongly" report that FDE.)
constexpr iterator find(address_size_type pc) const {
return std::lower_bound(begin(), end(), pc);
}
// This is the vaddr of the start of the .eh_frame table. This is not really
// needed since .eh_frame_hdr table entries have the vaddr of an FDE inside.
// When the lookup table is omitted (empty() returns true), .eh_frame can be
// searched linearly and will be terminated by an invalid CFI entry with zero
// initial length (i.e. a lone zero uint32_t after the valid last entry).
constexpr address_size_type eh_frame_ptr() const { return eh_frame_ptr_; }
// Initialize directly from the PT_GNU_EH_FRAME phdr.
template <class Diagnostics, class Memory, typename... ErrorArgs>
constexpr bool Init(Diagnostics& diag, Memory& memory, const Phdr& phdr,
ErrorArgs&&... error_args) {
return Init(diag, memory, phdr.vaddr, std::forward<ErrorArgs>(error_args)...);
}
// Initialize from .eh_frame_hdr data read from the vaddr via the Memory
// object. The return value is propagated from the Diagnostics object.
template <class Diagnostics, class Memory, typename... ErrorArgs>
constexpr bool Init(Diagnostics& diag, Memory& memory, address_size_type vaddr,
ErrorArgs&&... error_args) {
using namespace std::string_view_literals;
// Record the vaddr of the start of .eh_frame_hdr (the header). The vaddr
// local is advanced as we read, and used in error formatting so it gives
// the precise vaddr of the problem data.
vaddr_ = vaddr;
auto fail = [&vaddr, &diag, &error_args...](auto... args) {
return diag.FormatError(args..., FileAddress{vaddr}, std::forward<ErrorArgs>(error_args)...);
};
auto cannot_read = [&fail]() { return fail("cannot read PT_GNU_EH_FRAME header"sv); };
if (auto read = memory.template ReadArray<EhFrameHdrEncoding>(vaddr, 1)) {
const EhFrameHdrEncoding& hdr = read->front();
if (hdr.version != EhFrameHdrEncoding::kVersion) [[unlikely]] {
return fail("PT_GNU_EH_FRAME header version "sv, hdr.version, " != expected "sv,
EhFrameHdrEncoding::kVersion);
}
encoding_ = hdr;
} else [[unlikely]] {
return cannot_read();
}
if (encoding_.eh_frame_ptr == EncodedPtr::kOmit || encoding_.fde_count == EncodedPtr::kOmit ||
encoding_.fde_table == EncodedPtr::kOmit) {
// Empty table.
return true;
}
uint8_t ptr_size = EncodedPtr::EncodedSize(encoding_.eh_frame_ptr, kAddressSize);
uint8_t count_size = EncodedPtr::EncodedSize(encoding_.fde_count, kAddressSize);
entry_size_ = EncodedPtr::EncodedSize(encoding_.fde_table, kAddressSize);
if (ptr_size == EncodedPtr::kDynamicSize || count_size == EncodedPtr::kDynamicSize ||
entry_size_ == EncodedPtr::kDynamicSize) [[unlikely]] {
return fail("LEB128 encoded not allowed in PT_GNU_EH_FRAME"sv);
}
entry_size_ *= 2; // PC + FDE each with the same encoding.
vaddr += sizeof(EhFrameHdrEncoding);
if (auto eh_frame_ptr = EncodedPtr::FromMemory<Elf>(encoding_.eh_frame_ptr, memory, vaddr)) {
eh_frame_ptr_ = static_cast<address_size_type>(*eh_frame_ptr);
} else {
return fail("cannot decode PT_GNU_EH_FRAME .eh_frame pointer"sv);
}
vaddr += ptr_size;
auto count = EncodedPtr::FromMemory<Elf>(encoding_.fde_count, memory, vaddr);
if (!count) {
return fail("cannot decode PT_GNU_EH_FRAME FDE count"sv);
}
vaddr += count_size;
table_offset_ = sizeof(EhFrameHdrEncoding) + ptr_size + count_size;
size_t table_size = *count * entry_size_;
auto table = memory.template ReadArray<std::byte>(vaddr, table_size);
if (!table) {
return fail("cannot read PT_GNU_EH_FRAME FDE table of "sv, table_size, " bytes ("sv, *count,
" * "sv, static_cast<unsigned int>(entry_size_), " bytes per entry)"sv);
}
fde_table_ = *table;
assert(fde_table_.size_bytes() == table_size);
// Make sure iterator::Decode() will know what to do.
switch (EncodedPtr::Modifier(encoding_.fde_table)) {
case EncodedPtr::kAbs:
case EncodedPtr::kPcrel:
case EncodedPtr::kDatarel:
break;
default:
[[unlikely]];
return fail("PT_GNU_EH_FRAME uses unsupported relative encoding ",
static_cast<unsigned int>(encoding_.fde_table), " for FDE table");
}
return true;
}
private:
cpp20::span<const std::byte> fde_table_;
address_size_type vaddr_ = 0;
address_size_type eh_frame_ptr_ = 0;
EhFrameHdrEncoding encoding_;
uint8_t entry_size_ = 0;
uint8_t table_offset_ = 0;
};
} // namespace elfldltl::dwarf
#endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DWARF_EH_FRAME_HDR_H_