blob: 4ca3abfdb2592ceb6551b1f84b1bc89fe396c5ae [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_CFI_ENTRY_H_
#define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DWARF_CFI_ENTRY_H_
#include <lib/stdcompat/span.h>
#include <lib/stdcompat/string_view.h>
#include <cstdint>
#include "../diagnostics.h"
#include "encoding.h"
#include "section-data.h"
namespace elfldltl::dwarf {
// This appears in the CIE_id / CIE_pointer location in the header encoding
// to distinguish a CIE from an FDE. This encoding of CIE_id / CIE_pointer
// is the primary difference between DWARF .debug_frame and GNU .eh_frame:
//
// * In .debug_frame format, CIE_id is all ones to indicate a CIE and
// otherwise it's CIE_pointer, an absolute offset into the .debug_frame
// section.
//
// * In .eh_frame format, CIE_id is zero to indicate a CIE and otherwise it's
// a signed offset *backwards* relative to the location of that CIE_pointer
// itself: CIE_position = FDE_position - CIE_pointer.
//
inline constexpr uint64_t kCfiCieId = -1;
inline constexpr uint64_t kEhFrameCieId = 0;
// This describes the information from decoding a CIE header in .debug_frame
// (or .eh_frame) format. Usually many FDEs will refer to the same CIE, so
// each CIE should be decoded once and cached.
struct CfiCie {
// This value is used in the fde_augmentation_data_size member when there
// is a nonempty augmentation string that doesn't include "z".
static constexpr size_t kUnknownAugmentation = -1;
// Version of .debug_frame format.
uint8_t version = 0;
// Sizes of addresses and segment selectors in this CIE's FDEs.
uint8_t address_size = 0;
uint8_t segment_selector_size = 0;
// Factors for the "scaling factor" DW_CFA_*_sf operations.
uint32_t code_alignment_factor = 0;
int32_t data_alignment_factor = 0;
// DWARF register number representing the caller's PC. If the
// signal_frame member (below) is true, this is an exact PC that was
// interrupted by some sort of exception frame. If false (common case),
// this is the PC in the caller that the current frame will return to.
uint32_t return_address_register = 0;
// These are the DWARF CFI instructions that modify the initial state
// specified by the ABI (i.e. as if DW_CFA_same_value for all call-saved
// and fixed registers, DW_CFA_undefined for all call-clobbered
// registers, and an appropriate definition of the CFA for the entry
// point to a function on this machine).
cpp20::span<const std::byte> initial_instructions;
// The augmentation string is DWARF CFI's main extension mechanism.
// It's always a NUL terminated UTF-8 string, though the NUL is not
// included in the std::string_view::size(). Letters in the string
// indicate nonstandard extensions, called "augmentations", which might
// indicate that additional "augmentation data" follows. The de facto
// standard is to start with the "z" augmentation, which says:
// * The first piece of CIE augmentation data is a ULEB128 size of all
// the CIE augmentation data (including that ULEB128)
// * FDEs also start with a ULEB128 that gives the size of their own
// FDE augmentation data. Other augmentation letters can indicate
// what is encoded after that ULEB128.
std::string_view augmentation;
// This is the augmentation data, not including its own size ULEB128.
// If there were any unrecognized augmentation letters and no "z", then
// no augmentation data can be decoded and this will be empty.
cpp20::span<const std::byte> augmentation_data;
// If there is no "z" augmentation, then this indicates the size of known
// augmentation data included in FDEs, if any. If it's kUnknownAugmentation
// then there was no "z" but some unrecognized augmentation letters, so it's
// impossible to know how much augmentation data there is in each FDE. Note
// this also means that the CIE might include unrecognized augmentation data
// that appears at the start of initial_instructions.
size_t fde_augmentation_data_size = 0;
// "P" augmentation can include a personality routine directly in the CIE.
uint64_t personality = 0;
// This is true when "z" augmentation indicates that each FDE's augmentation
// data starts with a ULEB128 giving its own size.
bool fde_augmentation_sized = false;
// "R" augmentation can indicate a different encoding for the FDE
// initial_location (start PC) and address_range (length) values.
// Otherwise (in standard .debug_frame) they are absolute addresses of
// the width the address_size field says.
uint8_t fde_encoding = EncodedPtr::kPtr;
// "L" augmentation can indicate an LSDA pointer is present in the FDE.
// In standard .debug_frame, there is no LSDA pointer present.
uint8_t lsda_encoding = EncodedPtr::kOmit;
// "S" augmentation (.cfi_signal_frame) indicates that FDEs using this
// CIE represent a set of exact registers where the "caller PC" is the
// precise instruction of interest, rather than being a return address
// of a function call instruction that precedes it.
bool signal_frame = false;
};
// This describes the information from decoding an FDE header in
// .debug_frame (or .eh_frame) format according to its CIE (see above).
struct CfiFde {
// Normalized CIE_pointer field: vaddr or .debug_frame offset of the CIE.
// The decoded CfiCie is necessary to decode an FDE, so a reference to the
// CfiCie used for decoding should usually travel alongside the CfiFde
// rather than looking it up afresh via this pointer.
uint64_t cie_pointer = 0;
// This FDE covers a range of PCs starting at initial_location and covering
// address_range bytes: [initial_location, initial_location + address_range).
uint64_t initial_location = 0;
uint64_t address_range = 0;
// This all the FDE's augmentation data, whose exact interpretation is
// determined by the CIE's augmentation string.
cpp20::span<const std::byte> augmentation_data;
// This is set only if the CIE uses "L" augmentation to indicate the FDE
// has an LSDA pointer (set by the .cfi_lsda assembly directive).
uint64_t lsda = 0;
// These are the DWARF CFI instructions for the FDE. They modify the
// initial state the CIE yields, and advance the PC from initial_location
// to define additional rows.
cpp20::span<const std::byte> instructions;
};
struct CfiEntry {
public:
// Read a single CFI entry (either CIE or FDE) from the byte stream.
// Failures will be sent to the diagnostics object with any error_args
// appended to the basic details in the FormatError call. The Elf template
// parameter indicates the byte order.
template <class Elf = Elf<>, class Diagnostics, typename... ErrorArgs>
static constexpr std::optional<CfiEntry> Read( //
Diagnostics& diag, cpp20::span<const std::byte> bytes, ErrorArgs&&... error_args) {
// The error_args are passed by reference since they may be used again.
std::optional data = SectionData::Read<Elf>(diag, bytes, error_args...);
if (!data) {
return std::nullopt;
}
std::optional cie_pointer = data->template read_offset<Elf>();
if (!cie_pointer) [[unlikely]] {
// Now the error_args can be moved.
diag.FormatError("DWARF CFI entry of ", data->size_bytes(), " bytes is too small",
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
CfiEntry entry;
entry.cie_pointer_ = *cie_pointer;
entry.data_ = *data;
if (entry.data_.format() == SectionData::Format::kDwarf32 &&
entry.cie_pointer_ == kCfiCieId32) {
// Extend to the 64-bit CIE_id. In .eh_frame format, -1 is not the
// CIE_id but -1 is always in invalid value since the relative CIE
// position must be more than one byte away from the FDE to have space
// for the CIE header; the CIE_id is 0 so no extension is needed.
entry.cie_pointer_ = kCfiCieId;
}
return entry;
}
// After Read yields an entry from .eh_frame, normalize it to standard DWARF
// CFI format as used in .debug_frame. The argument must be the virtual
// address that corresponds to the beginning of the buffer passed to Read.
constexpr void NormalizeEhFrame(uint64_t vaddr) {
if (cie_pointer_ == kEhFrameCieId) {
// Normalize CIE_id value.
cie_pointer_ = kCfiCieId;
} else {
// The CIE_pointer is an offset backwards from its own location. The
// vaddr passed in is the start of the FDE, which has its initial length
// header before the CIE_pointer.
vaddr += data_.initial_length_size();
if (data_.format() == SectionData::Format::kDwarf32) {
// Sign-extend from 32 bits, though it's stored as uint64_t and was
// zero-extended from uint32_t by SectionData::read_offset().
const uint32_t renarrowed = static_cast<uint32_t>(cie_pointer_);
const int32_t offset_back = cpp20::bit_cast<int32_t>(renarrowed);
const int64_t sign_extended = static_cast<int64_t>(offset_back);
cie_pointer_ = cpp20::bit_cast<uint64_t>(sign_extended);
}
// The stored cie_pointer_ is signed 64 bits, though stored as uint64_t.
cie_pointer_ = vaddr - cpp20::bit_cast<int64_t>(cie_pointer_);
}
}
// Like Read, but with NormalizeEhFrame applied to the results.
template <class Elf = Elf<>, class Diagnostics, typename... ErrorArgs>
static constexpr std::optional<CfiEntry> ReadEhFrame( //
Diagnostics& diag, cpp20::span<const std::byte> bytes, typename Elf::size_type vaddr,
ErrorArgs&&... error_args) {
std::optional<CfiEntry> result = Read<Elf>(diag, bytes, std::forward<ErrorArgs>(error_args)...);
if (result) {
result->NormalizeEhFrame(vaddr);
}
return result;
}
// This does ReadEhFrame, but using a Memory object to read the FDE at vaddr.
// The error_args do not need to include reporting the FDE vaddr passed here.
template <class Elf = Elf<>, class Diagnostics, class Memory, typename... ErrorArgs>
static constexpr std::optional<CfiEntry> ReadEhFrameFromMemory( //
Diagnostics& diag, Memory& memory, typename Elf::size_type vaddr, ErrorArgs&&... error_args) {
auto bytes = memory.template ReadArray<std::byte>(vaddr);
if (!bytes) [[unlikely]] {
diag.FormatError("invalid FDE pointer ", FileAddress{vaddr},
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
return ReadEhFrame<Elf>(diag, *bytes, vaddr, " in FDE", FileAddress{vaddr},
std::forward<ErrorArgs>(error_args)...);
}
constexpr size_t size_bytes() const { return data_.size_bytes(); }
// Returns true if this is a CIE, false if it's an FDE.
constexpr bool IsCie() const { return cie_pointer_ == kCfiCieId; }
// Returns std::nullopt for a CIE, or the CIE pointer for an FDE.
constexpr std::optional<uint64_t> cie_pointer() const {
if (IsCie()) {
return std::nullopt;
}
return cie_pointer_;
}
// Read the CIE referenced by this FDE. The Memory object is used to read
// from virtual addresses in the same address space used in NormalizeEhFrame
// on this object.
template <class Elf = Elf<>, class Diagnostics, class Memory, typename... ErrorArgs>
constexpr std::optional<CfiEntry> ReadEhFrameCieFromMemory( //
Diagnostics& diag, Memory& memory, ErrorArgs&&... error_args) const {
using size_type = typename Elf::size_type;
if (IsCie()) [[unlikely]] {
diag.FormatError("DWARF CFI FDE is actually a CIE", std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
auto bytes = memory.template ReadArray<std::byte>(cie_pointer_);
if (!bytes) [[unlikely]] {
diag.FormatError("DWARF CFI FDE has invalid CIE pointer", FileAddress{cie_pointer_},
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
// Pass error_args by reference since they may be used again below.
std::optional<CfiEntry> result =
ReadEhFrame<Elf>(diag, *bytes, static_cast<size_type>(cie_pointer_), error_args...);
if (result) {
if (!result->IsCie()) [[unlikely]] {
diag.FormatError("DWARF CFI FDE's CIE pointer", FileAddress{cie_pointer_},
" points to another FDE", std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
}
return result;
}
// Decode a CIE into the internal form described above. The vaddr should be
// that of the CIE itself if in .eh_frame format, where "PC-relative"
// encodings might be used. The error_args do not need to include reporting
// the CIE vaddr passed here.
template <class Elf = Elf<>, class Diagnostics, typename... ErrorArgs>
std::optional<CfiCie> DecodeCie(Diagnostics& diag, uint64_t vaddr,
ErrorArgs&&... error_args) const {
auto truncated = [vaddr, &diag, &error_args...]() {
diag.FormatError("DWARF CFI has truncated CIE", FileAddress{vaddr},
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
};
// Skip the common header already read.
cpp20::span<const std::byte> bytes = data_.contents().subspan(data_.offset_size());
if (bytes.size_bytes() < 2) [[unlikely]] {
return truncated();
}
// The first thing is the version byte.
CfiCie cie = {.version = static_cast<uint8_t>(bytes.front())};
bytes = bytes.subspan(1);
// The augmentation string is next and it's NUL-terminated.
cie.augmentation = std::string_view{
reinterpret_cast<const char*>(bytes.data()),
bytes.size(),
};
if (size_t len = cie.augmentation.find('\0'); len != std::string_view::npos) {
cie.augmentation = cie.augmentation.substr(0, len);
bytes = bytes.subspan(len + 1);
} else {
return truncated();
}
switch (cie.version) {
case 1:
case 3: // There is no version 2 of this format.
// Older versions have implied address size.
cie.address_size = sizeof(typename Elf::Addr);
break;
case 4:
// DWARF 4 introduced explicit address size, and segment selectors.
if (bytes.size_bytes() < 2) [[unlikely]] {
return truncated();
}
cie.address_size = static_cast<uint8_t>(bytes[0]);
cie.segment_selector_size = static_cast<uint8_t>(bytes[1]);
bytes = bytes.subspan(2);
break;
default:
diag.FormatError("DWARF CFI has unsupported CIE version ", cie.version, FileAddress{vaddr},
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
// In the ancient G++ v2 format, there is a pointer right after the
// augmentation string, before the standard fields.
if (cpp20::starts_with(cie.augmentation, "eh")) {
if (bytes.size_bytes() < cie.address_size) [[unlikely]] {
return truncated();
}
bytes = bytes.subspan(cie.address_size);
}
// Next is the code_alignment_factor.
if (auto factor = Uleb128::Read(bytes)) {
cie.code_alignment_factor = static_cast<uint32_t>(factor->value);
if (cie.code_alignment_factor != factor->value) [[unlikely]] {
diag.FormatError("DWARF CFI CIE has unreasonable code_alignment_factor ", factor->value,
FileAddress{vaddr}, std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
bytes = bytes.subspan(factor->size_bytes);
} else [[unlikely]] {
return truncated();
}
// Next is the data_alignment_factor.
if (auto factor = Sleb128::Read(bytes)) {
cie.data_alignment_factor = static_cast<int32_t>(factor->value);
if (cie.data_alignment_factor != factor->value) [[unlikely]] {
diag.FormatError("DWARF CFI CIE has unreasonable data_alignment_factor ", factor->value,
FileAddress{vaddr}, std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
bytes = bytes.subspan(factor->size_bytes);
} else [[unlikely]] {
return truncated();
}
// Next is the return_address_register. Before DWARF 3, this was one byte.
if (cie.version < 3) {
if (bytes.empty()) [[unlikely]] {
return truncated();
}
cie.return_address_register = static_cast<uint8_t>(bytes.front());
bytes = bytes.subspan(1);
} else if (auto rar = Uleb128::Read(bytes)) {
cie.return_address_register = static_cast<uint32_t>(rar->value);
if (cie.return_address_register != rar->value) [[unlikely]] {
diag.FormatError("DWARF CFI CIE has unreasonable return_address_register ", rar->value,
FileAddress{vaddr}, std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
bytes = bytes.subspan(rar->size_bytes);
} else [[unlikely]] {
return truncated();
}
// Grok the augmentation string, starting with "z".
if (cpp20::starts_with(cie.augmentation, 'z')) {
cie.fde_augmentation_sized = true;
auto data_size = Uleb128::Read(bytes);
if (!data_size || bytes.size_bytes() - data_size->size_bytes < data_size->value)
[[unlikely]] {
return truncated();
}
bytes = bytes.subspan(data_size->size_bytes);
cie.augmentation_data = bytes.first(data_size->value);
cie.initial_instructions = bytes.subspan(data_size->value);
} else {
// This will be reduced later.
cie.augmentation_data = bytes;
}
// Interpret other known augmentations after the "z". This consumes from
// bytes, which is also cie.augmentation_data when sized.
for (const char c : cie.augmentation.substr(cie.fde_augmentation_sized ? 1 : 0)) {
switch (c) {
case 'L':
// CIE augmentation gives LSDA encoding used in FDE augmentation.
if (bytes.empty()) [[unlikely]] {
return truncated();
}
cie.lsda_encoding = static_cast<uint8_t>(bytes.front());
bytes = bytes.subspan(1);
if (!cie.fde_augmentation_sized &&
cie.fde_augmentation_data_size != CfiCie::kUnknownAugmentation) {
cie.fde_augmentation_data_size +=
EncodedPtr::EncodedSize(cie.lsda_encoding, cie.address_size);
}
break;
case 'P': {
// CIE augmentation gives the personality routine vaddr.
if (bytes.empty()) [[unlikely]] {
return truncated();
}
uint8_t encoding = static_cast<uint8_t>(bytes.front());
bytes = bytes.subspan(1);
if (auto personality = EncodedPtr::Read<Elf>(encoding, bytes)) {
cie.personality = personality->ptr;
} else [[unlikely]] {
return truncated();
}
switch (EncodedPtr::Modifier(encoding)) {
case EncodedPtr::kAbs:
break;
case EncodedPtr::kPcrel:
// Add in the vaddr of CIE and the distance from there to here.
cie.personality += vaddr;
cie.personality += data_.initial_length_size();
cie.personality += bytes.data() - data_.contents().data();
break;
default:
diag.FormatError("DWARF CFI uses invalid personality routine encoding ", encoding,
FileAddress{vaddr}, std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
}
break;
}
case 'R':
// FDE initial_instructions and address_range use a special encoding.
if (bytes.empty()) [[unlikely]] {
return truncated();
}
cie.fde_encoding = static_cast<uint8_t>(bytes.front());
bytes = bytes.subspan(1);
break;
case 'S':
cie.signal_frame = true;
break;
case 'B':
case 'G':
// These are Aarch64 extensions we don't support but they don't use
// any augmentation data so it's safe to ignore them.
break;
default:
// Any unknown letter might have augmentation data.
if (!cie.fde_augmentation_sized) {
cie.fde_augmentation_data_size = CfiCie::kUnknownAugmentation;
}
break;
}
}
// Without "z", a (suspected) size of augmentation data is known only now.
if (!cie.fde_augmentation_sized) {
size_t size = cie.augmentation_data.size_bytes() - bytes.size_bytes();
cie.augmentation_data = cie.augmentation_data.first(size);
cie.initial_instructions = bytes;
}
return cie;
}
// Decode an FDE into the internal form described above, after the CIE it
// points to has already been decoded. The vaddr should be that of the FDE
// itself if in .eh_frame format, where "PC-relative" encodings might be
// used. The error_args do not need to include reporting the FDE vaddr passed
// here.
template <class Elf = Elf<>, class Diagnostics, typename... ErrorArgs>
std::optional<CfiFde> DecodeFde(Diagnostics& diag, uint64_t vaddr, const CfiCie& cie,
ErrorArgs&&... error_args) const {
auto truncated = [vaddr, &diag, &error_args...](uint8_t encoding) {
diag.FormatError("DWARF CFI has invalid encoding ", static_cast<unsigned int>(encoding),
" or truncated FDE", FileAddress{vaddr},
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
};
// Normalize an encoded address to be absolute.
auto normalize = [fde_vaddr = vaddr, &diag, &error_args...]( //
uint64_t encoding_vaddr, EncodedPtr& encoded) {
if (EncodedPtr::Signed(encoded.encoding)) {
encoded.ptr = cpp20::bit_cast<uint64_t>(encoded.sptr);
}
switch (EncodedPtr::Modifier(encoded.encoding)) {
case EncodedPtr::kAbs:
break;
case EncodedPtr::kPcrel:
encoded.ptr += encoding_vaddr;
break;
default:
[[unlikely]];
diag.FormatError("DWARF CFI uses unsupported encoding ",
static_cast<unsigned int>(encoded.encoding), " in FDE",
FileAddress{fde_vaddr}, std::forward<ErrorArgs>(error_args)...);
return false;
}
return true;
};
// Skip the common header already read.
cpp20::span<const std::byte> bytes = data_.contents().subspan(data_.offset_size());
// The data_.contents() started after the initial length header, and then
// the CIE_pointer was just skipped to reach the vaddr of the encoded PC.
const uint64_t pc_vaddr = vaddr + data_.initial_length_size() + data_.offset_size();
auto pc = EncodedPtr::Read<Elf>(cie.fde_encoding, bytes, cie.address_size);
if (!pc) [[unlikely]] {
return truncated(cie.fde_encoding);
}
bytes = bytes.subspan(pc->encoded_size);
// One encoding byte indicates the encoding for both initial_location (pc)
// and address_range (length), but the length ignores the relative and
// indirect flags and only uses the same base encoding: usually the PC is
// encoded as PC-relative and the count as absolute, of the same width.
const uint8_t length_encoding = EncodedPtr::Type(cie.fde_encoding);
auto length = EncodedPtr::Read<Elf>(length_encoding, bytes, cie.address_size);
if (!length) [[unlikely]] {
return truncated(cie.fde_encoding);
}
bytes = bytes.subspan(length->encoded_size);
if (!normalize(pc_vaddr, *pc)) [[unlikely]] {
return std::nullopt;
}
CfiFde fde = {
.cie_pointer = cie_pointer_,
.initial_location = pc->ptr,
.address_range = length->ptr,
};
// Figure out the bounds of the augmentation data.
if (cie.fde_augmentation_sized) {
auto size = Uleb128::Read(bytes);
if (!size) [[unlikely]] {
return truncated(EncodedPtr::kUleb128);
}
bytes = bytes.subspan(size->size_bytes);
if (bytes.size_bytes() < size->value) [[unlikely]] {
return truncated(EncodedPtr::kUleb128);
}
fde.augmentation_data = bytes.first(size->value);
bytes = bytes.subspan(size->value);
} else if (cie.fde_augmentation_data_size == CfiCie::kUnknownAugmentation) [[unlikely]] {
diag.FormatError("DWARF CFI has CIE", FileAddress{cie_pointer_},
" using unrecognized augmentation string \"", cie.augmentation, "\"",
std::forward<ErrorArgs>(error_args)...);
return std::nullopt;
} else {
fde.augmentation_data = bytes.first(cie.fde_augmentation_data_size);
bytes = bytes.subspan(cie.fde_augmentation_data_size);
}
// After that, the rest is the CFI instructions.
fde.instructions = bytes;
// The only part of the augmentation data we know about is the LSDA. When
// there is no LSDA, the empty EncodedPtr has a zero .ptr anyway.
auto lsda = EncodedPtr::Read<Elf>(cie.lsda_encoding, fde.augmentation_data, cie.address_size);
if (!lsda) [[unlikely]] {
return truncated(cie.lsda_encoding);
}
fde.lsda = lsda->ptr;
return fde;
}
private:
static constexpr uint32_t kCfiCieId32 = -1;
uint64_t cie_pointer_ = 0;
SectionData data_;
};
} // namespace elfldltl::dwarf
#endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_DWARF_CFI_ENTRY_H_