dart-sdk/runtime/vm/elf.cc
Ryan Macnak 32715d1d5b [vm] Mark ELF libraries produced directly by the VM as not requiring an executable stack.
Even if the main program disables executable stacks, dlopen'ing a library that doesn't itself disable executable stack will switch the stack to executable. (Presumably in the name of compatability with GNU nested functions.)

Dart does not need executable stacks, and executable stacks are an additional attack surface.

TEST=readelf
Bug: b/229648756
Change-Id: Ia8c234ebc6178a26528d37b1359a10dd42039a9b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241540
Reviewed-by: Tess Strickland <sstrickl@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2022-04-19 18:51:47 +00:00

2022 lines
74 KiB
C++

// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/elf.h"
#include "platform/elf.h"
#include "vm/cpu.h"
#include "vm/dwarf.h"
#include "vm/hash_map.h"
#include "vm/image_snapshot.h"
#include "vm/stack_frame.h"
#include "vm/thread.h"
#include "vm/zone_text_buffer.h"
namespace dart {
#if defined(DART_PRECOMPILER)
// A wrapper around BaseWriteStream that provides methods useful for
// writing ELF files (e.g., using ELF definitions of data sizes).
class ElfWriteStream : public ValueObject {
public:
explicit ElfWriteStream(BaseWriteStream* stream, const Elf& elf)
: stream_(ASSERT_NOTNULL(stream)),
elf_(elf),
start_(stream_->Position()) {
// So that we can use the underlying stream's Align, as all alignments
// will be less than or equal to this alignment.
ASSERT(Utils::IsAligned(start_, Elf::kPageSize));
}
// Subclasses of Section may need to query the Elf object during Write(),
// so we store it in the ElfWriteStream for easy access.
const Elf& elf() const { return elf_; }
// We return positions in terms of the ELF content that has been written,
// ignoring any previous content on the stream.
intptr_t Position() const { return stream_->Position() - start_; }
void Align(const intptr_t alignment) {
ASSERT(Utils::IsPowerOfTwo(alignment));
ASSERT(alignment <= Elf::kPageSize);
stream_->Align(alignment);
}
void WriteBytes(const uint8_t* b, intptr_t size) {
stream_->WriteBytes(b, size);
}
void WriteByte(uint8_t value) { stream_->WriteByte(value); }
void WriteHalf(uint16_t value) { stream_->WriteFixed(value); }
void WriteWord(uint32_t value) { stream_->WriteFixed(value); }
void WriteAddr(compiler::target::uword value) { stream_->WriteFixed(value); }
void WriteOff(compiler::target::uword value) { stream_->WriteFixed(value); }
#if defined(TARGET_ARCH_IS_64_BIT)
void WriteXWord(uint64_t value) { stream_->WriteFixed(value); }
#endif
private:
BaseWriteStream* const stream_;
const Elf& elf_;
const intptr_t start_;
};
static constexpr intptr_t kLinearInitValue = -1;
#define DEFINE_LINEAR_FIELD_METHODS(name) \
intptr_t name() const { \
ASSERT(name##_ != kLinearInitValue); \
return name##_; \
} \
bool name##_is_set() const { return name##_ != kLinearInitValue; } \
void set_##name(intptr_t value) { \
ASSERT(value != kLinearInitValue); \
ASSERT_EQUAL(name##_, kLinearInitValue); \
name##_ = value; \
}
#define DEFINE_LINEAR_FIELD(name) intptr_t name##_ = kLinearInitValue;
// We only allow for dynamic casting to a subset of section types, since
// these are the only ones we need to distinguish at runtime.
#define FOR_EACH_SECTION_TYPE(V) \
V(ReservedSection) \
V(SymbolTable) \
V(DynamicTable) \
V(BitsContainer) \
V(TextSection) V(DataSection) V(BssSection) V(PseudoSection) V(SectionTable)
#define DEFINE_TYPE_CHECK_FOR(Type) \
bool Is##Type() const { return true; }
#define DECLARE_SECTION_TYPE_CLASS(Type) class Type;
FOR_EACH_SECTION_TYPE(DECLARE_SECTION_TYPE_CLASS)
#undef DECLARE_SECTION_TYPE_CLASS
class BitsContainer;
class Segment;
// Align note sections and segments to 4 byte boundries.
static constexpr intptr_t kNoteAlignment = 4;
class Section : public ZoneAllocated {
public:
Section(elf::SectionHeaderType t,
bool allocate,
bool executable,
bool writable,
intptr_t align = compiler::target::kWordSize)
: type(t),
flags(EncodeFlags(allocate, executable, writable)),
alignment(align),
// Non-segments will never have a memory offset, here represented by 0.
memory_offset_(allocate ? kLinearInitValue : 0) {
// Only SHT_NULL sections (namely, the reserved section) are allowed to have
// an alignment of 0 (as the written section header entry for the reserved
// section must be all 0s).
ASSERT(alignment > 0 || type == elf::SectionHeaderType::SHT_NULL);
// Non-zero alignments must be a power of 2.
ASSERT(alignment == 0 || Utils::IsPowerOfTwo(alignment));
}
virtual ~Section() {}
// Linker view.
const elf::SectionHeaderType type;
const intptr_t flags;
const intptr_t alignment;
// These are fields that only are not set for most kinds of sections and so we
// set them to a reasonable default.
intptr_t link = elf::SHN_UNDEF;
intptr_t info = 0;
intptr_t entry_size = 0;
// This field is set for all sections, but due to reordering, we may set it
// more than once.
intptr_t index = elf::SHN_UNDEF;
#define FOR_EACH_SECTION_LINEAR_FIELD(M) \
M(name) \
M(file_offset)
FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
// Only needs to be overridden for sections that may not be allocated or
// for allocated sections where MemorySize() and FileSize() may differ.
virtual intptr_t FileSize() const {
if (!IsAllocated()) {
UNREACHABLE();
}
return MemorySize();
}
// Loader view.
#define FOR_EACH_SEGMENT_LINEAR_FIELD(M) M(memory_offset)
FOR_EACH_SEGMENT_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
// Only needs to be overridden for sections that may be allocated.
virtual intptr_t MemorySize() const {
if (IsAllocated()) {
UNREACHABLE();
}
return 0;
}
// Other methods.
bool IsAllocated() const {
return (flags & elf::SHF_ALLOC) == elf::SHF_ALLOC;
}
bool IsExecutable() const {
return (flags & elf::SHF_EXECINSTR) == elf::SHF_EXECINSTR;
}
bool IsWritable() const { return (flags & elf::SHF_WRITE) == elf::SHF_WRITE; }
bool HasBits() const { return type != elf::SectionHeaderType::SHT_NOBITS; }
// Returns whether the size of a section can change.
bool HasBeenFinalized() const {
// Sections can grow or shrink up until Elf::ComputeOffsets has been run,
// which sets the file (and memory, if applicable) offsets.
return file_offset_is_set();
}
#define DEFINE_BASE_TYPE_CHECKS(Type) \
Type* As##Type() { \
return Is##Type() ? reinterpret_cast<Type*>(this) : nullptr; \
} \
const Type* As##Type() const { \
return const_cast<Type*>(const_cast<Section*>(this)->As##Type()); \
} \
virtual bool Is##Type() const { return false; }
FOR_EACH_SECTION_TYPE(DEFINE_BASE_TYPE_CHECKS)
#undef DEFINE_BASE_TYPE_CHECKS
// Only some sections support merging.
virtual bool CanMergeWith(const Section& other) const { return false; }
virtual void Merge(const Section& other) { UNREACHABLE(); }
// Writes the file contents of the section.
virtual void Write(ElfWriteStream* stream) const { UNREACHABLE(); }
virtual void WriteSectionHeader(ElfWriteStream* stream) const {
#if defined(TARGET_ARCH_IS_32_BIT)
stream->WriteWord(name());
stream->WriteWord(static_cast<uint32_t>(type));
stream->WriteWord(flags);
stream->WriteAddr(memory_offset());
stream->WriteOff(file_offset());
stream->WriteWord(FileSize());
stream->WriteWord(link);
stream->WriteWord(info);
stream->WriteWord(alignment);
stream->WriteWord(entry_size);
#else
stream->WriteWord(name());
stream->WriteWord(static_cast<uint32_t>(type));
stream->WriteXWord(flags);
stream->WriteAddr(memory_offset());
stream->WriteOff(file_offset());
stream->WriteXWord(FileSize());
stream->WriteWord(link);
stream->WriteWord(info);
stream->WriteXWord(alignment);
stream->WriteXWord(entry_size);
#endif
}
private:
static intptr_t EncodeFlags(bool allocate, bool executable, bool writable) {
// We currently don't allow sections that are both executable and writable.
ASSERT(!executable || !writable);
intptr_t flags = 0;
if (allocate) flags |= elf::SHF_ALLOC;
if (executable) flags |= elf::SHF_EXECINSTR;
if (writable) flags |= elf::SHF_WRITE;
return flags;
}
FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
FOR_EACH_SEGMENT_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
#undef FOR_EACH_SECTION_LINEAR_FIELD
#undef FOR_EACH_SEGMENT_LINEAR_FIELD
};
#undef DEFINE_LINEAR_FIELD
#undef DEFINE_LINEAR_FIELD_METHODS
class Segment : public ZoneAllocated {
public:
Segment(Zone* zone,
Section* initial_section,
elf::ProgramHeaderType segment_type)
: type(segment_type),
// Flags for the segment are the same as the initial section.
flags(EncodeFlags(ASSERT_NOTNULL(initial_section)->IsExecutable(),
ASSERT_NOTNULL(initial_section)->IsWritable())),
sections_(zone, 0) {
// Unlike sections, we don't have a reserved segment with the null type,
// so we never should pass this value.
ASSERT(segment_type != elf::ProgramHeaderType::PT_NULL);
// All segments should have at least one section.
ASSERT(initial_section != nullptr);
sections_.Add(initial_section);
}
virtual ~Segment() {}
const GrowableArray<Section*>& sections() const { return sections_; }
intptr_t Alignment() const {
switch (type) {
case elf::ProgramHeaderType::PT_LOAD:
return Elf::kPageSize;
case elf::ProgramHeaderType::PT_PHDR:
case elf::ProgramHeaderType::PT_DYNAMIC:
return compiler::target::kWordSize;
case elf::ProgramHeaderType::PT_NOTE:
return kNoteAlignment;
case elf::ProgramHeaderType::PT_GNU_STACK:
return 1;
default:
UNREACHABLE();
return 0;
}
}
bool IsExecutable() const { return (flags & elf::PF_X) == elf::PF_X; }
bool IsWritable() const { return (flags & elf::PF_W) == elf::PF_W; }
void WriteProgramHeader(ElfWriteStream* stream) const {
#if defined(TARGET_ARCH_IS_32_BIT)
stream->WriteWord(static_cast<uint32_t>(type));
stream->WriteOff(FileOffset());
stream->WriteAddr(MemoryOffset()); // Virtual address.
stream->WriteAddr(MemoryOffset()); // Physical address.
stream->WriteWord(FileSize());
stream->WriteWord(MemorySize());
stream->WriteWord(flags);
stream->WriteWord(Alignment());
#else
stream->WriteWord(static_cast<uint32_t>(type));
stream->WriteWord(flags);
stream->WriteOff(FileOffset());
stream->WriteAddr(MemoryOffset()); // Virtual address.
stream->WriteAddr(MemoryOffset()); // Physical address.
stream->WriteXWord(FileSize());
stream->WriteXWord(MemorySize());
stream->WriteXWord(Alignment());
#endif
}
// Adds a given section to the end of this segment. Returns whether the
// section was successfully added.
bool Add(Section* section) {
ASSERT(section != nullptr);
// We can't add if memory offsets have already been calculated.
ASSERT(!section->memory_offset_is_set());
// We only add additional sections to load segments.
ASSERT(type == elf::ProgramHeaderType::PT_LOAD);
// We only add sections with the same executable and writable bits.
if (IsExecutable() != section->IsExecutable() ||
IsWritable() != section->IsWritable()) {
return false;
}
sections_.Add(section);
return true;
}
intptr_t FileOffset() const { return sections_[0]->file_offset(); }
intptr_t FileSize() const {
auto const last = sections_.Last();
const intptr_t end = last->file_offset() + last->FileSize();
return end - FileOffset();
}
intptr_t MemoryOffset() const { return sections_[0]->memory_offset(); }
intptr_t MemorySize() const {
auto const last = sections_.Last();
const intptr_t end = last->memory_offset() + last->MemorySize();
return end - MemoryOffset();
}
intptr_t MemoryEnd() const { return MemoryOffset() + MemorySize(); }
const elf::ProgramHeaderType type;
const intptr_t flags;
private:
static intptr_t EncodeFlags(bool executable, bool writable) {
intptr_t flags = elf::PF_R;
if (executable) flags |= elf::PF_X;
if (writable) flags |= elf::PF_W;
return flags;
}
GrowableArray<Section*> sections_;
};
// Represents the first entry in the section table, which should only contain
// zero values and does not correspond to a memory segment.
class ReservedSection : public Section {
public:
ReservedSection()
: Section(elf::SectionHeaderType::SHT_NULL,
/*allocate=*/false,
/*executable=*/false,
/*writable=*/false,
/*alignment=*/0) {
set_file_offset(0);
}
DEFINE_TYPE_CHECK_FOR(ReservedSection);
intptr_t FileSize() const { return 0; }
};
// Specifies the permissions used for the stack, notably whether the stack
// should be executable. If absent, the stack will be executable.
class GnuStackSection : public Section {
public:
GnuStackSection()
: Section(elf::SectionHeaderType::SHT_NULL,
/*allocate=*/false,
/*executable=*/false,
/*writable=*/true) {
set_file_offset(0);
}
intptr_t FileSize() const { return 0; }
};
class StringTable : public Section {
public:
explicit StringTable(Zone* zone, bool allocate)
: Section(elf::SectionHeaderType::SHT_STRTAB,
allocate,
/*executable=*/false,
/*writable=*/false),
dynamic_(allocate),
text_(zone, 128),
text_indices_(zone) {
Add("");
}
intptr_t FileSize() const { return text_.length(); }
intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; }
void Write(ElfWriteStream* stream) const {
stream->WriteBytes(reinterpret_cast<const uint8_t*>(text_.buffer()),
text_.length());
}
intptr_t Add(const char* str) {
ASSERT(str != nullptr);
if (auto const kv = text_indices_.Lookup(str)) {
return kv->value;
}
intptr_t offset = text_.length();
text_.AddString(str);
text_.AddChar('\0');
text_indices_.Insert({str, offset});
return offset;
}
const char* At(intptr_t index) const {
if (index >= text_.length()) return nullptr;
return text_.buffer() + index;
}
static const intptr_t kNotIndexed = CStringIntMapKeyValueTrait::kNoValue;
// Returns the index of |str| if it is present in the string table
// and |kNotIndexed| otherwise.
intptr_t Lookup(const char* str) const {
return text_indices_.LookupValue(str);
}
const bool dynamic_;
ZoneTextBuffer text_;
CStringIntMap text_indices_;
};
class SymbolTable : public Section {
public:
SymbolTable(Zone* zone, StringTable* table, bool dynamic)
: Section(dynamic ? elf::SectionHeaderType::SHT_DYNSYM
: elf::SectionHeaderType::SHT_SYMTAB,
dynamic,
/*executable=*/false,
/*writable=*/false),
zone_(zone),
table_(table),
dynamic_(dynamic),
symbols_(zone, 1),
by_name_index_(zone) {
link = table_->index;
entry_size = sizeof(elf::Symbol);
// The first symbol table entry is reserved and must be all zeros.
// (String tables always have the empty string at the 0th index.)
ASSERT_EQUAL(table_->Lookup(""), 0);
symbols_.Add({/*name_index=*/0, elf::STB_LOCAL, elf::STT_NOTYPE, /*size=*/0,
elf::SHN_UNDEF, /*offset=*/0});
// The info field on a symbol table section holds the index of the first
// non-local symbol, so since there are none yet, it points past the single
// symbol we do have.
info = 1;
}
DEFINE_TYPE_CHECK_FOR(SymbolTable)
const StringTable& strtab() const { return *table_; }
intptr_t FileSize() const { return symbols_.length() * entry_size; }
intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; }
struct Symbol {
void Write(ElfWriteStream* stream) const {
const intptr_t start = stream->Position();
ASSERT(section_index == elf::SHN_UNDEF || offset > 0);
stream->WriteWord(name_index);
#if defined(TARGET_ARCH_IS_32_BIT)
stream->WriteAddr(offset);
stream->WriteWord(size);
stream->WriteByte(elf::SymbolInfo(binding, type));
stream->WriteByte(0);
stream->WriteHalf(section_index);
#else
stream->WriteByte(elf::SymbolInfo(binding, type));
stream->WriteByte(0);
stream->WriteHalf(section_index);
stream->WriteAddr(offset);
stream->WriteXWord(size);
#endif
ASSERT_EQUAL(stream->Position() - start, sizeof(elf::Symbol));
}
intptr_t name_index;
intptr_t binding;
intptr_t type;
intptr_t size;
// Must be updated whenever sections are reordered.
intptr_t section_index;
// Initialized to the section-relative offset, must be updated to the
// snapshot-relative offset before writing.
intptr_t offset;
private:
DISALLOW_ALLOCATION();
};
const GrowableArray<Symbol>& symbols() const { return symbols_; }
void Initialize(const GrowableArray<Section*>& sections);
void Write(ElfWriteStream* stream) const {
for (const auto& symbol : symbols_) {
const intptr_t start = stream->Position();
symbol.Write(stream);
ASSERT_EQUAL(stream->Position() - start, entry_size);
}
}
void AddSymbol(const char* name,
intptr_t binding,
intptr_t type,
intptr_t size,
intptr_t index,
intptr_t offset) {
ASSERT(!table_->HasBeenFinalized());
auto const name_index = table_->Add(name);
ASSERT(name_index != 0);
const intptr_t new_index = symbols_.length();
symbols_.Add({name_index, binding, type, size, index, offset});
by_name_index_.Insert(name_index, new_index);
// The info field on a symbol table section holds the index of the first
// non-local symbol, so they can be skipped if desired. Thus, we need to
// make sure local symbols are before any non-local ones.
if (binding == elf::STB_LOCAL) {
if (info != new_index) {
// There are non-local symbols, as otherwise [info] would be the
// index of the new symbol. Since the order doesn't otherwise matter,
// swap the new local symbol with the value at index [info], so when
// [info] is incremented it will point just past the new local symbol.
ASSERT(symbols_[info].binding != elf::STB_LOCAL);
symbols_.Swap(info, new_index);
// Since by_name_index has indices into symbols_, we need to update it.
by_name_index_.Update({symbols_[info].name_index, info});
by_name_index_.Update({symbols_[new_index].name_index, new_index});
}
info += 1;
}
}
void UpdateSectionIndices(const GrowableArray<intptr_t>& index_map) {
#if defined(DEBUG)
const intptr_t map_size = index_map.length();
// The first entry must be 0 so that symbols with index SHN_UNDEF, like
// the initial reserved symbol, are unchanged.
ASSERT_EQUAL(index_map[0], 0);
for (intptr_t i = 1; i < map_size; i++) {
ASSERT(index_map[i] != 0);
ASSERT(index_map[i] < map_size);
}
#endif
for (auto& symbol : symbols_) {
DEBUG_ASSERT(symbol.section_index < map_size);
symbol.section_index = index_map[symbol.section_index];
}
}
void Finalize(const GrowableArray<intptr_t>& address_map) {
#if defined(DEBUG)
const intptr_t map_size = address_map.length();
// The first entry must be 0 so that symbols with index SHN_UNDEF, like
// the initial reserved symbol, are unchanged.
ASSERT_EQUAL(address_map[0], 0);
for (intptr_t i = 1; i < map_size; i++) {
// No section begins at the start of the snapshot.
ASSERT(address_map[i] != 0);
}
#endif
for (auto& symbol : symbols_) {
DEBUG_ASSERT(symbol.section_index < map_size);
symbol.offset += address_map[symbol.section_index];
}
}
const Symbol* Find(const char* name) const {
ASSERT(name != nullptr);
const intptr_t name_index = table_->Lookup(name);
// 0 is kNoValue for by_name_index, but luckily that's the name of the
// initial reserved symbol.
if (name_index == 0) return &symbols_[0];
const intptr_t symbols_index = by_name_index_.Lookup(name_index);
if (symbols_index == 0) return nullptr; // Not found.
return &symbols_[symbols_index];
}
private:
Zone* const zone_;
StringTable* const table_;
const bool dynamic_;
GrowableArray<Symbol> symbols_;
// Maps name indexes in table_ to indexes in symbols_. Does not include an
// entry for the reserved symbol (name ""), as 0 is kNoValue.
IntMap<intptr_t> by_name_index_;
};
class SymbolHashTable : public Section {
public:
SymbolHashTable(Zone* zone, SymbolTable* symtab)
: Section(elf::SectionHeaderType::SHT_HASH,
/*allocate=*/true,
/*executable=*/false,
/*writable=*/false),
buckets_(zone, 0),
chains_(zone, 0) {
link = symtab->index;
entry_size = sizeof(int32_t);
const auto& symbols = symtab->symbols();
const intptr_t num_symbols = symbols.length();
buckets_.FillWith(elf::STN_UNDEF, 0, num_symbols);
chains_.FillWith(elf::STN_UNDEF, 0, num_symbols);
for (intptr_t i = 1; i < num_symbols; i++) {
const auto& symbol = symbols[i];
uint32_t hash = HashSymbolName(symtab->strtab().At(symbol.name_index));
uint32_t probe = hash % num_symbols;
chains_[i] = buckets_[probe]; // next = head
buckets_[probe] = i; // head = symbol
}
}
intptr_t MemorySize() const {
return entry_size * (buckets_.length() + chains_.length() + 2);
}
void Write(ElfWriteStream* stream) const {
stream->WriteWord(buckets_.length());
stream->WriteWord(chains_.length());
for (const int32_t bucket : buckets_) {
stream->WriteWord(bucket);
}
for (const int32_t chain : chains_) {
stream->WriteWord(chain);
}
}
static uint32_t HashSymbolName(const void* p) {
auto* name = reinterpret_cast<const uint8_t*>(p);
uint32_t h = 0;
while (*name != '\0') {
h = (h << 4) + *name++;
uint32_t g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
private:
GrowableArray<int32_t> buckets_; // "Head"
GrowableArray<int32_t> chains_; // "Next"
};
class DynamicTable : public Section {
public:
// .dynamic section is expected to be writable on most Linux systems
// unless dynamic linker is explicitly built with support for an read-only
// .dynamic section (DL_RO_DYN_SECTION).
DynamicTable(Zone* zone, SymbolTable* symtab, SymbolHashTable* hash)
: Section(elf::SectionHeaderType::SHT_DYNAMIC,
/*allocate=*/true,
/*executable=*/false,
/*writable=*/true),
symtab_(symtab),
hash_(hash) {
link = strtab().index;
entry_size = sizeof(elf::DynamicEntry);
AddEntry(zone, elf::DynamicEntryType::DT_HASH, kInvalidEntry);
AddEntry(zone, elf::DynamicEntryType::DT_STRTAB, kInvalidEntry);
AddEntry(zone, elf::DynamicEntryType::DT_STRSZ, kInvalidEntry);
AddEntry(zone, elf::DynamicEntryType::DT_SYMTAB, kInvalidEntry);
AddEntry(zone, elf::DynamicEntryType::DT_SYMENT, sizeof(elf::Symbol));
AddEntry(zone, elf::DynamicEntryType::DT_NULL, 0);
}
static constexpr intptr_t kInvalidEntry = -1;
DEFINE_TYPE_CHECK_FOR(DynamicTable)
const SymbolHashTable& hash() const { return *hash_; }
const SymbolTable& symtab() const { return *symtab_; }
const StringTable& strtab() const { return symtab().strtab(); }
intptr_t MemorySize() const { return entries_.length() * entry_size; }
void Write(ElfWriteStream* stream) const {
for (intptr_t i = 0; i < entries_.length(); i++) {
entries_[i]->Write(stream);
}
}
void Finalize() {
FinalizeEntry(elf::DynamicEntryType::DT_HASH, hash().memory_offset());
FinalizeEntry(elf::DynamicEntryType::DT_STRTAB, strtab().memory_offset());
FinalizeEntry(elf::DynamicEntryType::DT_STRSZ, strtab().MemorySize());
FinalizeEntry(elf::DynamicEntryType::DT_SYMTAB, symtab().memory_offset());
}
private:
struct Entry : public ZoneAllocated {
Entry(elf::DynamicEntryType tag, intptr_t value) : tag(tag), value(value) {}
void Write(ElfWriteStream* stream) const {
ASSERT(value != kInvalidEntry);
const intptr_t start = stream->Position();
#if defined(TARGET_ARCH_IS_32_BIT)
stream->WriteWord(static_cast<uint32_t>(tag));
stream->WriteAddr(value);
#else
stream->WriteXWord(static_cast<uint64_t>(tag));
stream->WriteAddr(value);
#endif
ASSERT_EQUAL(stream->Position() - start, sizeof(elf::DynamicEntry));
}
elf::DynamicEntryType tag;
intptr_t value;
};
void AddEntry(Zone* zone, elf::DynamicEntryType tag, intptr_t value) {
auto const entry = new (zone) Entry(tag, value);
entries_.Add(entry);
}
void FinalizeEntry(elf::DynamicEntryType tag, intptr_t value) {
for (auto* entry : entries_) {
if (entry->tag == tag) {
entry->value = value;
break;
}
}
}
SymbolTable* const symtab_;
SymbolHashTable* const hash_;
GrowableArray<Entry*> entries_;
};
class BitsContainer : public Section {
public:
// Fully specified BitsContainer information. Unless otherwise specified,
// BitContainers are aligned on byte boundaries (i.e., no padding is used).
BitsContainer(elf::SectionHeaderType type,
bool allocate,
bool executable,
bool writable,
int alignment = 1)
: Section(type, allocate, executable, writable, alignment) {}
// For BitsContainers used only as unallocated sections.
explicit BitsContainer(elf::SectionHeaderType type, intptr_t alignment = 1)
: BitsContainer(type,
/*allocate=*/false,
/*executable=*/false,
/*writable=*/false,
alignment) {}
// For BitsContainers used as segments whose type differ on the type of the
// ELF file. Creates an elf::SHT_PROGBITS section if type is Snapshot,
// otherwise creates an elf::SHT_NOBITS section.
BitsContainer(Elf::Type t,
bool executable,
bool writable,
intptr_t alignment = 1)
: BitsContainer(t == Elf::Type::Snapshot
? elf::SectionHeaderType::SHT_PROGBITS
: elf::SectionHeaderType::SHT_NOBITS,
/*allocate=*/true,
executable,
writable,
alignment) {}
DEFINE_TYPE_CHECK_FOR(BitsContainer)
bool IsNoBits() const { return type == elf::SectionHeaderType::SHT_NOBITS; }
bool HasBytes() const {
return portions_.length() != 0 && portions_[0].bytes != nullptr;
}
struct Portion {
void Write(ElfWriteStream* stream, intptr_t section_start) const {
ASSERT(bytes != nullptr);
if (relocations == nullptr) {
stream->WriteBytes(bytes, size);
return;
}
const SymbolTable& symtab = stream->elf().symtab();
// Resolve relocations as we write.
intptr_t current_pos = 0;
for (const auto& reloc : *relocations) {
// We assume here that the relocations are sorted in increasing order,
// with unique section offsets.
ASSERT(current_pos <= reloc.section_offset);
if (current_pos < reloc.section_offset) {
stream->WriteBytes(bytes + current_pos,
reloc.section_offset - current_pos);
}
intptr_t source_address = reloc.source_offset;
if (reloc.source_symbol != nullptr) {
auto* const source_symbol = symtab.Find(reloc.source_symbol);
ASSERT(source_symbol != nullptr);
source_address += source_symbol->offset;
} else {
source_address += section_start + offset + reloc.section_offset;
}
ASSERT(reloc.size_in_bytes <= kWordSize);
word to_write = reloc.target_offset - source_address;
if (reloc.target_symbol != nullptr) {
if (auto* const symbol = symtab.Find(reloc.target_symbol)) {
to_write += symbol->offset;
} else {
ASSERT_EQUAL(strcmp(reloc.target_symbol, kSnapshotBuildIdAsmSymbol),
0);
ASSERT_EQUAL(reloc.target_offset, 0);
ASSERT_EQUAL(reloc.source_offset, 0);
ASSERT_EQUAL(reloc.size_in_bytes, compiler::target::kWordSize);
// TODO(dartbug.com/43516): Special case for snapshots with deferred
// sections that handles the build ID relocation in an
// InstructionsSection when there is no build ID.
to_write = Image::kNoRelocatedAddress;
}
} else {
to_write += section_start + offset + reloc.section_offset;
}
ASSERT(Utils::IsInt(reloc.size_in_bytes * kBitsPerByte, to_write));
stream->WriteBytes(reinterpret_cast<const uint8_t*>(&to_write),
reloc.size_in_bytes);
current_pos = reloc.section_offset + reloc.size_in_bytes;
}
stream->WriteBytes(bytes + current_pos, size - current_pos);
}
intptr_t offset;
const char* symbol_name;
const uint8_t* bytes;
intptr_t size;
const ZoneGrowableArray<Elf::Relocation>* relocations;
const ZoneGrowableArray<Elf::SymbolData>* symbols;
private:
DISALLOW_ALLOCATION();
};
const GrowableArray<Portion>& portions() const { return portions_; }
const Portion& AddPortion(
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Elf::Relocation>* relocations = nullptr,
const ZoneGrowableArray<Elf::SymbolData>* symbols = nullptr,
const char* symbol_name = nullptr) {
ASSERT(IsNoBits() || bytes != nullptr);
ASSERT(bytes != nullptr || relocations == nullptr);
// Make sure all portions are consistent in containing bytes.
ASSERT(portions_.is_empty() || HasBytes() == (bytes != nullptr));
const intptr_t offset = Utils::RoundUp(total_size_, alignment);
portions_.Add({offset, symbol_name, bytes, size, relocations, symbols});
const Portion& portion = portions_.Last();
total_size_ = offset + size;
return portion;
}
void Write(ElfWriteStream* stream) const {
if (type == elf::SectionHeaderType::SHT_NOBITS) return;
intptr_t start_position = stream->Position(); // Used for checks.
for (const auto& portion : portions_) {
stream->Align(alignment);
ASSERT_EQUAL(stream->Position(), start_position + portion.offset);
portion.Write(stream, memory_offset());
}
ASSERT_EQUAL(stream->Position(), start_position + total_size_);
}
// Returns the hash for the portion corresponding to symbol_name.
// Returns 0 if the portion has no bytes or no portions have that name.
uint32_t Hash(const char* symbol_name) const {
for (const auto& portion : portions_) {
if (strcmp(symbol_name, portion.symbol_name) == 0) {
if (portion.bytes == nullptr) return 0;
const uint32_t hash = Utils::StringHash(portion.bytes, portion.size);
// Ensure a non-zero return.
return hash == 0 ? 1 : hash;
}
}
return 0;
}
intptr_t FileSize() const { return IsNoBits() ? 0 : total_size_; }
intptr_t MemorySize() const { return IsAllocated() ? total_size_ : 0; }
private:
GrowableArray<Portion> portions_;
intptr_t total_size_ = 0;
};
class NoteSection : public BitsContainer {
public:
NoteSection()
: BitsContainer(elf::SectionHeaderType::SHT_NOTE,
/*allocate=*/true,
/*executable=*/false,
/*writable=*/false,
kNoteAlignment) {}
};
// Abstract bits container that allows merging by just appending the portion
// information (with properly adjusted offsets) of the other to this one.
class ConcatenableBitsContainer : public BitsContainer {
public:
ConcatenableBitsContainer(Elf::Type type,
bool executable,
bool writable,
intptr_t alignment)
: BitsContainer(type, executable, writable, alignment) {}
virtual bool CanMergeWith(const Section& other) const = 0;
virtual void Merge(const Section& other) {
ASSERT(other.IsBitsContainer());
ASSERT(CanMergeWith(other));
for (const auto& portion : other.AsBitsContainer()->portions()) {
AddPortion(portion.bytes, portion.size, portion.relocations,
portion.symbols, portion.symbol_name);
}
}
};
class TextSection : public ConcatenableBitsContainer {
public:
explicit TextSection(Elf::Type t)
: ConcatenableBitsContainer(t,
/*executable=*/true,
/*writable=*/false,
ImageWriter::kTextAlignment) {}
DEFINE_TYPE_CHECK_FOR(TextSection);
virtual bool CanMergeWith(const Section& other) const {
return other.IsTextSection();
}
};
class DataSection : public ConcatenableBitsContainer {
public:
explicit DataSection(Elf::Type t)
: ConcatenableBitsContainer(t,
/*executable=*/false,
/*writable=*/false,
ImageWriter::kRODataAlignment) {}
DEFINE_TYPE_CHECK_FOR(DataSection);
virtual bool CanMergeWith(const Section& other) const {
return other.IsDataSection();
}
};
class BssSection : public ConcatenableBitsContainer {
public:
explicit BssSection(Elf::Type t)
: ConcatenableBitsContainer(t,
/*executable=*/false,
/*writable=*/true,
ImageWriter::kBssAlignment) {}
DEFINE_TYPE_CHECK_FOR(BssSection);
virtual bool CanMergeWith(const Section& other) const {
return other.IsBssSection();
}
};
// Represents portions of the file/memory space which do not correspond to
// sections from the section header. Should never be added to the section table,
// but may be added to segments.
class PseudoSection : public Section {
public:
// All PseudoSections are aligned to target word size.
static const intptr_t kAlignment = compiler::target::kWordSize;
PseudoSection(bool allocate, bool executable, bool writable)
: Section(elf::SectionHeaderType::SHT_NULL,
allocate,
executable,
writable,
kAlignment) {}
DEFINE_TYPE_CHECK_FOR(PseudoSection)
void Write(ElfWriteStream* stream) const = 0;
};
class ProgramTable : public PseudoSection {
public:
explicit ProgramTable(Zone* zone)
: PseudoSection(/*allocate=*/true,
/*executable=*/false,
/*writable=*/false),
segments_(zone, 0) {
entry_size = sizeof(elf::ProgramHeader);
}
const GrowableArray<Segment*>& segments() const { return segments_; }
intptr_t SegmentCount() const { return segments_.length(); }
intptr_t MemorySize() const {
return segments_.length() * sizeof(elf::ProgramHeader);
}
void Add(Segment* segment) {
ASSERT(segment != nullptr);
segments_.Add(segment);
}
void Write(ElfWriteStream* stream) const;
private:
GrowableArray<Segment*> segments_;
};
// This particular PseudoSection should not appear in segments either (hence
// being marked non-allocated), but is directly held by the Elf object.
class SectionTable : public PseudoSection {
public:
explicit SectionTable(Zone* zone)
: PseudoSection(/*allocate=*/false,
/*executable=*/false,
/*writable=*/false),
zone_(zone),
sections_(zone_, 2),
shstrtab_(zone_, /*allocate=*/false) {
entry_size = sizeof(elf::SectionHeader);
// The section at index 0 (elf::SHN_UNDEF) must be all 0s.
ASSERT_EQUAL(shstrtab_.Lookup(""), 0);
Add(new (zone_) ReservedSection(), "");
Add(&shstrtab_, ".shstrtab");
}
const GrowableArray<Section*>& sections() const { return sections_; }
intptr_t SectionCount() const { return sections_.length(); }
intptr_t StringTableIndex() const { return shstrtab_.index; }
bool HasSectionNamed(const char* name) {
return shstrtab_.Lookup(name) != StringTable::kNotIndexed;
}
void Add(Section* section, const char* name = nullptr) {
ASSERT(!section->IsPseudoSection());
ASSERT(name != nullptr || section->name_is_set());
if (name != nullptr) {
// First, check for an existing section with the same table name.
if (auto* const old_section = Find(name)) {
ASSERT(old_section->CanMergeWith(*section));
old_section->Merge(*section);
return;
}
// No existing section with this name.
const intptr_t name_index = shstrtab_.Add(name);
section->set_name(name_index);
}
section->index = sections_.length();
sections_.Add(section);
}
Section* Find(const char* name) {
const intptr_t name_index = shstrtab_.Lookup(name);
if (name_index == StringTable::kNotIndexed) {
// We're guaranteed that no section with this name has been added yet.
return nullptr;
}
// We check walk all sections to check for uniqueness in DEBUG mode.
Section* result = nullptr;
for (Section* const section : sections_) {
if (section->name() == name_index) {
#if defined(DEBUG)
ASSERT(result == nullptr);
result = section;
#else
return section;
#endif
}
}
return result;
}
intptr_t FileSize() const {
return sections_.length() * sizeof(elf::SectionHeader);
}
void Write(ElfWriteStream* stream) const;
// Reorders the sections for creating a minimal amount of segments and
// creates and returns an appropriate program table.
//
// Also takes and adjusts section indices in the static symbol table, since it
// is not recorded in sections_ for stripped outputs.
ProgramTable* CreateProgramTable(SymbolTable* symtab);
private:
Zone* const zone_;
GrowableArray<Section*> sections_;
StringTable shstrtab_;
};
class ElfHeader : public PseudoSection {
public:
ElfHeader(const ProgramTable& program_table,
const SectionTable& section_table)
: PseudoSection(/*allocate=*/true,
/*executable=*/false,
/*writable=*/false),
program_table_(program_table),
section_table_(section_table) {}
intptr_t MemorySize() const { return sizeof(elf::ElfHeader); }
void Write(ElfWriteStream* stream) const;
private:
const ProgramTable& program_table_;
const SectionTable& section_table_;
};
#undef DEFINE_TYPE_CHECK_FOR
#undef FOR_EACH_SECTION_TYPE
Elf::Elf(Zone* zone, BaseWriteStream* stream, Type type, Dwarf* dwarf)
: zone_(zone),
unwrapped_stream_(stream),
type_(type),
dwarf_(dwarf),
section_table_(new (zone) SectionTable(zone)) {
// Separate debugging information should always have a Dwarf object.
ASSERT(type_ == Type::Snapshot || dwarf_ != nullptr);
// Assumed by various offset logic in this file.
ASSERT_EQUAL(unwrapped_stream_->Position(), 0);
}
void Elf::AddText(const char* name,
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Relocation>* relocations,
const ZoneGrowableArray<SymbolData>* symbols) {
auto* const container = new (zone_) TextSection(type_);
container->AddPortion(bytes, size, relocations, symbols, name);
section_table_->Add(container, kTextName);
}
void Elf::CreateBSS() {
// Not idempotent.
ASSERT(section_table_->Find(kBssName) == nullptr);
// No text section means no BSS section.
auto* const text_section = section_table_->Find(kTextName);
if (text_section == nullptr) return;
ASSERT(text_section->IsTextSection());
auto* const bss_container = new (zone_) BssSection(type_);
for (const auto& portion : text_section->AsBitsContainer()->portions()) {
size_t size;
const char* symbol_name;
// First determine whether this is the VM's text portion or the isolate's.
if (strcmp(portion.symbol_name, kVmSnapshotInstructionsAsmSymbol) == 0) {
size = BSS::kVmEntryCount * compiler::target::kWordSize;
symbol_name = kVmSnapshotBssAsmSymbol;
} else if (strcmp(portion.symbol_name,
kIsolateSnapshotInstructionsAsmSymbol) == 0) {
size = BSS::kIsolateEntryCount * compiler::target::kWordSize;
symbol_name = kIsolateSnapshotBssAsmSymbol;
} else {
// Not VM or isolate text.
UNREACHABLE();
continue;
}
uint8_t* bytes = nullptr;
if (type_ == Type::Snapshot) {
// Ideally the BSS segment would take no space in the object, but
// Android's "strip" utility truncates the memory-size of our segments to
// their file-size.
//
// Therefore we must insert zero-filled data for the BSS.
bytes = zone_->Alloc<uint8_t>(size);
memset(bytes, 0, size);
}
// For the BSS section, we add the section symbols as local symbols in the
// static symbol table, as these addresses are only used for relocation.
// (This matches the behavior in the assembly output.)
auto* symbols = new (zone_) ZoneGrowableArray<Elf::SymbolData>();
symbols->Add({symbol_name, elf::STT_SECTION, 0, size});
bss_container->AddPortion(bytes, size, /*relocations=*/nullptr, symbols);
}
section_table_->Add(bss_container, kBssName);
}
void Elf::AddROData(const char* name,
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Relocation>* relocations,
const ZoneGrowableArray<SymbolData>* symbols) {
auto* const container = new (zone_) DataSection(type_);
container->AddPortion(bytes, size, relocations, symbols, name);
section_table_->Add(container, kDataName);
}
#if defined(DART_PRECOMPILER)
class DwarfElfStream : public DwarfWriteStream {
public:
DwarfElfStream(Zone* zone, NonStreamingWriteStream* stream)
: zone_(ASSERT_NOTNULL(zone)),
stream_(ASSERT_NOTNULL(stream)),
relocations_(new (zone) ZoneGrowableArray<Elf::Relocation>()) {}
const uint8_t* buffer() const { return stream_->buffer(); }
intptr_t bytes_written() const { return stream_->bytes_written(); }
intptr_t Position() const { return stream_->Position(); }
void sleb128(intptr_t value) { stream_->WriteSLEB128(value); }
void uleb128(uintptr_t value) { stream_->WriteLEB128(value); }
void u1(uint8_t value) { stream_->WriteByte(value); }
void u2(uint16_t value) { stream_->WriteFixed(value); }
void u4(uint32_t value) { stream_->WriteFixed(value); }
void u8(uint64_t value) { stream_->WriteFixed(value); }
void string(const char* cstr) { // NOLINT
// Unlike stream_->WriteString(), we want the null terminator written.
stream_->WriteBytes(cstr, strlen(cstr) + 1);
}
// The prefix is ignored for DwarfElfStreams.
EncodedPosition WritePrefixedLength(const char* symbol_prefix,
std::function<void()> body) {
const intptr_t fixup = stream_->Position();
// We assume DWARF v2 currently, so all sizes are 32-bit.
u4(0);
// All sizes for DWARF sections measure the size of the section data _after_
// the size value.
const intptr_t start = stream_->Position();
body();
const intptr_t end = stream_->Position();
stream_->SetPosition(fixup);
u4(end - start);
stream_->SetPosition(end);
return EncodedPosition(fixup);
}
// Shorthand for when working directly with DwarfElfStreams.
intptr_t WritePrefixedLength(std::function<void()> body) {
const EncodedPosition& pos = WritePrefixedLength(nullptr, body);
return pos.position();
}
void OffsetFromSymbol(const char* symbol, intptr_t offset) {
relocations_->Add(
{kAddressSize, stream_->Position(), "", 0, symbol, offset});
addr(0); // Resolved later.
}
template <typename T>
void RelativeSymbolOffset(const char* symbol) {
relocations_->Add({sizeof(T), stream_->Position(), nullptr, 0, symbol, 0});
stream_->WriteFixed<T>(0); // Resolved later.
}
void InitializeAbstractOrigins(intptr_t size) {
abstract_origins_size_ = size;
abstract_origins_ = zone_->Alloc<uint32_t>(abstract_origins_size_);
}
void RegisterAbstractOrigin(intptr_t index) {
ASSERT(abstract_origins_ != nullptr);
ASSERT(index < abstract_origins_size_);
abstract_origins_[index] = stream_->Position();
}
void AbstractOrigin(intptr_t index) { u4(abstract_origins_[index]); }
const ZoneGrowableArray<Elf::Relocation>* relocations() const {
return relocations_;
}
protected:
#if defined(TARGET_ARCH_IS_32_BIT)
static constexpr intptr_t kAddressSize = kInt32Size;
#else
static constexpr intptr_t kAddressSize = kInt64Size;
#endif
void addr(uword value) {
#if defined(TARGET_ARCH_IS_32_BIT)
u4(value);
#else
u8(value);
#endif
}
Zone* const zone_;
NonStreamingWriteStream* const stream_;
ZoneGrowableArray<Elf::Relocation>* relocations_ = nullptr;
uint32_t* abstract_origins_ = nullptr;
intptr_t abstract_origins_size_ = -1;
private:
DISALLOW_COPY_AND_ASSIGN(DwarfElfStream);
};
static constexpr intptr_t kInitialDwarfBufferSize = 64 * KB;
#endif
void SymbolTable::Initialize(const GrowableArray<Section*>& sections) {
for (auto* const section : sections) {
// The values of all added symbols are memory addresses.
if (!section->IsAllocated()) continue;
if (auto* const bits = section->AsBitsContainer()) {
for (const auto& portion : section->AsBitsContainer()->portions()) {
if (portion.symbol_name != nullptr) {
// Global dynamic symbols for the content of a given section, which is
// always a single structured element (and thus we use STT_OBJECT).
const intptr_t binding = elf::STB_GLOBAL;
const intptr_t type = elf::STT_OBJECT;
// Some tools assume the static symbol table is a superset of the
// dynamic symbol table when it exists and only use it, so put all
// dynamic symbols there also. (see dartbug.com/41783).
AddSymbol(portion.symbol_name, binding, type, portion.size,
section->index, portion.offset);
}
if (!dynamic_ && portion.symbols != nullptr) {
for (const auto& symbol_data : *portion.symbols) {
// Local static-only symbols, e.g., code payloads or RO objects.
AddSymbol(symbol_data.name, elf::STB_LOCAL, symbol_data.type,
symbol_data.size, section->index,
portion.offset + symbol_data.offset);
}
}
}
}
}
}
void Elf::InitializeSymbolTables() {
// Not idempotent.
ASSERT(symtab_ == nullptr);
// Create static and dynamic symbol tables.
auto* const dynstrtab = new (zone_) StringTable(zone_, /*allocate=*/true);
section_table_->Add(dynstrtab, ".dynstr");
auto* const dynsym =
new (zone_) SymbolTable(zone_, dynstrtab, /*dynamic=*/true);
section_table_->Add(dynsym, ".dynsym");
dynsym->Initialize(section_table_->sections());
// Now the dynamic symbol table is populated, set up the hash table and
// dynamic table.
auto* const hash = new (zone_) SymbolHashTable(zone_, dynsym);
section_table_->Add(hash, ".hash");
auto* const dynamic = new (zone_) DynamicTable(zone_, dynsym, hash);
section_table_->Add(dynamic, kDynamicTableName);
// We only add the static string and symbol tables to the section table if
// this is an unstripped output, but we always create them as they are used
// to resolve relocations.
auto* const strtab = new (zone_) StringTable(zone_, /*allocate=*/false);
if (!IsStripped()) {
section_table_->Add(strtab, ".strtab");
}
symtab_ = new (zone_) SymbolTable(zone_, strtab, /*dynamic=*/false);
if (!IsStripped()) {
section_table_->Add(symtab_, ".symtab");
}
symtab_->Initialize(section_table_->sections());
}
void Elf::FinalizeEhFrame() {
#if !defined(TARGET_ARCH_IA32)
#if defined(TARGET_ARCH_X64)
// The x86_64 psABI defines the DWARF register numbers, which differ from
// the registers' usual encoding within instructions.
const intptr_t DWARF_RA = 16; // No corresponding register.
const intptr_t DWARF_FP = 6; // RBP
#else
const intptr_t DWARF_RA = ConcreteRegister(LINK_REGISTER);
const intptr_t DWARF_FP = FP;
#endif
// No text section added means no .eh_frame.
TextSection* text_section = nullptr;
if (auto* const section = section_table_->Find(kTextName)) {
text_section = section->AsTextSection();
ASSERT(text_section != nullptr);
}
// No text section added means no .eh_frame.
if (text_section == nullptr) return;
// Multiplier which will be used to scale operands of DW_CFA_offset and
// DW_CFA_val_offset.
const intptr_t kDataAlignment = -compiler::target::kWordSize;
static const uint8_t DW_EH_PE_pcrel = 0x10;
static const uint8_t DW_EH_PE_sdata4 = 0x0b;
ZoneWriteStream stream(zone(), kInitialDwarfBufferSize);
DwarfElfStream dwarf_stream(zone_, &stream);
// Emit CIE.
// Used to calculate offset to CIE in FDEs.
const intptr_t cie_start = dwarf_stream.WritePrefixedLength([&] {
dwarf_stream.u4(0); // CIE
dwarf_stream.u1(1); // Version (must be 1 or 3)
// Augmentation String
dwarf_stream.string("zR"); // NOLINT
dwarf_stream.uleb128(1); // Code alignment (must be 1).
dwarf_stream.sleb128(kDataAlignment); // Data alignment
dwarf_stream.u1(DWARF_RA); // Return address register
dwarf_stream.uleb128(1); // Augmentation size
dwarf_stream.u1(DW_EH_PE_pcrel | DW_EH_PE_sdata4); // FDE encoding.
// CFA is caller's SP (FP+kCallerSpSlotFromFp*kWordSize)
dwarf_stream.u1(Dwarf::DW_CFA_def_cfa);
dwarf_stream.uleb128(DWARF_FP);
dwarf_stream.uleb128(kCallerSpSlotFromFp * compiler::target::kWordSize);
});
// Emit rule defining that |reg| value is stored at CFA+offset.
const auto cfa_offset = [&](intptr_t reg, intptr_t offset) {
const intptr_t scaled_offset = offset / kDataAlignment;
RELEASE_ASSERT(scaled_offset >= 0);
dwarf_stream.u1(Dwarf::DW_CFA_offset | reg);
dwarf_stream.uleb128(scaled_offset);
};
// Emit an FDE covering each .text section.
for (const auto& portion : text_section->portions()) {
ASSERT(portion.symbol_name != nullptr); // Needed for relocations.
dwarf_stream.WritePrefixedLength([&]() {
// Offset to CIE. Note that unlike pcrel this offset is encoded
// backwards: it will be subtracted from the current position.
dwarf_stream.u4(stream.Position() - cie_start);
// Start address as a PC relative reference.
dwarf_stream.RelativeSymbolOffset<int32_t>(portion.symbol_name);
dwarf_stream.u4(portion.size); // Size.
dwarf_stream.u1(0); // Augmentation Data length.
// Caller FP at FP+kSavedCallerPcSlotFromFp*kWordSize,
// where FP is CFA - kCallerSpSlotFromFp*kWordSize.
COMPILE_ASSERT((kSavedCallerFpSlotFromFp - kCallerSpSlotFromFp) <= 0);
cfa_offset(DWARF_FP, (kSavedCallerFpSlotFromFp - kCallerSpSlotFromFp) *
compiler::target::kWordSize);
// Caller LR at FP+kSavedCallerPcSlotFromFp*kWordSize,
// where FP is CFA - kCallerSpSlotFromFp*kWordSize
COMPILE_ASSERT((kSavedCallerPcSlotFromFp - kCallerSpSlotFromFp) <= 0);
cfa_offset(DWARF_RA, (kSavedCallerPcSlotFromFp - kCallerSpSlotFromFp) *
compiler::target::kWordSize);
});
}
dwarf_stream.u4(0); // end of section (FDE with zero length)
auto* const eh_frame = new (zone_)
BitsContainer(type_, /*writable=*/false, /*executable=*/false);
eh_frame->AddPortion(dwarf_stream.buffer(), dwarf_stream.bytes_written(),
dwarf_stream.relocations());
section_table_->Add(eh_frame, ".eh_frame");
#endif // !defined(TARGET_ARCH_IA32)
}
void Elf::FinalizeDwarfSections() {
if (dwarf_ == nullptr) return;
// Currently we only output DWARF information involving code.
ASSERT(section_table_->HasSectionNamed(kTextName));
auto add_debug = [&](const char* name, const DwarfElfStream& stream) {
auto const container =
new (zone_) BitsContainer(elf::SectionHeaderType::SHT_PROGBITS);
container->AddPortion(stream.buffer(), stream.bytes_written(),
stream.relocations());
section_table_->Add(container, name);
};
{
ZoneWriteStream stream(zone(), kInitialDwarfBufferSize);
DwarfElfStream dwarf_stream(zone_, &stream);
dwarf_->WriteAbbreviations(&dwarf_stream);
add_debug(".debug_abbrev", dwarf_stream);
}
{
ZoneWriteStream stream(zone(), kInitialDwarfBufferSize);
DwarfElfStream dwarf_stream(zone_, &stream);
dwarf_->WriteDebugInfo(&dwarf_stream);
add_debug(".debug_info", dwarf_stream);
}
{
ZoneWriteStream stream(zone(), kInitialDwarfBufferSize);
DwarfElfStream dwarf_stream(zone_, &stream);
dwarf_->WriteLineNumberProgram(&dwarf_stream);
add_debug(".debug_line", dwarf_stream);
}
}
ProgramTable* SectionTable::CreateProgramTable(SymbolTable* symtab) {
const intptr_t num_sections = sections_.length();
// Should have at least the reserved entry in sections_.
ASSERT(!sections_.is_empty());
ASSERT_EQUAL(sections_[0]->alignment, 0);
// The new program table that collects the segments for allocated sections
// and a few special segments.
auto* const program_table = new (zone_) ProgramTable(zone_);
GrowableArray<Section*> reordered_sections(zone_, num_sections);
// Maps the old indices of sections to the new ones.
GrowableArray<intptr_t> index_map(zone_, num_sections);
index_map.FillWith(0, 0, num_sections);
Segment* current_segment = nullptr;
// Only called for sections in the section table (i.e., not special sections
// appearing in segments only or the section table itself).
auto add_to_reordered_sections = [&](Section* section) {
intptr_t new_index = reordered_sections.length();
index_map[section->index] = new_index;
section->index = new_index;
reordered_sections.Add(section);
if (section->IsAllocated()) {
ASSERT(current_segment != nullptr);
if (!current_segment->Add(section)) {
// The current segment is incompatible for the current sectioni, so
// create a new one.
current_segment = new (zone_)
Segment(zone_, section, elf::ProgramHeaderType::PT_LOAD);
program_table->Add(current_segment);
}
}
};
// The first section in the section header table is always a reserved
// entry containing only 0 values, so copy it over from sections_.
add_to_reordered_sections(sections_[0]);
// There are few important invariants originating from Android idiosyncrasies
// we are trying to maintain when ordering sections:
//
// - Android requires the program header table be in the first load segment,
// so create PseudoSections representing the ELF header and program header
// table to initialize that segment.
//
// - The Android dynamic linker in Jelly Bean incorrectly assumes that all
// non-writable segments are continguous. Thus we write them all together.
// The bug is here: https://github.com/aosp-mirror/platform_bionic/blob/94963af28e445384e19775a838a29e6a71708179/linker/linker.c#L1991-L2001
//
// - On Android native libraries can be mapped directly from an APK
// they are stored uncompressed in it. In such situations the name
// of the mapping no longer provides enough information for libunwindstack
// to find the original ELF file and instead it has to rely on heuristics
// to locate program header table. These heuristics currently assume that
// program header table will be located in the RO mapping which precedes
// RX mapping.
//
// These invariants imply the following order of segments: RO (program
// header, .note.gnu.build-id, .dynstr, .dynsym, .hash, .rodata
// and .eh_frame), RX (.text), RW (.dynamic and .bss).
//
auto* const elf_header = new (zone_) ElfHeader(*program_table, *this);
// Self-reference to program header table. Required by Android but not by
// Linux. Must appear before any PT_LOAD entries.
program_table->Add(new (zone_) Segment(zone_, program_table,
elf::ProgramHeaderType::PT_PHDR));
// Create the initial load segment which contains the ELF header and program
// table.
current_segment =
new (zone_) Segment(zone_, elf_header, elf::ProgramHeaderType::PT_LOAD);
program_table->Add(current_segment);
current_segment->Add(program_table);
// We now do several passes over the collected sections to reorder them in
// a way that minimizes segments (and thus padding) in the resulting snapshot.
auto add_sections_matching =
[&](const std::function<bool(Section*)>& should_add) {
// We order the sections in a segment so all non-NOBITS sections come
// before NOBITS sections, since the former sections correspond to the
// file contents for the segment.
for (auto* const section : sections_) {
if (!section->HasBits()) continue;
if (should_add(section)) {
add_to_reordered_sections(section);
}
}
for (auto* const section : sections_) {
if (section->HasBits()) continue;
if (should_add(section)) {
add_to_reordered_sections(section);
}
}
};
// If a build ID was created, we put it right after the program table so it
// can be read with a minimum number of bytes from the ELF file.
auto* const build_id = Find(Elf::kBuildIdNoteName);
if (build_id != nullptr) {
ASSERT(build_id->type == elf::SectionHeaderType::SHT_NOTE);
add_to_reordered_sections(build_id);
}
// Now add the other non-writable, non-executable allocated sections.
add_sections_matching([&](Section* section) -> bool {
if (section == build_id) return false; // Already added.
return section->IsAllocated() && !section->IsWritable() &&
!section->IsExecutable();
});
// Now add the executable sections in a new segment.
add_sections_matching([](Section* section) -> bool {
return section->IsExecutable(); // Implies IsAllocated() && !IsWritable()
});
// Now add all the writable sections.
add_sections_matching([](Section* section) -> bool {
return section->IsWritable(); // Implies IsAllocated() && !IsExecutable()
});
// We put all non-reserved unallocated sections last. Otherwise, they would
// affect the file offset but not the memory offset of any following allocated
// sections. Doing it in this order makes it easier to keep file and memory
// offsets page-aligned with respect to each other, which is required for
// some loaders.
add_sections_matching([](Section* section) -> bool {
// Don't re-add the initial reserved section.
return !section->IsReservedSection() && !section->IsAllocated();
});
// All sections should have been accounted for in the loops above.
ASSERT_EQUAL(sections_.length(), reordered_sections.length());
// Replace the content of sections_ with the reordered sections.
sections_.Clear();
sections_.AddArray(reordered_sections);
// This must be true for uses of the map to be correct.
ASSERT_EQUAL(index_map[elf::SHN_UNDEF], elf::SHN_UNDEF);
// Since the section indices have been updated, change links to match
// and update the indexes of symbols in any symbol tables.
for (auto* const section : sections_) {
// SHN_UNDEF maps to SHN_UNDEF, so no need to check for it.
section->link = index_map[section->link];
if (auto* const table = section->AsSymbolTable()) {
table->UpdateSectionIndices(index_map);
}
}
if (symtab->index == elf::SHN_UNDEF) {
// The output is stripped, so this wasn't finalized during the loop above.
symtab->UpdateSectionIndices(index_map);
}
// Add any special non-load segments.
if (build_id != nullptr) {
// Add a PT_NOTE segment for the build ID.
program_table->Add(
new (zone_) Segment(zone_, build_id, elf::ProgramHeaderType::PT_NOTE));
}
// Add a PT_DYNAMIC segment for the dynamic symbol table.
ASSERT(HasSectionNamed(Elf::kDynamicTableName));
auto* const dynamic = Find(Elf::kDynamicTableName)->AsDynamicTable();
program_table->Add(
new (zone_) Segment(zone_, dynamic, elf::ProgramHeaderType::PT_DYNAMIC));
// Add a PT_GNU_STACK segment to prevent the loading of our snapshot from
// switch the stack to be executable.
auto* const gnu_stack = new (zone_) GnuStackSection();
program_table->Add(new (zone_) Segment(zone_, gnu_stack,
elf::ProgramHeaderType::PT_GNU_STACK));
return program_table;
}
void Elf::Finalize() {
// Generate the build ID now that we have all user-provided sections.
GenerateBuildId();
// We add a BSS section in all cases, even to the separate debugging
// information, to ensure that relocated addresses are consistent between ELF
// snapshots and the corresponding separate debugging information.
CreateBSS();
FinalizeEhFrame();
FinalizeDwarfSections();
// Create and initialize the dynamic and static symbol tables and any
// other associated sections now that all other sections have been added.
InitializeSymbolTables();
// Creates an appropriate program table containing load segments for allocated
// sections and any other segments needed. May reorder sections to minimize
// the number of load segments, so also takes the static symbol table so
// symbol section indices can be adjusted if needed.
program_table_ = section_table_->CreateProgramTable(symtab_);
// Calculate file and memory offsets, and finalizes symbol values in any
// symbol tables.
ComputeOffsets();
#if defined(DEBUG)
if (type_ == Type::Snapshot) {
// For files that will be dynamically loaded, ensure the file offsets
// of allocated sections are page aligned to the memory offsets.
for (auto* const segment : program_table_->segments()) {
for (auto* const section : segment->sections()) {
ASSERT_EQUAL(section->file_offset() % Elf::kPageSize,
section->memory_offset() % Elf::kPageSize);
}
}
}
#endif
// Finally, write the ELF file contents.
ElfWriteStream wrapped(unwrapped_stream_, *this);
auto write_section = [&](const Section* section) {
wrapped.Align(section->alignment);
ASSERT_EQUAL(wrapped.Position(), section->file_offset());
section->Write(&wrapped);
ASSERT_EQUAL(wrapped.Position(),
section->file_offset() + section->FileSize());
};
// To match ComputeOffsets, first we write allocated sections and then
// unallocated sections. We access the allocated sections via the load
// segments so we can properly align the stream for each entered segment.
intptr_t section_index = 1; // We don't visit the reserved section.
for (auto* const segment : program_table_->segments()) {
if (segment->type != elf::ProgramHeaderType::PT_LOAD) continue;
wrapped.Align(segment->Alignment());
for (auto* const section : segment->sections()) {
ASSERT(section->IsAllocated());
write_section(section);
if (!section->IsPseudoSection()) {
ASSERT_EQUAL(section->index, section_index);
section_index++;
}
}
}
const auto& sections = section_table_->sections();
for (; section_index < sections.length(); section_index++) {
auto* const section = sections[section_index];
ASSERT(!section->IsAllocated());
write_section(section);
}
// Finally, write the section table.
write_section(section_table_);
}
// For the build ID, we generate a 128-bit hash, where each 32 bits is a hash of
// the contents of the following segments in order:
//
// .text(VM) | .text(Isolate) | .rodata(VM) | .rodata(Isolate)
static constexpr const char* kBuildIdSegmentNames[]{
kVmSnapshotInstructionsAsmSymbol,
kIsolateSnapshotInstructionsAsmSymbol,
kVmSnapshotDataAsmSymbol,
kIsolateSnapshotDataAsmSymbol,
};
static constexpr intptr_t kBuildIdSegmentNamesLength =
ARRAY_SIZE(kBuildIdSegmentNames);
// Includes the note name, but not the description.
static constexpr intptr_t kBuildIdHeaderSize =
sizeof(elf::Note) + sizeof(elf::ELF_NOTE_GNU);
void Elf::GenerateBuildId() {
// Not idempotent.
ASSERT(section_table_->Find(kBuildIdNoteName) == nullptr);
uint32_t hashes[kBuildIdSegmentNamesLength];
// Currently, we construct the build ID out of data from two different
// sections: the .text section and the .rodata section. We only create
// a build ID when we have all four sections and when we have the actual
// bytes from those sections.
//
// TODO(dartbug.com/43274): Generate build IDs for separate debugging
// information for assembly snapshots.
//
// TODO(dartbug.com/43516): Generate build IDs for snapshots with deferred
// sections.
auto* const text_section = section_table_->Find(kTextName);
if (text_section == nullptr) return;
ASSERT(text_section->IsTextSection());
auto* const text_bits = text_section->AsBitsContainer();
auto* const data_section = section_table_->Find(kDataName);
if (data_section == nullptr) return;
ASSERT(data_section->IsDataSection());
auto* const data_bits = data_section->AsBitsContainer();
// Now try to find
for (intptr_t i = 0; i < kBuildIdSegmentNamesLength; i++) {
auto* const name = kBuildIdSegmentNames[i];
hashes[i] = text_bits->Hash(name);
if (hashes[i] == 0) {
hashes[i] = data_bits->Hash(name);
}
// The symbol wasn't found in either section or there were no bytes
// associated with the symbol.
if (hashes[i] == 0) return;
}
auto const description_bytes = reinterpret_cast<uint8_t*>(hashes);
const size_t description_length = sizeof(hashes);
// Now that we have the description field contents, create the section.
ZoneWriteStream stream(zone(), kBuildIdHeaderSize + description_length);
stream.WriteFixed<decltype(elf::Note::name_size)>(sizeof(elf::ELF_NOTE_GNU));
stream.WriteFixed<decltype(elf::Note::description_size)>(description_length);
stream.WriteFixed<decltype(elf::Note::type)>(elf::NoteType::NT_GNU_BUILD_ID);
ASSERT_EQUAL(stream.Position(), sizeof(elf::Note));
stream.WriteBytes(elf::ELF_NOTE_GNU, sizeof(elf::ELF_NOTE_GNU));
ASSERT_EQUAL(stream.bytes_written(), kBuildIdHeaderSize);
stream.WriteBytes(description_bytes, description_length);
auto* const container = new (zone_) NoteSection();
container->AddPortion(stream.buffer(), stream.bytes_written(),
/*relocations=*/nullptr, /*symbols=*/nullptr,
kSnapshotBuildIdAsmSymbol);
section_table_->Add(container, kBuildIdNoteName);
}
void Elf::ComputeOffsets() {
intptr_t file_offset = 0;
intptr_t memory_offset = 0;
// Maps indices of allocated sections in the section table to memory offsets.
const intptr_t num_sections = section_table_->SectionCount();
GrowableArray<intptr_t> address_map(zone_, num_sections);
address_map.Add(0); // Don't adjust offsets for symbols with index SHN_UNDEF.
auto calculate_section_offsets = [&](Section* section) {
file_offset = Utils::RoundUp(file_offset, section->alignment);
section->set_file_offset(file_offset);
file_offset += section->FileSize();
if (section->IsAllocated()) {
memory_offset = Utils::RoundUp(memory_offset, section->alignment);
section->set_memory_offset(memory_offset);
memory_offset += section->MemorySize();
}
};
intptr_t section_index = 1; // We don't visit the reserved section.
for (auto* const segment : program_table_->segments()) {
if (segment->type != elf::ProgramHeaderType::PT_LOAD) continue;
// Adjust file and memory offsets for segment alignment on entry.
file_offset = Utils::RoundUp(file_offset, segment->Alignment());
memory_offset = Utils::RoundUp(memory_offset, segment->Alignment());
for (auto* const section : segment->sections()) {
ASSERT(section->IsAllocated());
calculate_section_offsets(section);
if (!section->IsPseudoSection()) {
// Note: this assumes that the sections in the section header has all
// allocated sections before all (non-reserved) unallocated sections and
// in the same order as the load segments in in the program table.
address_map.Add(section->memory_offset());
ASSERT_EQUAL(section->index, section_index);
section_index++;
}
}
}
const auto& sections = section_table_->sections();
for (; section_index < sections.length(); section_index++) {
auto* const section = sections[section_index];
ASSERT(!section->IsAllocated());
calculate_section_offsets(section);
}
ASSERT_EQUAL(section_index, sections.length());
// Now that all sections have been handled, set the file offset for the
// section table, as it will be written after the last section.
calculate_section_offsets(section_table_);
#if defined(DEBUG)
// Double check that segment starts are aligned as expected.
for (auto* const segment : program_table_->segments()) {
ASSERT(Utils::IsAligned(segment->MemoryOffset(), segment->Alignment()));
}
#endif
// This must be true for uses of the map to be correct.
ASSERT_EQUAL(address_map[elf::SHN_UNDEF], 0);
// Adjust addresses in symbol tables as we now have section memory offsets.
// Also finalize the entries of the dynamic table, as some are memory offsets.
for (auto* const section : sections) {
if (auto* const table = section->AsSymbolTable()) {
table->Finalize(address_map);
} else if (auto* const dynamic = section->AsDynamicTable()) {
dynamic->Finalize();
}
}
// Also adjust addresses in symtab for stripped snapshots.
if (IsStripped()) {
ASSERT_EQUAL(symtab_->index, elf::SHN_UNDEF);
symtab_->Finalize(address_map);
}
}
void ElfHeader::Write(ElfWriteStream* stream) const {
ASSERT_EQUAL(file_offset(), 0);
ASSERT_EQUAL(memory_offset(), 0);
#if defined(TARGET_ARCH_IS_32_BIT)
uint8_t size = elf::ELFCLASS32;
#else
uint8_t size = elf::ELFCLASS64;
#endif
uint8_t e_ident[16] = {0x7f,
'E',
'L',
'F',
size,
elf::ELFDATA2LSB,
elf::EV_CURRENT,
elf::ELFOSABI_SYSV,
0,
0,
0,
0,
0,
0,
0,
0};
stream->WriteBytes(e_ident, 16);
stream->WriteHalf(elf::ET_DYN); // Shared library.
#if defined(TARGET_ARCH_IA32)
stream->WriteHalf(elf::EM_386);
#elif defined(TARGET_ARCH_X64)
stream->WriteHalf(elf::EM_X86_64);
#elif defined(TARGET_ARCH_ARM)
stream->WriteHalf(elf::EM_ARM);
#elif defined(TARGET_ARCH_ARM64)
stream->WriteHalf(elf::EM_AARCH64);
#elif defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
stream->WriteHalf(elf::EM_RISCV);
#else
FATAL("Unknown ELF architecture");
#endif
stream->WriteWord(elf::EV_CURRENT); // Version
stream->WriteAddr(0); // "Entry point"
stream->WriteOff(program_table_.file_offset());
stream->WriteOff(section_table_.file_offset());
#if defined(TARGET_ARCH_ARM)
uword flags = elf::EF_ARM_ABI | (TargetCPUFeatures::hardfp_supported()
? elf::EF_ARM_ABI_FLOAT_HARD
: elf::EF_ARM_ABI_FLOAT_SOFT);
#else
uword flags = 0;
#endif
stream->WriteWord(flags);
stream->WriteHalf(sizeof(elf::ElfHeader));
stream->WriteHalf(program_table_.entry_size);
stream->WriteHalf(program_table_.SegmentCount());
stream->WriteHalf(section_table_.entry_size);
stream->WriteHalf(section_table_.SectionCount());
stream->WriteHalf(stream->elf().section_table().StringTableIndex());
}
void ProgramTable::Write(ElfWriteStream* stream) const {
ASSERT(segments_.length() > 0);
// Make sure all relevant segments were created by checking the type of the
// first.
ASSERT(segments_[0]->type == elf::ProgramHeaderType::PT_PHDR);
const intptr_t start = stream->Position();
// Should be immediately following the ELF header.
ASSERT_EQUAL(start, sizeof(elf::ElfHeader));
#if defined(DEBUG)
// Here, we count the number of times that a PT_LOAD writable segment is
// followed by a non-writable segment. We initialize last_writable to true
// so that we catch the case where the first segment is non-writable.
bool last_writable = true;
int non_writable_groups = 0;
#endif
for (intptr_t i = 0; i < segments_.length(); i++) {
const Segment* const segment = segments_[i];
ASSERT(segment->type != elf::ProgramHeaderType::PT_NULL);
ASSERT_EQUAL(i == 0, segment->type == elf::ProgramHeaderType::PT_PHDR);
#if defined(DEBUG)
if (segment->type == elf::ProgramHeaderType::PT_LOAD) {
if (last_writable && !segment->IsWritable()) {
non_writable_groups++;
}
last_writable = segment->IsWritable();
}
#endif
const intptr_t start = stream->Position();
segment->WriteProgramHeader(stream);
const intptr_t end = stream->Position();
ASSERT_EQUAL(end - start, entry_size);
}
#if defined(DEBUG)
// All PT_LOAD non-writable segments must be contiguous. If not, some older
// Android dynamic linkers fail to handle writable segments between
// non-writable ones. See https://github.com/flutter/flutter/issues/43259.
ASSERT(non_writable_groups <= 1);
#endif
}
void SectionTable::Write(ElfWriteStream* stream) const {
for (intptr_t i = 0; i < sections_.length(); i++) {
const Section* const section = sections_[i];
ASSERT_EQUAL(i == 0, section->IsReservedSection());
ASSERT_EQUAL(section->index, i);
ASSERT(section->link < sections_.length());
const intptr_t start = stream->Position();
section->WriteSectionHeader(stream);
const intptr_t end = stream->Position();
ASSERT_EQUAL(end - start, entry_size);
}
}
#endif // DART_PRECOMPILER
} // namespace dart