blob: 942ed59d01d1c3c492d82ebeb8cfa46568152958 [file] [log] [blame]
// 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) {}
// 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_; }
intptr_t Position() const { return stream_->Position(); }
void Align(const intptr_t alignment) {
ASSERT(Utils::IsPowerOfTwo(alignment));
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_;
};
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;
class BitsContainer;
class Segment;
static constexpr intptr_t kDefaultAlignment = -1;
// 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 = kDefaultAlignment)
: type(t),
flags(EncodeFlags(allocate, executable, writable)),
alignment(align == kDefaultAlignment ? DefaultAlignment(t) : align),
// Non-segments will never have a memory offset, here represented by 0.
memory_offset_(allocate ? kLinearInitValue : 0) {
// Only sections with type SHT_NULL are allowed to have an alignment of 0.
ASSERT(type == elf::SectionHeaderType::SHT_NULL || alignment > 0);
// 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;
const char* symbol_name = nullptr;
#define FOR_EACH_SECTION_LINEAR_FIELD(M) \
M(name) \
M(index) \
M(file_offset)
FOR_EACH_SECTION_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
virtual intptr_t FileSize() const = 0;
// Loader view.
#define FOR_EACH_SEGMENT_LINEAR_FIELD(M) M(memory_offset)
FOR_EACH_SEGMENT_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
// Each section belongs to at most one PT_LOAD segment.
Segment* load_segment = nullptr;
virtual intptr_t MemorySize() const = 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; }
// 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 offset (and memory offset for allocated sections).
return file_offset_is_set();
}
virtual const BitsContainer* AsBitsContainer() const { return nullptr; }
// Writes the file contents of the section.
virtual void Write(ElfWriteStream* stream) = 0;
virtual void WriteSectionHeader(ElfWriteStream* stream) {
#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()); // Has different meaning for BSS.
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()); // Has different meaning for BSS.
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) {
if (!allocate) return 0;
intptr_t flags = elf::SHF_ALLOC;
if (executable) flags |= elf::SHF_EXECINSTR;
if (writable) flags |= elf::SHF_WRITE;
return flags;
}
static intptr_t DefaultAlignment(elf::SectionHeaderType type) {
switch (type) {
case elf::SectionHeaderType::SHT_SYMTAB:
case elf::SectionHeaderType::SHT_DYNSYM:
case elf::SectionHeaderType::SHT_HASH:
case elf::SectionHeaderType::SHT_DYNAMIC:
return compiler::target::kWordSize;
default:
return 1;
}
}
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->IsAllocated());
sections_.Add(initial_section);
if (type == elf::ProgramHeaderType::PT_LOAD) {
ASSERT(initial_section->load_segment == nullptr);
}
}
virtual ~Segment() {}
static intptr_t Alignment(elf::ProgramHeaderType segment_type) {
switch (segment_type) {
case elf::ProgramHeaderType::PT_PHDR:
case elf::ProgramHeaderType::PT_DYNAMIC:
return compiler::target::kWordSize;
case elf::ProgramHeaderType::PT_NOTE:
return kNoteAlignment;
default:
return Elf::kPageSize;
}
}
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) {
#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, not used.
stream->WriteWord(FileSize());
stream->WriteWord(MemorySize());
stream->WriteWord(flags);
stream->WriteWord(Alignment(type));
#else
stream->WriteWord(static_cast<uint32_t>(type));
stream->WriteWord(flags);
stream->WriteOff(FileOffset());
stream->WriteAddr(MemoryOffset()); // Virtual address.
stream->WriteAddr(MemoryOffset()); // Physical address, not used.
stream->WriteXWord(FileSize());
stream->WriteXWord(MemorySize());
stream->WriteXWord(Alignment(type));
#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 only add additional sections to load segments.
ASSERT(type == elf::ProgramHeaderType::PT_LOAD);
// Don't use this to change a section's segment.
ASSERT(section->load_segment == nullptr);
// We only add sections with the same executable and writable bits.
if (IsExecutable() != section->IsExecutable() ||
IsWritable() != section->IsWritable()) {
return false;
}
sections_.Add(section);
section->load_segment = this;
return true;
}
bool Merge(Segment* other) {
ASSERT(other != nullptr);
// We only add additional sections to load segments.
ASSERT(type == elf::ProgramHeaderType::PT_LOAD);
// We only merge segments with the same executable and writable bits.
if (IsExecutable() != other->IsExecutable() ||
IsWritable() != other->IsWritable()) {
return false;
}
for (auto* section : other->sections_) {
// Don't merge segments where the memory offsets have already been
// calculated.
ASSERT(!section->memory_offset_is_set());
sections_.Add(section);
section->load_segment = this;
}
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(); }
private:
static constexpr intptr_t kInitValue = -1;
static_assert(kInitValue < 0, "init value must be negative");
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;
}
public:
const elf::ProgramHeaderType type;
const intptr_t flags;
private:
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_name(0);
set_index(0);
set_file_offset(0);
}
intptr_t FileSize() const { return 0; }
intptr_t MemorySize() const { return 0; }
void Write(ElfWriteStream* stream) {}
};
// Represents portions of the file/memory space which do not correspond to
// actual sections. Should never be added to sections_.
class PseudoSection : public Section {
public:
PseudoSection(bool executable,
bool writable,
intptr_t file_offset,
intptr_t file_size,
intptr_t memory_offset,
intptr_t memory_size)
: Section(elf::SectionHeaderType::SHT_NULL,
/*allocate=*/true,
executable,
writable,
/*alignment=*/0),
file_size_(file_size),
memory_size_(memory_size) {
set_file_offset(file_offset);
set_memory_offset(memory_offset);
}
intptr_t FileSize() const { return file_size_; }
intptr_t MemorySize() const { return memory_size_; }
void WriteSectionHeader(ElfWriteStream* stream) { UNREACHABLE(); }
void Write(ElfWriteStream* stream) { UNREACHABLE(); }
private:
const intptr_t file_size_;
const intptr_t memory_size_;
};
// A segment for representing the program header table self-reference in the
// program header table.
class ProgramTableSelfSegment : public Segment {
public:
ProgramTableSelfSegment(Zone* zone, intptr_t offset, intptr_t size)
: Segment(zone,
new (zone) PseudoSection(/*executable=*/false,
/*writable=*/false,
offset,
size,
offset,
size),
elf::ProgramHeaderType::PT_PHDR) {}
};
// A segment for representing the program header table load segment in the
// program header table.
class ProgramTableLoadSegment : public Segment {
public:
// The Android dynamic linker in Jelly Bean incorrectly assumes that all
// non-writable segments are continguous. Since the BSS segment comes directly
// after the program header segment, we must make this segment writable so
// later non-writable segments does not cause the BSS to be also marked as
// read-only.
//
// The bug is here:
// https://github.com/aosp-mirror/platform_bionic/blob/94963af28e445384e19775a838a29e6a71708179/linker/linker.c#L1991-L2001
explicit ProgramTableLoadSegment(Zone* zone, intptr_t size)
: Segment(zone,
// This segment should always start at address 0.
new (zone) PseudoSection(/*executable=*/false,
/*writable=*/true,
0,
size,
0,
size),
elf::ProgramHeaderType::PT_LOAD) {}
};
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) {
AddString("");
}
intptr_t FileSize() const { return text_.length(); }
intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; }
void Write(ElfWriteStream* stream) {
stream->WriteBytes(reinterpret_cast<const uint8_t*>(text_.buffer()),
text_.length());
}
intptr_t AddString(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) {
ASSERT(index < text_.length());
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) {
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.)
const char* const kReservedName = "";
AddSymbol(kReservedName, elf::STB_LOCAL, elf::STT_NOTYPE, elf::SHN_UNDEF,
/*size=*/0);
FinalizeSymbol(kReservedName, elf::SHN_UNDEF, /*offset=*/0);
}
intptr_t FileSize() const { return Length() * entry_size; }
intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; }
class Symbol : public ZoneAllocated {
public:
Symbol(const char* cstr,
intptr_t name,
intptr_t binding,
intptr_t type,
intptr_t initial_section_index,
intptr_t size)
: name_index(name),
binding(binding),
type(type),
size(size),
section_index(initial_section_index),
cstr_(cstr) {}
void Finalize(intptr_t final_section_index, intptr_t offset) {
ASSERT(!HasBeenFinalized()); // No symbol should be re-finalized.
section_index = final_section_index;
offset_ = offset;
}
bool HasBeenFinalized() const { return offset_ != kNotFinalizedMarker; }
intptr_t offset() const {
ASSERT(HasBeenFinalized());
// Only the reserved initial symbol should have an offset of 0.
ASSERT_EQUAL(type == elf::STT_NOTYPE, offset_ == 0);
return offset_;
}
void Write(ElfWriteStream* stream) const {
const intptr_t start = stream->Position();
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));
}
const intptr_t name_index;
const intptr_t binding;
const intptr_t type;
const intptr_t size;
// Is set twice: once in Elf::AddSection to the section's initial index into
// sections_, and then in Elf::FinalizeSymbols to the section's final index
// into sections_ after reordering.
intptr_t section_index;
private:
static const intptr_t kNotFinalizedMarker = -1;
const char* const cstr_;
intptr_t offset_ = kNotFinalizedMarker;
friend class SymbolHashTable; // For cstr_ access.
};
void Write(ElfWriteStream* stream) {
for (intptr_t i = 0; i < Length(); i++) {
auto const symbol = At(i);
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 section_index,
intptr_t size) {
ASSERT(!table_->HasBeenFinalized());
auto const name_index = table_->AddString(name);
ASSERT(by_name_index_.Lookup(name_index) == nullptr);
auto const symbol = new (zone_)
Symbol(name, name_index, binding, type, section_index, size);
symbols_.Add(symbol);
by_name_index_.Insert(name_index, symbol);
// 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 != symbols_.length() - 1) {
// 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, symbols_.length() - 1);
}
info += 1;
}
}
void FinalizeSymbol(const char* name,
intptr_t final_section_index,
intptr_t offset) {
const intptr_t name_index = table_->Lookup(name);
ASSERT(name_index != StringTable::kNotIndexed);
Symbol* symbol = by_name_index_.Lookup(name_index);
ASSERT(symbol != nullptr);
symbol->Finalize(final_section_index, offset);
}
intptr_t Length() const { return symbols_.length(); }
const Symbol* At(intptr_t i) const { return symbols_[i]; }
const Symbol* Find(const char* name) const {
ASSERT(name != nullptr);
auto const name_index = table_->Lookup(name);
return by_name_index_.Lookup(name_index);
}
private:
Zone* const zone_;
StringTable* const table_;
const bool dynamic_;
GrowableArray<Symbol*> symbols_;
mutable IntMap<Symbol*> by_name_index_;
};
static uint32_t ElfHash(const unsigned char* name) {
uint32_t h = 0;
while (*name != '\0') {
h = (h << 4) + *name++;
uint32_t g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
class SymbolHashTable : public Section {
public:
SymbolHashTable(Zone* zone, StringTable* strtab, SymbolTable* symtab)
: Section(elf::SectionHeaderType::SHT_HASH,
/*allocate=*/true,
/*executable=*/false,
/*writable=*/false) {
entry_size = sizeof(int32_t);
nchain_ = symtab->Length();
nbucket_ = symtab->Length();
bucket_ = zone->Alloc<int32_t>(nbucket_);
for (intptr_t i = 0; i < nbucket_; i++) {
bucket_[i] = elf::STN_UNDEF;
}
chain_ = zone->Alloc<int32_t>(nchain_);
for (intptr_t i = 0; i < nchain_; i++) {
chain_[i] = elf::STN_UNDEF;
}
for (intptr_t i = 1; i < symtab->Length(); i++) {
auto const symbol = symtab->At(i);
uint32_t hash = ElfHash((const unsigned char*)symbol->cstr_);
uint32_t probe = hash % nbucket_;
chain_[i] = bucket_[probe]; // next = head
bucket_[probe] = i; // head = symbol
}
}
intptr_t FileSize() const { return entry_size * (nbucket_ + nchain_ + 2); }
intptr_t MemorySize() const { return FileSize(); }
void Write(ElfWriteStream* stream) {
stream->WriteWord(nbucket_);
stream->WriteWord(nchain_);
for (intptr_t i = 0; i < nbucket_; i++) {
stream->WriteWord(bucket_[i]);
}
for (intptr_t i = 0; i < nchain_; i++) {
stream->WriteWord(chain_[i]);
}
}
private:
int32_t nbucket_;
int32_t nchain_;
int32_t* bucket_; // "Head"
int32_t* chain_; // "Next"
};
class DynamicTable : public Section {
public:
explicit DynamicTable(Zone* zone)
: Section(elf::SectionHeaderType::SHT_DYNAMIC,
/*allocate=*/true,
/*executable=*/false,
/*writable=*/true) {
entry_size = sizeof(elf::DynamicEntry);
// Entries that are not constants are fixed during Elf::Finalize().
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;
intptr_t FileSize() const { return entries_.length() * entry_size; }
intptr_t MemorySize() const { return FileSize(); }
void Write(ElfWriteStream* stream) {
for (intptr_t i = 0; i < entries_.length(); i++) {
entries_[i]->Write(stream);
}
}
struct Entry : public ZoneAllocated {
Entry(elf::DynamicEntryType tag, intptr_t value) : tag(tag), value(value) {}
void Write(ElfWriteStream* stream) {
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;
}
}
}
void FinalizeEntries(StringTable* strtab,
SymbolTable* symtab,
SymbolHashTable* hash) {
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:
GrowableArray<Entry*> entries_;
};
// A segment for representing the dynamic table segment in the program header
// table. There is no corresponding section for this segment.
class DynamicSegment : public Segment {
public:
explicit DynamicSegment(Zone* zone, DynamicTable* dynamic)
: Segment(zone, dynamic, elf::ProgramHeaderType::PT_DYNAMIC) {}
};
// A segment for representing the dynamic table segment in the program header
// table. There is no corresponding section for this segment.
class NoteSegment : public Segment {
public:
NoteSegment(Zone* zone, Section* note)
: Segment(zone, note, elf::ProgramHeaderType::PT_NOTE) {
ASSERT_EQUAL(static_cast<uint32_t>(note->type),
static_cast<uint32_t>(elf::SectionHeaderType::SHT_NOTE));
}
};
class BitsContainer : public Section {
public:
// Fully specified BitsContainer information.
BitsContainer(elf::SectionHeaderType type,
bool allocate,
bool executable,
bool writable,
intptr_t size,
const uint8_t* bytes,
const ZoneGrowableArray<Elf::Relocation>* relocations,
const ZoneGrowableArray<Elf::SymbolData>* symbols,
int alignment = kDefaultAlignment)
: Section(type, allocate, executable, writable, alignment),
file_size_(type == elf::SectionHeaderType::SHT_NOBITS ? 0 : size),
memory_size_(allocate ? size : 0),
bytes_(bytes),
relocations_(relocations),
symbols_(symbols) {
ASSERT(type == elf::SectionHeaderType::SHT_NOBITS || bytes != nullptr);
}
// For BitsContainers used only as sections.
BitsContainer(elf::SectionHeaderType type,
intptr_t size,
const uint8_t* bytes,
const ZoneGrowableArray<Elf::Relocation>* relocations,
const ZoneGrowableArray<Elf::SymbolData>* symbols,
intptr_t alignment = kDefaultAlignment)
: BitsContainer(type,
/*allocate=*/false,
/*executable=*/false,
/*writable=*/false,
size,
bytes,
relocations,
symbols,
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 size,
const uint8_t* bytes,
const ZoneGrowableArray<Elf::Relocation>* relocations,
const ZoneGrowableArray<Elf::SymbolData>* symbols,
intptr_t alignment = kDefaultAlignment)
: BitsContainer(t == Elf::Type::Snapshot
? elf::SectionHeaderType::SHT_PROGBITS
: elf::SectionHeaderType::SHT_NOBITS,
/*allocate=*/true,
executable,
writable,
size,
bytes,
relocations,
symbols,
alignment) {}
const BitsContainer* AsBitsContainer() const { return this; }
const ZoneGrowableArray<Elf::SymbolData>* symbols() const { return symbols_; }
void Write(ElfWriteStream* stream) {
if (type == elf::SectionHeaderType::SHT_NOBITS) return;
if (relocations_ == nullptr) {
return stream->WriteBytes(bytes(), FileSize());
}
const SymbolTable* symtab = ASSERT_NOTNULL(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;
intptr_t target_address = reloc.target_offset;
// Null symbols denote that the corresponding offset should be treated
// as an absolute offset in the ELF memory space.
if (reloc.source_symbol != nullptr) {
if (strcmp(reloc.source_symbol, ".") == 0) {
source_address += memory_offset() + reloc.section_offset;
} else {
auto* const source_symbol = symtab->Find(reloc.source_symbol);
ASSERT(source_symbol != nullptr);
source_address += source_symbol->offset();
}
}
if (reloc.target_symbol != nullptr) {
if (strcmp(reloc.target_symbol, ".") == 0) {
target_address += memory_offset() + reloc.section_offset;
} else {
auto* const target_symbol = symtab->Find(reloc.target_symbol);
if (target_symbol == nullptr) {
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.
const word to_write = Image::kNoRelocatedAddress;
stream->WriteBytes(reinterpret_cast<const uint8_t*>(&to_write),
reloc.size_in_bytes);
current_pos = reloc.section_offset + reloc.size_in_bytes;
continue;
}
target_address += target_symbol->offset();
}
}
ASSERT(reloc.size_in_bytes <= kWordSize);
const word to_write = target_address - source_address;
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, FileSize() - current_pos);
}
uint32_t Hash() const {
ASSERT(bytes() != nullptr);
return Utils::StringHash(bytes(), MemorySize());
}
intptr_t FileSize() const { return file_size_; }
intptr_t MemorySize() const { return memory_size_; }
const uint8_t* bytes() const { return bytes_; }
private:
const intptr_t file_size_;
const intptr_t memory_size_;
const uint8_t* const bytes_;
const ZoneGrowableArray<Elf::Relocation>* const relocations_;
const ZoneGrowableArray<Elf::SymbolData>* const symbols_;
};
Elf::Elf(Zone* zone, BaseWriteStream* stream, Type type, Dwarf* dwarf)
: zone_(zone),
unwrapped_stream_(stream),
type_(type),
dwarf_(dwarf),
shstrtab_(new (zone) StringTable(zone, /*allocate=*/false)),
dynstrtab_(new (zone) StringTable(zone, /*allocate=*/true)),
dynsym_(new (zone) SymbolTable(zone, dynstrtab_, /*dynamic=*/true)),
strtab_(new (zone_) StringTable(zone_, /*allocate=*/false)),
symtab_(new (zone_) SymbolTable(zone, strtab_, /*dynamic=*/false)) {
// 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::AddSection(Section* section,
const char* name,
const char* symbol_name) {
ASSERT(section_table_file_size_ < 0);
ASSERT(!shstrtab_->HasBeenFinalized());
section->set_name(shstrtab_->AddString(name));
// We do not set the section index yet, that will be done during Finalize().
sections_.Add(section);
// We do set the initial section index in initialized symbols for quick lookup
// until reordering happens.
const intptr_t initial_section_index = sections_.length() - 1;
if (symbol_name != nullptr) {
ASSERT(section->IsAllocated());
section->symbol_name = symbol_name;
// While elf::STT_SECTION might seem more appropriate, section symbols are
// usually local and dlsym won't return them.
ASSERT(!dynsym_->HasBeenFinalized());
dynsym_->AddSymbol(symbol_name, elf::STB_GLOBAL, elf::STT_FUNC,
initial_section_index, section->MemorySize());
// Some tools assume the static symbol table is a superset of the dynamic
// symbol table when it exists (see dartbug.com/41783).
ASSERT(!symtab_->HasBeenFinalized());
symtab_->AddSymbol(symbol_name, elf::STB_GLOBAL, elf::STT_FUNC,
initial_section_index, section->FileSize());
}
if (auto const container = section->AsBitsContainer()) {
if (container->symbols() != nullptr) {
ASSERT(section->IsAllocated());
for (const auto& symbol_data : *container->symbols()) {
ASSERT(!symtab_->HasBeenFinalized());
symtab_->AddSymbol(symbol_data.name, elf::STB_LOCAL, symbol_data.type,
initial_section_index, symbol_data.size);
}
}
}
}
void Elf::AddText(const char* name,
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Relocation>* relocations,
const ZoneGrowableArray<SymbolData>* symbols) {
auto const image =
new (zone_) BitsContainer(type_, /*executable=*/true,
/*writable=*/false, size, bytes, relocations,
symbols, ImageWriter::kTextAlignment);
AddSection(image, ".text", name);
}
// Here, both VM and isolate will be compiled into a single snapshot.
// In assembly generation, each serialized text section gets a separate
// pointer into the BSS segment and BSS slots are created for each, since
// we may not serialize both VM and isolate. Here, we always serialize both,
// so make a BSS segment large enough for both, with the VM entries coming
// first.
static constexpr intptr_t kBssVmSize =
BSS::kVmEntryCount * compiler::target::kWordSize;
static constexpr intptr_t kBssIsolateSize =
BSS::kIsolateEntryCount * compiler::target::kWordSize;
static constexpr intptr_t kBssSize = kBssVmSize + kBssIsolateSize;
void Elf::CreateBSS() {
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>(kBssSize);
memset(bytes, 0, kBssSize);
}
// For the BSS section, we add two local symbols to the static symbol table,
// one for each isolate. We use local symbols because these addresses are only
// used for relocation. (This matches the behavior in the assembly output,
// where these symbols are also local.)
auto* bss_symbols = new (zone_) ZoneGrowableArray<Elf::SymbolData>();
bss_symbols->Add({kVmSnapshotBssAsmSymbol, elf::STT_SECTION, 0, kBssVmSize});
bss_symbols->Add({kIsolateSnapshotBssAsmSymbol, elf::STT_SECTION, kBssVmSize,
kBssIsolateSize});
bss_ = new (zone_) BitsContainer(
type_, /*executable=*/false, /*writable=*/true, kBssSize, bytes,
/*relocations=*/nullptr, bss_symbols, ImageWriter::kBssAlignment);
AddSection(bss_, ".bss");
}
void Elf::AddROData(const char* name,
const uint8_t* bytes,
intptr_t size,
const ZoneGrowableArray<Relocation>* relocations,
const ZoneGrowableArray<SymbolData>* symbols) {
auto const image =
new (zone_) BitsContainer(type_, /*executable=*/false,
/*writable=*/false, size, bytes, relocations,
symbols, ImageWriter::kRODataAlignment);
AddSection(image, ".rodata", name);
}
#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(); }
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(), nullptr, 0, symbol, offset});
addr(0); // Resolved later.
}
template <typename T>
void RelativeSymbolOffset(const char* symbol) {
relocations_->Add({sizeof(T), stream_->Position(), ".", 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
const Section* Elf::FindSectionBySymbolName(const char* name) const {
auto* const symbol = symtab_->Find(name);
if (symbol == nullptr) return nullptr;
// Should not be run between OrderSectionsAndCreateSegments (when section
// indices may change) and FinalizeSymbols() (sets the final section index).
ASSERT(segments_.length() == 0 || symbol->HasBeenFinalized());
const Section* const section = sections_[symbol->section_index];
ASSERT_EQUAL(strcmp(section->symbol_name, name), 0);
return section;
}
void Elf::FinalizeSymbols() {
// Must be run after OrderSectionsAndCreateSegments and ComputeOffsets.
ASSERT(segments_.length() > 0);
ASSERT(section_table_file_offset_ > 0);
for (const auto& section : sections_) {
if (section->symbol_name != nullptr) {
dynsym_->FinalizeSymbol(section->symbol_name, section->index(),
section->memory_offset());
symtab_->FinalizeSymbol(section->symbol_name, section->index(),
section->memory_offset());
}
if (auto const container = section->AsBitsContainer()) {
if (container->symbols() != nullptr) {
for (const auto& symbol_data : *container->symbols()) {
symtab_->FinalizeSymbol(
symbol_data.name, section->index(),
section->memory_offset() + symbol_data.offset);
}
}
}
}
}
void Elf::FinalizeEhFrame() {
#if defined(DART_PRECOMPILER) && \
(defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64))
// 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(
ConcreteRegister(LINK_REGISTER)); // Return address register
dwarf_stream.uleb128(1); // Augmentation size
dwarf_stream.u1(DW_EH_PE_pcrel | DW_EH_PE_sdata4); // FDE encoding.
// CFA is FP+0
dwarf_stream.u1(Dwarf::DW_CFA_def_cfa);
dwarf_stream.uleb128(FP);
dwarf_stream.uleb128(0);
});
// Emit an FDE covering each .text section.
const auto text_name = shstrtab_->Lookup(".text");
ASSERT(text_name != StringTable::kNotIndexed);
for (auto section : sections_) {
if (section->name() != text_name) continue;
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>(section->symbol_name);
dwarf_stream.u4(section->MemorySize()); // Size.
dwarf_stream.u1(0); // Augmentation Data length.
// FP at FP+kSavedCallerPcSlotFromFp*kWordSize
COMPILE_ASSERT(kSavedCallerFpSlotFromFp >= 0);
dwarf_stream.u1(Dwarf::DW_CFA_offset | FP);
dwarf_stream.uleb128(kSavedCallerFpSlotFromFp);
// LR at FP+kSavedCallerPcSlotFromFp*kWordSize
COMPILE_ASSERT(kSavedCallerPcSlotFromFp >= 0);
dwarf_stream.u1(Dwarf::DW_CFA_offset | ConcreteRegister(LINK_REGISTER));
dwarf_stream.uleb128(kSavedCallerPcSlotFromFp);
// SP is FP+kCallerSpSlotFromFp*kWordSize
COMPILE_ASSERT(kCallerSpSlotFromFp >= 0);
dwarf_stream.u1(Dwarf::DW_CFA_val_offset);
#if defined(TARGET_ARCH_ARM64)
dwarf_stream.uleb128(ConcreteRegister(CSP));
#elif defined(TARGET_ARCH_ARM)
dwarf_stream.uleb128(SP);
#else
#error "Unsupported .eh_frame architecture"
#endif
dwarf_stream.uleb128(kCallerSpSlotFromFp);
});
}
dwarf_stream.u4(0); // end of section
auto const eh_frame = new (zone_)
BitsContainer(type_, /*writable=*/false, /*executable=*/false,
dwarf_stream.bytes_written(), dwarf_stream.buffer(),
dwarf_stream.relocations(), /*symbols=*/nullptr);
AddSection(eh_frame, ".eh_frame");
#endif // defined(DART_PRECOMPILER) && \
// (defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64))
}
void Elf::FinalizeDwarfSections() {
if (dwarf_ == nullptr) return;
#if defined(DART_PRECOMPILER)
auto add_debug = [&](const char* name, const DwarfElfStream& stream) {
auto const image = new (zone_) BitsContainer(
elf::SectionHeaderType::SHT_PROGBITS, stream.bytes_written(),
stream.buffer(), stream.relocations(), /*symbols=*/nullptr);
AddSection(image, 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);
}
#endif
}
void Elf::OrderSectionsAndCreateSegments() {
GrowableArray<Section*> reordered_sections;
// The first section in the section header table is always a reserved
// entry containing only 0 values.
reordered_sections.Add(new (zone_) ReservedSection());
Segment* current_segment = nullptr;
auto add_to_reordered_sections = [&](Section* section) {
section->set_index(reordered_sections.length());
reordered_sections.Add(section);
if (!section->IsAllocated()) return;
const bool was_added =
current_segment == nullptr ? false : current_segment->Add(section);
if (!was_added) {
// There is no current segment or it is incompatible for merging, so
// following compatible segments will be merged into this one if possible.
current_segment =
new (zone_) Segment(zone_, section, elf::ProgramHeaderType::PT_LOAD);
section->load_segment = current_segment;
segments_.Add(current_segment);
}
};
// Add writable, non-executable sections first, due to a bug in Jelly Bean's
// ELF loader when a writable segment is placed between two non-writable
// segments. See also Elf::WriteProgramTable(), which double-checks this.
for (auto* const section : sections_) {
if (section->IsAllocated() && section->IsWritable() &&
!section->IsExecutable()) {
add_to_reordered_sections(section);
}
}
// Now add the non-writable, non-executable allocated sections in a new
// segment, starting with the data sections.
for (auto* const section : sections_) {
if (section->IsAllocated() && !section->IsWritable() &&
!section->IsExecutable()) {
add_to_reordered_sections(section);
}
}
// Now add the non-writable, executable sections in a new segment.
for (auto* const section : sections_) {
if (section->IsAllocated() && !section->IsWritable() &&
section->IsExecutable()) {
add_to_reordered_sections(section);
}
}
// We put all unallocated sections last because 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.
for (auto* const section : sections_) {
if (!section->IsAllocated()) {
add_to_reordered_sections(section);
}
}
// Now replace sections_.
sections_.Clear();
sections_.AddArray(reordered_sections);
}
void Elf::Finalize() {
ASSERT(program_table_file_size_ < 0);
// Generate the build ID now that we have all user-provided sections.
// Generating it at this point also means it'll be the first writable
// non-executable section added to sections_ and thus end up right after the
// program table after reordering. This limits how much of the ELF file needs
// to be read to get the build ID (header + program table + note segment).
GenerateBuildId();
// We add BSS in all cases, even to the separate debugging information ELF,
// to ensure that relocated addresses are consistent between ELF snapshots
// and ELF separate debugging information.
CreateBSS();
// Adding the dynamic symbol table and associated sections.
AddSection(dynstrtab_, ".dynstr");
AddSection(dynsym_, ".dynsym");
auto const hash = new (zone_) SymbolHashTable(zone_, dynstrtab_, dynsym_);
AddSection(hash, ".hash");
auto const dynamic = new (zone_) DynamicTable(zone_);
AddSection(dynamic, ".dynamic");
if (!IsStripped()) {
AddSection(strtab_, ".strtab");
AddSection(symtab_, ".symtab");
}
AddSection(shstrtab_, ".shstrtab");
FinalizeEhFrame();
FinalizeDwarfSections();
OrderSectionsAndCreateSegments();
// Now that the sections have indices, set up links between them as needed.
dynsym_->link = dynstrtab_->index();
hash->link = dynsym_->index();
dynamic->link = dynstrtab_->index();
if (!IsStripped()) {
symtab_->link = strtab_->index();
}
// Now add any special non-load segments.
if (build_id_ != nullptr) {
// Add a PT_NOTE segment for the build ID.
segments_.Add(new (zone_) NoteSegment(zone_, build_id_));
}
// Add a PT_DYNAMIC segment for the dynamic symbol table.
segments_.Add(new (zone_) DynamicSegment(zone_, dynamic));
// At this point, all sections have been added and ordered and all sections
// appropriately grouped into segments. Add the program table and then
// calculate file and memory offsets.
FinalizeProgramTable();
ComputeOffsets();
// Now that we have reordered the sections and set memory offsets, we can
// update the symbol tables to add index and address information. This must
// be done prior to writing the symbol tables and any sections with
// relocations.
FinalizeSymbols();
// Also update the entries in the dynamic table.
dynamic->FinalizeEntries(dynstrtab_, dynsym_, hash);
// Finally, write the ELF file contents.
ElfWriteStream wrapped(unwrapped_stream_, *this);
WriteHeader(&wrapped);
WriteProgramTable(&wrapped);
WriteSections(&wrapped);
WriteSectionTable(&wrapped);
}
// 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() {
uint32_t hashes[kBuildIdSegmentNamesLength];
for (intptr_t i = 0; i < kBuildIdSegmentNamesLength; i++) {
auto const name = kBuildIdSegmentNames[i];
auto const section = FindSectionBySymbolName(name);
// If we're missing a section, then we don't generate a final build ID.
if (section == nullptr) return;
auto const bits = section->AsBitsContainer();
if (bits == nullptr) {
FATAL1("Section for symbol %s is not a BitsContainer", name);
}
// For now, if we don't have section contents (because we're generating
// assembly), don't generate a final build ID, as we'll have different
// build IDs in the snapshot and the separate debugging information.
//
// TODO(dartbug.com/43274): Change once we generate consistent build IDs
// between assembly snapshots and their debugging information.
if (bits->bytes() == nullptr) return;
hashes[i] = bits->Hash();
}
auto const description_bytes = reinterpret_cast<uint8_t*>(hashes);
const size_t description_length = sizeof(hashes);
// To ensure we can quickly check for a final build ID, we ensure the first
// byte contains a non-zero value.
if (description_bytes[0] == 0) {
description_bytes[0] = 1;
}
// 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);
// While the build ID section does not need to be writable, the first segment
// in our ELF files is writable (see Elf::WriteProgramTable) and so this
// ensures we can put it right after the program table without padding.
build_id_ = new (zone_) BitsContainer(
elf::SectionHeaderType::SHT_NOTE,
/*allocate=*/true, /*executable=*/false,
/*writable=*/true, stream.bytes_written(), stream.buffer(),
/*relocations=*/nullptr, /*symbols=*/nullptr, kNoteAlignment);
AddSection(build_id_, kBuildIdNoteName, kSnapshotBuildIdAsmSymbol);
}
void Elf::FinalizeProgramTable() {
ASSERT(program_table_file_size_ < 0);
program_table_file_offset_ = sizeof(elf::ElfHeader);
// There is one additional segment we need the size of the program table to
// create, so calculate it as if that segment were already in place.
program_table_file_size_ =
(1 + segments_.length()) * sizeof(elf::ProgramHeader);
auto const program_table_segment_size =
program_table_file_offset_ + program_table_file_size_;
// Segment for loading the initial part of the ELF file, including the
// program header table. Required by Android but not by Linux.
Segment* const initial_load =
new (zone_) ProgramTableLoadSegment(zone_, program_table_segment_size);
// Merge the initial writable segment into this one and replace it (so it
// doesn't change the number of segments).
const bool was_merged = initial_load->Merge(segments_[0]);
ASSERT(was_merged);
segments_[0] = initial_load;
// Self-reference to program header table. Required by Android but not by
// Linux. Must appear before any PT_LOAD entries.
segments_.InsertAt(
0, new (zone_) ProgramTableSelfSegment(zone_, program_table_file_offset_,
program_table_file_size_));
}
static const intptr_t kElfSectionTableAlignment = compiler::target::kWordSize;
void Elf::ComputeOffsets() {
// We calculate the size and offset of the program header table during
// finalization.
ASSERT(program_table_file_offset_ > 0 && program_table_file_size_ > 0);
intptr_t file_offset = program_table_file_offset_ + program_table_file_size_;
// Program table memory size is same as file size.
intptr_t memory_offset = file_offset;
// When calculating memory and file offsets for sections, we'll need to know
// if we've changed segments. Start with the one for the program table.
ASSERT(segments_[0]->type != elf::ProgramHeaderType::PT_LOAD);
const auto* current_segment = segments_[1];
ASSERT(current_segment->type == elf::ProgramHeaderType::PT_LOAD);
// The non-reserved sections are output to the file in order after the program
// header table. If we're entering a new segment, then we need to align
// according to the PT_LOAD segment alignment as well to keep the file offsets
// aligned with the memory addresses.
for (intptr_t i = 1; i < sections_.length(); i++) {
auto const section = sections_[i];
file_offset = Utils::RoundUp(file_offset, section->alignment);
memory_offset = Utils::RoundUp(memory_offset, section->alignment);
if (section->IsAllocated() && section->load_segment != current_segment) {
current_segment = section->load_segment;
ASSERT(current_segment->type == elf::ProgramHeaderType::PT_LOAD);
const intptr_t load_align = Segment::Alignment(current_segment->type);
file_offset = Utils::RoundUp(file_offset, load_align);
memory_offset = Utils::RoundUp(memory_offset, load_align);
}
section->set_file_offset(file_offset);
if (section->IsAllocated()) {
section->set_memory_offset(memory_offset);
#if defined(DEBUG)
if (type_ == Type::Snapshot) {
// For files that will be dynamically loaded, make sure the file offsets
// of allocated sections are page aligned to the memory offsets.
ASSERT_EQUAL(section->file_offset() % Elf::kPageSize,
section->memory_offset() % Elf::kPageSize);
}
#endif
}
file_offset += section->FileSize();
memory_offset += section->MemorySize();
}
file_offset = Utils::RoundUp(file_offset, kElfSectionTableAlignment);
section_table_file_offset_ = file_offset;
section_table_file_size_ = sections_.length() * sizeof(elf::SectionHeader);
file_offset += section_table_file_size_;
}
void Elf::WriteHeader(ElfWriteStream* stream) {
#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);
#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(sizeof(elf::ProgramHeader));
stream->WriteHalf(segments_.length());
stream->WriteHalf(sizeof(elf::SectionHeader));
stream->WriteHalf(sections_.length());
stream->WriteHalf(shstrtab_->index());
ASSERT_EQUAL(stream->Position(), sizeof(elf::ElfHeader));
}
void Elf::WriteProgramTable(ElfWriteStream* stream) {
ASSERT(program_table_file_size_ >= 0); // Check for finalization.
ASSERT(stream->Position() == program_table_file_offset_);
#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 (auto const segment : segments_) {
#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, sizeof(elf::ProgramHeader));
}
#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 Elf::WriteSectionTable(ElfWriteStream* stream) {
ASSERT(section_table_file_size_ >= 0); // Check for finalization.
stream->Align(kElfSectionTableAlignment);
ASSERT_EQUAL(stream->Position(), section_table_file_offset_);
for (auto const section : sections_) {
const intptr_t start = stream->Position();
section->WriteSectionHeader(stream);
const intptr_t end = stream->Position();
ASSERT_EQUAL(end - start, sizeof(elf::SectionHeader));
}
}
void Elf::WriteSections(ElfWriteStream* stream) {
ASSERT(section_table_file_size_ >= 0); // Check for finalization.
// Should be writing the first section immediately after the program table.
ASSERT_EQUAL(stream->Position(),
program_table_file_offset_ + program_table_file_size_);
// Skip the reserved first section, as its alignment is 0 (which will cause
// stream->Align() to fail) and it never contains file contents anyway.
ASSERT_EQUAL(static_cast<uint32_t>(sections_[0]->type),
static_cast<uint32_t>(elf::SectionHeaderType::SHT_NULL));
ASSERT_EQUAL(sections_[0]->alignment, 0);
// The program table is considered part of the first load segment (the
// second segment in segments_), so other sections in the same segment should
// not have extra segment alignment added.
ASSERT(segments_[0]->type != elf::ProgramHeaderType::PT_LOAD);
const Segment* current_segment = segments_[1];
ASSERT(current_segment->type == elf::ProgramHeaderType::PT_LOAD);
for (intptr_t i = 1; i < sections_.length(); i++) {
Section* section = sections_[i];
stream->Align(section->alignment);
if (section->IsAllocated() && section->load_segment != current_segment) {
// Changing segments, so align accordingly.
current_segment = section->load_segment;
ASSERT(current_segment->type == elf::ProgramHeaderType::PT_LOAD);
const intptr_t load_align = Segment::Alignment(current_segment->type);
stream->Align(load_align);
}
ASSERT_EQUAL(stream->Position(), section->file_offset());
section->Write(stream);
ASSERT_EQUAL(stream->Position(),
section->file_offset() + section->FileSize());
}
}
#endif // DART_PRECOMPILER
} // namespace dart