// 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/thread.h" #include "vm/zone_text_buffer.h" namespace dart { // 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) : stream_(ASSERT_NOTNULL(stream)) {} 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_; }; 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; #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 new content can be added to a section. bool HasBeenFinalized() const { if (IsAllocated()) { // The contents of a section that is allocated (part of a segment) must // not change after the section is added. return memory_offset_is_set(); } else { // Unallocated sections can have new content added until we calculate // file offsets. 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(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(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. The first one is added // during initialization. Unlike others added later, it should already have // a memory offset since we use it to determine the segment memory offset. ASSERT(initial_section->IsAllocated()); ASSERT(initial_section->memory_offset_is_set()); // Make sure the memory offset chosen for the initial section is consistent // with the alignment for the segment. ASSERT(Utils::IsAligned(initial_section->memory_offset(), Alignment(type))); sections_.Add(initial_section); if (type == elf::ProgramHeaderType::PT_LOAD) { ASSERT(initial_section->load_segment == nullptr); initial_section->load_segment = this; } } 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(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(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 the given section to this segment. // // Returns whether the Section could be added to the segment. If not, a // new segment will need to be created for this section. // // Sets the memory offset of the section if added. bool Add(Section* section) { // We only add additional sections to load segments. ASSERT(type == elf::ProgramHeaderType::PT_LOAD); ASSERT(section != nullptr); // Only sections with the allocate flag set should be added to segments, // and sections with already-set memory offsets cannot be added. ASSERT(section->IsAllocated()); ASSERT(!section->memory_offset_is_set()); ASSERT(section->load_segment == nullptr); switch (sections_.Last()->type) { // We only use SHT_NULL sections as pseudo sections that will not appear // in the final ELF file. Don't pack sections into these segments, as we // may remove/replace the segments during finalization. case elf::SectionHeaderType::SHT_NULL: // If the last section in the segments is NOBITS, then we don't add it, // as otherwise we'll be guaranteed the file offset and memory offset // won't be page aligned without padding. case elf::SectionHeaderType::SHT_NOBITS: return false; default: break; } // We don't add if the W or X bits don't match. if (IsExecutable() != section->IsExecutable() || IsWritable() != section->IsWritable()) { return false; } auto const start_address = Utils::RoundUp(MemoryEnd(), section->alignment); section->set_memory_offset(start_address); sections_.Add(section); section->load_segment = this; return true; } void Replace(Section* old_section, Section* new_section) { ASSERT(old_section->load_segment == this); // All these must be true for replacement to be safe. ASSERT_EQUAL(static_cast(old_section->type), static_cast(new_section->type)); ASSERT_EQUAL(old_section->MemorySize(), new_section->MemorySize()); ASSERT_EQUAL(old_section->IsExecutable(), new_section->IsExecutable()); ASSERT_EQUAL(old_section->IsWritable(), new_section->IsWritable()); ASSERT(old_section->memory_offset_is_set()); ASSERT(!new_section->memory_offset_is_set()); for (intptr_t i = 0; i < sections_.length(); i++) { auto const section = sections_[i]; if (section != old_section) { continue; } new_section->set_memory_offset(old_section->memory_offset()); sections_[i] = new_section; new_section->load_segment = this; old_section->load_segment = nullptr; return; } UNREACHABLE(); } 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 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 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, 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) { 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, intptr_t alignment = kDefaultAlignment) : BitsContainer(type, /*allocate=*/false, /*executable=*/false, /*writable=*/false, size, bytes, 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, intptr_t alignment = kDefaultAlignment) : BitsContainer(t == Elf::Type::Snapshot ? elf::SectionHeaderType::SHT_PROGBITS : elf::SectionHeaderType::SHT_NOBITS, /*allocate=*/true, executable, writable, size, bytes, alignment) {} const BitsContainer* AsBitsContainer() const { return this; } void Write(ElfWriteStream* stream) { if (type != elf::SectionHeaderType::SHT_NOBITS) { stream->WriteBytes(bytes_, FileSize()); } } 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_; }; 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(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; } intptr_t Lookup(const char* str) const { return text_indices_.LookupValue(str); } const bool dynamic_; ZoneTextBuffer text_; CStringIntMap text_indices_; }; class Symbol : public ZoneAllocated { public: Symbol(const char* cstr, intptr_t name, intptr_t binding, intptr_t type, intptr_t section, intptr_t offset, intptr_t size) : name_index(name), binding(binding), type(type), section_index(section), offset(offset), size(size), cstr_(cstr) {} 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 section_index; const intptr_t offset; const intptr_t size; private: friend class SymbolHashTable; // For cstr_ access. const char* const cstr_; }; 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.) AddSymbol("", elf::STB_LOCAL, elf::STT_NOTYPE, elf::SHN_UNDEF, /*offset=*/0, /*size=*/0); } intptr_t FileSize() const { return Length() * entry_size; } intptr_t MemorySize() const { return dynamic_ ? FileSize() : 0; } 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 offset, 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, offset, 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; } } 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 symbols_; mutable IntMap 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) { link = symtab->index(); entry_size = sizeof(int32_t); nchain_ = symtab->Length(); nbucket_ = symtab->Length(); bucket_ = zone->Alloc(nbucket_); for (intptr_t i = 0; i < nbucket_; i++) { bucket_[i] = elf::STN_UNDEF; } chain_ = zone->Alloc(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: DynamicTable(Zone* zone, StringTable* strtab, SymbolTable* symtab, SymbolHashTable* hash) : Section(elf::SectionHeaderType::SHT_DYNAMIC, /*allocate=*/true, /*executable=*/false, /*writable=*/true) { link = strtab->index(); entry_size = sizeof(elf::DynamicEntry); AddEntry(zone, elf::DynamicEntryType::DT_HASH, hash->memory_offset()); AddEntry(zone, elf::DynamicEntryType::DT_STRTAB, strtab->memory_offset()); AddEntry(zone, elf::DynamicEntryType::DT_STRSZ, strtab->MemorySize()); AddEntry(zone, elf::DynamicEntryType::DT_SYMTAB, symtab->memory_offset()); AddEntry(zone, elf::DynamicEntryType::DT_SYMENT, sizeof(elf::Symbol)); AddEntry(zone, elf::DynamicEntryType::DT_NULL, 0); } 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) { const intptr_t start = stream->Position(); #if defined(TARGET_ARCH_IS_32_BIT) stream->WriteWord(static_cast(tag)); stream->WriteAddr(value); #else stream->WriteXWord(static_cast(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); } private: GrowableArray 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(note->type), static_cast(elf::SectionHeaderType::SHT_NOTE)); } }; // We assume that the final program table fits in a single page of memory. static constexpr intptr_t kProgramTableSegmentSize = Elf::kPageSize; // 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; // 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); Elf::Elf(Zone* zone, BaseWriteStream* stream, Type type, Dwarf* dwarf) : zone_(zone), unwrapped_stream_(stream), type_(type), dwarf_(dwarf), bss_(CreateBSS(zone, type, kBssSize)), shstrtab_(new (zone) StringTable(zone, /*allocate=*/false)), dynstrtab_(new (zone) StringTable(zone, /*allocate=*/true)), dynsym_(new (zone) SymbolTable(zone, dynstrtab_, /*dynamic=*/true)) { // 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); // The first section in the section header table is always a reserved // entry containing only 0 values. sections_.Add(new (zone_) ReservedSection()); if (!IsStripped()) { // Not a stripped ELF file, so allocate static string and symbol tables. strtab_ = new (zone_) StringTable(zone_, /* allocate= */ false); symtab_ = new (zone_) SymbolTable(zone, strtab_, /*dynamic=*/false); } // We add an initial segment to represent reserved space for the program // header, and so we can always assume there's at least one segment in the // segments_ array. We later remove this and replace it with appropriately // calculated segments in Elf::FinalizeProgramTable(). auto const start_segment = new (zone_) ProgramTableLoadSegment(zone_, kProgramTableSegmentSize); segments_.Add(start_segment); // We allocate an initial build ID of all zeroes, since we need the build ID // memory offset for the InstructionsSection (see BlobImageWriter::WriteText). // We replace it with the real build ID during finalization. (We add this // prior to BSS because we make the BuildID section writable also, so they are // placed in the same segment before any non-writable ones, and if we add it // after, then in separate debugging information, it'll go into a separate // segment because the BSS section for debugging info is NOBITS.) { uint32_t zeroes[kBuildIdSegmentNamesLength] = {0}; build_id_ = CreateBuildIdNote(&zeroes, sizeof(zeroes)); AddSection(build_id_, kBuildIdNoteName, kSnapshotBuildIdAsmSymbol); } // Note that the BSS segment must be in the first user-defined segment because // it cannot be placed in between any two non-writable segments, due to a bug // in Jelly Bean's ELF loader. (For this reason, the program table segments // generated during finalization are marked as writable.) See also // Elf::WriteProgramTable(). // // We add it 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. auto const bss_start = AddSection(bss_, ".bss"); // 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.) AddStaticSymbol(kVmSnapshotBssAsmSymbol, elf::STB_LOCAL, elf::STT_SECTION, bss_->index(), bss_start, kBssVmSize); AddStaticSymbol(kIsolateSnapshotBssAsmSymbol, elf::STB_LOCAL, elf::STT_SECTION, bss_->index(), bss_start + kBssVmSize, kBssIsolateSize); } intptr_t Elf::NextMemoryOffset(intptr_t alignment) const { // Without more information, we won't know whether we might create a new // segment or put the section into the current one. Thus, for now, only allow // the offset to be queried ahead of time if it matches the load segment // alignment. auto const type = elf::ProgramHeaderType::PT_LOAD; ASSERT_EQUAL(alignment, Segment::Alignment(type)); return Utils::RoundUp(LastLoadSegment()->MemoryEnd(), alignment); } uword Elf::SymbolAddress(const char* name) const { ASSERT(name != nullptr); // Check the static symbol table first if it exists, since the dynamic // table is a subset of it. Fall back on the dynamic otherwise. if (symtab_ != nullptr) { if (auto const symbol = symtab_->Find(name)) { return symbol->offset; } } else if (auto const symbol = dynsym_->Find(name)) { return symbol->offset; } // If stripping, then we won't have symbols for the BSS sections because // they're only added to the static symbol table. Check for these special // cases before returning kNoSectionStart. if (strcmp(name, kVmSnapshotBssAsmSymbol) == 0) { ASSERT(bss_ != nullptr); ASSERT(bss_->memory_offset_is_set()); return bss_->memory_offset(); } else if (strcmp(name, kIsolateSnapshotBssAsmSymbol) == 0) { ASSERT(bss_ != nullptr); ASSERT(bss_->memory_offset_is_set()); return bss_->memory_offset() + kBssVmSize; } return kNoSectionStart; } intptr_t 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)); section->set_index(sections_.length()); sections_.Add(section); // No memory offset, so just return -1. if (!section->IsAllocated()) return -1; ASSERT(program_table_file_size_ < 0); auto const last_load = LastLoadSegment(); if (!last_load->Add(section)) { // We can't add this section to the last load segment, so create a new one. // The new segment starts at the next aligned address. auto const type = elf::ProgramHeaderType::PT_LOAD; intptr_t alignment = Utils::Maximum(section->alignment, Segment::Alignment(type)); auto const start_address = Utils::RoundUp(last_load->MemoryEnd(), alignment); section->set_memory_offset(start_address); auto const segment = new (zone_) Segment(zone_, section, type); segments_.Add(segment); } if (symbol_name != nullptr) { // While elf::STT_SECTION might seem more appropriate, section symbols are // usually local and dlsym won't return them. AddDynamicSymbol(symbol_name, elf::STB_GLOBAL, elf::STT_FUNC, section->index(), section->memory_offset(), section->MemorySize()); } return section->memory_offset(); } void Elf::ReplaceSection(Section* old_section, Section* new_section) { ASSERT(section_table_file_size_ < 0); ASSERT(old_section->index_is_set()); ASSERT(!new_section->index_is_set()); ASSERT_EQUAL(new_section->IsAllocated(), old_section->IsAllocated()); new_section->set_name(old_section->name()); new_section->set_index(old_section->index()); sections_[old_section->index()] = new_section; if (!old_section->IsAllocated()) { return; } ASSERT(program_table_file_size_ < 0); ASSERT(old_section->load_segment != nullptr); old_section->load_segment->Replace(old_section, new_section); } intptr_t Elf::AddText(const char* name, const uint8_t* bytes, intptr_t size) { auto const image = new (zone_) BitsContainer(type_, /*executable=*/true, /*writable=*/false, size, bytes, ImageWriter::kTextAlignment); return AddSection(image, ".text", name); } Section* Elf::CreateBSS(Zone* zone, Type type, intptr_t size) { 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 pages for the BSS. bytes = zone->Alloc(size); memset(bytes, 0, size); } return new (zone) BitsContainer(type, /*executable=*/false, /*writable=*/true, kBssSize, bytes, ImageWriter::kBssAlignment); } intptr_t Elf::AddROData(const char* name, const uint8_t* bytes, intptr_t size) { auto const image = new (zone_) BitsContainer(type_, /*executable=*/false, /*writable=*/false, size, bytes, ImageWriter::kRODataAlignment); return AddSection(image, ".rodata", name); } void Elf::AddDebug(const char* name, const uint8_t* bytes, intptr_t size) { ASSERT(!IsStripped()); ASSERT(bytes != nullptr); auto const image = new (zone_) BitsContainer(elf::SectionHeaderType::SHT_PROGBITS, size, bytes); AddSection(image, name); } void Elf::AddLocalSymbol(const char* name, intptr_t type, intptr_t offset, intptr_t size) { const intptr_t section_index = sections_.length(); // Assume the next section will go into its own segment (currently true // because we write writable sections, data vm (non-writable, non-executable), // text vm (executable), data isolate (non-executable), text isolate // (executable), and we only call this for data and text sections). const intptr_t address = NextMemoryOffset(ImageWriter::kTextAlignment) + offset; AddStaticSymbol(name, elf::STB_LOCAL, type, section_index, address, size); } void Elf::AddDynamicSymbol(const char* name, intptr_t binding, intptr_t type, intptr_t section_index, intptr_t address, intptr_t size) { ASSERT(!dynsym_->HasBeenFinalized()); dynsym_->AddSymbol(name, binding, type, section_index, address, size); // Some tools assume the static symbol table is a superset of the dynamic // symbol table when it exists (see dartbug.com/41783). AddStaticSymbol(name, binding, type, section_index, address, size); } void Elf::AddStaticSymbol(const char* name, intptr_t binding, intptr_t type, intptr_t section_index, intptr_t address, intptr_t size) { if (IsStripped()) return; // No static info kept in stripped ELF files. ASSERT(!symtab_->HasBeenFinalized()); symtab_->AddSymbol(name, binding, type, section_index, address, size); } #if defined(DART_PRECOMPILER) class DwarfElfStream : public DwarfWriteStream { public: explicit DwarfElfStream(Zone* zone, NonStreamingWriteStream* stream, const SymbolTable* table) : zone_(ASSERT_NOTNULL(zone)), stream_(ASSERT_NOTNULL(stream)), table_(table) {} 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); } intptr_t ReserveSize(const char* prefix, intptr_t* start) { ASSERT(start != nullptr); intptr_t fixup = stream_->Position(); // We assume DWARF v2, so all sizes are 32-bit. u4(0); // All sizes for DWARF sections measure the size of the section data _after_ // the size value. *start = stream_->Position(); return fixup; } void SetSize(intptr_t fixup, const char* prefix, intptr_t start) { const intptr_t old_position = stream_->Position(); stream_->SetPosition(fixup); stream_->WriteFixed(static_cast(old_position - start)); stream_->SetPosition(old_position); } void OffsetFromSymbol(const char* symbol, intptr_t offset) { addr(RelocatedAddress(symbol, offset)); } void DistanceBetweenSymbolOffsets(const char* symbol1, intptr_t offset1, const char* symbol2, intptr_t offset2) { auto const address1 = RelocatedAddress(symbol1, offset1); auto const address2 = RelocatedAddress(symbol2, offset2); RELEASE_ASSERT(address1 >= address2); auto const delta = address1 - address2; uleb128(delta); } void InitializeAbstractOrigins(intptr_t size) { abstract_origins_size_ = size; abstract_origins_ = zone_->Alloc(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]); } private: uword RelocatedAddress(const char* name, intptr_t offset) { auto const symbol = table_->Find(name); ASSERT(symbol != nullptr); return symbol->offset + offset; } void addr(uword value) { #if defined(TARGET_ARCH_IS_32_BIT) u4(value); #else u8(value); #endif } Zone* const zone_; NonStreamingWriteStream* const stream_; const SymbolTable* table_; uint32_t* abstract_origins_ = nullptr; intptr_t abstract_origins_size_ = -1; DISALLOW_COPY_AND_ASSIGN(DwarfElfStream); }; static constexpr intptr_t kInitialDwarfBufferSize = 64 * KB; #endif Segment* Elf::LastLoadSegment() const { for (intptr_t i = segments_.length() - 1; i >= 0; i--) { auto const segment = segments_.At(i); if (segment->type == elf::ProgramHeaderType::PT_LOAD) { return segment; } } // There should always be a load segment, since one is added in construction. UNREACHABLE(); } const Section* Elf::FindSectionForAddress(intptr_t address) const { for (auto const section : sections_) { if (!section->IsAllocated()) continue; auto const start = section->memory_offset(); auto const end = start + section->MemorySize(); if (address >= start && address < end) { return section; } } return nullptr; } void Elf::FinalizeDwarfSections() { if (dwarf_ == nullptr) return; #if defined(DART_PRECOMPILER) { ZoneWriteStream stream(zone(), kInitialDwarfBufferSize); // We can use symtab_ without checking because this is an unstripped // snapshot or separate debugging information, both of which have static // symbol tables, and the static symbol table is a superset of the dynamic. DwarfElfStream dwarf_stream(zone_, &stream, symtab_); dwarf_->WriteAbbreviations(&dwarf_stream); AddDebug(".debug_abbrev", stream.buffer(), stream.bytes_written()); } { ZoneWriteStream stream(zone(), kInitialDwarfBufferSize); DwarfElfStream dwarf_stream(zone_, &stream, symtab_); dwarf_->WriteDebugInfo(&dwarf_stream); AddDebug(".debug_info", stream.buffer(), stream.bytes_written()); } { ZoneWriteStream stream(zone(), kInitialDwarfBufferSize); DwarfElfStream dwarf_stream(zone_, &stream, symtab_); dwarf_->WriteLineNumberProgram(&dwarf_stream); AddDebug(".debug_line", stream.buffer(), stream.bytes_written()); } #endif } void Elf::Finalize() { if (auto const new_build_id = GenerateFinalBuildId()) { ReplaceSection(build_id_, new_build_id); // Add a PT_NOTE segment for the build ID. segments_.Add(new (zone_) NoteSegment(zone_, new_build_id)); } // Adding the dynamic symbol table and associated sections. AddSection(dynstrtab_, ".dynstr"); AddSection(dynsym_, ".dynsym"); dynsym_->link = dynstrtab_->index(); auto const hash = new (zone_) SymbolHashTable(zone_, dynstrtab_, dynsym_); AddSection(hash, ".hash"); auto const dynamic = new (zone_) DynamicTable(zone_, dynstrtab_, dynsym_, hash); AddSection(dynamic, ".dynamic"); // Add a PT_DYNAMIC segment for the dynamic symbol table. segments_.Add(new (zone_) DynamicSegment(zone_, dynamic)); // Currently, we add all (non-reserved) unallocated sections after all // allocated sections. If we put unallocated sections between allocated // sections, they would affect the file offset but not the memory offset // of the later allocated sections. // // However, memory offsets must be page-aligned to the file offset for the // ELF file to be successfully loaded. This means we'd either have to add // extra padding _or_ determine file offsets before memory offsets. The // latter would require us to handle BSS relocations during ELF finalization, // instead of while writing the .text section content. if (!IsStripped()) { AddSection(strtab_, ".strtab"); AddSection(symtab_, ".symtab"); symtab_->link = strtab_->index(); } AddSection(shstrtab_, ".shstrtab"); FinalizeDwarfSections(); // At this point, all non-programmatically calculated sections and segments // have been added. Add any programatically calculated sections and segments // and then calculate file offsets. FinalizeProgramTable(); ComputeFileOffsets(); // Finally, write the ELF file contents. ElfWriteStream wrapped(unwrapped_stream_); WriteHeader(&wrapped); WriteProgramTable(&wrapped); WriteSections(&wrapped); WriteSectionTable(&wrapped); } static uint32_t HashBitsContainer(const BitsContainer* bits) { uint32_t hash = 0; auto const size = bits->MemorySize(); if (bits->bytes() == nullptr) { // Just hash the size as a fallback if this section has no contents. return FinalizeHash(size, 32); } auto const end = bits->bytes() + size; auto const non_word_size = size % kWordSize; auto const end_of_words = reinterpret_cast(bits->bytes() + (size - non_word_size)); for (auto cursor = reinterpret_cast(bits->bytes()); cursor < end_of_words; cursor++) { hash = CombineHashes(hash, *cursor); } for (auto cursor = reinterpret_cast(end_of_words); cursor < end; cursor++) { hash = CombineHashes(hash, *cursor); } return FinalizeHash(hash, 32); } Section* Elf::GenerateFinalBuildId() { uint32_t hashes[kBuildIdSegmentNamesLength]; for (intptr_t i = 0; i < kBuildIdSegmentNamesLength; i++) { auto const name = kBuildIdSegmentNames[i]; auto const symbol = dynsym_->Find(name); if (symbol == nullptr) { // If we're missing a section, then we don't generate a final build ID. return nullptr; } auto const bits = sections_[symbol->section_index]->AsBitsContainer(); if (bits == nullptr) { FATAL1("Section for symbol %s is not a BitsContainer", name); } if (bits->bytes() == nullptr) { // 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. return nullptr; } ASSERT_EQUAL(bits->MemorySize(), symbol->size); hashes[i] = HashBitsContainer(bits); } // To ensure we can quickly check for a final build ID, we ensure the first // byte contains a non-zero value. auto const bytes = reinterpret_cast(hashes); if (bytes[0] == 0) { bytes[0] = 1; } return CreateBuildIdNote(&hashes, sizeof(hashes)); } Section* Elf::CreateBuildIdNote(const void* description_bytes, intptr_t description_length) { ASSERT(description_length == 0 || description_bytes != nullptr); ZoneWriteStream stream(zone(), kBuildIdHeaderSize + description_length); stream.WriteFixed(sizeof(elf::ELF_NOTE_GNU)); stream.WriteFixed(description_length); stream.WriteFixed(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, it and the // BSS section are allocated segments at the same time. Having the same flags // ensures they will be combined in the same segment and not unnecessarily // aligned into a new page. return new (zone_) BitsContainer(elf::SectionHeaderType::SHT_NOTE, /*allocate=*/true, /*executable=*/false, /*writable=*/true, stream.bytes_written(), stream.buffer(), kNoteAlignment); } void Elf::FinalizeProgramTable() { ASSERT(program_table_file_size_ < 0); program_table_file_offset_ = sizeof(elf::ElfHeader); // There are two segments we need the size of the program table to create, so // calculate it as if those two segments were already in place. program_table_file_size_ = (2 + segments_.length()) * sizeof(elf::ProgramHeader); // We pre-allocated the virtual memory space for the program table itself. // Check that we didn't generate too many segments. Currently we generate a // fixed num of segments based on the four pieces of a snapshot, but if we // use more in the future we'll likely need to do something more compilated // to generate DWARF without knowing a piece's virtual address in advance. auto const program_table_segment_size = program_table_file_offset_ + program_table_file_size_; RELEASE_ASSERT(program_table_segment_size < kProgramTableSegmentSize); // Remove the original stand-in segment we added in the constructor. segments_.EraseAt(0); // 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_)); // Segment for loading the initial part of the ELF file, including the // program header table. Required by Android but not by Linux. segments_.InsertAt(1, new (zone_) ProgramTableLoadSegment( zone_, program_table_segment_size)); } static const intptr_t kElfSectionTableAlignment = compiler::target::kWordSize; void Elf::ComputeFileOffsets() { // 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_; // When calculating file offsets for sections, we'll need to know if we've // changed segments. Start with the one for the program table. const auto* current_segment = segments_[1]; // 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. auto const load_align = Segment::Alignment(elf::ProgramHeaderType::PT_LOAD); for (intptr_t i = 1; i < sections_.length(); i++) { auto const section = sections_[i]; file_offset = Utils::RoundUp(file_offset, section->alignment); if (section->IsAllocated() && section->load_segment != current_segment) { file_offset = Utils::RoundUp(file_offset, load_align); current_segment = section->load_segment; } section->set_file_offset(file_offset); #if defined(DEBUG) if (section->IsAllocated()) { // 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() % load_align, section->memory_offset() % load_align); } #endif file_offset += section->FileSize(); } 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. // 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(sections_[0]->type), static_cast(elf::SectionHeaderType::SHT_NULL)); ASSERT_EQUAL(sections_[0]->alignment, 0); auto const load_align = Segment::Alignment(elf::ProgramHeaderType::PT_LOAD); const Segment* current_segment = segments_[1]; 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. stream->Align(load_align); current_segment = section->load_segment; } ASSERT_EQUAL(stream->Position(), section->file_offset()); section->Write(stream); ASSERT_EQUAL(stream->Position(), section->file_offset() + section->FileSize()); } } } // namespace dart